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