allow copying text in the log dialogs
[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/textfile.h"
49 #include "wx/statline.h"
50 #include "wx/artprov.h"
51 #include "wx/collpane.h"
52 #include "wx/arrstr.h"
53
54 #if wxUSE_THREADS
55 #include "wx/thread.h"
56 #endif // wxUSE_THREADS
57
58 #ifdef __WXMSW__
59 // for OutputDebugString()
60 #include "wx/msw/private.h"
61 #endif // Windows
62
63
64 #ifdef __WXPM__
65 #include <time.h>
66 #endif
67
68 #if wxUSE_LOG_DIALOG
69 #include "wx/listctrl.h"
70 #include "wx/imaglist.h"
71 #include "wx/image.h"
72 #endif // wxUSE_LOG_DIALOG/!wxUSE_LOG_DIALOG
73
74 #if defined(__MWERKS__) && wxUSE_UNICODE
75 #include <wtime.h>
76 #endif
77
78 #include "wx/datetime.h"
79
80 // the suffix we add to the button to show that the dialog can be expanded
81 #define EXPAND_SUFFIX _T(" >>")
82
83 #define CAN_SAVE_FILES (wxUSE_FILE && wxUSE_FILEDLG)
84
85 // ----------------------------------------------------------------------------
86 // private classes
87 // ----------------------------------------------------------------------------
88
89 #if wxUSE_LOG_DIALOG
90
91 // this function is a wrapper around strftime(3)
92 // allows to exclude the usage of wxDateTime
93 static wxString TimeStamp(const wxString& format, time_t t)
94 {
95 #if wxUSE_DATETIME
96 wxChar buf[4096];
97 struct tm tm;
98 if ( !wxStrftime(buf, WXSIZEOF(buf), format, wxLocaltime_r(&t, &tm)) )
99 {
100 // buffer is too small?
101 wxFAIL_MSG(_T("strftime() failed"));
102 }
103 return wxString(buf);
104 #else // !wxUSE_DATETIME
105 return wxEmptyString;
106 #endif // wxUSE_DATETIME/!wxUSE_DATETIME
107 }
108
109
110 class wxLogDialog : public wxDialog
111 {
112 public:
113 wxLogDialog(wxWindow *parent,
114 const wxArrayString& messages,
115 const wxArrayInt& severity,
116 const wxArrayLong& timess,
117 const wxString& caption,
118 long style);
119 virtual ~wxLogDialog();
120
121 // event handlers
122 void OnOk(wxCommandEvent& event);
123 #if wxUSE_CLIPBOARD
124 void OnCopy(wxCommandEvent& event);
125 #endif // wxUSE_CLIPBOARD
126 #if CAN_SAVE_FILES
127 void OnSave(wxCommandEvent& event);
128 #endif // CAN_SAVE_FILES
129 void OnListSelect(wxListEvent& event);
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 DECLARE_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_SELECTED(wxID_ANY, wxLogDialog::OnListSelect)
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 msg.PrintfV(format, argptr);
229
230 wxASSERT( gs_pFrame == NULL ); // should be reset!
231 gs_pFrame = pFrame;
232 #ifdef __WXWINCE__
233 wxLog::OnLog(wxLOG_Status, msg, 0);
234 #else
235 wxLog::OnLog(wxLOG_Status, msg, time(NULL));
236 #endif
237 gs_pFrame = (wxFrame *) NULL;
238 }
239 }
240
241 #if !wxUSE_UTF8_LOCALE_ONLY
242 void wxDoLogStatusWchar(wxFrame *pFrame, const wxChar *format, ...)
243 {
244 va_list argptr;
245 va_start(argptr, format);
246 wxVLogStatus(pFrame, format, argptr);
247 va_end(argptr);
248 }
249 #endif // !wxUSE_UTF8_LOCALE_ONLY
250
251 #if wxUSE_UNICODE_UTF8
252 void wxDoLogStatusUtf8(wxFrame *pFrame, const char *format, ...)
253 {
254 va_list argptr;
255 va_start(argptr, format);
256 wxVLogStatus(pFrame, format, argptr);
257 va_end(argptr);
258 }
259 #endif // wxUSE_UNICODE_UTF8
260
261 // ----------------------------------------------------------------------------
262 // wxLogGui implementation (FIXME MT-unsafe)
263 // ----------------------------------------------------------------------------
264
265 #if wxUSE_LOGGUI
266
267 wxLogGui::wxLogGui()
268 {
269 Clear();
270 }
271
272 void wxLogGui::Clear()
273 {
274 m_bErrors =
275 m_bWarnings =
276 m_bHasMessages = false;
277
278 m_aMessages.Empty();
279 m_aSeverity.Empty();
280 m_aTimes.Empty();
281 }
282
283 void wxLogGui::Flush()
284 {
285 if ( !m_bHasMessages )
286 return;
287
288 // do it right now to block any new calls to Flush() while we're here
289 m_bHasMessages = false;
290
291 const unsigned repeatCount = LogLastRepeatIfNeeded();
292
293 wxString appName = wxTheApp->GetAppDisplayName();
294
295 long style;
296 wxString titleFormat;
297 if ( m_bErrors ) {
298 titleFormat = _("%s Error");
299 style = wxICON_STOP;
300 }
301 else if ( m_bWarnings ) {
302 titleFormat = _("%s Warning");
303 style = wxICON_EXCLAMATION;
304 }
305 else {
306 titleFormat = _("%s Information");
307 style = wxICON_INFORMATION;
308 }
309
310 wxString title;
311 title.Printf(titleFormat, appName.c_str());
312
313 size_t nMsgCount = m_aMessages.GetCount();
314
315 // avoid showing other log dialogs until we're done with the dialog we're
316 // showing right now: nested modal dialogs make for really bad UI!
317 Suspend();
318
319 wxString str;
320 if ( nMsgCount == 1 )
321 {
322 str = m_aMessages[0];
323 }
324 else // more than one message
325 {
326 #if wxUSE_LOG_DIALOG
327
328 if ( repeatCount > 0 )
329 {
330 m_aMessages[nMsgCount - 1]
331 << " (" << m_aMessages[nMsgCount - 2] << ")";
332 }
333
334 wxLogDialog dlg(NULL,
335 m_aMessages, m_aSeverity, m_aTimes,
336 title, style);
337
338 // clear the message list before showing the dialog because while it's
339 // shown some new messages may appear
340 Clear();
341
342 (void)dlg.ShowModal();
343 #else // !wxUSE_LOG_DIALOG
344 // concatenate all strings (but not too many to not overfill the msg box)
345 size_t nLines = 0;
346
347 // start from the most recent message
348 for ( size_t n = nMsgCount; n > 0; n-- ) {
349 // for Windows strings longer than this value are wrapped (NT 4.0)
350 const size_t nMsgLineWidth = 156;
351
352 nLines += (m_aMessages[n - 1].Len() + nMsgLineWidth - 1) / nMsgLineWidth;
353
354 if ( nLines > 25 ) // don't put too many lines in message box
355 break;
356
357 str << m_aMessages[n - 1] << wxT("\n");
358 }
359 #endif // wxUSE_LOG_DIALOG/!wxUSE_LOG_DIALOG
360 }
361
362 // this catches both cases of 1 message with wxUSE_LOG_DIALOG and any
363 // situation without it
364 if ( !str.empty() )
365 {
366 // we use a message dialog with 2 buttons to be able to use one of them
367 // for copying the message text to clipboard
368 #if wxUSE_CLIPBOARD
369 wxMessageDialog dlg(NULL, str, title, style | wxYES_NO);
370 if ( !dlg.SetYesNoLabels(wxID_COPY, wxID_OK) )
371 #endif // wxUSE_CLIPBOARD
372 {
373 // but if custom labels are not supported it makes no sense to keep
374 // two buttons so revert to a single one
375 dlg.SetMessageDialogStyle(style | wxOK);
376 }
377
378 #if wxUSE_CLIPBOARD
379 if ( dlg.ShowModal() == wxID_YES )
380 {
381 // this means the wxID_COPY button was selected
382 wxClipboardLocker clip;
383 if ( !clip || !wxTheClipboard->AddData(new wxTextDataObject(str)) )
384 wxLogError(_("Failed to copy dialog contents to the clipboard."));
385 }
386 #endif // wxUSE_CLIPBOARD
387
388 // no undisplayed messages whatsoever
389 Clear();
390 }
391
392 // allow flushing the logs again
393 Resume();
394 }
395
396 // log all kinds of messages
397 void wxLogGui::DoLog(wxLogLevel level, const wxString& szString, time_t t)
398 {
399 switch ( level ) {
400 case wxLOG_Info:
401 if ( GetVerbose() )
402 case wxLOG_Message:
403 {
404 m_aMessages.Add(szString);
405 m_aSeverity.Add(wxLOG_Message);
406 m_aTimes.Add((long)t);
407 m_bHasMessages = true;
408 }
409 break;
410
411 case wxLOG_Status:
412 #if wxUSE_STATUSBAR
413 {
414 // find the top window and set it's status text if it has any
415 wxFrame *pFrame = gs_pFrame;
416 if ( pFrame == NULL ) {
417 wxWindow *pWin = wxTheApp->GetTopWindow();
418 if ( pWin != NULL && pWin->IsKindOf(CLASSINFO(wxFrame)) ) {
419 pFrame = (wxFrame *)pWin;
420 }
421 }
422
423 if ( pFrame && pFrame->GetStatusBar() )
424 pFrame->SetStatusText(szString);
425 }
426 #endif // wxUSE_STATUSBAR
427 break;
428
429 case wxLOG_Trace:
430 case wxLOG_Debug:
431 #ifdef __WXDEBUG__
432 {
433 wxString str;
434 TimeStamp(&str);
435 str += szString;
436
437 #if defined(__WXMSW__) && !defined(__WXMICROWIN__)
438 // don't prepend debug/trace here: it goes to the
439 // debug window anyhow
440 str += wxT("\r\n");
441 OutputDebugString(str.wx_str());
442 #else
443 // send them to stderr
444 wxFprintf(stderr, wxT("[%s] %s\n"),
445 level == wxLOG_Trace ? wxT("Trace")
446 : wxT("Debug"),
447 str.c_str());
448 fflush(stderr);
449 #endif
450 }
451 #endif // __WXDEBUG__
452
453 break;
454
455 case wxLOG_FatalError:
456 // show this one immediately
457 wxMessageBox(szString, _("Fatal error"), wxICON_HAND);
458 wxExit();
459 break;
460
461 case wxLOG_Error:
462 if ( !m_bErrors ) {
463 #if !wxUSE_LOG_DIALOG
464 // discard earlier informational messages if this is the 1st
465 // error because they might not make sense any more and showing
466 // them in a message box might be confusing
467 m_aMessages.Empty();
468 m_aSeverity.Empty();
469 m_aTimes.Empty();
470 #endif // wxUSE_LOG_DIALOG
471 m_bErrors = true;
472 }
473 // fall through
474
475 case wxLOG_Warning:
476 if ( !m_bErrors ) {
477 // for the warning we don't discard the info messages
478 m_bWarnings = true;
479 }
480
481 m_aMessages.Add(szString);
482 m_aSeverity.Add((int)level);
483 m_aTimes.Add((long)t);
484 m_bHasMessages = true;
485 break;
486 }
487 }
488
489 #endif // wxUSE_LOGGUI
490
491 // ----------------------------------------------------------------------------
492 // wxLogWindow and wxLogFrame implementation
493 // ----------------------------------------------------------------------------
494
495 #if wxUSE_LOGWINDOW
496
497 // log frame class
498 // ---------------
499 class wxLogFrame : public wxFrame
500 {
501 public:
502 // ctor & dtor
503 wxLogFrame(wxWindow *pParent, wxLogWindow *log, const wxString& szTitle);
504 virtual ~wxLogFrame();
505
506 // menu callbacks
507 void OnClose(wxCommandEvent& event);
508 void OnCloseWindow(wxCloseEvent& event);
509 #if CAN_SAVE_FILES
510 void OnSave(wxCommandEvent& event);
511 #endif // CAN_SAVE_FILES
512 void OnClear(wxCommandEvent& event);
513
514 // this function is safe to call from any thread (notice that it should be
515 // also called from the main thread to ensure that the messages logged from
516 // it appear in correct order with the messages from the other threads)
517 void AddLogMessage(const wxString& message);
518
519 // actually append the messages logged from secondary threads to the text
520 // control during idle time in the main thread
521 virtual void OnInternalIdle();
522
523 private:
524 // use standard ids for our commands!
525 enum
526 {
527 Menu_Close = wxID_CLOSE,
528 Menu_Save = wxID_SAVE,
529 Menu_Clear = wxID_CLEAR
530 };
531
532 // common part of OnClose() and OnCloseWindow()
533 void DoClose();
534
535 // do show the message in the text control
536 void DoShowLogMessage(const wxString& message)
537 {
538 m_pTextCtrl->AppendText(message);
539 }
540
541 wxTextCtrl *m_pTextCtrl;
542 wxLogWindow *m_log;
543
544 // queue of messages logged from other threads which need to be displayed
545 wxArrayString m_pendingMessages;
546
547 #if wxUSE_THREADS
548 // critical section to protect access to m_pendingMessages
549 wxCriticalSection m_critSection;
550 #endif // wxUSE_THREADS
551
552
553 DECLARE_EVENT_TABLE()
554 DECLARE_NO_COPY_CLASS(wxLogFrame)
555 };
556
557 BEGIN_EVENT_TABLE(wxLogFrame, wxFrame)
558 // wxLogWindow menu events
559 EVT_MENU(Menu_Close, wxLogFrame::OnClose)
560 #if CAN_SAVE_FILES
561 EVT_MENU(Menu_Save, wxLogFrame::OnSave)
562 #endif // CAN_SAVE_FILES
563 EVT_MENU(Menu_Clear, wxLogFrame::OnClear)
564
565 EVT_CLOSE(wxLogFrame::OnCloseWindow)
566 END_EVENT_TABLE()
567
568 wxLogFrame::wxLogFrame(wxWindow *pParent, wxLogWindow *log, const wxString& szTitle)
569 : wxFrame(pParent, wxID_ANY, szTitle)
570 {
571 m_log = log;
572
573 m_pTextCtrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition,
574 wxDefaultSize,
575 wxTE_MULTILINE |
576 wxHSCROLL |
577 // needed for Win32 to avoid 65Kb limit but it doesn't work well
578 // when using RichEdit 2.0 which we always do in the Unicode build
579 #if !wxUSE_UNICODE
580 wxTE_RICH |
581 #endif // !wxUSE_UNICODE
582 wxTE_READONLY);
583
584 #if wxUSE_MENUS
585 // create menu
586 wxMenuBar *pMenuBar = new wxMenuBar;
587 wxMenu *pMenu = new wxMenu;
588 #if CAN_SAVE_FILES
589 pMenu->Append(Menu_Save, _("&Save..."), _("Save log contents to file"));
590 #endif // CAN_SAVE_FILES
591 pMenu->Append(Menu_Clear, _("C&lear"), _("Clear the log contents"));
592 pMenu->AppendSeparator();
593 pMenu->Append(Menu_Close, _("&Close"), _("Close this window"));
594 pMenuBar->Append(pMenu, _("&Log"));
595 SetMenuBar(pMenuBar);
596 #endif // wxUSE_MENUS
597
598 #if wxUSE_STATUSBAR
599 // status bar for menu prompts
600 CreateStatusBar();
601 #endif // wxUSE_STATUSBAR
602
603 m_log->OnFrameCreate(this);
604 }
605
606 void wxLogFrame::DoClose()
607 {
608 if ( m_log->OnFrameClose(this) )
609 {
610 // instead of closing just hide the window to be able to Show() it
611 // later
612 Show(false);
613 }
614 }
615
616 void wxLogFrame::OnClose(wxCommandEvent& WXUNUSED(event))
617 {
618 DoClose();
619 }
620
621 void wxLogFrame::OnCloseWindow(wxCloseEvent& WXUNUSED(event))
622 {
623 DoClose();
624 }
625
626 #if CAN_SAVE_FILES
627 void wxLogFrame::OnSave(wxCommandEvent& WXUNUSED(event))
628 {
629 wxString filename;
630 wxFile file;
631 int rc = OpenLogFile(file, &filename, this);
632 if ( rc == -1 )
633 {
634 // cancelled
635 return;
636 }
637
638 bool bOk = rc != 0;
639
640 // retrieve text and save it
641 // -------------------------
642 int nLines = m_pTextCtrl->GetNumberOfLines();
643 for ( int nLine = 0; bOk && nLine < nLines; nLine++ ) {
644 bOk = file.Write(m_pTextCtrl->GetLineText(nLine) +
645 wxTextFile::GetEOL());
646 }
647
648 if ( bOk )
649 bOk = file.Close();
650
651 if ( !bOk ) {
652 wxLogError(_("Can't save log contents to file."));
653 }
654 else {
655 wxLogStatus((wxFrame*)this, _("Log saved to the file '%s'."), filename.c_str());
656 }
657 }
658 #endif // CAN_SAVE_FILES
659
660 void wxLogFrame::OnClear(wxCommandEvent& WXUNUSED(event))
661 {
662 m_pTextCtrl->Clear();
663 }
664
665 void wxLogFrame::OnInternalIdle()
666 {
667 {
668 wxCRIT_SECT_LOCKER(locker, m_critSection);
669
670 const size_t count = m_pendingMessages.size();
671 for ( size_t n = 0; n < count; n++ )
672 {
673 DoShowLogMessage(m_pendingMessages[n]);
674 }
675
676 m_pendingMessages.clear();
677 } // release m_critSection
678
679 wxFrame::OnInternalIdle();
680 }
681
682 void wxLogFrame::AddLogMessage(const wxString& message)
683 {
684 wxCRIT_SECT_LOCKER(locker, m_critSection);
685
686 #if wxUSE_THREADS
687 if ( !wxThread::IsMain() || !m_pendingMessages.empty() )
688 {
689 // message needs to be queued for later showing
690 m_pendingMessages.Add(message);
691
692 wxWakeUpIdle();
693 }
694 else // we are the main thread and no messages are queued, so we can
695 // log the message directly
696 #endif // wxUSE_THREADS
697 {
698 DoShowLogMessage(message);
699 }
700 }
701
702 wxLogFrame::~wxLogFrame()
703 {
704 m_log->OnFrameDelete(this);
705 }
706
707 // wxLogWindow
708 // -----------
709
710 wxLogWindow::wxLogWindow(wxWindow *pParent,
711 const wxString& szTitle,
712 bool bShow,
713 bool bDoPass)
714 {
715 PassMessages(bDoPass);
716
717 m_pLogFrame = new wxLogFrame(pParent, this, szTitle);
718
719 if ( bShow )
720 m_pLogFrame->Show();
721 }
722
723 void wxLogWindow::Show(bool bShow)
724 {
725 m_pLogFrame->Show(bShow);
726 }
727
728 void wxLogWindow::DoLog(wxLogLevel level, const wxString& szString, time_t t)
729 {
730 // first let the previous logger show it
731 wxLogPassThrough::DoLog(level, szString, t);
732
733 if ( m_pLogFrame ) {
734 switch ( level ) {
735 case wxLOG_Status:
736 // by default, these messages are ignored by wxLog, so process
737 // them ourselves
738 if ( !szString.empty() )
739 {
740 wxString str;
741 str << _("Status: ") << szString;
742 LogString(str, t);
743 }
744 break;
745
746 // don't put trace messages in the text window for 2 reasons:
747 // 1) there are too many of them
748 // 2) they may provoke other trace messages thus sending a program
749 // into an infinite loop
750 case wxLOG_Trace:
751 break;
752
753 default:
754 // and this will format it nicely and call our DoLogString()
755 wxLog::DoLog(level, szString, t);
756 }
757 }
758 }
759
760 void wxLogWindow::DoLogString(const wxString& szString, time_t WXUNUSED(t))
761 {
762 wxString msg;
763
764 TimeStamp(&msg);
765 msg << szString << wxT('\n');
766
767 m_pLogFrame->AddLogMessage(msg);
768 }
769
770 wxFrame *wxLogWindow::GetFrame() const
771 {
772 return m_pLogFrame;
773 }
774
775 void wxLogWindow::OnFrameCreate(wxFrame * WXUNUSED(frame))
776 {
777 }
778
779 bool wxLogWindow::OnFrameClose(wxFrame * WXUNUSED(frame))
780 {
781 // allow to close
782 return true;
783 }
784
785 void wxLogWindow::OnFrameDelete(wxFrame * WXUNUSED(frame))
786 {
787 m_pLogFrame = (wxLogFrame *)NULL;
788 }
789
790 wxLogWindow::~wxLogWindow()
791 {
792 // may be NULL if log frame already auto destroyed itself
793 delete m_pLogFrame;
794 }
795
796 #endif // wxUSE_LOGWINDOW
797
798 // ----------------------------------------------------------------------------
799 // wxLogDialog
800 // ----------------------------------------------------------------------------
801
802 #if wxUSE_LOG_DIALOG
803
804 wxString wxLogDialog::ms_details;
805 size_t wxLogDialog::ms_maxLength = 0;
806
807 wxLogDialog::wxLogDialog(wxWindow *parent,
808 const wxArrayString& messages,
809 const wxArrayInt& severity,
810 const wxArrayLong& times,
811 const wxString& caption,
812 long style)
813 : wxDialog(parent, wxID_ANY, caption,
814 wxDefaultPosition, wxDefaultSize,
815 wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
816 {
817 // init the static variables:
818
819 if ( ms_details.empty() )
820 {
821 // ensure that we won't loop here if wxGetTranslation()
822 // happens to pop up a Log message while translating this :-)
823 ms_details = wxTRANSLATE("&Details");
824 ms_details = wxGetTranslation(ms_details);
825 #ifdef __SMARTPHONE__
826 ms_details = wxStripMenuCodes(ms_details);
827 #endif
828 }
829
830 if ( ms_maxLength == 0 )
831 {
832 ms_maxLength = (2 * wxGetDisplaySize().x/3) / GetCharWidth();
833 }
834
835 size_t count = messages.GetCount();
836 m_messages.Alloc(count);
837 m_severity.Alloc(count);
838 m_times.Alloc(count);
839
840 for ( size_t n = 0; n < count; n++ )
841 {
842 m_messages.Add(messages[n]);
843 m_severity.Add(severity[n]);
844 m_times.Add(times[n]);
845 }
846
847 m_listctrl = NULL;
848
849 bool isPda = (wxSystemSettings::GetScreenType() <= wxSYS_SCREEN_PDA);
850
851 // create the controls which are always shown and layout them: we use
852 // sizers even though our window is not resizeable to calculate the size of
853 // the dialog properly
854 wxBoxSizer *sizerTop = new wxBoxSizer(wxVERTICAL);
855 wxBoxSizer *sizerAll = new wxBoxSizer(isPda ? wxVERTICAL : wxHORIZONTAL);
856
857 if (!isPda)
858 {
859 wxStaticBitmap *icon = new wxStaticBitmap
860 (
861 this,
862 wxID_ANY,
863 wxArtProvider::GetMessageBoxIcon(style)
864 );
865 sizerAll->Add(icon, wxSizerFlags().Centre());
866 }
867
868 // create the text sizer with a minimal size so that we are sure it won't be too small
869 wxString message = EllipsizeString(messages.Last());
870 wxSizer *szText = CreateTextSizer(message);
871 szText->SetMinSize(wxMin(300, wxGetDisplaySize().x / 3), -1);
872
873 sizerAll->Add(szText, wxSizerFlags(1).Centre().Border(wxLEFT | wxRIGHT));
874
875 wxButton *btnOk = new wxButton(this, wxID_OK);
876 sizerAll->Add(btnOk, wxSizerFlags().Centre());
877
878 sizerTop->Add(sizerAll, wxSizerFlags().Expand().Border());
879
880
881 // add the details pane
882 #ifndef __SMARTPHONE__
883 wxCollapsiblePane * const
884 collpane = new wxCollapsiblePane(this, wxID_ANY, ms_details);
885 sizerTop->Add(collpane, wxSizerFlags(1).Expand().Border());
886
887 wxWindow *win = collpane->GetPane();
888 wxSizer * const paneSz = new wxBoxSizer(wxVERTICAL);
889
890 CreateDetailsControls(win);
891
892 paneSz->Add(m_listctrl, wxSizerFlags(1).Expand().Border(wxTOP));
893
894 #if wxUSE_CLIPBOARD || CAN_SAVE_FILES
895 wxBoxSizer * const btnSizer = new wxBoxSizer(wxHORIZONTAL);
896
897 wxSizerFlags flagsBtn;
898 flagsBtn.Border(wxLEFT);
899
900 #if wxUSE_CLIPBOARD
901 btnSizer->Add(new wxButton(win, wxID_COPY), flagsBtn);
902 #endif // wxUSE_CLIPBOARD
903
904 #if CAN_SAVE_FILES
905 btnSizer->Add(new wxButton(win, wxID_SAVE), flagsBtn);
906 #endif // CAN_SAVE_FILES
907
908 paneSz->Add(btnSizer, wxSizerFlags().Right().Border(wxTOP));
909 #endif // wxUSE_CLIPBOARD || CAN_SAVE_FILES
910
911 win->SetSizer(paneSz);
912 paneSz->SetSizeHints(win);
913 #else // __SMARTPHONE__
914 SetLeftMenu(wxID_OK);
915 SetRightMenu(wxID_MORE, ms_details + EXPAND_SUFFIX);
916 #endif // __SMARTPHONE__/!__SMARTPHONE__
917
918 SetSizerAndFit(sizerTop);
919
920 Centre();
921
922 if (isPda)
923 {
924 // Move up the screen so that when we expand the dialog,
925 // there's enough space.
926 Move(wxPoint(GetPosition().x, GetPosition().y / 2));
927 }
928 }
929
930 void wxLogDialog::CreateDetailsControls(wxWindow *parent)
931 {
932 // create the list ctrl now
933 m_listctrl = new wxListCtrl(parent, wxID_ANY,
934 wxDefaultPosition, wxDefaultSize,
935 wxSUNKEN_BORDER |
936 wxLC_REPORT |
937 wxLC_NO_HEADER |
938 wxLC_SINGLE_SEL);
939 #ifdef __WXWINCE__
940 // This makes a big aesthetic difference on WinCE but I
941 // don't want to risk problems on other platforms
942 m_listctrl->Hide();
943 #endif
944
945 // no need to translate these strings as they're not shown to the
946 // user anyhow (we use wxLC_NO_HEADER style)
947 m_listctrl->InsertColumn(0, _T("Message"));
948 m_listctrl->InsertColumn(1, _T("Time"));
949
950 // prepare the imagelist
951 static const int ICON_SIZE = 16;
952 wxImageList *imageList = new wxImageList(ICON_SIZE, ICON_SIZE);
953
954 // order should be the same as in the switch below!
955 static const wxChar* icons[] =
956 {
957 wxART_ERROR,
958 wxART_WARNING,
959 wxART_INFORMATION
960 };
961
962 bool loadedIcons = true;
963
964 for ( size_t icon = 0; icon < WXSIZEOF(icons); icon++ )
965 {
966 wxBitmap bmp = wxArtProvider::GetBitmap(icons[icon], wxART_MESSAGE_BOX,
967 wxSize(ICON_SIZE, ICON_SIZE));
968
969 // This may very well fail if there are insufficient colours available.
970 // Degrade gracefully.
971 if ( !bmp.Ok() )
972 {
973 loadedIcons = false;
974
975 break;
976 }
977
978 imageList->Add(bmp);
979 }
980
981 m_listctrl->SetImageList(imageList, wxIMAGE_LIST_SMALL);
982
983 // and fill it
984 wxString fmt = wxLog::GetTimestamp();
985 if ( !fmt )
986 {
987 // default format
988 fmt = _T("%c");
989 }
990
991 size_t count = m_messages.GetCount();
992 for ( size_t n = 0; n < count; n++ )
993 {
994 int image;
995
996 if ( loadedIcons )
997 {
998 switch ( m_severity[n] )
999 {
1000 case wxLOG_Error:
1001 image = 0;
1002 break;
1003
1004 case wxLOG_Warning:
1005 image = 1;
1006 break;
1007
1008 default:
1009 image = 2;
1010 }
1011 }
1012 else // failed to load images
1013 {
1014 image = -1;
1015 }
1016
1017 wxString msg = m_messages[n];
1018 msg.Replace(wxT("\n"), wxT(" "));
1019 msg = EllipsizeString(msg);
1020
1021 m_listctrl->InsertItem(n, msg, image);
1022 m_listctrl->SetItem(n, 1, TimeStamp(fmt, (time_t)m_times[n]));
1023 }
1024
1025 // let the columns size themselves
1026 m_listctrl->SetColumnWidth(0, wxLIST_AUTOSIZE);
1027 m_listctrl->SetColumnWidth(1, wxLIST_AUTOSIZE);
1028
1029 // calculate an approximately nice height for the listctrl
1030 int height = GetCharHeight()*(count + 4);
1031
1032 // but check that the dialog won't fall fown from the screen
1033 //
1034 // we use GetMinHeight() to get the height of the dialog part without the
1035 // details and we consider that the "Save" button below and the separator
1036 // line (and the margins around it) take about as much, hence double it
1037 int heightMax = wxGetDisplaySize().y - GetPosition().y - 2*GetMinHeight();
1038
1039 // we should leave a margin
1040 heightMax *= 9;
1041 heightMax /= 10;
1042
1043 m_listctrl->SetSize(wxDefaultCoord, wxMin(height, heightMax));
1044 }
1045
1046 void wxLogDialog::OnListSelect(wxListEvent& event)
1047 {
1048 // we can't just disable the control because this looks ugly under Windows
1049 // (wrong bg colour, no scrolling...), but we still want to disable
1050 // selecting items - it makes no sense here
1051 m_listctrl->SetItemState(event.GetIndex(), 0, wxLIST_STATE_SELECTED);
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 = false;
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