X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/224ff7a50ddb527dfacda6a40a77afb086bdd11e..13e24d79876b78545ba300e6b6871bc47af60848:/src/generic/logg.cpp diff --git a/src/generic/logg.cpp b/src/generic/logg.cpp index b0c80bea5c..71580f7fa9 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" @@ -42,34 +37,67 @@ #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" #ifdef __WXMSW__ - // for OutputDebugString() - #include "wx/msw/private.h" + // for OutputDebugString() + #include "wx/msw/private.h" #endif // Windows -// may be defined to 0 for old behavior (using wxMessageBox) - shouldn't be -// changed normally (that's why it's here and not in setup.h) -#define wxUSE_LOG_DIALOG 1 +#ifdef __WXPM__ + #include +#endif #if wxUSE_LOG_DIALOG - #include "wx/datetime.h" #include "wx/listctrl.h" + #include "wx/imaglist.h" #include "wx/image.h" -#else // !wxUSE_TEXTFILE - #include "wx/msgdlg.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 wxChar *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: @@ -83,38 +111,90 @@ public: // event handlers void OnOk(wxCommandEvent& event); - void OnDetails(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 "toggle" button and its state - wxButton *m_btnDetails; - bool m_showingDetails; - - // the listctrl (not shown initially) + // 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) - EVT_BUTTON(wxID_MORE, wxLogDialog::OnDetails) + 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 @@ -126,46 +206,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 << wxT('\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(); @@ -173,7 +258,10 @@ 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(); @@ -185,71 +273,96 @@ void wxLogGui::Flush() return; // do it right now to block any new calls to Flush() while we're here - m_bHasMessages = FALSE; + m_bHasMessages = false; - wxString title = wxTheApp->GetAppName(); - if ( !!title ) + unsigned repeatCount = 0; + if ( wxLog::GetRepetitionCounting() ) { - title[0u] = wxToupper(title[0u]); - title += _T(' '); + repeatCount = wxLog::DoLogNumberOfRepeats(); } + wxString appName = wxTheApp->GetAppName(); + if ( !appName.empty() ) + appName[0u] = (wxChar)wxToupper(appName[0u]); + 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; } + wxString title; + title.Printf(titleFormat, appName.c_str()); + + 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 - wxLogDialog dlg(wxTheApp->GetTopWindow(), - 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(); + if ( repeatCount > 0 ) + m_aMessages[nMsgCount-1] += wxString::Format(wxT(" (%s)"), m_aMessages[nMsgCount-2].c_str()); + wxLogDialog dlg(NULL, + m_aMessages, m_aSeverity, m_aTimes, + title, style); - (void)dlg.ShowModal(); + // 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) - wxString str; - size_t nLines = 0, - nMsgCount = m_aMessages.Count(); + // 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; + // 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; + nLines += (m_aMessages[n - 1].Len() + nMsgLineWidth - 1) / nMsgLineWidth; - if ( nLines > 25 ) // don't put too many lines in message box - break; + if ( nLines > 25 ) // don't put too many lines in message box + break; - str << m_aMessages[n - 1] << wxT("\n"); + str << m_aMessages[n - 1] << wxT("\n"); + } +#endif // wxUSE_LOG_DIALOG/!wxUSE_LOG_DIALOG } - wxMessageBox(str, title, wxOK | style); + // 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(); -#endif // wxUSE_LOG_DIALOG/!wxUSE_LOG_DIALOG + // no undisplayed messages whatsoever + Clear(); + } - // do it here again - m_bHasMessages = FALSE; + // allow flushing the logs again + Resume(); } -// the default behaviour is to discard all informational messages if there -// are any errors/warnings. +// log all kinds of messages void wxLogGui::DoLog(wxLogLevel level, const wxChar *szString, time_t t) { switch ( level ) { @@ -257,12 +370,10 @@ void wxLogGui::DoLog(wxLogLevel level, const wxChar *szString, time_t t) if ( GetVerbose() ) case wxLOG_Message: { - if ( !m_bErrors ) { - m_aMessages.Add(szString); - m_aSeverity.Add(wxLOG_Message); - m_aTimes.Add((long)t); - m_bHasMessages = TRUE; - } + m_aMessages.Add(szString); + m_aSeverity.Add(wxLOG_Message); + m_aTimes.Add((long)t); + m_bHasMessages = true; } break; @@ -288,19 +399,21 @@ void wxLogGui::DoLog(wxLogLevel level, const wxChar *szString, time_t t) case wxLOG_Debug: #ifdef __WXDEBUG__ { - #ifdef __WXMSW__ + wxString str; + TimeStamp(&str); + str += szString; + + #if defined(__WXMSW__) && !defined(__WXMICROWIN__) // don't prepend debug/trace here: it goes to the - // debug window anyhow, but do put a timestamp - wxString str; - TimeStamp(&str); - str << szString << wxT("\n\r"); + // debug window anyhow + str += wxT("\r\n"); OutputDebugString(str); #else // send them to stderr - wxFprintf(stderr, wxT("%s: %s\n"), + wxFprintf(stderr, wxT("[%s] %s\n"), level == wxLOG_Trace ? wxT("Trace") : wxT("Debug"), - szString); + str.c_str()); fflush(stderr); #endif } @@ -311,6 +424,7 @@ void wxLogGui::DoLog(wxLogLevel level, const wxChar *szString, time_t t) case wxLOG_FatalError: // show this one immediately wxMessageBox(szString, _("Fatal error"), wxICON_HAND); + wxExit(); break; case wxLOG_Error: @@ -323,35 +437,39 @@ void wxLogGui::DoLog(wxLogLevel level, const wxChar *szString, time_t t) m_aSeverity.Empty(); m_aTimes.Empty(); #endif // wxUSE_LOG_DIALOG - m_bErrors = TRUE; + 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_bWarnings = true; } m_aMessages.Add(szString); - m_aSeverity.Add(level); + m_aSeverity.Add((int)level); m_aTimes.Add((long)t); - m_bHasMessages = TRUE; + 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 wxChar *szTitle); virtual ~wxLogFrame(); // menu callbacks @@ -362,26 +480,26 @@ public: #endif // wxUSE_FILE void OnClear(wxCommandEvent& event); - void OnIdle(wxIdleEvent&); - // accessors wxTextCtrl *TextCtrl() const { return m_pTextCtrl; } 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(); wxTextCtrl *m_pTextCtrl; wxLogWindow *m_log; DECLARE_EVENT_TABLE() + DECLARE_NO_COPY_CLASS(wxLogFrame) }; BEGIN_EVENT_TABLE(wxLogFrame, wxFrame) @@ -395,17 +513,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 wxChar *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; @@ -417,6 +541,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 @@ -426,6 +551,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(); @@ -439,62 +574,24 @@ void wxLogFrame::OnCloseWindow(wxCloseEvent& WXUNUSED(event)) #if wxUSE_FILE void wxLogFrame::OnSave(wxCommandEvent& WXUNUSED(event)) { - // get the file name - // ----------------- - const wxChar *szFileName = wxSaveFileSelector(wxT("log"), wxT("txt"), wxT("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 ) @@ -504,8 +601,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 @@ -521,18 +619,18 @@ wxLogFrame::~wxLogFrame() // wxLogWindow // ----------- -wxLogWindow::wxLogWindow(wxFrame *pParent, + +wxLogWindow::wxLogWindow(wxWindow *pParent, const wxChar *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) @@ -540,21 +638,10 @@ 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) { // first let the previous logger show it - if ( m_pOldLog != NULL && m_bPassMessages ) { - // bogus cast just to access protected DoLog - ((wxLogWindow *)m_pOldLog)->DoLog(level, szString, t); - } + wxLogPassThrough::DoLog(level, szString, t); if ( m_pLogFrame ) { switch ( level ) { @@ -581,8 +668,6 @@ 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)) @@ -592,7 +677,7 @@ void wxLogWindow::DoLogString(const wxChar *szString, time_t WXUNUSED(t)) // remove selection (WriteText is in fact ReplaceSelection) #ifdef __WXMSW__ - long nLen = pText->GetLastPosition(); + wxTextPos nLen = pText->GetLastPosition(); pText->SetSelection(nLen, nLen); #endif // Windows @@ -614,6 +699,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; @@ -621,19 +712,26 @@ 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, @@ -641,158 +739,342 @@ wxLogDialog::wxLogDialog(wxWindow *parent, const wxArrayLong& times, const wxString& caption, long style) - : wxDialog(parent, -1, caption ), - m_messages(messages), m_severity(severity), m_times(times) + : wxDialog(parent, wxID_ANY, caption, + wxDefaultPosition, wxDefaultSize, + wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { - m_showingDetails = FALSE; // not initially + // 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 *sizerButtons = new wxBoxSizer(wxVERTICAL); - wxBoxSizer *sizerAll = new wxBoxSizer(wxHORIZONTAL); + wxBoxSizer *sizerAll = new wxBoxSizer(isPda ? wxVERTICAL : wxHORIZONTAL); - wxButton *btnOk = new wxButton(this, wxID_OK, _T("Ok")); - sizerButtons->Add(btnOk, 0, wxCENTRE|wxBOTTOM, MARGIN/2); - m_btnDetails = new wxButton(this, wxID_MORE, _T("&Details >>")); - sizerButtons->Add(m_btnDetails, 0, wxCENTRE|wxTOP, MARGIN/2 - 1); + wxBitmap bitmap; + switch ( style & wxICON_MASK ) + { + case wxICON_ERROR: + bitmap = wxArtProvider::GetBitmap(wxART_ERROR, wxART_MESSAGE_BOX); +#ifdef __WXPM__ + bitmap.SetId(wxICON_SMALL_ERROR); +#endif + break; - wxIcon icon = wxTheApp->GetStdIcon(style & wxICON_MASK); - sizerAll->Add(new wxStaticBitmap(this, -1, icon), 0, wxCENTRE); - const wxString& message = messages.Last(); - sizerAll->Add(CreateTextSizer(message), 0, wxCENTRE|wxLEFT|wxRIGHT, MARGIN); - sizerAll->Add(sizerButtons, 0, wxALIGN_RIGHT|wxLEFT, MARGIN); + case wxICON_INFORMATION: + bitmap = wxArtProvider::GetBitmap(wxART_INFORMATION, wxART_MESSAGE_BOX); +#ifdef __WXPM__ + bitmap.SetId(wxICON_SMALL_INFO); +#endif + break; - sizerTop->Add(sizerAll, 0, wxCENTRE|wxALL, MARGIN); + case wxICON_WARNING: + bitmap = wxArtProvider::GetBitmap(wxART_WARNING, wxART_MESSAGE_BOX); +#ifdef __WXPM__ + bitmap.SetId(wxICON_SMALL_WARNING); +#endif + break; - SetAutoLayout(TRUE); - SetSizer(sizerTop); + default: + wxFAIL_MSG(_T("incorrect log style")); + } - sizerTop->SetSizeHints(this); - sizerTop->Fit(this); + if (!isPda) + sizerAll->Add(new wxStaticBitmap(this, wxID_ANY, bitmap), 0, + wxALIGN_CENTRE_VERTICAL); - btnOk->SetFocus(); + // 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); - if ( m_messages.GetCount() == 1 ) - { - // no details... it's easier to disable a button than to change the - // dialog layout depending on whether we have details or not - m_btnDetails->Disable(); - } + 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(); -} -void wxLogDialog::OnOk(wxCommandEvent& WXUNUSED(event)) -{ - EndModal(wxID_OK); + 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::OnDetails(wxCommandEvent& WXUNUSED(event)) +void wxLogDialog::CreateDetailsControls(wxWindow *parent) { - wxSizer *sizer = GetSizer(); +#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; - if ( m_showingDetails ) + for ( size_t icon = 0; icon < WXSIZEOF(icons); icon++ ) { - m_btnDetails->SetLabel(_T("&Details >>")); + 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; - sizer->Remove(m_listctrl); + break; + } + + imageList->Add(bmp); } - else // show details now + + m_listctrl->SetImageList(imageList, wxIMAGE_LIST_SMALL); + + // and fill it + wxString fmt = wxLog::GetTimestamp(); + if ( !fmt ) { - m_btnDetails->SetLabel(_T("<< &Details")); + // default format + fmt = _T("%c"); + } + + size_t count = m_messages.GetCount(); + for ( size_t n = 0; n < count; n++ ) + { + int image; - if ( !m_listctrl ) + if ( loadedIcons ) { - // create it now - m_listctrl = new wxListCtrl(this, -1, - wxDefaultPosition, wxDefaultSize, - wxSUNKEN_BORDER | - wxLC_REPORT | - wxLC_NO_HEADER ); - m_listctrl->InsertColumn(0, _("Message")); - m_listctrl->InsertColumn(1, _("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 int icons[] = + switch ( m_severity[n] ) { - wxICON_ERROR, - wxICON_EXCLAMATION, - wxICON_INFORMATION - }; + case wxLOG_Error: + image = 0; + break; - for ( size_t icon = 0; icon < WXSIZEOF(icons); icon++ ) - { - wxBitmap bmp = wxTheApp->GetStdIcon(icons[icon]); - imageList->Add(wxImage(bmp). - Rescale(ICON_SIZE, ICON_SIZE). - ConvertToBitmap()); + case wxLOG_Warning: + image = 1; + break; + + default: + image = 2; } + } + else // failed to load images + { + image = -1; + } - m_listctrl->SetImageList(imageList, wxIMAGE_LIST_SMALL); + wxString msg = m_messages[n]; + msg.Replace(wxT("\n"), wxT(" ")); + msg = EllipsizeString(msg); - // and fill it - wxString fmt = wxLog::GetTimestamp(); - if ( !fmt ) - { - // default format - fmt = _T("%X"); - } + m_listctrl->InsertItem(n, msg, image); + m_listctrl->SetItem(n, 1, TimeStamp(fmt, (time_t)m_times[n])); + } - size_t count = m_messages.GetCount(); - for ( size_t n = 0; n < count; n++ ) - { - int image; - switch ( m_severity[n] ) - { - case wxLOG_Error: - image = 0; - break; + // let the columns size themselves + m_listctrl->SetColumnWidth(0, wxLIST_AUTOSIZE); + m_listctrl->SetColumnWidth(1, wxLIST_AUTOSIZE); - case wxLOG_Warning: - image = 1; - break; + // calculate an approximately nice height for the listctrl + int height = GetCharHeight()*(count + 4); - default: - image = 2; - } + // 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(); - m_listctrl->InsertItem(n, m_messages[n], image); - m_listctrl->SetItem(n, 1, - wxDateTime((time_t)m_times[n]).Format(fmt)); - } + // we should leave a margin + heightMax *= 9; + heightMax /= 10; + + m_listctrl->SetSize(wxDefaultCoord, wxMin(height, heightMax)); +} - // let the columns size themselves - m_listctrl->SetColumnWidth(0, wxLIST_AUTOSIZE); - m_listctrl->SetColumnWidth(1, wxLIST_AUTOSIZE); +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); +} - // get the approx height of the listctrl - wxFont font = GetFont(); - if ( !font.Ok() ) - font = *wxSWISS_FONT; +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); +} - int y; - GetTextExtent(_T("H"), (int*)NULL, &y, (int*)NULL, (int*)NULL, &font); - int height = wxMin(y*(count + 3), 100); - m_listctrl->SetSize(-1, height); - } +void wxLogDialog::OnOk(wxCommandEvent& WXUNUSED(event)) +{ + EndModal(wxID_OK); +} - sizer->Add(m_listctrl, 1, wxEXPAND|(wxALL & ~wxTOP), MARGIN); +#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; } - m_showingDetails = !m_showingDetails; + bool ok = rc != 0; - // in any case, our size changed - update - sizer->SetSizeHints(this); - sizer->Fit(this); + 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 ) @@ -802,3 +1084,87 @@ wxLogDialog::~wxLogDialog() } #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 wxDUMMY_INITIALIZE(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 wxChar *szString, time_t WXUNUSED(t)) +{ + wxString msg; + TimeStamp(&msg); + + msg << szString << wxT('\n'); + m_pTextCtrl->AppendText(msg); +} + +#endif // wxUSE_LOG && wxUSE_TEXTCTRL