X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/50af7fa9f237be0b6e1c4880b9b89b2b49a7b841..8b3fddc49326c0b6019cd7082218726aa17a5727:/src/generic/logg.cpp?ds=sidebyside diff --git a/src/generic/logg.cpp b/src/generic/logg.cpp index 168c3d5074..fd60b1f972 100644 --- a/src/generic/logg.cpp +++ b/src/generic/logg.cpp @@ -33,6 +33,7 @@ #ifndef WX_PRECOMP #include "wx/app.h" + #include "wx/button.h" #include "wx/intl.h" #include "wx/log.h" #include "wx/menu.h" @@ -42,25 +43,26 @@ #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" #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 @@ -70,6 +72,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: @@ -84,37 +100,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 @@ -126,16 +177,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; @@ -144,22 +192,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); } // ---------------------------------------------------------------------------- @@ -173,7 +211,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(); @@ -187,64 +228,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 ) { @@ -252,12 +315,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; @@ -283,19 +344,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 } @@ -306,6 +369,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: @@ -329,7 +393,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; @@ -363,15 +427,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; @@ -399,8 +464,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; @@ -412,6 +483,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 @@ -421,6 +493,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(); @@ -434,62 +516,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 ) @@ -499,8 +543,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 @@ -516,15 +561,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); @@ -535,21 +580,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 ) { @@ -609,6 +643,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; @@ -616,8 +656,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; } @@ -630,18 +668,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 @@ -649,18 +725,25 @@ wxLogDialog::wxLogDialog(wxWindow *parent, wxBoxSizer *sizerButtons = new wxBoxSizer(wxVERTICAL); wxBoxSizer *sizerAll = new wxBoxSizer(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); + // 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__ + wxIcon icon = wxTheApp->GetStdIcon((int)(style & wxICON_MASK)); + sizerAll->Add(new wxStaticBitmap(this, -1, icon), 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); @@ -670,113 +753,241 @@ wxLogDialog::wxLogDialog(wxWindow *parent, btnOk->SetFocus(); - if ( m_messages.GetCount() == 1 ) + // 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 int icons[] = + { + wxICON_ERROR, + wxICON_EXCLAMATION, + wxICON_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 = wxTheApp->GetStdIcon(icons[icon]); - sizer->Remove(m_listctrl); + // This may very well fail if there are insufficient colours available. + // Degrade gracefully. + if ( !bmp.Ok() ) + { + loadedIcons = FALSE; + + break; + } + + wxImage img(bmp); + imageList->Add(img.Rescale(ICON_SIZE, ICON_SIZE).ConvertToBitmap()); } - 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)); +} - // 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::OnOk(wxCommandEvent& WXUNUSED(event)) +{ + EndModal(wxID_OK); +} + +#if wxUSE_FILE - 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::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); + } + + 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); + + 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; @@ -784,6 +995,13 @@ void wxLogDialog::OnDetails(wxCommandEvent& WXUNUSED(event)) // 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() @@ -795,3 +1013,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 +