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