#include "wx/filedlg.h"
#include "wx/msgdlg.h"
#include "wx/textctrl.h"
+ #include "wx/sizer.h"
+ #include "wx/statbmp.h"
#endif // WX_PRECOMP
#include "wx/file.h"
#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/image.h"
+#else // !wxUSE_TEXTFILE
+ #include "wx/msgdlg.h"
+#endif // wxUSE_LOG_DIALOG/!wxUSE_LOG_DIALOG
+
+// ----------------------------------------------------------------------------
+// private classes
+// ----------------------------------------------------------------------------
+
+#if wxUSE_LOG_DIALOG
+
+class wxLogDialog : public wxDialog
+{
+public:
+ wxLogDialog(wxWindow *parent,
+ const wxArrayString& messages,
+ const wxArrayInt& severity,
+ const wxArrayLong& timess,
+ const wxString& caption,
+ long style);
+ virtual ~wxLogDialog();
+
+ // event handlers
+ void OnOk(wxCommandEvent& event);
+ void OnDetails(wxCommandEvent& event);
+ void OnListSelect(wxListEvent& event);
+
+private:
+ // 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)
+ wxListCtrl *m_listctrl;
+
+ DECLARE_EVENT_TABLE()
+};
+
+BEGIN_EVENT_TABLE(wxLogDialog, wxDialog)
+ EVT_BUTTON(wxID_OK, wxLogDialog::OnOk)
+ EVT_BUTTON(wxID_MORE, wxLogDialog::OnDetails)
+ EVT_LIST_ITEM_SELECTED(-1, wxLogDialog::OnListSelect)
+END_EVENT_TABLE()
+
+#endif // wxUSE_LOG_DIALOG
+
// ----------------------------------------------------------------------------
// global variables
// ----------------------------------------------------------------------------
{
wxString msg;
TimeStamp(&msg);
- msg << szString << T('\n');
+ msg << szString << wxT('\n');
m_pTextCtrl->AppendText(msg);
}
{
m_bErrors = m_bWarnings = FALSE;
m_aMessages.Empty();
+ m_aSeverity.Empty();
m_aTimes.Empty();
}
// do it right now to block any new calls to Flush() while we're here
m_bHasMessages = FALSE;
- // concatenate all strings (but not too many to not overfill the msg box)
- wxString str;
- size_t nLines = 0,
- nMsgCount = m_aMessages.Count();
-
- // start from the most recent message
- for ( size_t n = nMsgCount; n > 0; n-- ) {
- // for Windows strings longer than this value are wrapped (NT 4.0)
- const size_t nMsgLineWidth = 156;
-
- nLines += (m_aMessages[n - 1].Len() + nMsgLineWidth - 1) / nMsgLineWidth;
-
- if ( nLines > 25 ) // don't put too many lines in message box
- break;
-
- str << m_aMessages[n - 1] << T("\n");
+ wxString title = wxTheApp->GetAppName();
+ if ( !!title )
+ {
+ title[0u] = wxToupper(title[0u]);
+ title += _T(' ');
}
- const wxChar *title;
long style;
-
if ( m_bErrors ) {
- title = _("Error");
+ title += _("Error");
style = wxICON_STOP;
}
else if ( m_bWarnings ) {
- title = _("Warning");
+ title += _("Warning");
style = wxICON_EXCLAMATION;
}
else {
- title = _("Information");
+ title += _("Information");
style = wxICON_INFORMATION;
}
- wxMessageBox(str, title, wxOK | style);
+ // this is the best we can do here
+ wxWindow *parent = wxTheApp->GetTopWindow();
+
+ size_t nMsgCount = m_aMessages.Count();
+
+ wxString str;
+ if ( nMsgCount == 1 )
+ {
+ str = m_aMessages[0];
+ }
+ else // more than one message
+ {
+#if wxUSE_LOG_DIALOG
+ wxLogDialog dlg(parent,
+ m_aMessages, m_aSeverity, m_aTimes,
+ title, style);
+
+ // clear the message list before showing the dialog because while it's
+ // shown some new messages may appear
+ Clear();
+
+ (void)dlg.ShowModal();
+#else // !wxUSE_LOG_DIALOG
+ // concatenate all strings (but not too many to not overfill the msg box)
+ size_t nLines = 0;
+
+ // start from the most recent message
+ for ( size_t n = nMsgCount; n > 0; n-- ) {
+ // for Windows strings longer than this value are wrapped (NT 4.0)
+ const size_t nMsgLineWidth = 156;
+
+ nLines += (m_aMessages[n - 1].Len() + nMsgLineWidth - 1) / nMsgLineWidth;
+
+ if ( nLines > 25 ) // don't put too many lines in message box
+ break;
+
+ str << m_aMessages[n - 1] << wxT("\n");
+ }
+#endif // wxUSE_LOG_DIALOG/!wxUSE_LOG_DIALOG
+ }
+
+ // this catches both cases of 1 message with wxUSE_LOG_DIALOG and any
+ // situation without it
+ if ( !!str )
+ {
+ wxMessageBox(str, title, wxOK | style, parent);
+
+ // no undisplayed messages whatsoever
+ Clear();
+ }
- // no undisplayed messages whatsoever
- Clear();
-
// do it here again
m_bHasMessages = FALSE;
}
-// 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 ) {
case wxLOG_Info:
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;
}
- break;
+ }
+ break;
case wxLOG_Status:
#if wxUSE_STATUSBAR
- {
- // find the top window and set it's status text if it has any
- wxFrame *pFrame = gs_pFrame;
- if ( pFrame == NULL ) {
- wxWindow *pWin = wxTheApp->GetTopWindow();
- if ( pWin != NULL && pWin->IsKindOf(CLASSINFO(wxFrame)) ) {
- pFrame = (wxFrame *)pWin;
- }
+ {
+ // find the top window and set it's status text if it has any
+ wxFrame *pFrame = gs_pFrame;
+ if ( pFrame == NULL ) {
+ wxWindow *pWin = wxTheApp->GetTopWindow();
+ if ( pWin != NULL && pWin->IsKindOf(CLASSINFO(wxFrame)) ) {
+ pFrame = (wxFrame *)pWin;
}
-
- if ( pFrame != NULL )
- pFrame->SetStatusText(szString);
}
+
+ if ( pFrame && pFrame->GetStatusBar() )
+ pFrame->SetStatusText(szString);
+ }
#endif // wxUSE_STATUSBAR
- break;
+ break;
case wxLOG_Trace:
case wxLOG_Debug:
- #ifdef __WXDEBUG__
- {
- #ifdef __WXMSW__
- // don't prepend debug/trace here: it goes to the
- // debug window anyhow, but do put a timestamp
- wxString str;
- TimeStamp(&str);
- str << szString << T("\n\r");
- OutputDebugString(str);
- #else
- // send them to stderr
- wxFprintf(stderr, T("%s: %s\n"),
- level == wxLOG_Trace ? T("Trace")
- : T("Debug"),
- szString);
- fflush(stderr);
- #endif
- }
- #endif // __WXDEBUG__
+ #ifdef __WXDEBUG__
+ {
+ #ifdef __WXMSW__
+ // don't prepend debug/trace here: it goes to the
+ // debug window anyhow, but do put a timestamp
+ wxString str;
+ TimeStamp(&str);
+ str << szString << wxT("\n\r");
+ OutputDebugString(str);
+ #else
+ // send them to stderr
+ wxFprintf(stderr, wxT("%s: %s\n"),
+ level == wxLOG_Trace ? wxT("Trace")
+ : wxT("Debug"),
+ szString);
+ fflush(stderr);
+ #endif
+ }
+ #endif // __WXDEBUG__
- break;
+ break;
case wxLOG_FatalError:
- // show this one immediately
- wxMessageBox(szString, _("Fatal error"), wxICON_HAND);
- break;
+ // show this one immediately
+ wxMessageBox(szString, _("Fatal error"), wxICON_HAND);
+ wxExit();
+ break;
case wxLOG_Error:
+ if ( !m_bErrors ) {
+#if !wxUSE_LOG_DIALOG
// discard earlier informational messages if this is the 1st
- // error because they might not make sense any more
- if ( !m_bErrors ) {
- m_aMessages.Empty();
- m_aTimes.Empty();
- m_bErrors = TRUE;
- }
- // fall through
+ // error because they might not make sense any more and showing
+ // them in a message box might be confusing
+ m_aMessages.Empty();
+ m_aSeverity.Empty();
+ m_aTimes.Empty();
+#endif // wxUSE_LOG_DIALOG
+ m_bErrors = TRUE;
+ }
+ // fall through
case wxLOG_Warning:
- if ( !m_bErrors ) {
- // for the warning we don't discard the info messages
- m_bWarnings = TRUE;
- }
-
- m_aMessages.Add(szString);
- m_aTimes.Add((long)t);
- m_bHasMessages = TRUE;
- break;
+ if ( !m_bErrors ) {
+ // for the warning we don't discard the info messages
+ m_bWarnings = TRUE;
+ }
+
+ m_aMessages.Add(szString);
+ m_aSeverity.Add((int)level);
+ m_aTimes.Add((long)t);
+ m_bHasMessages = TRUE;
+ break;
}
}
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
{
// get the file name
// -----------------
- const wxChar *szFileName = wxSaveFileSelector(T("log"), T("txt"), T("log.txt"));
+ const wxChar *szFileName = wxSaveFileSelector(wxT("log"), wxT("txt"), wxT("log.txt"));
if ( szFileName == NULL ) {
// cancelled
return;
{
// first let the previous logger show it
if ( m_pOldLog != NULL && m_bPassMessages ) {
- // FIXME why can't we access protected wxLog method from here (we derive
- // from wxLog)? gcc gives "DoLog is protected in this context", what
- // does this mean? Anyhow, the cast is harmless and let's us do what
- // we want.
+ // bogus cast just to access protected DoLog
((wxLogWindow *)m_pOldLog)->DoLog(level, szString, t);
}
wxString msg;
TimeStamp(&msg);
- msg << szString << T('\n');
+ msg << szString << wxT('\n');
pText->AppendText(msg);
delete m_pLogFrame;
}
+// ----------------------------------------------------------------------------
+// wxLogDialog
+// ----------------------------------------------------------------------------
+
+#if wxUSE_LOG_DIALOG
+
+static const size_t MARGIN = 10;
+
+wxLogDialog::wxLogDialog(wxWindow *parent,
+ const wxArrayString& messages,
+ const wxArrayInt& severity,
+ const wxArrayLong& times,
+ const wxString& caption,
+ long style)
+ : wxDialog(parent, -1, caption )
+{
+ 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;
+
+ // 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);
+
+ 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);
+
+ wxIcon icon = wxTheApp->GetStdIcon((int)(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);
+
+ sizerTop->Add(sizerAll, 0, wxCENTRE|wxALL, MARGIN);
+
+ SetAutoLayout(TRUE);
+ SetSizer(sizerTop);
+
+ sizerTop->SetSizeHints(this);
+ sizerTop->Fit(this);
+
+ 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::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);
+}
+
+void wxLogDialog::OnDetails(wxCommandEvent& WXUNUSED(event))
+{
+ wxSizer *sizer = GetSizer();
+
+ if ( m_showingDetails )
+ {
+ m_btnDetails->SetLabel(_T("&Details >>"));
+
+ sizer->Remove(m_listctrl);
+ }
+ else // show details now
+ {
+ m_btnDetails->SetLabel(_T("<< &Details"));
+
+ if ( !m_listctrl )
+ {
+ // create it now
+ m_listctrl = new wxListCtrl(this, -1,
+ wxDefaultPosition, wxDefaultSize,
+ wxSUNKEN_BORDER |
+ wxLC_REPORT |
+ wxLC_NO_HEADER |
+ wxLC_SINGLE_SEL);
+ 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[] =
+ {
+ wxICON_ERROR,
+ wxICON_EXCLAMATION,
+ wxICON_INFORMATION
+ };
+
+ 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());
+ }
+
+ m_listctrl->SetImageList(imageList, wxIMAGE_LIST_SMALL);
+
+ // and fill it
+ wxString fmt = wxLog::GetTimestamp();
+ if ( !fmt )
+ {
+ // default format
+ fmt = _T("%X");
+ }
+
+ 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;
+
+ case wxLOG_Warning:
+ image = 1;
+ break;
+
+ default:
+ image = 2;
+ }
+
+ m_listctrl->InsertItem(n, m_messages[n], image);
+ m_listctrl->SetItem(n, 1,
+ wxDateTime((time_t)m_times[n]).Format(fmt));
+ }
+
+ // let the columns size themselves
+ m_listctrl->SetColumnWidth(0, wxLIST_AUTOSIZE);
+ m_listctrl->SetColumnWidth(1, wxLIST_AUTOSIZE);
+
+ // get the approx height of the listctrl
+ wxFont font = GetFont();
+ if ( !font.Ok() )
+ font = *wxSWISS_FONT;
+
+ 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);
+ }
+
+ sizer->Add(m_listctrl, 1, wxEXPAND|(wxALL & ~wxTOP), MARGIN);
+ }
+
+ m_showingDetails = !m_showingDetails;
+
+ // in any case, our size changed - update
+ sizer->SetSizeHints(this);
+ sizer->Fit(this);
+}
+
+wxLogDialog::~wxLogDialog()
+{
+ if ( m_listctrl )
+ {
+ delete m_listctrl->GetImageList(wxIMAGE_LIST_SMALL);
+ }
+}
+
+#endif // wxUSE_LOG_DIALOG