X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/f1df09276caf8575eed4e9bf2689c69903a749a9..6d9022fe3bb50a75b9b5eaf5cf32558e518ba310:/src/generic/logg.cpp diff --git a/src/generic/logg.cpp b/src/generic/logg.cpp index e158cec030..345cb33530 100644 --- a/src/generic/logg.cpp +++ b/src/generic/logg.cpp @@ -33,33 +33,37 @@ #ifndef WX_PRECOMP #include "wx/app.h" + #include "wx/button.h" #include "wx/intl.h" #include "wx/log.h" #include "wx/menu.h" #include "wx/frame.h" #include "wx/filedlg.h" + #include "wx/msgdlg.h" #include "wx/textctrl.h" #include "wx/sizer.h" #include "wx/statbmp.h" + #include "wx/button.h" + #include "wx/settings.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" #ifdef __WXMSW__ // 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 - #if wxUSE_LOG_DIALOG - #include "wx/datetime.h" #include "wx/listctrl.h" + #include "wx/imaglist.h" #include "wx/image.h" -#else // !wxUSE_TEXTFILE +#else // !wxUSE_LOG_DIALOG #include "wx/msgdlg.h" #endif // wxUSE_LOG_DIALOG/!wxUSE_LOG_DIALOG @@ -69,6 +73,20 @@ #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) +{ + wxChar buf[4096]; + if ( !wxStrftime(buf, WXSIZEOF(buf), format, localtime(&t)) ) + { + // buffer is too small? + wxFAIL_MSG(_T("strftime() failed")); + } + return wxString(buf); +} + + class wxLogDialog : public wxDialog { public: @@ -83,37 +101,72 @@ 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); private: + // create controls needed for the details display + void CreateDetailsControls(); + // the data for the listctrl - const wxArrayString& m_messages; - const wxArrayInt& m_severity; - const wxArrayLong& m_times; + 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; +#if wxUSE_STATLINE + wxStaticLine *m_statline; +#endif // wxUSE_STATLINE +#if wxUSE_FILE + wxButton *m_btnSave; +#endif // wxUSE_FILE + + // the translated "Details" string + static wxString ms_details; DECLARE_EVENT_TABLE() }; BEGIN_EVENT_TABLE(wxLogDialog, wxDialog) - EVT_BUTTON(wxID_OK, wxLogDialog::OnOk) + EVT_BUTTON(wxID_CANCEL, wxLogDialog::OnOk) EVT_BUTTON(wxID_MORE, wxLogDialog::OnDetails) +#if wxUSE_FILE + EVT_BUTTON(wxID_SAVE, wxLogDialog::OnSave) +#endif // wxUSE_FILE + EVT_LIST_ITEM_SELECTED(-1, wxLogDialog::OnListSelect) 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); + +#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 @@ -125,16 +178,13 @@ 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 wxChar *szFormat, 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); wxASSERT( gs_pFrame == NULL ); // should be reset! gs_pFrame = pFrame; @@ -143,22 +193,12 @@ void wxLogStatus(wxFrame *pFrame, const wxChar *szFormat, ...) } } -// ---------------------------------------------------------------------------- -// wxLogTextCtrl implementation -// ---------------------------------------------------------------------------- - -wxLogTextCtrl::wxLogTextCtrl(wxTextCtrl *pTextCtrl) -{ - m_pTextCtrl = pTextCtrl; -} - -void wxLogTextCtrl::DoLogString(const wxChar *szString, time_t WXUNUSED(t)) +void wxLogStatus(wxFrame *pFrame, const wxChar *szFormat, ...) { - wxString msg; - TimeStamp(&msg); - msg << szString << wxT('\n'); - - m_pTextCtrl->AppendText(msg); + va_list argptr; + va_start(argptr, szFormat); + wxVLogStatus(pFrame, szFormat, argptr); + va_end(argptr); } // ---------------------------------------------------------------------------- @@ -172,7 +212,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(); @@ -186,64 +229,86 @@ void wxLogGui::Flush() // do it right now to block any new calls to Flush() while we're here m_bHasMessages = FALSE; - wxString title = wxTheApp->GetAppName(); - if ( !!title ) - { - title[0u] = wxToupper(title[0u]); - title += _T(' '); - } + wxString appName = wxTheApp->GetAppName(); + if ( !!appName ) + appName[0u] = 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.Count(); + + // 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); - (void)dlg.ShowModal(); + 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) - 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); -#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 ) + { + wxMessageBox(str, title, wxOK | style); - // no undisplayed messages whatsoever - Clear(); + // 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 ) { @@ -251,12 +316,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; @@ -282,19 +345,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 } @@ -305,6 +370,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: @@ -328,7 +394,7 @@ void wxLogGui::DoLog(wxLogLevel level, const wxChar *szString, time_t t) } m_aMessages.Add(szString); - m_aSeverity.Add(level); + m_aSeverity.Add((int)level); m_aTimes.Add((long)t); m_bHasMessages = TRUE; break; @@ -362,15 +428,16 @@ public: 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; @@ -398,8 +465,14 @@ wxLogFrame::wxLogFrame(wxFrame *pParent, wxLogWindow *log, const wxChar *szTitle 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; @@ -411,6 +484,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 @@ -420,6 +494,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(); @@ -433,62 +517,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); + 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 ) @@ -498,8 +544,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(this, _("Log saved to the file '%s'."), filename.c_str()); } +#endif } #endif // wxUSE_FILE @@ -515,15 +562,15 @@ wxLogFrame::~wxLogFrame() // wxLogWindow // ----------- + wxLogWindow::wxLogWindow(wxFrame *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); @@ -534,21 +581,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 ) { @@ -608,6 +644,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; @@ -615,8 +657,6 @@ void wxLogWindow::OnFrameDelete(wxFrame * WXUNUSED(frame)) wxLogWindow::~wxLogWindow() { - delete m_pOldLog; - // may be NULL if log frame already auto destroyed itself delete m_pLogFrame; } @@ -629,18 +669,56 @@ wxLogWindow::~wxLogWindow() static const size_t MARGIN = 10; +wxString wxLogDialog::ms_details; + wxLogDialog::wxLogDialog(wxWindow *parent, const wxArrayString& messages, const wxArrayInt& severity, const wxArrayLong& times, const wxString& caption, long style) - : wxDialog(parent, -1, caption), - m_messages(messages), m_severity(severity), m_times(times) + : wxDialog(parent, -1, caption, + wxDefaultPosition, wxDefaultSize, + wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { + if ( ms_details.IsEmpty() ) + { + // 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); + } + + 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++ ) + { + wxString msg = messages[n]; + do + { + m_messages.Add(msg.BeforeFirst(_T('\n'))); + msg = msg.AfterFirst(_T('\n')); + + m_severity.Add(severity[n]); + m_times.Add(times[n]); + } + while ( !!msg ); + } + m_showingDetails = FALSE; // not initially m_listctrl = (wxListCtrl *)NULL; +#if wxUSE_STATLINE + m_statline = (wxStaticLine *)NULL; +#endif // wxUSE_STATLINE + +#if wxUSE_FILE + m_btnSave = (wxButton *)NULL; +#endif // wxUSE_FILE + // 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 @@ -648,18 +726,36 @@ wxLogDialog::wxLogDialog(wxWindow *parent, wxBoxSizer *sizerButtons = new wxBoxSizer(wxVERTICAL); wxBoxSizer *sizerAll = new wxBoxSizer(wxHORIZONTAL); - wxButton *btnOk = new wxButton(this, wxID_OK, _T("Ok")); - m_btnDetails = new wxButton(this, wxID_MORE, _T("&Details >>")); - sizerButtons->Add(btnOk, 0, wxCENTRE|wxBOTTOM, MARGIN/2); - sizerButtons->Add(m_btnDetails, 0, wxCENTRE|wxTOP, MARGIN/2 - 1); + // this "Ok" button has wxID_CANCEL id - not very logical, but this allows + // to close the log dialog with which wouldn't work otherwise (as it + // translates into click on cancel button) + wxButton *btnOk = new wxButton(this, wxID_CANCEL, _("OK")); + sizerButtons->Add(btnOk, 0, wxCENTRE | wxBOTTOM, MARGIN/2); + m_btnDetails = new wxButton(this, wxID_MORE, ms_details + _T(" >>")); + sizerButtons->Add(m_btnDetails, 0, wxCENTRE | wxTOP, MARGIN/2 - 1); + +#ifndef __WIN16__ + wxBitmap bitmap; + switch ( style & wxICON_MASK ) + { + case wxICON_ERROR: + bitmap = wxArtProvider::GetIcon(wxART_ERROR, wxART_MESSAGE_BOX); break; + case wxICON_INFORMATION: + bitmap = wxArtProvider::GetIcon(wxART_INFORMATION, wxART_MESSAGE_BOX); break; + case wxICON_WARNING: + bitmap = wxArtProvider::GetIcon(wxART_WARNING, wxART_MESSAGE_BOX); break; + default: + wxFAIL_MSG(_T("incorrect log style")); + } + sizerAll->Add(new wxStaticBitmap(this, -1, bitmap), 0); +#endif // !Win16 - 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); + sizerAll->Add(CreateTextSizer(message), 1, + wxALIGN_CENTRE_VERTICAL | wxLEFT | wxRIGHT, MARGIN); + sizerAll->Add(sizerButtons, 0, wxALIGN_RIGHT | wxLEFT, MARGIN); - sizerTop->Add(sizerAll, 0, wxCENTRE|wxALL, MARGIN); + sizerTop->Add(sizerAll, 0, wxALL | wxEXPAND, MARGIN); SetAutoLayout(TRUE); SetSizer(sizerTop); @@ -669,112 +765,255 @@ wxLogDialog::wxLogDialog(wxWindow *parent, btnOk->SetFocus(); + // this can't happen any more as we don't use this dialog in this case +#if 0 + if ( count == 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(); + } +#endif // 0 + Centre(); } -void wxLogDialog::OnOk(wxCommandEvent& WXUNUSED(event)) +void wxLogDialog::CreateDetailsControls() { - EndModal(wxID_OK); -} + // create the save button and separator line if possible +#if wxUSE_FILE + m_btnSave = new wxButton(this, wxID_SAVE, _("&Save...")); +#endif // wxUSE_FILE -void wxLogDialog::OnDetails(wxCommandEvent& WXUNUSED(event)) -{ - wxSizer *sizer = GetSizer(); +#if wxUSE_STATLINE + m_statline = new wxStaticLine(this, -1); +#endif // wxUSE_STATLINE + + // create the list ctrl now + m_listctrl = new wxListCtrl(this, -1, + wxDefaultPosition, wxDefaultSize, + wxSUNKEN_BORDER | + wxLC_REPORT | + wxLC_NO_HEADER | + wxLC_SINGLE_SEL); + + // 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 + }; - if ( m_showingDetails ) + bool loadedIcons = TRUE; + +#ifndef __WIN16__ + 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)); - sizer->Remove(m_listctrl); + // This may very well fail if there are insufficient colours available. + // Degrade gracefully. + if ( !bmp.Ok() ) + { + loadedIcons = FALSE; + + break; + } + + imageList->Add(bmp); } - else // show details now + + m_listctrl->SetImageList(imageList, wxIMAGE_LIST_SMALL); +#endif // !Win16 + + // and fill it + wxString fmt = wxLog::GetTimestamp(); + if ( !fmt ) { - m_btnDetails->SetLabel(_T("<< &Details")); + // default format + fmt = _T("%c"); + } - if ( !m_listctrl ) + size_t count = m_messages.GetCount(); + for ( size_t n = 0; n < count; n++ ) + { + int image; + +#ifndef __WIN16__ + if ( loadedIcons ) { - // create it now - m_listctrl = new wxListCtrl(this, -1, - wxDefaultPosition, wxDefaultSize, - 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 +#endif // !Win16 + { + image = -1; + } - m_listctrl->SetImageList(imageList, wxIMAGE_LIST_SMALL); + m_listctrl->InsertItem(n, m_messages[n], image); + m_listctrl->SetItem(n, 1, TimeStamp(fmt, (time_t)m_times[n])); + } - // and fill it - wxString fmt = wxLog::GetTimestamp(); - if ( !fmt ) - { - // default format - fmt = _T("%X"); - } + // let the columns size themselves + m_listctrl->SetColumnWidth(0, wxLIST_AUTOSIZE); + m_listctrl->SetColumnWidth(1, wxLIST_AUTOSIZE); - 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; + // calculate an approximately nice height for the listctrl + int height = GetCharHeight()*(count + 4); - case wxLOG_Warning: - image = 1; - break; + // 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(); - default: - image = 2; - } + // we should leave a margin + heightMax *= 9; + heightMax /= 10; - m_listctrl->InsertItem(n, m_messages[n], image); - m_listctrl->SetItem(n, 1, - wxDateTime((time_t)m_times[n]).Format(fmt)); - } + m_listctrl->SetSize(-1, 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::OnOk(wxCommandEvent& WXUNUSED(event)) +{ + EndModal(wxID_OK); +} + +#if wxUSE_FILE - // let the columns size themselves (TODO does this work under GTK?) - m_listctrl->SetColumnWidth(0, wxLIST_AUTOSIZE); - m_listctrl->SetColumnWidth(1, wxLIST_AUTOSIZE); +void wxLogDialog::OnSave(wxCommandEvent& WXUNUSED(event)) +{ +#if wxUSE_FILEDLG + wxFile file; + int rc = OpenLogFile(file); + 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); + } - // get the approx height of the listctrl - wxFont font = GetFont(); - if ( !font.Ok() ) - font = *wxSWISS_FONT; + if ( ok ) + ok = file.Close(); + + if ( !ok ) + wxLogError(_("Can't save log contents to file.")); +#endif // wxUSE_FILEDLG +} + +#endif // wxUSE_FILE + +void wxLogDialog::OnDetails(wxCommandEvent& WXUNUSED(event)) +{ + wxSizer *sizer = GetSizer(); + + if ( m_showingDetails ) + { + m_btnDetails->SetLabel(ms_details + _T(">>")); + + sizer->Remove(m_listctrl); + +#if wxUSE_STATLINE + sizer->Remove(m_statline); +#endif // wxUSE_STATLINE + +#if wxUSE_FILE + sizer->Remove(m_btnSave); +#endif // wxUSE_FILE + } + else // show details now + { + m_btnDetails->SetLabel(wxString(_T("<< ")) + ms_details); - 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); + if ( !m_listctrl ) + { + CreateDetailsControls(); } - sizer->Add(m_listctrl, 1, wxEXPAND|(wxALL & ~wxTOP), MARGIN); +#if wxUSE_STATLINE + sizer->Add(m_statline, 0, wxEXPAND | (wxALL & ~wxTOP), MARGIN); +#endif // wxUSE_STATLINE + + sizer->Add(m_listctrl, 1, wxEXPAND | (wxALL & ~wxTOP), MARGIN); + + // VZ: this doesn't work as this becomes the initial (and not only + // minimal) listctrl height as well - why? +#if 0 + // allow the user to make the dialog shorter than its initial height - + // without this it wouldn't work as the list ctrl would have been + // incompressible + sizer->SetItemMinSize(m_listctrl, 100, 3*GetCharHeight()); +#endif // 0 + +#if wxUSE_FILE + sizer->Add(m_btnSave, 0, wxALIGN_RIGHT | (wxALL & ~wxTOP), MARGIN); +#endif // wxUSE_FILE } m_showingDetails = !m_showingDetails; // in any case, our size changed - update + sizer->SetSizeHints(this); sizer->Fit(this); + +#ifdef __WXGTK__ + // VS: this is neccessary in order to force frame redraw under + // WindowMaker or fvwm2 (and probably other broken WMs). + // Otherwise, detailed list wouldn't be displayed. + Show(TRUE); +#endif // wxGTK } wxLogDialog::~wxLogDialog() @@ -786,3 +1025,95 @@ 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) +{ + // get the file name + // ----------------- + wxString filename = wxSaveFileSelector(wxT("log"), wxT("txt"), wxT("log.txt")); + 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_TEXTCTRL + +// ---------------------------------------------------------------------------- +// wxLogTextCtrl implementation +// ---------------------------------------------------------------------------- + +wxLogTextCtrl::wxLogTextCtrl(wxTextCtrl *pTextCtrl) +{ + m_pTextCtrl = pTextCtrl; +} + +void wxLogTextCtrl::DoLogString(const wxChar *szString, time_t WXUNUSED(t)) +{ + wxString msg; + TimeStamp(&msg); + +#if defined(__WXMAC__) && !defined(__DARWIN__) + // VZ: this is a bug in wxMac, it *must* accept '\n' as new line, the + // translation must be done in wxTextCtrl, not here! (FIXME) + msg << szString << wxT('\r'); +#else + msg << szString << wxT('\n'); +#endif + + m_pTextCtrl->AppendText(msg); +} + +#endif // wxUSE_TEXTCTRL +