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