Changed wxLog DoLogXXX() callbacks and introduced wxLogRecordInfo.
[wxWidgets.git] / src / generic / logg.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/generic/logg.cpp
3 // Purpose: wxLog-derived classes which need GUI support (the rest is in
4 // src/common/log.cpp)
5 // Author: Vadim Zeitlin
6 // Modified by:
7 // Created: 20.09.99 (extracted from src/common/log.cpp)
8 // RCS-ID: $Id$
9 // Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
10 // Licence: wxWindows licence
11 /////////////////////////////////////////////////////////////////////////////
12
13 // ============================================================================
14 // declarations
15 // ============================================================================
16
17 // ----------------------------------------------------------------------------
18 // headers
19 // ----------------------------------------------------------------------------
20
21 // For compilers that support precompilation, includes "wx.h".
22 #include "wx/wxprec.h"
23
24 #ifdef __BORLANDC__
25 #pragma hdrstop
26 #endif
27
28 #ifndef WX_PRECOMP
29 #include "wx/app.h"
30 #include "wx/button.h"
31 #include "wx/intl.h"
32 #include "wx/log.h"
33 #include "wx/menu.h"
34 #include "wx/frame.h"
35 #include "wx/filedlg.h"
36 #include "wx/msgdlg.h"
37 #include "wx/textctrl.h"
38 #include "wx/sizer.h"
39 #include "wx/statbmp.h"
40 #include "wx/settings.h"
41 #include "wx/wxcrtvararg.h"
42 #endif // WX_PRECOMP
43
44 #if wxUSE_LOGGUI || wxUSE_LOGWINDOW
45
46 #include "wx/file.h"
47 #include "wx/clipbrd.h"
48 #include "wx/dataobj.h"
49 #include "wx/textfile.h"
50 #include "wx/statline.h"
51 #include "wx/artprov.h"
52 #include "wx/collpane.h"
53 #include "wx/arrstr.h"
54 #include "wx/msgout.h"
55
56 #if wxUSE_THREADS
57 #include "wx/thread.h"
58 #endif // wxUSE_THREADS
59
60 #ifdef __WXMSW__
61 // for OutputDebugString()
62 #include "wx/msw/private.h"
63 #endif // Windows
64
65
66 #ifdef __WXPM__
67 #include <time.h>
68 #endif
69
70 #if wxUSE_LOG_DIALOG
71 #include "wx/listctrl.h"
72 #include "wx/imaglist.h"
73 #include "wx/image.h"
74 #endif // wxUSE_LOG_DIALOG/!wxUSE_LOG_DIALOG
75
76 #if defined(__MWERKS__) && wxUSE_UNICODE
77 #include <wtime.h>
78 #endif
79
80 #include "wx/datetime.h"
81
82 // the suffix we add to the button to show that the dialog can be expanded
83 #define EXPAND_SUFFIX _T(" >>")
84
85 #define CAN_SAVE_FILES (wxUSE_FILE && wxUSE_FILEDLG)
86
87 // ----------------------------------------------------------------------------
88 // private classes
89 // ----------------------------------------------------------------------------
90
91 #if wxUSE_LOG_DIALOG
92
93 // this function is a wrapper around strftime(3)
94 // allows to exclude the usage of wxDateTime
95 static wxString TimeStamp(const wxString& format, time_t t)
96 {
97 #if wxUSE_DATETIME
98 wxChar buf[4096];
99 struct tm tm;
100 if ( !wxStrftime(buf, WXSIZEOF(buf), format, wxLocaltime_r(&t, &tm)) )
101 {
102 // buffer is too small?
103 wxFAIL_MSG(_T("strftime() failed"));
104 }
105 return wxString(buf);
106 #else // !wxUSE_DATETIME
107 return wxEmptyString;
108 #endif // wxUSE_DATETIME/!wxUSE_DATETIME
109 }
110
111
112 class wxLogDialog : public wxDialog
113 {
114 public:
115 wxLogDialog(wxWindow *parent,
116 const wxArrayString& messages,
117 const wxArrayInt& severity,
118 const wxArrayLong& timess,
119 const wxString& caption,
120 long style);
121 virtual ~wxLogDialog();
122
123 // event handlers
124 void OnOk(wxCommandEvent& event);
125 #if wxUSE_CLIPBOARD
126 void OnCopy(wxCommandEvent& event);
127 #endif // wxUSE_CLIPBOARD
128 #if CAN_SAVE_FILES
129 void OnSave(wxCommandEvent& event);
130 #endif // CAN_SAVE_FILES
131 void OnListItemActivated(wxListEvent& event);
132
133 private:
134 // create controls needed for the details display
135 void CreateDetailsControls(wxWindow *);
136
137 // if necessary truncates the given string and adds an ellipsis
138 wxString EllipsizeString(const wxString &text)
139 {
140 if (ms_maxLength > 0 &&
141 text.length() > ms_maxLength)
142 {
143 wxString ret(text);
144 ret.Truncate(ms_maxLength);
145 ret << "...";
146 return ret;
147 }
148
149 return text;
150 }
151
152 #if CAN_SAVE_FILES || wxUSE_CLIPBOARD
153 // return the contents of the dialog as a multiline string
154 wxString GetLogMessages() const;
155 #endif // CAN_SAVE_FILES || wxUSE_CLIPBOARD
156
157
158 // the data for the listctrl
159 wxArrayString m_messages;
160 wxArrayInt m_severity;
161 wxArrayLong m_times;
162
163 // the controls which are not shown initially (but only when details
164 // button is pressed)
165 wxListCtrl *m_listctrl;
166
167 // the translated "Details" string
168 static wxString ms_details;
169
170 // the maximum length of the log message
171 static size_t ms_maxLength;
172
173 DECLARE_EVENT_TABLE()
174 wxDECLARE_NO_COPY_CLASS(wxLogDialog);
175 };
176
177 BEGIN_EVENT_TABLE(wxLogDialog, wxDialog)
178 EVT_BUTTON(wxID_OK, wxLogDialog::OnOk)
179 #if wxUSE_CLIPBOARD
180 EVT_BUTTON(wxID_COPY, wxLogDialog::OnCopy)
181 #endif // wxUSE_CLIPBOARD
182 #if CAN_SAVE_FILES
183 EVT_BUTTON(wxID_SAVE, wxLogDialog::OnSave)
184 #endif // CAN_SAVE_FILES
185 EVT_LIST_ITEM_ACTIVATED(wxID_ANY, wxLogDialog::OnListItemActivated)
186 END_EVENT_TABLE()
187
188 #endif // wxUSE_LOG_DIALOG
189
190 // ----------------------------------------------------------------------------
191 // private functions
192 // ----------------------------------------------------------------------------
193
194 #if CAN_SAVE_FILES
195
196 // pass an uninitialized file object, the function will ask the user for the
197 // filename and try to open it, returns true on success (file was opened),
198 // false if file couldn't be opened/created and -1 if the file selection
199 // dialog was cancelled
200 static int OpenLogFile(wxFile& file, wxString *filename = NULL, wxWindow *parent = NULL);
201
202 #endif // CAN_SAVE_FILES
203
204 // ----------------------------------------------------------------------------
205 // global variables
206 // ----------------------------------------------------------------------------
207
208 // we use a global variable to store the frame pointer for wxLogStatus - bad,
209 // but it's the easiest way
210 static wxFrame *gs_pFrame = NULL; // FIXME MT-unsafe
211
212 // ============================================================================
213 // implementation
214 // ============================================================================
215
216 // ----------------------------------------------------------------------------
217 // global functions
218 // ----------------------------------------------------------------------------
219
220 // accepts an additional argument which tells to which frame the output should
221 // be directed
222 void wxVLogStatus(wxFrame *pFrame, const wxString& format, va_list argptr)
223 {
224 wxString msg;
225
226 wxLog *pLog = wxLog::GetActiveTarget();
227 if ( pLog != NULL )
228 {
229 msg.PrintfV(format, argptr);
230
231 wxASSERT( gs_pFrame == NULL ); // should be reset!
232 gs_pFrame = pFrame;
233 wxLog::OnLog(wxLOG_Status, msg);
234 gs_pFrame = NULL;
235 }
236 }
237
238 #if !wxUSE_UTF8_LOCALE_ONLY
239 void wxDoLogStatusWchar(wxFrame *pFrame, const wxChar *format, ...)
240 {
241 va_list argptr;
242 va_start(argptr, format);
243 wxVLogStatus(pFrame, format, argptr);
244 va_end(argptr);
245 }
246 #endif // !wxUSE_UTF8_LOCALE_ONLY
247
248 #if wxUSE_UNICODE_UTF8
249 void wxDoLogStatusUtf8(wxFrame *pFrame, const char *format, ...)
250 {
251 va_list argptr;
252 va_start(argptr, format);
253 wxVLogStatus(pFrame, format, argptr);
254 va_end(argptr);
255 }
256 #endif // wxUSE_UNICODE_UTF8
257
258 // ----------------------------------------------------------------------------
259 // wxLogGui implementation (FIXME MT-unsafe)
260 // ----------------------------------------------------------------------------
261
262 #if wxUSE_LOGGUI
263
264 wxLogGui::wxLogGui()
265 {
266 Clear();
267 }
268
269 void wxLogGui::Clear()
270 {
271 m_bErrors =
272 m_bWarnings =
273 m_bHasMessages = false;
274
275 m_aMessages.Empty();
276 m_aSeverity.Empty();
277 m_aTimes.Empty();
278 }
279
280 int wxLogGui::GetSeverityIcon() const
281 {
282 return m_bErrors ? wxICON_STOP
283 : m_bWarnings ? wxICON_EXCLAMATION
284 : wxICON_INFORMATION;
285 }
286
287 wxString wxLogGui::GetTitle() const
288 {
289 wxString titleFormat;
290 switch ( GetSeverityIcon() )
291 {
292 case wxICON_STOP:
293 titleFormat = _("%s Error");
294 break;
295
296 case wxICON_EXCLAMATION:
297 titleFormat = _("%s Warning");
298 break;
299
300 default:
301 wxFAIL_MSG( "unexpected icon severity" );
302 // fall through
303
304 case wxICON_INFORMATION:
305 titleFormat = _("%s Information");
306 }
307
308 return wxString::Format(titleFormat, wxTheApp->GetAppDisplayName());
309 }
310
311 void
312 wxLogGui::DoShowSingleLogMessage(const wxString& message,
313 const wxString& title,
314 int style)
315 {
316 wxMessageBox(message, title, wxOK | style);
317 }
318
319 void
320 wxLogGui::DoShowMultipleLogMessages(const wxArrayString& messages,
321 const wxArrayInt& severities,
322 const wxArrayLong& times,
323 const wxString& title,
324 int style)
325 {
326 #if wxUSE_LOG_DIALOG
327 wxLogDialog dlg(NULL,
328 messages, severities, times,
329 title, style);
330
331 // clear the message list before showing the dialog because while it's
332 // shown some new messages may appear
333 Clear();
334
335 (void)dlg.ShowModal();
336 #else // !wxUSE_LOG_DIALOG
337 // start from the most recent message
338 wxString message;
339 const size_t nMsgCount = messages.size();
340 message.reserve(nMsgCount*100);
341 for ( size_t n = nMsgCount; n > 0; n-- ) {
342 message << m_aMessages[n - 1] << wxT("\n");
343 }
344
345 DoShowSingleLogMessage(message, title, style);
346 #endif // wxUSE_LOG_DIALOG/!wxUSE_LOG_DIALOG
347 }
348
349 void wxLogGui::Flush()
350 {
351 if ( !m_bHasMessages )
352 return;
353
354 // do it right now to block any new calls to Flush() while we're here
355 m_bHasMessages = false;
356
357 // note that this must be done before examining m_aMessages as it may log
358 // yet another message
359 const unsigned repeatCount = LogLastRepeatIfNeeded();
360
361 const size_t nMsgCount = m_aMessages.size();
362
363 if ( repeatCount > 0 )
364 {
365 m_aMessages[nMsgCount - 1] << " (" << m_aMessages[nMsgCount - 2] << ")";
366 }
367
368 const wxString title = GetTitle();
369 const int style = GetSeverityIcon();
370
371 // avoid showing other log dialogs until we're done with the dialog we're
372 // showing right now: nested modal dialogs make for really bad UI!
373 Suspend();
374
375 if ( nMsgCount == 1 )
376 {
377 // make a copy before calling Clear()
378 const wxString message(m_aMessages[0]);
379 Clear();
380
381 DoShowSingleLogMessage(message, title, style);
382 }
383 else // more than one message
384 {
385 wxArrayString messages;
386 wxArrayInt severities;
387 wxArrayLong times;
388
389 messages.swap(m_aMessages);
390 severities.swap(m_aSeverity);
391 times.swap(m_aTimes);
392
393 Clear();
394
395 DoShowMultipleLogMessages(messages, severities, times, title, style);
396 }
397
398 // allow flushing the logs again
399 Resume();
400 }
401
402 // log all kinds of messages
403 void wxLogGui::DoLogRecord(wxLogLevel level,
404 const wxString& msg,
405 const wxLogRecordInfo& info)
406 {
407 switch ( level )
408 {
409 case wxLOG_Info:
410 if ( GetVerbose() )
411 case wxLOG_Message:
412 {
413 m_aMessages.Add(msg);
414 m_aSeverity.Add(wxLOG_Message);
415 m_aTimes.Add((long)info.timestamp);
416 m_bHasMessages = true;
417 }
418 break;
419
420 case wxLOG_Status:
421 #if wxUSE_STATUSBAR
422 {
423 // find the top window and set it's status text if it has any
424 wxFrame *pFrame = gs_pFrame;
425 if ( pFrame == NULL ) {
426 wxWindow *pWin = wxTheApp->GetTopWindow();
427 if ( pWin != NULL && pWin->IsKindOf(CLASSINFO(wxFrame)) ) {
428 pFrame = (wxFrame *)pWin;
429 }
430 }
431
432 if ( pFrame && pFrame->GetStatusBar() )
433 pFrame->SetStatusText(msg);
434 }
435 #endif // wxUSE_STATUSBAR
436 break;
437
438 case wxLOG_Error:
439 if ( !m_bErrors ) {
440 #if !wxUSE_LOG_DIALOG
441 // discard earlier informational messages if this is the 1st
442 // error because they might not make sense any more and showing
443 // them in a message box might be confusing
444 m_aMessages.Empty();
445 m_aSeverity.Empty();
446 m_aTimes.Empty();
447 #endif // wxUSE_LOG_DIALOG
448 m_bErrors = true;
449 }
450 // fall through
451
452 case wxLOG_Warning:
453 if ( !m_bErrors ) {
454 // for the warning we don't discard the info messages
455 m_bWarnings = true;
456 }
457
458 m_aMessages.Add(msg);
459 m_aSeverity.Add((int)level);
460 m_aTimes.Add((long)info.timestamp);
461 m_bHasMessages = true;
462 break;
463
464 default:
465 // let the base class deal with debug/trace messages as well as any
466 // custom levels
467 wxLog::DoLogRecord(level, msg, info);
468 }
469 }
470
471 #endif // wxUSE_LOGGUI
472
473 // ----------------------------------------------------------------------------
474 // wxLogWindow and wxLogFrame implementation
475 // ----------------------------------------------------------------------------
476
477 #if wxUSE_LOGWINDOW
478
479 // log frame class
480 // ---------------
481 class wxLogFrame : public wxFrame
482 {
483 public:
484 // ctor & dtor
485 wxLogFrame(wxWindow *pParent, wxLogWindow *log, const wxString& szTitle);
486 virtual ~wxLogFrame();
487
488 // menu callbacks
489 void OnClose(wxCommandEvent& event);
490 void OnCloseWindow(wxCloseEvent& event);
491 #if CAN_SAVE_FILES
492 void OnSave(wxCommandEvent& event);
493 #endif // CAN_SAVE_FILES
494 void OnClear(wxCommandEvent& event);
495
496 // this function is safe to call from any thread (notice that it should be
497 // also called from the main thread to ensure that the messages logged from
498 // it appear in correct order with the messages from the other threads)
499 void AddLogMessage(const wxString& message);
500
501 // actually append the messages logged from secondary threads to the text
502 // control during idle time in the main thread
503 virtual void OnInternalIdle();
504
505 private:
506 // use standard ids for our commands!
507 enum
508 {
509 Menu_Close = wxID_CLOSE,
510 Menu_Save = wxID_SAVE,
511 Menu_Clear = wxID_CLEAR
512 };
513
514 // common part of OnClose() and OnCloseWindow()
515 void DoClose();
516
517 // do show the message in the text control
518 void DoShowLogMessage(const wxString& message)
519 {
520 m_pTextCtrl->AppendText(message + wxS('\n'));
521 }
522
523 wxTextCtrl *m_pTextCtrl;
524 wxLogWindow *m_log;
525
526 // queue of messages logged from other threads which need to be displayed
527 wxArrayString m_pendingMessages;
528
529 #if wxUSE_THREADS
530 // critical section to protect access to m_pendingMessages
531 wxCriticalSection m_critSection;
532 #endif // wxUSE_THREADS
533
534
535 DECLARE_EVENT_TABLE()
536 wxDECLARE_NO_COPY_CLASS(wxLogFrame);
537 };
538
539 BEGIN_EVENT_TABLE(wxLogFrame, wxFrame)
540 // wxLogWindow menu events
541 EVT_MENU(Menu_Close, wxLogFrame::OnClose)
542 #if CAN_SAVE_FILES
543 EVT_MENU(Menu_Save, wxLogFrame::OnSave)
544 #endif // CAN_SAVE_FILES
545 EVT_MENU(Menu_Clear, wxLogFrame::OnClear)
546
547 EVT_CLOSE(wxLogFrame::OnCloseWindow)
548 END_EVENT_TABLE()
549
550 wxLogFrame::wxLogFrame(wxWindow *pParent, wxLogWindow *log, const wxString& szTitle)
551 : wxFrame(pParent, wxID_ANY, szTitle)
552 {
553 m_log = log;
554
555 m_pTextCtrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition,
556 wxDefaultSize,
557 wxTE_MULTILINE |
558 wxHSCROLL |
559 // needed for Win32 to avoid 65Kb limit but it doesn't work well
560 // when using RichEdit 2.0 which we always do in the Unicode build
561 #if !wxUSE_UNICODE
562 wxTE_RICH |
563 #endif // !wxUSE_UNICODE
564 wxTE_READONLY);
565
566 #if wxUSE_MENUS
567 // create menu
568 wxMenuBar *pMenuBar = new wxMenuBar;
569 wxMenu *pMenu = new wxMenu;
570 #if CAN_SAVE_FILES
571 pMenu->Append(Menu_Save, _("&Save..."), _("Save log contents to file"));
572 #endif // CAN_SAVE_FILES
573 pMenu->Append(Menu_Clear, _("C&lear"), _("Clear the log contents"));
574 pMenu->AppendSeparator();
575 pMenu->Append(Menu_Close, _("&Close"), _("Close this window"));
576 pMenuBar->Append(pMenu, _("&Log"));
577 SetMenuBar(pMenuBar);
578 #endif // wxUSE_MENUS
579
580 #if wxUSE_STATUSBAR
581 // status bar for menu prompts
582 CreateStatusBar();
583 #endif // wxUSE_STATUSBAR
584
585 m_log->OnFrameCreate(this);
586 }
587
588 void wxLogFrame::DoClose()
589 {
590 if ( m_log->OnFrameClose(this) )
591 {
592 // instead of closing just hide the window to be able to Show() it
593 // later
594 Show(false);
595 }
596 }
597
598 void wxLogFrame::OnClose(wxCommandEvent& WXUNUSED(event))
599 {
600 DoClose();
601 }
602
603 void wxLogFrame::OnCloseWindow(wxCloseEvent& WXUNUSED(event))
604 {
605 DoClose();
606 }
607
608 #if CAN_SAVE_FILES
609 void wxLogFrame::OnSave(wxCommandEvent& WXUNUSED(event))
610 {
611 wxString filename;
612 wxFile file;
613 int rc = OpenLogFile(file, &filename, this);
614 if ( rc == -1 )
615 {
616 // cancelled
617 return;
618 }
619
620 bool bOk = rc != 0;
621
622 // retrieve text and save it
623 // -------------------------
624 int nLines = m_pTextCtrl->GetNumberOfLines();
625 for ( int nLine = 0; bOk && nLine < nLines; nLine++ ) {
626 bOk = file.Write(m_pTextCtrl->GetLineText(nLine) +
627 wxTextFile::GetEOL());
628 }
629
630 if ( bOk )
631 bOk = file.Close();
632
633 if ( !bOk ) {
634 wxLogError(_("Can't save log contents to file."));
635 }
636 else {
637 wxLogStatus((wxFrame*)this, _("Log saved to the file '%s'."), filename.c_str());
638 }
639 }
640 #endif // CAN_SAVE_FILES
641
642 void wxLogFrame::OnClear(wxCommandEvent& WXUNUSED(event))
643 {
644 m_pTextCtrl->Clear();
645 }
646
647 void wxLogFrame::OnInternalIdle()
648 {
649 {
650 wxCRIT_SECT_LOCKER(locker, m_critSection);
651
652 const size_t count = m_pendingMessages.size();
653 for ( size_t n = 0; n < count; n++ )
654 {
655 DoShowLogMessage(m_pendingMessages[n]);
656 }
657
658 m_pendingMessages.clear();
659 } // release m_critSection
660
661 wxFrame::OnInternalIdle();
662 }
663
664 void wxLogFrame::AddLogMessage(const wxString& message)
665 {
666 wxCRIT_SECT_LOCKER(locker, m_critSection);
667
668 #if wxUSE_THREADS
669 if ( !wxThread::IsMain() || !m_pendingMessages.empty() )
670 {
671 // message needs to be queued for later showing
672 m_pendingMessages.Add(message);
673
674 wxWakeUpIdle();
675 }
676 else // we are the main thread and no messages are queued, so we can
677 // log the message directly
678 #endif // wxUSE_THREADS
679 {
680 DoShowLogMessage(message);
681 }
682 }
683
684 wxLogFrame::~wxLogFrame()
685 {
686 m_log->OnFrameDelete(this);
687 }
688
689 // wxLogWindow
690 // -----------
691
692 wxLogWindow::wxLogWindow(wxWindow *pParent,
693 const wxString& szTitle,
694 bool bShow,
695 bool bDoPass)
696 {
697 PassMessages(bDoPass);
698
699 m_pLogFrame = new wxLogFrame(pParent, this, szTitle);
700
701 if ( bShow )
702 m_pLogFrame->Show();
703 }
704
705 void wxLogWindow::Show(bool bShow)
706 {
707 m_pLogFrame->Show(bShow);
708 }
709
710 void wxLogWindow::DoLogTextAtLevel(wxLogLevel level, const wxString& msg)
711 {
712 // first let the previous logger show it
713 wxLogPassThrough::DoLogTextAtLevel(level, msg);
714
715 if ( !m_pLogFrame )
716 return;
717
718 // don't put trace messages in the text window for 2 reasons:
719 // 1) there are too many of them
720 // 2) they may provoke other trace messages (e.g. wxMSW code uses
721 // wxLogTrace to log Windows messages and adding text to the control
722 // sends more of them) thus sending a program into an infinite loop
723 if ( level == wxLOG_Trace )
724 return;
725
726 m_pLogFrame->AddLogMessage(msg);
727 }
728
729 wxFrame *wxLogWindow::GetFrame() const
730 {
731 return m_pLogFrame;
732 }
733
734 void wxLogWindow::OnFrameCreate(wxFrame * WXUNUSED(frame))
735 {
736 }
737
738 bool wxLogWindow::OnFrameClose(wxFrame * WXUNUSED(frame))
739 {
740 // allow to close
741 return true;
742 }
743
744 void wxLogWindow::OnFrameDelete(wxFrame * WXUNUSED(frame))
745 {
746 m_pLogFrame = NULL;
747 }
748
749 wxLogWindow::~wxLogWindow()
750 {
751 // may be NULL if log frame already auto destroyed itself
752 delete m_pLogFrame;
753 }
754
755 #endif // wxUSE_LOGWINDOW
756
757 // ----------------------------------------------------------------------------
758 // wxLogDialog
759 // ----------------------------------------------------------------------------
760
761 #if wxUSE_LOG_DIALOG
762
763 wxString wxLogDialog::ms_details;
764 size_t wxLogDialog::ms_maxLength = 0;
765
766 wxLogDialog::wxLogDialog(wxWindow *parent,
767 const wxArrayString& messages,
768 const wxArrayInt& severity,
769 const wxArrayLong& times,
770 const wxString& caption,
771 long style)
772 : wxDialog(parent, wxID_ANY, caption,
773 wxDefaultPosition, wxDefaultSize,
774 wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
775 {
776 // init the static variables:
777
778 if ( ms_details.empty() )
779 {
780 // ensure that we won't loop here if wxGetTranslation()
781 // happens to pop up a Log message while translating this :-)
782 ms_details = wxTRANSLATE("&Details");
783 ms_details = wxGetTranslation(ms_details);
784 #ifdef __SMARTPHONE__
785 ms_details = wxStripMenuCodes(ms_details);
786 #endif
787 }
788
789 if ( ms_maxLength == 0 )
790 {
791 ms_maxLength = (2 * wxGetDisplaySize().x/3) / GetCharWidth();
792 }
793
794 size_t count = messages.GetCount();
795 m_messages.Alloc(count);
796 m_severity.Alloc(count);
797 m_times.Alloc(count);
798
799 for ( size_t n = 0; n < count; n++ )
800 {
801 m_messages.Add(messages[n]);
802 m_severity.Add(severity[n]);
803 m_times.Add(times[n]);
804 }
805
806 m_listctrl = NULL;
807
808 bool isPda = (wxSystemSettings::GetScreenType() <= wxSYS_SCREEN_PDA);
809
810 // create the controls which are always shown and layout them: we use
811 // sizers even though our window is not resizeable to calculate the size of
812 // the dialog properly
813 wxBoxSizer *sizerTop = new wxBoxSizer(wxVERTICAL);
814 wxBoxSizer *sizerAll = new wxBoxSizer(isPda ? wxVERTICAL : wxHORIZONTAL);
815
816 if (!isPda)
817 {
818 wxStaticBitmap *icon = new wxStaticBitmap
819 (
820 this,
821 wxID_ANY,
822 wxArtProvider::GetMessageBoxIcon(style)
823 );
824 sizerAll->Add(icon, wxSizerFlags().Centre());
825 }
826
827 // create the text sizer with a minimal size so that we are sure it won't be too small
828 wxString message = EllipsizeString(messages.Last());
829 wxSizer *szText = CreateTextSizer(message);
830 szText->SetMinSize(wxMin(300, wxGetDisplaySize().x / 3), -1);
831
832 sizerAll->Add(szText, wxSizerFlags(1).Centre().Border(wxLEFT | wxRIGHT));
833
834 wxButton *btnOk = new wxButton(this, wxID_OK);
835 sizerAll->Add(btnOk, wxSizerFlags().Centre());
836
837 sizerTop->Add(sizerAll, wxSizerFlags().Expand().Border());
838
839
840 // add the details pane
841 #ifndef __SMARTPHONE__
842 wxCollapsiblePane * const
843 collpane = new wxCollapsiblePane(this, wxID_ANY, ms_details);
844 sizerTop->Add(collpane, wxSizerFlags(1).Expand().Border());
845
846 wxWindow *win = collpane->GetPane();
847 wxSizer * const paneSz = new wxBoxSizer(wxVERTICAL);
848
849 CreateDetailsControls(win);
850
851 paneSz->Add(m_listctrl, wxSizerFlags(1).Expand().Border(wxTOP));
852
853 #if wxUSE_CLIPBOARD || CAN_SAVE_FILES
854 wxBoxSizer * const btnSizer = new wxBoxSizer(wxHORIZONTAL);
855
856 wxSizerFlags flagsBtn;
857 flagsBtn.Border(wxLEFT);
858
859 #if wxUSE_CLIPBOARD
860 btnSizer->Add(new wxButton(win, wxID_COPY), flagsBtn);
861 #endif // wxUSE_CLIPBOARD
862
863 #if CAN_SAVE_FILES
864 btnSizer->Add(new wxButton(win, wxID_SAVE), flagsBtn);
865 #endif // CAN_SAVE_FILES
866
867 paneSz->Add(btnSizer, wxSizerFlags().Right().Border(wxTOP));
868 #endif // wxUSE_CLIPBOARD || CAN_SAVE_FILES
869
870 win->SetSizer(paneSz);
871 paneSz->SetSizeHints(win);
872 #else // __SMARTPHONE__
873 SetLeftMenu(wxID_OK);
874 SetRightMenu(wxID_MORE, ms_details + EXPAND_SUFFIX);
875 #endif // __SMARTPHONE__/!__SMARTPHONE__
876
877 SetSizerAndFit(sizerTop);
878
879 Centre();
880
881 if (isPda)
882 {
883 // Move up the screen so that when we expand the dialog,
884 // there's enough space.
885 Move(wxPoint(GetPosition().x, GetPosition().y / 2));
886 }
887 }
888
889 void wxLogDialog::CreateDetailsControls(wxWindow *parent)
890 {
891 wxString fmt = wxLog::GetTimestamp();
892 bool hasTimeStamp = !fmt.IsEmpty();
893
894 // create the list ctrl now
895 m_listctrl = new wxListCtrl(parent, wxID_ANY,
896 wxDefaultPosition, wxDefaultSize,
897 wxSUNKEN_BORDER |
898 wxLC_REPORT |
899 wxLC_NO_HEADER |
900 wxLC_SINGLE_SEL);
901 #ifdef __WXWINCE__
902 // This makes a big aesthetic difference on WinCE but I
903 // don't want to risk problems on other platforms
904 m_listctrl->Hide();
905 #endif
906
907 // no need to translate these strings as they're not shown to the
908 // user anyhow (we use wxLC_NO_HEADER style)
909 m_listctrl->InsertColumn(0, _T("Message"));
910
911 if (hasTimeStamp)
912 m_listctrl->InsertColumn(1, _T("Time"));
913
914 // prepare the imagelist
915 static const int ICON_SIZE = 16;
916 wxImageList *imageList = new wxImageList(ICON_SIZE, ICON_SIZE);
917
918 // order should be the same as in the switch below!
919 static const wxChar* icons[] =
920 {
921 wxART_ERROR,
922 wxART_WARNING,
923 wxART_INFORMATION
924 };
925
926 bool loadedIcons = true;
927
928 for ( size_t icon = 0; icon < WXSIZEOF(icons); icon++ )
929 {
930 wxBitmap bmp = wxArtProvider::GetBitmap(icons[icon], wxART_MESSAGE_BOX,
931 wxSize(ICON_SIZE, ICON_SIZE));
932
933 // This may very well fail if there are insufficient colours available.
934 // Degrade gracefully.
935 if ( !bmp.Ok() )
936 {
937 loadedIcons = false;
938
939 break;
940 }
941
942 imageList->Add(bmp);
943 }
944
945 m_listctrl->SetImageList(imageList, wxIMAGE_LIST_SMALL);
946
947 // fill the listctrl
948 size_t count = m_messages.GetCount();
949 for ( size_t n = 0; n < count; n++ )
950 {
951 int image;
952
953 if ( loadedIcons )
954 {
955 switch ( m_severity[n] )
956 {
957 case wxLOG_Error:
958 image = 0;
959 break;
960
961 case wxLOG_Warning:
962 image = 1;
963 break;
964
965 default:
966 image = 2;
967 }
968 }
969 else // failed to load images
970 {
971 image = -1;
972 }
973
974 wxString msg = m_messages[n];
975 msg.Replace(wxT("\n"), wxT(" "));
976 msg = EllipsizeString(msg);
977
978 m_listctrl->InsertItem(n, msg, image);
979
980 if (hasTimeStamp)
981 m_listctrl->SetItem(n, 1, TimeStamp(fmt, (time_t)m_times[n]));
982 }
983
984 // let the columns size themselves
985 m_listctrl->SetColumnWidth(0, wxLIST_AUTOSIZE);
986 if (hasTimeStamp)
987 m_listctrl->SetColumnWidth(1, wxLIST_AUTOSIZE);
988
989 // calculate an approximately nice height for the listctrl
990 int height = GetCharHeight()*(count + 4);
991
992 // but check that the dialog won't fall fown from the screen
993 //
994 // we use GetMinHeight() to get the height of the dialog part without the
995 // details and we consider that the "Save" button below and the separator
996 // line (and the margins around it) take about as much, hence double it
997 int heightMax = wxGetDisplaySize().y - GetPosition().y - 2*GetMinHeight();
998
999 // we should leave a margin
1000 heightMax *= 9;
1001 heightMax /= 10;
1002
1003 m_listctrl->SetSize(wxDefaultCoord, wxMin(height, heightMax));
1004 }
1005
1006 void wxLogDialog::OnListItemActivated(wxListEvent& event)
1007 {
1008 // show the activated item in a message box
1009 // This allow the user to correctly display the logs which are longer
1010 // than the listctrl and thus gets truncated or those which contains
1011 // newlines.
1012
1013 // NB: don't do:
1014 // wxString str = m_listctrl->GetItemText(event.GetIndex());
1015 // as there's a 260 chars limit on the items inside a wxListCtrl in wxMSW.
1016 wxString str = m_messages[event.GetIndex()];
1017
1018 // wxMessageBox will nicely handle the '\n' in the string (if any)
1019 // and supports long strings
1020 wxMessageBox(str, wxT("Log message"), wxOK, this);
1021 }
1022
1023 void wxLogDialog::OnOk(wxCommandEvent& WXUNUSED(event))
1024 {
1025 EndModal(wxID_OK);
1026 }
1027
1028 #if CAN_SAVE_FILES || wxUSE_CLIPBOARD
1029
1030 wxString wxLogDialog::GetLogMessages() const
1031 {
1032 wxString fmt = wxLog::GetTimestamp();
1033 if ( fmt.empty() )
1034 {
1035 // use the default format
1036 fmt = "%c";
1037 }
1038
1039 const size_t count = m_messages.GetCount();
1040
1041 wxString text;
1042 text.reserve(count*m_messages[0].length());
1043 for ( size_t n = 0; n < count; n++ )
1044 {
1045 text << TimeStamp(fmt, (time_t)m_times[n])
1046 << ": "
1047 << m_messages[n]
1048 << wxTextFile::GetEOL();
1049 }
1050
1051 return text;
1052 }
1053
1054 #endif // CAN_SAVE_FILES || wxUSE_CLIPBOARD
1055
1056 #if wxUSE_CLIPBOARD
1057
1058 void wxLogDialog::OnCopy(wxCommandEvent& WXUNUSED(event))
1059 {
1060 wxClipboardLocker clip;
1061 if ( !clip ||
1062 !wxTheClipboard->AddData(new wxTextDataObject(GetLogMessages())) )
1063 {
1064 wxLogError(_("Failed to copy dialog contents to the clipboard."));
1065 }
1066 }
1067
1068 #endif // wxUSE_CLIPBOARD
1069
1070 #if CAN_SAVE_FILES
1071
1072 void wxLogDialog::OnSave(wxCommandEvent& WXUNUSED(event))
1073 {
1074 wxFile file;
1075 int rc = OpenLogFile(file, NULL, this);
1076 if ( rc == -1 )
1077 {
1078 // cancelled
1079 return;
1080 }
1081
1082 if ( !rc || !file.Write(GetLogMessages()) || !file.Close() )
1083 wxLogError(_("Can't save log contents to file."));
1084 }
1085
1086 #endif // CAN_SAVE_FILES
1087
1088 wxLogDialog::~wxLogDialog()
1089 {
1090 if ( m_listctrl )
1091 {
1092 delete m_listctrl->GetImageList(wxIMAGE_LIST_SMALL);
1093 }
1094 }
1095
1096 #endif // wxUSE_LOG_DIALOG
1097
1098 #if CAN_SAVE_FILES
1099
1100 // pass an uninitialized file object, the function will ask the user for the
1101 // filename and try to open it, returns true on success (file was opened),
1102 // false if file couldn't be opened/created and -1 if the file selection
1103 // dialog was cancelled
1104 static int OpenLogFile(wxFile& file, wxString *pFilename, wxWindow *parent)
1105 {
1106 // get the file name
1107 // -----------------
1108 wxString filename = wxSaveFileSelector(wxT("log"), wxT("txt"), wxT("log.txt"), parent);
1109 if ( !filename ) {
1110 // cancelled
1111 return -1;
1112 }
1113
1114 // open file
1115 // ---------
1116 bool bOk = true; // suppress warning about it being possible uninitialized
1117 if ( wxFile::Exists(filename) ) {
1118 bool bAppend = false;
1119 wxString strMsg;
1120 strMsg.Printf(_("Append log to file '%s' (choosing [No] will overwrite it)?"),
1121 filename.c_str());
1122 switch ( wxMessageBox(strMsg, _("Question"),
1123 wxICON_QUESTION | wxYES_NO | wxCANCEL) ) {
1124 case wxYES:
1125 bAppend = true;
1126 break;
1127
1128 case wxNO:
1129 bAppend = false;
1130 break;
1131
1132 case wxCANCEL:
1133 return -1;
1134
1135 default:
1136 wxFAIL_MSG(_("invalid message box return value"));
1137 }
1138
1139 if ( bAppend ) {
1140 bOk = file.Open(filename, wxFile::write_append);
1141 }
1142 else {
1143 bOk = file.Create(filename, true /* overwrite */);
1144 }
1145 }
1146 else {
1147 bOk = file.Create(filename);
1148 }
1149
1150 if ( pFilename )
1151 *pFilename = filename;
1152
1153 return bOk;
1154 }
1155
1156 #endif // CAN_SAVE_FILES
1157
1158 #endif // !(wxUSE_LOGGUI || wxUSE_LOGWINDOW)
1159
1160 #if wxUSE_LOG && wxUSE_TEXTCTRL
1161
1162 // ----------------------------------------------------------------------------
1163 // wxLogTextCtrl implementation
1164 // ----------------------------------------------------------------------------
1165
1166 wxLogTextCtrl::wxLogTextCtrl(wxTextCtrl *pTextCtrl)
1167 {
1168 m_pTextCtrl = pTextCtrl;
1169 }
1170
1171 void wxLogTextCtrl::DoLogText(const wxString& msg)
1172 {
1173 m_pTextCtrl->AppendText(msg + wxS('\n'));
1174 }
1175
1176 #endif // wxUSE_LOG && wxUSE_TEXTCTRL