X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/e90c1d2a19361551eb07778280f22be3e759cf64..05e0b047d879cdbfade7f2ab346c0acdf3e29f96:/src/generic/logg.cpp?ds=inline diff --git a/src/generic/logg.cpp b/src/generic/logg.cpp index bf9af38c48..368cfe4d8b 100644 --- a/src/generic/logg.cpp +++ b/src/generic/logg.cpp @@ -1,5 +1,5 @@ ///////////////////////////////////////////////////////////////////////////// -// Name: logg.cpp +// Name: src/generic/logg.cpp // Purpose: wxLog-derived classes which need GUI support (the rest is in // src/common/log.cpp) // Author: Vadim Zeitlin @@ -7,7 +7,7 @@ // Created: 20.09.99 (extracted from src/common/log.cpp) // RCS-ID: $Id$ // Copyright: (c) 1998 Vadim Zeitlin -// Licence: wxWindows license +// Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// // ============================================================================ @@ -18,21 +18,16 @@ // headers // ---------------------------------------------------------------------------- -// no #pragma implementation "log.h" because it's already in src/common/log.cpp - // For compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" #ifdef __BORLANDC__ - #pragma hdrstop -#endif - -#if !wxUSE_GUI - #error "This file can't be compiled without GUI!" + #pragma hdrstop #endif #ifndef WX_PRECOMP #include "wx/app.h" + #include "wx/button.h" #include "wx/intl.h" #include "wx/log.h" #include "wx/menu.h" @@ -40,18 +35,172 @@ #include "wx/filedlg.h" #include "wx/msgdlg.h" #include "wx/textctrl.h" + #include "wx/sizer.h" + #include "wx/statbmp.h" + #include "wx/settings.h" + #include "wx/wxcrtvararg.h" #endif // WX_PRECOMP +#if wxUSE_LOGGUI || wxUSE_LOGWINDOW + #include "wx/file.h" #include "wx/textfile.h" +#include "wx/statline.h" +#include "wx/artprov.h" +#include "wx/collpane.h" +#include "wx/arrstr.h" + +#if wxUSE_THREADS + #include "wx/thread.h" +#endif // wxUSE_THREADS + +#ifdef __WXMSW__ + // for OutputDebugString() + #include "wx/msw/private.h" +#endif // Windows + + +#ifdef __WXPM__ + #include +#endif + +#if wxUSE_LOG_DIALOG + #include "wx/listctrl.h" + #include "wx/imaglist.h" + #include "wx/image.h" +#endif // wxUSE_LOG_DIALOG/!wxUSE_LOG_DIALOG + +#if defined(__MWERKS__) && wxUSE_UNICODE + #include +#endif + +#include "wx/datetime.h" + +// the suffix we add to the button to show that the dialog can be expanded +#define EXPAND_SUFFIX _T(" >>") + +// ---------------------------------------------------------------------------- +// private classes +// ---------------------------------------------------------------------------- + +#if wxUSE_LOG_DIALOG + +// this function is a wrapper around strftime(3) +// allows to exclude the usage of wxDateTime +static wxString TimeStamp(const wxString& format, time_t t) +{ +#if wxUSE_DATETIME + wxChar buf[4096]; + struct tm tm; + if ( !wxStrftime(buf, WXSIZEOF(buf), format, wxLocaltime_r(&t, &tm)) ) + { + // buffer is too small? + wxFAIL_MSG(_T("strftime() failed")); + } + return wxString(buf); +#else // !wxUSE_DATETIME + return wxEmptyString; +#endif // wxUSE_DATETIME/!wxUSE_DATETIME +} + + +class wxLogDialog : public wxDialog +{ +public: + wxLogDialog(wxWindow *parent, + const wxArrayString& messages, + const wxArrayInt& severity, + const wxArrayLong& timess, + const wxString& caption, + long style); + virtual ~wxLogDialog(); + + // event handlers + void OnOk(wxCommandEvent& event); +#if wxUSE_FILE + void OnSave(wxCommandEvent& event); +#endif // wxUSE_FILE + void OnListSelect(wxListEvent& event); + void OnListItemActivated(wxListEvent& event); + +private: + // create controls needed for the details display + void CreateDetailsControls(wxWindow *); + + // if necessary truncates the given string and adds an ellipsis + wxString EllipsizeString(const wxString &text) + { + if (ms_maxLength > 0 && + text.length() > ms_maxLength) + { + wxString ret(text); + ret.Truncate(ms_maxLength); + ret << "..."; + return ret; + } + + return text; + } + + // the data for the listctrl + wxArrayString m_messages; + wxArrayInt m_severity; + wxArrayLong m_times; + + // the controls which are not shown initially (but only when details + // button is pressed) + wxListCtrl *m_listctrl; +#ifndef __SMARTPHONE__ +#if wxUSE_STATLINE + wxStaticLine *m_statline; +#endif // wxUSE_STATLINE +#if wxUSE_FILE + wxButton *m_btnSave; +#endif // wxUSE_FILE +#endif // __SMARTPHONE__ + + // the translated "Details" string + static wxString ms_details; + + // the maximum length of the log message + static size_t ms_maxLength; + + DECLARE_EVENT_TABLE() + DECLARE_NO_COPY_CLASS(wxLogDialog) +}; + +BEGIN_EVENT_TABLE(wxLogDialog, wxDialog) + EVT_BUTTON(wxID_OK, wxLogDialog::OnOk) +#if wxUSE_FILE + EVT_BUTTON(wxID_SAVE, wxLogDialog::OnSave) +#endif // wxUSE_FILE + EVT_LIST_ITEM_SELECTED(wxID_ANY, wxLogDialog::OnListSelect) + EVT_LIST_ITEM_ACTIVATED(wxID_ANY, wxLogDialog::OnListItemActivated) +END_EVENT_TABLE() + +#endif // wxUSE_LOG_DIALOG + +// ---------------------------------------------------------------------------- +// private functions +// ---------------------------------------------------------------------------- + +#if wxUSE_FILE && wxUSE_FILEDLG + +// pass an uninitialized file object, the function will ask the user for the +// filename and try to open it, returns true on success (file was opened), +// false if file couldn't be opened/created and -1 if the file selection +// dialog was cancelled +static int OpenLogFile(wxFile& file, wxString *filename = NULL, wxWindow *parent = NULL); + +#endif // wxUSE_FILE // ---------------------------------------------------------------------------- // global variables // ---------------------------------------------------------------------------- // we use a global variable to store the frame pointer for wxLogStatus - bad, -// but it's he easiest way -static wxFrame *gs_pFrame; // FIXME MT-unsafe +// but it's the easiest way +static wxFrame *gs_pFrame = NULL; // FIXME MT-unsafe // ============================================================================ // implementation @@ -63,46 +212,51 @@ static wxFrame *gs_pFrame; // FIXME MT-unsafe // accepts an additional argument which tells to which frame the output should // be directed -void wxLogStatus(wxFrame *pFrame, const wxChar *szFormat, ...) +void wxVLogStatus(wxFrame *pFrame, const wxString& format, va_list argptr) { wxString msg; wxLog *pLog = wxLog::GetActiveTarget(); if ( pLog != NULL ) { - va_list argptr; - va_start(argptr, szFormat); - msg.PrintfV(szFormat, argptr); - va_end(argptr); + msg.PrintfV(format, argptr); wxASSERT( gs_pFrame == NULL ); // should be reset! gs_pFrame = pFrame; +#ifdef __WXWINCE__ + wxLog::OnLog(wxLOG_Status, msg, 0); +#else wxLog::OnLog(wxLOG_Status, msg, time(NULL)); +#endif gs_pFrame = (wxFrame *) NULL; } } -// ---------------------------------------------------------------------------- -// wxLogTextCtrl implementation -// ---------------------------------------------------------------------------- - -wxLogTextCtrl::wxLogTextCtrl(wxTextCtrl *pTextCtrl) +#if !wxUSE_UTF8_LOCALE_ONLY +void wxDoLogStatusWchar(wxFrame *pFrame, const wxChar *format, ...) { - m_pTextCtrl = pTextCtrl; + va_list argptr; + va_start(argptr, format); + wxVLogStatus(pFrame, format, argptr); + va_end(argptr); } +#endif // !wxUSE_UTF8_LOCALE_ONLY -void wxLogTextCtrl::DoLogString(const wxChar *szString, time_t WXUNUSED(t)) +#if wxUSE_UNICODE_UTF8 +void wxDoLogStatusUtf8(wxFrame *pFrame, const char *format, ...) { - wxString msg; - TimeStamp(&msg); - msg << szString << T('\n'); - - m_pTextCtrl->AppendText(msg); + va_list argptr; + va_start(argptr, format); + wxVLogStatus(pFrame, format, argptr); + va_end(argptr); } +#endif // wxUSE_UNICODE_UTF8 // ---------------------------------------------------------------------------- // wxLogGui implementation (FIXME MT-unsafe) // ---------------------------------------------------------------------------- +#if wxUSE_LOGGUI + wxLogGui::wxLogGui() { Clear(); @@ -110,8 +264,12 @@ wxLogGui::wxLogGui() void wxLogGui::Clear() { - m_bErrors = m_bWarnings = FALSE; + m_bErrors = + m_bWarnings = + m_bHasMessages = false; + m_aMessages.Empty(); + m_aSeverity.Empty(); m_aTimes.Empty(); } @@ -121,147 +279,201 @@ void wxLogGui::Flush() return; // do it right now to block any new calls to Flush() while we're here - m_bHasMessages = FALSE; - - // concatenate all strings (but not too many to not overfill the msg box) - wxString str; - size_t nLines = 0, - nMsgCount = m_aMessages.Count(); - - // start from the most recent message - for ( size_t n = nMsgCount; n > 0; n-- ) { - // for Windows strings longer than this value are wrapped (NT 4.0) - const size_t nMsgLineWidth = 156; - - nLines += (m_aMessages[n - 1].Len() + nMsgLineWidth - 1) / nMsgLineWidth; + m_bHasMessages = false; - if ( nLines > 25 ) // don't put too many lines in message box - break; + const unsigned repeatCount = LogLastRepeatIfNeeded(); - str << m_aMessages[n - 1] << T("\n"); - } + wxString appName = wxTheApp->GetAppDisplayName(); - const wxChar *title; long style; - + wxString titleFormat; if ( m_bErrors ) { - title = _("Error"); + titleFormat = _("%s Error"); style = wxICON_STOP; } else if ( m_bWarnings ) { - title = _("Warning"); + titleFormat = _("%s Warning"); style = wxICON_EXCLAMATION; } else { - title = _("Information"); + titleFormat = _("%s Information"); style = wxICON_INFORMATION; } - wxMessageBox(str, title, wxOK | style); + wxString title; + title.Printf(titleFormat, appName.c_str()); - // no undisplayed messages whatsoever - Clear(); - - // do it here again - m_bHasMessages = FALSE; + size_t nMsgCount = m_aMessages.GetCount(); + + // avoid showing other log dialogs until we're done with the dialog we're + // showing right now: nested modal dialogs make for really bad UI! + Suspend(); + + wxString str; + if ( nMsgCount == 1 ) + { + str = m_aMessages[0]; + } + else // more than one message + { +#if wxUSE_LOG_DIALOG + + if ( repeatCount > 0 ) + { + m_aMessages[nMsgCount - 1] + << " (" << m_aMessages[nMsgCount - 2] << ")"; + } + + wxLogDialog dlg(NULL, + m_aMessages, m_aSeverity, m_aTimes, + title, style); + + // clear the message list before showing the dialog because while it's + // shown some new messages may appear + Clear(); + + (void)dlg.ShowModal(); +#else // !wxUSE_LOG_DIALOG + // concatenate all strings (but not too many to not overfill the msg box) + size_t nLines = 0; + + // start from the most recent message + for ( size_t n = nMsgCount; n > 0; n-- ) { + // for Windows strings longer than this value are wrapped (NT 4.0) + const size_t nMsgLineWidth = 156; + + nLines += (m_aMessages[n - 1].Len() + nMsgLineWidth - 1) / nMsgLineWidth; + + if ( nLines > 25 ) // don't put too many lines in message box + break; + + str << m_aMessages[n - 1] << wxT("\n"); + } +#endif // wxUSE_LOG_DIALOG/!wxUSE_LOG_DIALOG + } + + // this catches both cases of 1 message with wxUSE_LOG_DIALOG and any + // situation without it + if ( !str.empty() ) + { + wxMessageBox(str, title, wxOK | style); + + // no undisplayed messages whatsoever + Clear(); + } + + // allow flushing the logs again + Resume(); } -// the default behaviour is to discard all informational messages if there -// are any errors/warnings. -void wxLogGui::DoLog(wxLogLevel level, const wxChar *szString, time_t t) +// log all kinds of messages +void wxLogGui::DoLog(wxLogLevel level, const wxString& szString, time_t t) { switch ( level ) { case wxLOG_Info: if ( GetVerbose() ) case wxLOG_Message: - if ( !m_bErrors ) { - m_aMessages.Add(szString); - m_aTimes.Add((long)t); - m_bHasMessages = TRUE; - } - break; + { + m_aMessages.Add(szString); + m_aSeverity.Add(wxLOG_Message); + m_aTimes.Add((long)t); + m_bHasMessages = true; + } + break; case wxLOG_Status: #if wxUSE_STATUSBAR - { - // find the top window and set it's status text if it has any - wxFrame *pFrame = gs_pFrame; - if ( pFrame == NULL ) { - wxWindow *pWin = wxTheApp->GetTopWindow(); - if ( pWin != NULL && pWin->IsKindOf(CLASSINFO(wxFrame)) ) { - pFrame = (wxFrame *)pWin; - } + { + // find the top window and set it's status text if it has any + wxFrame *pFrame = gs_pFrame; + if ( pFrame == NULL ) { + wxWindow *pWin = wxTheApp->GetTopWindow(); + if ( pWin != NULL && pWin->IsKindOf(CLASSINFO(wxFrame)) ) { + pFrame = (wxFrame *)pWin; } - - if ( pFrame != NULL ) - pFrame->SetStatusText(szString); } + + if ( pFrame && pFrame->GetStatusBar() ) + pFrame->SetStatusText(szString); + } #endif // wxUSE_STATUSBAR - break; + break; case wxLOG_Trace: case wxLOG_Debug: - #ifdef __WXDEBUG__ - { - #ifdef __WXMSW__ - // don't prepend debug/trace here: it goes to the - // debug window anyhow, but do put a timestamp - wxString str; - TimeStamp(&str); - str << szString << T("\n\r"); - OutputDebugString(str); - #else - // send them to stderr - wxFprintf(stderr, T("%s: %s\n"), - level == wxLOG_Trace ? T("Trace") - : T("Debug"), - szString); - fflush(stderr); - #endif - } - #endif // __WXDEBUG__ + #ifdef __WXDEBUG__ + { + wxString str; + TimeStamp(&str); + str += szString; + + #if defined(__WXMSW__) && !defined(__WXMICROWIN__) + // don't prepend debug/trace here: it goes to the + // debug window anyhow + str += wxT("\r\n"); + OutputDebugString(str.wx_str()); + #else + // send them to stderr + wxFprintf(stderr, wxT("[%s] %s\n"), + level == wxLOG_Trace ? wxT("Trace") + : wxT("Debug"), + str.c_str()); + fflush(stderr); + #endif + } + #endif // __WXDEBUG__ - break; + break; case wxLOG_FatalError: - // show this one immediately - wxMessageBox(szString, _("Fatal error"), wxICON_HAND); - break; + // show this one immediately + wxMessageBox(szString, _("Fatal error"), wxICON_HAND); + wxExit(); + break; case wxLOG_Error: + if ( !m_bErrors ) { +#if !wxUSE_LOG_DIALOG // discard earlier informational messages if this is the 1st - // error because they might not make sense any more - if ( !m_bErrors ) { - m_aMessages.Empty(); - m_aTimes.Empty(); - m_bErrors = TRUE; - } - // fall through + // error because they might not make sense any more and showing + // them in a message box might be confusing + m_aMessages.Empty(); + m_aSeverity.Empty(); + m_aTimes.Empty(); +#endif // wxUSE_LOG_DIALOG + m_bErrors = true; + } + // fall through case wxLOG_Warning: - if ( !m_bErrors ) { - // for the warning we don't discard the info messages - m_bWarnings = TRUE; - } - - m_aMessages.Add(szString); - m_aTimes.Add((long)t); - m_bHasMessages = TRUE; - break; + if ( !m_bErrors ) { + // for the warning we don't discard the info messages + m_bWarnings = true; + } + + m_aMessages.Add(szString); + m_aSeverity.Add((int)level); + m_aTimes.Add((long)t); + m_bHasMessages = true; + break; } } +#endif // wxUSE_LOGGUI + // ---------------------------------------------------------------------------- // wxLogWindow and wxLogFrame implementation // ---------------------------------------------------------------------------- +#if wxUSE_LOGWINDOW + // log frame class // --------------- class wxLogFrame : public wxFrame { public: // ctor & dtor - wxLogFrame(wxFrame *pParent, wxLogWindow *log, const wxChar *szTitle); + wxLogFrame(wxWindow *pParent, wxLogWindow *log, const wxString& szTitle); virtual ~wxLogFrame(); // menu callbacks @@ -272,26 +484,47 @@ public: #endif // wxUSE_FILE void OnClear(wxCommandEvent& event); - void OnIdle(wxIdleEvent&); + // this function is safe to call from any thread (notice that it should be + // also called from the main thread to ensure that the messages logged from + // it appear in correct order with the messages from the other threads) + void AddLogMessage(const wxString& message); - // accessors - wxTextCtrl *TextCtrl() const { return m_pTextCtrl; } + // actually append the messages logged from secondary threads to the text + // control during idle time in the main thread + virtual void OnInternalIdle(); private: + // use standard ids for our commands! enum { - Menu_Close = 100, - Menu_Save, - Menu_Clear + Menu_Close = wxID_CLOSE, + Menu_Save = wxID_SAVE, + Menu_Clear = wxID_CLEAR }; - // instead of closing just hide the window to be able to Show() it later - void DoClose() { Show(FALSE); } + // common part of OnClose() and OnCloseWindow() + void DoClose(); + + // do show the message in the text control + void DoShowLogMessage(const wxString& message) + { + m_pTextCtrl->AppendText(message); + } wxTextCtrl *m_pTextCtrl; wxLogWindow *m_log; + // queue of messages logged from other threads which need to be displayed + wxArrayString m_pendingMessages; + +#if wxUSE_THREADS + // critical section to protect access to m_pendingMessages + wxCriticalSection m_critSection; +#endif // wxUSE_THREADS + + DECLARE_EVENT_TABLE() + DECLARE_NO_COPY_CLASS(wxLogFrame) }; BEGIN_EVENT_TABLE(wxLogFrame, wxFrame) @@ -305,17 +538,23 @@ BEGIN_EVENT_TABLE(wxLogFrame, wxFrame) EVT_CLOSE(wxLogFrame::OnCloseWindow) END_EVENT_TABLE() -wxLogFrame::wxLogFrame(wxFrame *pParent, wxLogWindow *log, const wxChar *szTitle) - : wxFrame(pParent, -1, szTitle) +wxLogFrame::wxLogFrame(wxWindow *pParent, wxLogWindow *log, const wxString& szTitle) + : wxFrame(pParent, wxID_ANY, szTitle) { m_log = log; - m_pTextCtrl = new wxTextCtrl(this, -1, wxEmptyString, wxDefaultPosition, + m_pTextCtrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxHSCROLL | + // needed for Win32 to avoid 65Kb limit but it doesn't work well + // when using RichEdit 2.0 which we always do in the Unicode build +#if !wxUSE_UNICODE + wxTE_RICH | +#endif // !wxUSE_UNICODE wxTE_READONLY); +#if wxUSE_MENUS // create menu wxMenuBar *pMenuBar = new wxMenuBar; wxMenu *pMenu = new wxMenu; @@ -327,6 +566,7 @@ wxLogFrame::wxLogFrame(wxFrame *pParent, wxLogWindow *log, const wxChar *szTitle pMenu->Append(Menu_Close, _("&Close"), _("Close this window")); pMenuBar->Append(pMenu, _("&Log")); SetMenuBar(pMenuBar); +#endif // wxUSE_MENUS #if wxUSE_STATUSBAR // status bar for menu prompts @@ -336,6 +576,16 @@ wxLogFrame::wxLogFrame(wxFrame *pParent, wxLogWindow *log, const wxChar *szTitle m_log->OnFrameCreate(this); } +void wxLogFrame::DoClose() +{ + if ( m_log->OnFrameClose(this) ) + { + // instead of closing just hide the window to be able to Show() it + // later + Show(false); + } +} + void wxLogFrame::OnClose(wxCommandEvent& WXUNUSED(event)) { DoClose(); @@ -349,62 +599,24 @@ void wxLogFrame::OnCloseWindow(wxCloseEvent& WXUNUSED(event)) #if wxUSE_FILE void wxLogFrame::OnSave(wxCommandEvent& WXUNUSED(event)) { - // get the file name - // ----------------- - const wxChar *szFileName = wxSaveFileSelector(T("log"), T("txt"), T("log.txt")); - if ( szFileName == NULL ) { +#if wxUSE_FILEDLG + wxString filename; + wxFile file; + int rc = OpenLogFile(file, &filename, this); + if ( rc == -1 ) + { // cancelled return; } - // open file - // --------- - wxFile file; - bool bOk = FALSE; - if ( wxFile::Exists(szFileName) ) { - bool bAppend = FALSE; - wxString strMsg; - strMsg.Printf(_("Append log to file '%s' " - "(choosing [No] will overwrite it)?"), szFileName); - switch ( wxMessageBox(strMsg, _("Question"), wxYES_NO | wxCANCEL) ) { - case wxYES: - bAppend = TRUE; - break; - - case wxNO: - bAppend = FALSE; - break; - - case wxCANCEL: - return; - - default: - wxFAIL_MSG(_("invalid message box return value")); - } - - if ( bAppend ) { - bOk = file.Open(szFileName, wxFile::write_append); - } - else { - bOk = file.Create(szFileName, TRUE /* overwrite */); - } - } - else { - bOk = file.Create(szFileName); - } + bool bOk = rc != 0; // retrieve text and save it // ------------------------- int nLines = m_pTextCtrl->GetNumberOfLines(); for ( int nLine = 0; bOk && nLine < nLines; nLine++ ) { bOk = file.Write(m_pTextCtrl->GetLineText(nLine) + - // we're not going to pull in the whole wxTextFile if all we need is this... -#if wxUSE_TEXTFILE - wxTextFile::GetEOL() -#else // !wxUSE_TEXTFILE - '\n' -#endif // wxUSE_TEXTFILE - ); + wxTextFile::GetEOL()); } if ( bOk ) @@ -414,8 +626,9 @@ void wxLogFrame::OnSave(wxCommandEvent& WXUNUSED(event)) wxLogError(_("Can't save log contents to file.")); } else { - wxLogStatus(this, _("Log saved to the file '%s'."), szFileName); + wxLogStatus((wxFrame*)this, _("Log saved to the file '%s'."), filename.c_str()); } +#endif } #endif // wxUSE_FILE @@ -424,6 +637,43 @@ void wxLogFrame::OnClear(wxCommandEvent& WXUNUSED(event)) m_pTextCtrl->Clear(); } +void wxLogFrame::OnInternalIdle() +{ + { + wxCRIT_SECT_LOCKER(locker, m_critSection); + + const size_t count = m_pendingMessages.size(); + for ( size_t n = 0; n < count; n++ ) + { + DoShowLogMessage(m_pendingMessages[n]); + } + + m_pendingMessages.clear(); + } // release m_critSection + + wxFrame::OnInternalIdle(); +} + +void wxLogFrame::AddLogMessage(const wxString& message) +{ + wxCRIT_SECT_LOCKER(locker, m_critSection); + +#if wxUSE_THREADS + if ( !wxThread::IsMain() || !m_pendingMessages.empty() ) + { + // message needs to be queued for later showing + m_pendingMessages.Add(message); + + wxWakeUpIdle(); + } + else // we are the main thread and no messages are queued, so we can + // log the message directly +#endif // wxUSE_THREADS + { + DoShowLogMessage(message); + } +} + wxLogFrame::~wxLogFrame() { m_log->OnFrameDelete(this); @@ -431,18 +681,18 @@ wxLogFrame::~wxLogFrame() // wxLogWindow // ----------- -wxLogWindow::wxLogWindow(wxFrame *pParent, - const wxChar *szTitle, + +wxLogWindow::wxLogWindow(wxWindow *pParent, + const wxString& szTitle, bool bShow, bool bDoPass) { - m_bPassMessages = bDoPass; + PassMessages(bDoPass); m_pLogFrame = new wxLogFrame(pParent, this, szTitle); - m_pOldLog = wxLog::SetActiveTarget(this); if ( bShow ) - m_pLogFrame->Show(TRUE); + m_pLogFrame->Show(); } void wxLogWindow::Show(bool bShow) @@ -450,35 +700,21 @@ void wxLogWindow::Show(bool bShow) m_pLogFrame->Show(bShow); } -void wxLogWindow::Flush() -{ - if ( m_pOldLog != NULL ) - m_pOldLog->Flush(); - - m_bHasMessages = FALSE; -} - -void wxLogWindow::DoLog(wxLogLevel level, const wxChar *szString, time_t t) +void wxLogWindow::DoLog(wxLogLevel level, const wxString& szString, time_t t) { // first let the previous logger show it - if ( m_pOldLog != NULL && m_bPassMessages ) { - // FIXME why can't we access protected wxLog method from here (we derive - // from wxLog)? gcc gives "DoLog is protected in this context", what - // does this mean? Anyhow, the cast is harmless and let's us do what - // we want. - ((wxLogWindow *)m_pOldLog)->DoLog(level, szString, t); - } + wxLogPassThrough::DoLog(level, szString, t); if ( m_pLogFrame ) { switch ( level ) { case wxLOG_Status: // by default, these messages are ignored by wxLog, so process // them ourselves - if ( !wxIsEmpty(szString) ) + if ( !szString.empty() ) { wxString str; str << _("Status: ") << szString; - DoLogString(str, t); + LogString(str, t); } break; @@ -494,28 +730,16 @@ void wxLogWindow::DoLog(wxLogLevel level, const wxChar *szString, time_t t) wxLog::DoLog(level, szString, t); } } - - m_bHasMessages = TRUE; } -void wxLogWindow::DoLogString(const wxChar *szString, time_t WXUNUSED(t)) +void wxLogWindow::DoLogString(const wxString& szString, time_t WXUNUSED(t)) { - // put the text into our window - wxTextCtrl *pText = m_pLogFrame->TextCtrl(); - - // remove selection (WriteText is in fact ReplaceSelection) -#ifdef __WXMSW__ - long nLen = pText->GetLastPosition(); - pText->SetSelection(nLen, nLen); -#endif // Windows - wxString msg; - TimeStamp(&msg); - msg << szString << T('\n'); - pText->AppendText(msg); + TimeStamp(&msg); + msg << szString << wxT('\n'); - // TODO ensure that the line can be seen + m_pLogFrame->AddLogMessage(msg); } wxFrame *wxLogWindow::GetFrame() const @@ -527,6 +751,12 @@ void wxLogWindow::OnFrameCreate(wxFrame * WXUNUSED(frame)) { } +bool wxLogWindow::OnFrameClose(wxFrame * WXUNUSED(frame)) +{ + // allow to close + return true; +} + void wxLogWindow::OnFrameDelete(wxFrame * WXUNUSED(frame)) { m_pLogFrame = (wxLogFrame *)NULL; @@ -534,9 +764,438 @@ void wxLogWindow::OnFrameDelete(wxFrame * WXUNUSED(frame)) wxLogWindow::~wxLogWindow() { - delete m_pOldLog; - // may be NULL if log frame already auto destroyed itself delete m_pLogFrame; } +#endif // wxUSE_LOGWINDOW + +// ---------------------------------------------------------------------------- +// wxLogDialog +// ---------------------------------------------------------------------------- + +#if wxUSE_LOG_DIALOG + +#ifndef __SMARTPHONE__ +static const size_t MARGIN = 10; +#else +static const size_t MARGIN = 0; +#endif + +wxString wxLogDialog::ms_details; +size_t wxLogDialog::ms_maxLength = 0; + +wxLogDialog::wxLogDialog(wxWindow *parent, + const wxArrayString& messages, + const wxArrayInt& severity, + const wxArrayLong& times, + const wxString& caption, + long style) + : wxDialog(parent, wxID_ANY, caption, + wxDefaultPosition, wxDefaultSize, + wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +{ + // init the static variables: + + if ( ms_details.empty() ) + { + // ensure that we won't loop here if wxGetTranslation() + // happens to pop up a Log message while translating this :-) + ms_details = wxTRANSLATE("&Details"); + ms_details = wxGetTranslation(ms_details); +#ifdef __SMARTPHONE__ + ms_details = wxStripMenuCodes(ms_details); +#endif + } + + if ( ms_maxLength == 0 ) + { + ms_maxLength = (2 * wxGetDisplaySize().x/3) / GetCharWidth(); + } + + size_t count = messages.GetCount(); + m_messages.Alloc(count); + m_severity.Alloc(count); + m_times.Alloc(count); + + for ( size_t n = 0; n < count; n++ ) + { + m_messages.Add(messages[n]); + m_severity.Add(severity[n]); + m_times.Add(times[n]); + } + + m_listctrl = (wxListCtrl *)NULL; + +#ifndef __SMARTPHONE__ + +#if wxUSE_STATLINE + m_statline = (wxStaticLine *)NULL; +#endif // wxUSE_STATLINE + +#if wxUSE_FILE + m_btnSave = (wxButton *)NULL; +#endif // wxUSE_FILE + +#endif // __SMARTPHONE__ + + bool isPda = (wxSystemSettings::GetScreenType() <= wxSYS_SCREEN_PDA); + + // create the controls which are always shown and layout them: we use + // sizers even though our window is not resizeable to calculate the size of + // the dialog properly + wxBoxSizer *sizerTop = new wxBoxSizer(wxVERTICAL); + wxBoxSizer *sizerAll = new wxBoxSizer(isPda ? wxVERTICAL : wxHORIZONTAL); + + if (!isPda) + { + wxStaticBitmap *icon = new wxStaticBitmap + ( + this, + wxID_ANY, + wxArtProvider::GetMessageBoxIcon(style) + ); + sizerAll->Add(icon, 0, wxALIGN_CENTRE_VERTICAL); + } + + // create the text sizer with a minimal size so that we are sure it won't be too small + wxString message = EllipsizeString(messages.Last()); + wxSizer *szText = CreateTextSizer(message); + szText->SetMinSize(wxMin(300, wxGetDisplaySize().x / 3), -1); + + sizerAll->Add(szText, 1, + wxALIGN_CENTRE_VERTICAL | wxLEFT | wxRIGHT, MARGIN); + + wxButton *btnOk = new wxButton(this, wxID_OK); + sizerAll->Add(btnOk, 0, isPda ? wxCENTRE : wxCENTRE|wxBOTTOM, MARGIN/2); + + sizerTop->Add(sizerAll, 0, wxALL | wxEXPAND, MARGIN); + + + // add the details pane + +#ifndef __SMARTPHONE__ + wxCollapsiblePane *collpane = new wxCollapsiblePane(this, wxID_ANY, ms_details); + sizerTop->Add(collpane, 1, wxGROW|wxALL, MARGIN); + + wxWindow *win = collpane->GetPane(); + wxSizer *paneSz = new wxBoxSizer(wxVERTICAL); + + CreateDetailsControls(win); + + paneSz->Add(m_listctrl, 1, wxEXPAND | wxTOP, MARGIN); + +#if wxUSE_FILE && !defined(__SMARTPHONE__) + paneSz->Add(m_btnSave, 0, wxALIGN_RIGHT | wxTOP, MARGIN); +#endif // wxUSE_FILE + + win->SetSizer(paneSz); + paneSz->SetSizeHints(win); +#else // __SMARTPHONE__ + SetLeftMenu(wxID_OK); + SetRightMenu(wxID_MORE, ms_details + EXPAND_SUFFIX); +#endif // __SMARTPHONE__/!__SMARTPHONE__ + + SetSizerAndFit(sizerTop); + + Centre(); + + if (isPda) + { + // Move up the screen so that when we expand the dialog, + // there's enough space. + Move(wxPoint(GetPosition().x, GetPosition().y / 2)); + } +} + +void wxLogDialog::CreateDetailsControls(wxWindow *parent) +{ +#ifndef __SMARTPHONE__ + // create the save button and separator line if possible +#if wxUSE_FILE + m_btnSave = new wxButton(parent, wxID_SAVE); +#endif // wxUSE_FILE + +#endif // __SMARTPHONE__ + + // create the list ctrl now + m_listctrl = new wxListCtrl(parent, wxID_ANY, + wxDefaultPosition, wxDefaultSize, + wxSUNKEN_BORDER | + wxLC_REPORT | + wxLC_NO_HEADER | + wxLC_SINGLE_SEL); +#ifdef __WXWINCE__ + // This makes a big aesthetic difference on WinCE but I + // don't want to risk problems on other platforms + m_listctrl->Hide(); +#endif + + // no need to translate these strings as they're not shown to the + // user anyhow (we use wxLC_NO_HEADER style) + m_listctrl->InsertColumn(0, _T("Message")); + m_listctrl->InsertColumn(1, _T("Time")); + + // prepare the imagelist + static const int ICON_SIZE = 16; + wxImageList *imageList = new wxImageList(ICON_SIZE, ICON_SIZE); + + // order should be the same as in the switch below! + static const wxChar* icons[] = + { + wxART_ERROR, + wxART_WARNING, + wxART_INFORMATION + }; + + bool loadedIcons = true; + + for ( size_t icon = 0; icon < WXSIZEOF(icons); icon++ ) + { + wxBitmap bmp = wxArtProvider::GetBitmap(icons[icon], wxART_MESSAGE_BOX, + wxSize(ICON_SIZE, ICON_SIZE)); + + // This may very well fail if there are insufficient colours available. + // Degrade gracefully. + if ( !bmp.Ok() ) + { + loadedIcons = false; + + break; + } + + imageList->Add(bmp); + } + + m_listctrl->SetImageList(imageList, wxIMAGE_LIST_SMALL); + + // and fill it + wxString fmt = wxLog::GetTimestamp(); + if ( !fmt ) + { + // default format + fmt = _T("%c"); + } + + size_t count = m_messages.GetCount(); + for ( size_t n = 0; n < count; n++ ) + { + int image; + + if ( loadedIcons ) + { + switch ( m_severity[n] ) + { + case wxLOG_Error: + image = 0; + break; + + case wxLOG_Warning: + image = 1; + break; + + default: + image = 2; + } + } + else // failed to load images + { + image = -1; + } + + wxString msg = m_messages[n]; + msg.Replace(wxT("\n"), wxT(" ")); + msg = EllipsizeString(msg); + + m_listctrl->InsertItem(n, msg, image); + m_listctrl->SetItem(n, 1, TimeStamp(fmt, (time_t)m_times[n])); + } + + // let the columns size themselves + m_listctrl->SetColumnWidth(0, wxLIST_AUTOSIZE); + m_listctrl->SetColumnWidth(1, wxLIST_AUTOSIZE); + + // calculate an approximately nice height for the listctrl + int height = GetCharHeight()*(count + 4); + + // but check that the dialog won't fall fown from the screen + // + // we use GetMinHeight() to get the height of the dialog part without the + // details and we consider that the "Save" button below and the separator + // line (and the margins around it) take about as much, hence double it + int heightMax = wxGetDisplaySize().y - GetPosition().y - 2*GetMinHeight(); + + // we should leave a margin + heightMax *= 9; + heightMax /= 10; + + m_listctrl->SetSize(wxDefaultCoord, wxMin(height, heightMax)); +} + +void wxLogDialog::OnListSelect(wxListEvent& event) +{ + // we can't just disable the control because this looks ugly under Windows + // (wrong bg colour, no scrolling...), but we still want to disable + // selecting items - it makes no sense here + m_listctrl->SetItemState(event.GetIndex(), 0, wxLIST_STATE_SELECTED); +} + +void wxLogDialog::OnListItemActivated(wxListEvent& event) +{ + // show the activated item in a message box + // This allow the user to correctly display the logs which are longer + // than the listctrl and thus gets truncated or those which contains + // newlines. + + // NB: don't do: + // wxString str = m_listctrl->GetItemText(event.GetIndex()); + // as there's a 260 chars limit on the items inside a wxListCtrl in wxMSW. + wxString str = m_messages[event.GetIndex()]; + + // wxMessageBox will nicely handle the '\n' in the string (if any) + // and supports long strings + wxMessageBox(str, wxT("Log message"), wxOK, this); +} + +void wxLogDialog::OnOk(wxCommandEvent& WXUNUSED(event)) +{ + EndModal(wxID_OK); +} + +#if wxUSE_FILE + +void wxLogDialog::OnSave(wxCommandEvent& WXUNUSED(event)) +{ +#if wxUSE_FILEDLG + wxFile file; + int rc = OpenLogFile(file, NULL, this); + if ( rc == -1 ) + { + // cancelled + return; + } + + bool ok = rc != 0; + + wxString fmt = wxLog::GetTimestamp(); + if ( !fmt ) + { + // default format + fmt = _T("%c"); + } + + size_t count = m_messages.GetCount(); + for ( size_t n = 0; ok && (n < count); n++ ) + { + wxString line; + line << TimeStamp(fmt, (time_t)m_times[n]) + << _T(": ") + << m_messages[n] + << wxTextFile::GetEOL(); + + ok = file.Write(line); + } + + if ( ok ) + ok = file.Close(); + + if ( !ok ) + wxLogError(_("Can't save log contents to file.")); +#endif // wxUSE_FILEDLG +} + +#endif // wxUSE_FILE + +wxLogDialog::~wxLogDialog() +{ + if ( m_listctrl ) + { + delete m_listctrl->GetImageList(wxIMAGE_LIST_SMALL); + } +} + +#endif // wxUSE_LOG_DIALOG + +#if wxUSE_FILE && wxUSE_FILEDLG + +// pass an uninitialized file object, the function will ask the user for the +// filename and try to open it, returns true on success (file was opened), +// false if file couldn't be opened/created and -1 if the file selection +// dialog was cancelled +static int OpenLogFile(wxFile& file, wxString *pFilename, wxWindow *parent) +{ + // get the file name + // ----------------- + wxString filename = wxSaveFileSelector(wxT("log"), wxT("txt"), wxT("log.txt"), parent); + if ( !filename ) { + // cancelled + return -1; + } + + // open file + // --------- + bool bOk = false; + if ( wxFile::Exists(filename) ) { + bool bAppend = false; + wxString strMsg; + strMsg.Printf(_("Append log to file '%s' (choosing [No] will overwrite it)?"), + filename.c_str()); + switch ( wxMessageBox(strMsg, _("Question"), + wxICON_QUESTION | wxYES_NO | wxCANCEL) ) { + case wxYES: + bAppend = true; + break; + + case wxNO: + bAppend = false; + break; + + case wxCANCEL: + return -1; + + default: + wxFAIL_MSG(_("invalid message box return value")); + } + + if ( bAppend ) { + bOk = file.Open(filename, wxFile::write_append); + } + else { + bOk = file.Create(filename, true /* overwrite */); + } + } + else { + bOk = file.Create(filename); + } + + if ( pFilename ) + *pFilename = filename; + + return bOk; +} + +#endif // wxUSE_FILE + +#endif // !(wxUSE_LOGGUI || wxUSE_LOGWINDOW) + +#if wxUSE_LOG && wxUSE_TEXTCTRL + +// ---------------------------------------------------------------------------- +// wxLogTextCtrl implementation +// ---------------------------------------------------------------------------- + +wxLogTextCtrl::wxLogTextCtrl(wxTextCtrl *pTextCtrl) +{ + m_pTextCtrl = pTextCtrl; +} + +void wxLogTextCtrl::DoLogString(const wxString& szString, time_t WXUNUSED(t)) +{ + wxString msg; + TimeStamp(&msg); + + msg << szString << wxT('\n'); + m_pTextCtrl->AppendText(msg); +} + +#endif // wxUSE_LOG && wxUSE_TEXTCTRL