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