1///////////////////////////////////////////////////////////////////////////// 2// Name: src/generic/logg.cpp 3// Purpose: wxLog-derived classes which need GUI support (the rest is in 4// src/common/log.cpp) 5// Author: Vadim Zeitlin 6// Modified by: 7// Created: 20.09.99 (extracted from src/common/log.cpp) 8// RCS-ID: $Id: logg.cpp 43078 2006-11-04 23:46:02Z VZ $ 9// Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr> 10// Licence: wxWindows licence 11///////////////////////////////////////////////////////////////////////////// 12 13// ============================================================================ 14// declarations 15// ============================================================================ 16 17// ---------------------------------------------------------------------------- 18// headers 19// ---------------------------------------------------------------------------- 20 21// For compilers that support precompilation, includes "wx.h". 22#include "wx/wxprec.h" 23 24#ifdef __BORLANDC__ 25 #pragma hdrstop 26#endif 27 28#ifndef WX_PRECOMP 29 #include "wx/app.h" 30 #include "wx/button.h" 31 #include "wx/intl.h" 32 #include "wx/log.h" 33 #include "wx/menu.h" 34 #include "wx/frame.h" 35 #include "wx/filedlg.h" 36 #include "wx/msgdlg.h" 37 #include "wx/textctrl.h" 38 #include "wx/sizer.h" 39 #include "wx/statbmp.h" 40 #include "wx/settings.h" 41#endif // WX_PRECOMP 42 43#if wxUSE_LOGGUI || wxUSE_LOGWINDOW 44 45#include "wx/file.h" 46#include "wx/textfile.h" 47#include "wx/statline.h" 48#include "wx/artprov.h" 49 50#ifdef __WXMSW__ 51 // for OutputDebugString() 52 #include "wx/msw/private.h" 53#endif // Windows 54 55#ifdef __WXPM__ 56 #include <time.h> 57#endif 58 59#if wxUSE_LOG_DIALOG 60 #include "wx/listctrl.h" 61 #include "wx/imaglist.h" 62 #include "wx/image.h" 63#endif // wxUSE_LOG_DIALOG/!wxUSE_LOG_DIALOG 64 65#if defined(__MWERKS__) && wxUSE_UNICODE 66 #include <wtime.h> 67#endif 68 69#include "wx/datetime.h" 70 71// the suffix we add to the button to show that the dialog can be expanded 72#define EXPAND_SUFFIX _T(" >>") 73 74// ---------------------------------------------------------------------------- 75// private classes 76// ---------------------------------------------------------------------------- 77 78#if wxUSE_LOG_DIALOG 79 80// this function is a wrapper around strftime(3) 81// allows to exclude the usage of wxDateTime 82static wxString TimeStamp(const wxChar *format, time_t t) 83{ 84#if wxUSE_DATETIME 85 wxChar buf[4096]; 86 struct tm tm; 87 if ( !wxStrftime(buf, WXSIZEOF(buf), format, wxLocaltime_r(&t, &tm)) ) 88 { 89 // buffer is too small? 90 wxFAIL_MSG(_T("strftime() failed")); 91 } 92 return wxString(buf); 93#else // !wxUSE_DATETIME 94 return wxEmptyString; 95#endif // wxUSE_DATETIME/!wxUSE_DATETIME 96} 97 98 99class wxLogDialog : public wxDialog 100{ 101public: 102 wxLogDialog(wxWindow *parent, 103 const wxArrayString& messages, 104 const wxArrayInt& severity, 105 const wxArrayLong& timess, 106 const wxString& caption, 107 long style); 108 virtual ~wxLogDialog(); 109 110 // event handlers 111 void OnOk(wxCommandEvent& event); 112 void OnDetails(wxCommandEvent& event); 113#if wxUSE_FILE 114 void OnSave(wxCommandEvent& event); 115#endif // wxUSE_FILE 116 void OnListSelect(wxListEvent& event); 117 118private: 119 // create controls needed for the details display 120 void CreateDetailsControls(); 121 122 // the data for the listctrl 123 wxArrayString m_messages; 124 wxArrayInt m_severity; 125 wxArrayLong m_times; 126 127 // the "toggle" button and its state 128#ifndef __SMARTPHONE__ 129 wxButton *m_btnDetails; 130#endif 131 bool m_showingDetails; 132 133 // the controls which are not shown initially (but only when details 134 // button is pressed) 135 wxListCtrl *m_listctrl; 136#ifndef __SMARTPHONE__ 137#if wxUSE_STATLINE 138 wxStaticLine *m_statline; 139#endif // wxUSE_STATLINE 140#if wxUSE_FILE 141 wxButton *m_btnSave; 142#endif // wxUSE_FILE 143#endif // __SMARTPHONE__ 144 145 // the translated "Details" string 146 static wxString ms_details; 147 148 DECLARE_EVENT_TABLE() 149 DECLARE_NO_COPY_CLASS(wxLogDialog) 150}; 151 152BEGIN_EVENT_TABLE(wxLogDialog, wxDialog) 153 EVT_BUTTON(wxID_OK, wxLogDialog::OnOk) 154 EVT_BUTTON(wxID_MORE, wxLogDialog::OnDetails) 155#if wxUSE_FILE 156 EVT_BUTTON(wxID_SAVE, wxLogDialog::OnSave) 157#endif // wxUSE_FILE 158 EVT_LIST_ITEM_SELECTED(wxID_ANY, wxLogDialog::OnListSelect) 159END_EVENT_TABLE() 160 161#endif // wxUSE_LOG_DIALOG 162 163// ---------------------------------------------------------------------------- 164// private functions 165// ---------------------------------------------------------------------------- 166 167#if wxUSE_FILE && wxUSE_FILEDLG 168 169// pass an uninitialized file object, the function will ask the user for the 170// filename and try to open it, returns true on success (file was opened), 171// false if file couldn't be opened/created and -1 if the file selection 172// dialog was cancelled 173static int OpenLogFile(wxFile& file, wxString *filename = NULL, wxWindow *parent = NULL); 174 175#endif // wxUSE_FILE 176 177// ---------------------------------------------------------------------------- 178// global variables 179// ---------------------------------------------------------------------------- 180 181// we use a global variable to store the frame pointer for wxLogStatus - bad, 182// but it's the easiest way 183static wxFrame *gs_pFrame = NULL; // FIXME MT-unsafe 184 185// ============================================================================ 186// implementation 187// ============================================================================ 188 189// ---------------------------------------------------------------------------- 190// global functions 191// ---------------------------------------------------------------------------- 192 193// accepts an additional argument which tells to which frame the output should 194// be directed 195void wxVLogStatus(wxFrame *pFrame, const wxChar *szFormat, va_list argptr) 196{ 197 wxString msg; 198 199 wxLog *pLog = wxLog::GetActiveTarget(); 200 if ( pLog != NULL ) { 201 msg.PrintfV(szFormat, argptr); 202 203 wxASSERT( gs_pFrame == NULL ); // should be reset! 204 gs_pFrame = pFrame; 205#ifdef __WXWINCE__ 206 wxLog::OnLog(wxLOG_Status, msg, 0); 207#else 208 wxLog::OnLog(wxLOG_Status, msg, time(NULL)); 209#endif 210 gs_pFrame = (wxFrame *) NULL; 211 } 212} 213 214void wxLogStatus(wxFrame *pFrame, const wxChar *szFormat, ...) 215{ 216 va_list argptr; 217 va_start(argptr, szFormat); 218 wxVLogStatus(pFrame, szFormat, argptr); 219 va_end(argptr); 220} 221 222// ---------------------------------------------------------------------------- 223// wxLogGui implementation (FIXME MT-unsafe) 224// ---------------------------------------------------------------------------- 225 226#if wxUSE_LOGGUI 227 228wxLogGui::wxLogGui() 229{ 230 Clear(); 231} 232 233void wxLogGui::Clear() 234{ 235 m_bErrors = 236 m_bWarnings = 237 m_bHasMessages = false; 238 239 m_aMessages.Empty(); 240 m_aSeverity.Empty(); 241 m_aTimes.Empty(); 242} 243 244void wxLogGui::Flush() 245{ 246 if ( !m_bHasMessages ) 247 return; 248 249 // do it right now to block any new calls to Flush() while we're here 250 m_bHasMessages = false; 251 252 unsigned repeatCount = 0; 253 if ( wxLog::GetRepetitionCounting() ) 254 { 255 repeatCount = wxLog::DoLogNumberOfRepeats(); 256 } 257 258 wxString appName = wxTheApp->GetAppName(); 259 if ( !appName.empty() ) 260 appName[0u] = (wxChar)wxToupper(appName[0u]); 261 262 long style; 263 wxString titleFormat; 264 if ( m_bErrors ) { 265 titleFormat = _("%s Error"); 266 style = wxICON_STOP; 267 } 268 else if ( m_bWarnings ) { 269 titleFormat = _("%s Warning"); 270 style = wxICON_EXCLAMATION; 271 } 272 else { 273 titleFormat = _("%s Information"); 274 style = wxICON_INFORMATION; 275 } 276 277 wxString title; 278 title.Printf(titleFormat, appName.c_str()); 279 280 size_t nMsgCount = m_aMessages.Count(); 281 282 // avoid showing other log dialogs until we're done with the dialog we're 283 // showing right now: nested modal dialogs make for really bad UI! 284 Suspend(); 285 286 wxString str; 287 if ( nMsgCount == 1 ) 288 { 289 str = m_aMessages[0]; 290 } 291 else // more than one message 292 { 293#if wxUSE_LOG_DIALOG 294 295 if ( repeatCount > 0 ) 296 m_aMessages[nMsgCount-1] += wxString::Format(wxT(" (%s)"), m_aMessages[nMsgCount-2].c_str()); 297 wxLogDialog dlg(NULL, 298 m_aMessages, m_aSeverity, m_aTimes, 299 title, style); 300 301 // clear the message list before showing the dialog because while it's 302 // shown some new messages may appear 303 Clear(); 304 305 (void)dlg.ShowModal(); 306#else // !wxUSE_LOG_DIALOG 307 // concatenate all strings (but not too many to not overfill the msg box) 308 size_t nLines = 0; 309 310 // start from the most recent message 311 for ( size_t n = nMsgCount; n > 0; n-- ) { 312 // for Windows strings longer than this value are wrapped (NT 4.0) 313 const size_t nMsgLineWidth = 156; 314 315 nLines += (m_aMessages[n - 1].Len() + nMsgLineWidth - 1) / nMsgLineWidth; 316 317 if ( nLines > 25 ) // don't put too many lines in message box 318 break; 319 320 str << m_aMessages[n - 1] << wxT("\n"); 321 } 322#endif // wxUSE_LOG_DIALOG/!wxUSE_LOG_DIALOG 323 } 324 325 // this catches both cases of 1 message with wxUSE_LOG_DIALOG and any 326 // situation without it 327 if ( !str.empty() ) 328 { 329 wxMessageBox(str, title, wxOK | style); 330 331 // no undisplayed messages whatsoever 332 Clear(); 333 } 334 335 // allow flushing the logs again 336 Resume(); 337} 338 339// log all kinds of messages 340void wxLogGui::DoLog(wxLogLevel level, const wxChar *szString, time_t t) 341{ 342 switch ( level ) { 343 case wxLOG_Info: 344 if ( GetVerbose() ) 345 case wxLOG_Message: 346 { 347 m_aMessages.Add(szString); 348 m_aSeverity.Add(wxLOG_Message); 349 m_aTimes.Add((long)t); 350 m_bHasMessages = true; 351 } 352 break; 353 354 case wxLOG_Status: 355#if wxUSE_STATUSBAR 356 { 357 // find the top window and set it's status text if it has any 358 wxFrame *pFrame = gs_pFrame; 359 if ( pFrame == NULL ) { 360 wxWindow *pWin = wxTheApp->GetTopWindow(); 361 if ( pWin != NULL && pWin->IsKindOf(CLASSINFO(wxFrame)) ) { 362 pFrame = (wxFrame *)pWin; 363 } 364 } 365 366 if ( pFrame && pFrame->GetStatusBar() ) 367 pFrame->SetStatusText(szString); 368 } 369#endif // wxUSE_STATUSBAR 370 break; 371 372 case wxLOG_Trace: 373 case wxLOG_Debug: 374 #ifdef __WXDEBUG__ 375 { 376 wxString str; 377 TimeStamp(&str); 378 str += szString; 379 380 #if defined(__WXMSW__) && !defined(__WXMICROWIN__) 381 // don't prepend debug/trace here: it goes to the 382 // debug window anyhow 383 str += wxT("\r\n"); 384 OutputDebugString(str); 385 #else 386 // send them to stderr 387 wxFprintf(stderr, wxT("[%s] %s\n"), 388 level == wxLOG_Trace ? wxT("Trace") 389 : wxT("Debug"), 390 str.c_str()); 391 fflush(stderr); 392 #endif 393 } 394 #endif // __WXDEBUG__ 395 396 break; 397 398 case wxLOG_FatalError: 399 // show this one immediately 400 wxMessageBox(szString, _("Fatal error"), wxICON_HAND); 401 wxExit(); 402 break; 403 404 case wxLOG_Error: 405 if ( !m_bErrors ) { 406#if !wxUSE_LOG_DIALOG 407 // discard earlier informational messages if this is the 1st 408 // error because they might not make sense any more and showing 409 // them in a message box might be confusing 410 m_aMessages.Empty(); 411 m_aSeverity.Empty(); 412 m_aTimes.Empty(); 413#endif // wxUSE_LOG_DIALOG 414 m_bErrors = true; 415 } 416 // fall through 417 418 case wxLOG_Warning: 419 if ( !m_bErrors ) { 420 // for the warning we don't discard the info messages 421 m_bWarnings = true; 422 } 423 424 m_aMessages.Add(szString); 425 m_aSeverity.Add((int)level); 426 m_aTimes.Add((long)t); 427 m_bHasMessages = true; 428 break; 429 } 430} 431 432#endif // wxUSE_LOGGUI 433 434// ---------------------------------------------------------------------------- 435// wxLogWindow and wxLogFrame implementation 436// ---------------------------------------------------------------------------- 437 438#if wxUSE_LOGWINDOW 439 440// log frame class 441// --------------- 442class wxLogFrame : public wxFrame 443{ 444public: 445 // ctor & dtor 446 wxLogFrame(wxWindow *pParent, wxLogWindow *log, const wxChar *szTitle); 447 virtual ~wxLogFrame(); 448 449 // menu callbacks 450 void OnClose(wxCommandEvent& event); 451 void OnCloseWindow(wxCloseEvent& event); 452#if wxUSE_FILE 453 void OnSave (wxCommandEvent& event); 454#endif // wxUSE_FILE 455 void OnClear(wxCommandEvent& event); 456 457 // accessors 458 wxTextCtrl *TextCtrl() const { return m_pTextCtrl; } 459 460private: 461 // use standard ids for our commands! 462 enum 463 { 464 Menu_Close = wxID_CLOSE, 465 Menu_Save = wxID_SAVE, 466 Menu_Clear = wxID_CLEAR 467 }; 468 469 // common part of OnClose() and OnCloseWindow() 470 void DoClose(); 471 472 wxTextCtrl *m_pTextCtrl; 473 wxLogWindow *m_log; 474 475 DECLARE_EVENT_TABLE() 476 DECLARE_NO_COPY_CLASS(wxLogFrame) 477}; 478 479BEGIN_EVENT_TABLE(wxLogFrame, wxFrame) 480 // wxLogWindow menu events 481 EVT_MENU(Menu_Close, wxLogFrame::OnClose) 482#if wxUSE_FILE 483 EVT_MENU(Menu_Save, wxLogFrame::OnSave) 484#endif // wxUSE_FILE 485 EVT_MENU(Menu_Clear, wxLogFrame::OnClear) 486 487 EVT_CLOSE(wxLogFrame::OnCloseWindow) 488END_EVENT_TABLE() 489 490wxLogFrame::wxLogFrame(wxWindow *pParent, wxLogWindow *log, const wxChar *szTitle) 491 : wxFrame(pParent, wxID_ANY, szTitle) 492{ 493 m_log = log; 494 495 m_pTextCtrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, 496 wxDefaultSize, 497 wxTE_MULTILINE | 498 wxHSCROLL | 499 // needed for Win32 to avoid 65Kb limit but it doesn't work well 500 // when using RichEdit 2.0 which we always do in the Unicode build 501#if !wxUSE_UNICODE 502 wxTE_RICH | 503#endif // !wxUSE_UNICODE 504 wxTE_READONLY); 505 506#if wxUSE_MENUS 507 // create menu 508 wxMenuBar *pMenuBar = new wxMenuBar; 509 wxMenu *pMenu = new wxMenu; 510#if wxUSE_FILE 511 pMenu->Append(Menu_Save, _("&Save..."), _("Save log contents to file")); 512#endif // wxUSE_FILE 513 pMenu->Append(Menu_Clear, _("C&lear"), _("Clear the log contents")); 514 pMenu->AppendSeparator(); 515 pMenu->Append(Menu_Close, _("&Close"), _("Close this window")); 516 pMenuBar->Append(pMenu, _("&Log")); 517 SetMenuBar(pMenuBar); 518#endif // wxUSE_MENUS 519 520#if wxUSE_STATUSBAR 521 // status bar for menu prompts 522 CreateStatusBar(); 523#endif // wxUSE_STATUSBAR 524 525 m_log->OnFrameCreate(this); 526} 527 528void wxLogFrame::DoClose() 529{ 530 if ( m_log->OnFrameClose(this) ) 531 { 532 // instead of closing just hide the window to be able to Show() it 533 // later 534 Show(false); 535 } 536} 537 538void wxLogFrame::OnClose(wxCommandEvent& WXUNUSED(event)) 539{ 540 DoClose(); 541} 542 543void wxLogFrame::OnCloseWindow(wxCloseEvent& WXUNUSED(event)) 544{ 545 DoClose(); 546} 547 548#if wxUSE_FILE 549void wxLogFrame::OnSave(wxCommandEvent& WXUNUSED(event)) 550{ 551#if wxUSE_FILEDLG 552 wxString filename; 553 wxFile file; 554 int rc = OpenLogFile(file, &filename, this); 555 if ( rc == -1 ) 556 { 557 // cancelled 558 return; 559 } 560 561 bool bOk = rc != 0; 562 563 // retrieve text and save it 564 // ------------------------- 565 int nLines = m_pTextCtrl->GetNumberOfLines(); 566 for ( int nLine = 0; bOk && nLine < nLines; nLine++ ) { 567 bOk = file.Write(m_pTextCtrl->GetLineText(nLine) + 568 wxTextFile::GetEOL()); 569 } 570 571 if ( bOk ) 572 bOk = file.Close(); 573 574 if ( !bOk ) { 575 wxLogError(_("Can't save log contents to file.")); 576 } 577 else { 578 wxLogStatus(this, _("Log saved to the file '%s'."), filename.c_str()); 579 } 580#endif 581} 582#endif // wxUSE_FILE 583 584void wxLogFrame::OnClear(wxCommandEvent& WXUNUSED(event)) 585{ 586 m_pTextCtrl->Clear(); 587} 588 589wxLogFrame::~wxLogFrame() 590{ 591 m_log->OnFrameDelete(this); 592} 593 594// wxLogWindow 595// ----------- 596 597wxLogWindow::wxLogWindow(wxWindow *pParent, 598 const wxChar *szTitle, 599 bool bShow, 600 bool bDoPass) 601{ 602 PassMessages(bDoPass); 603 604 m_pLogFrame = new wxLogFrame(pParent, this, szTitle); 605 606 if ( bShow ) 607 m_pLogFrame->Show(); 608} 609 610void wxLogWindow::Show(bool bShow) 611{ 612 m_pLogFrame->Show(bShow); 613} 614 615void wxLogWindow::DoLog(wxLogLevel level, const wxChar *szString, time_t t) 616{ 617 // first let the previous logger show it 618 wxLogPassThrough::DoLog(level, szString, t); 619 620 if ( m_pLogFrame ) { 621 switch ( level ) { 622 case wxLOG_Status: 623 // by default, these messages are ignored by wxLog, so process 624 // them ourselves 625 if ( !wxIsEmpty(szString) ) 626 { 627 wxString str; 628 str << _("Status: ") << szString; 629 DoLogString(str, t); 630 } 631 break; 632 633 // don't put trace messages in the text window for 2 reasons: 634 // 1) there are too many of them 635 // 2) they may provoke other trace messages thus sending a program 636 // into an infinite loop 637 case wxLOG_Trace: 638 break; 639 640 default: 641 // and this will format it nicely and call our DoLogString() 642 wxLog::DoLog(level, szString, t); 643 } 644 } 645} 646 647void wxLogWindow::DoLogString(const wxChar *szString, time_t WXUNUSED(t)) 648{ 649 // put the text into our window 650 wxTextCtrl *pText = m_pLogFrame->TextCtrl(); 651 652 // remove selection (WriteText is in fact ReplaceSelection) 653#ifdef __WXMSW__ 654 wxTextPos nLen = pText->GetLastPosition(); 655 pText->SetSelection(nLen, nLen); 656#endif // Windows 657 658 wxString msg; 659 TimeStamp(&msg); 660 msg << szString << wxT('\n'); 661 662 pText->AppendText(msg); 663 664 // TODO ensure that the line can be seen 665} 666 667wxFrame *wxLogWindow::GetFrame() const 668{ 669 return m_pLogFrame; 670} 671 672void wxLogWindow::OnFrameCreate(wxFrame * WXUNUSED(frame)) 673{ 674} 675 676bool wxLogWindow::OnFrameClose(wxFrame * WXUNUSED(frame)) 677{ 678 // allow to close 679 return true; 680} 681 682void wxLogWindow::OnFrameDelete(wxFrame * WXUNUSED(frame)) 683{ 684 m_pLogFrame = (wxLogFrame *)NULL; 685} 686 687wxLogWindow::~wxLogWindow() 688{ 689 // may be NULL if log frame already auto destroyed itself 690 delete m_pLogFrame; 691} 692 693#endif // wxUSE_LOGWINDOW 694 695// ---------------------------------------------------------------------------- 696// wxLogDialog 697// ---------------------------------------------------------------------------- 698 699#if wxUSE_LOG_DIALOG 700 701#ifndef __SMARTPHONE__ 702static const size_t MARGIN = 10; 703#else 704static const size_t MARGIN = 0; 705#endif 706 707wxString wxLogDialog::ms_details; 708 709wxLogDialog::wxLogDialog(wxWindow *parent, 710 const wxArrayString& messages, 711 const wxArrayInt& severity, 712 const wxArrayLong& times, 713 const wxString& caption, 714 long style) 715 : wxDialog(parent, wxID_ANY, caption, 716 wxDefaultPosition, wxDefaultSize, 717 wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) 718{ 719 if ( ms_details.empty() ) 720 { 721 // ensure that we won't loop here if wxGetTranslation() 722 // happens to pop up a Log message while translating this :-) 723 ms_details = wxTRANSLATE("&Details"); 724 ms_details = wxGetTranslation(ms_details); 725#ifdef __SMARTPHONE__ 726 ms_details = wxStripMenuCodes(ms_details); 727#endif 728 } 729 730 size_t count = messages.GetCount(); 731 m_messages.Alloc(count); 732 m_severity.Alloc(count); 733 m_times.Alloc(count); 734 735 for ( size_t n = 0; n < count; n++ ) 736 { 737 wxString msg = messages[n]; 738 msg.Replace(wxT("\n"), wxT(" ")); 739 m_messages.Add(msg); 740 m_severity.Add(severity[n]); 741 m_times.Add(times[n]); 742 } 743 744 m_showingDetails = false; // not initially 745 m_listctrl = (wxListCtrl *)NULL; 746 747#ifndef __SMARTPHONE__ 748 749#if wxUSE_STATLINE 750 m_statline = (wxStaticLine *)NULL; 751#endif // wxUSE_STATLINE 752 753#if wxUSE_FILE 754 m_btnSave = (wxButton *)NULL; 755#endif // wxUSE_FILE 756 757#endif // __SMARTPHONE__ 758 759 bool isPda = (wxSystemSettings::GetScreenType() <= wxSYS_SCREEN_PDA); 760 761 // create the controls which are always shown and layout them: we use 762 // sizers even though our window is not resizeable to calculate the size of 763 // the dialog properly 764 wxBoxSizer *sizerTop = new wxBoxSizer(wxVERTICAL); 765#ifndef __SMARTPHONE__ 766 wxBoxSizer *sizerButtons = new wxBoxSizer(isPda ? wxHORIZONTAL : wxVERTICAL); 767#endif 768 wxBoxSizer *sizerAll = new wxBoxSizer(isPda ? wxVERTICAL : wxHORIZONTAL); 769 770#ifdef __SMARTPHONE__ 771 SetLeftMenu(wxID_OK); 772 SetRightMenu(wxID_MORE, ms_details + EXPAND_SUFFIX); 773#else 774 wxButton *btnOk = new wxButton(this, wxID_OK); 775 sizerButtons->Add(btnOk, 0, isPda ? wxCENTRE : wxCENTRE|wxBOTTOM, MARGIN/2); 776 m_btnDetails = new wxButton(this, wxID_MORE, ms_details + EXPAND_SUFFIX); 777 sizerButtons->Add(m_btnDetails, 0, isPda ? wxCENTRE|wxLEFT : wxCENTRE | wxTOP, MARGIN/2 - 1); 778#endif 779 780 wxBitmap bitmap; 781 switch ( style & wxICON_MASK ) 782 { 783 case wxICON_ERROR: 784 bitmap = wxArtProvider::GetBitmap(wxART_ERROR, wxART_MESSAGE_BOX); 785#ifdef __WXPM__ 786 bitmap.SetId(wxICON_SMALL_ERROR); 787#endif 788 break; 789 790 case wxICON_INFORMATION: 791 bitmap = wxArtProvider::GetBitmap(wxART_INFORMATION, wxART_MESSAGE_BOX); 792#ifdef __WXPM__ 793 bitmap.SetId(wxICON_SMALL_INFO); 794#endif 795 break; 796 797 case wxICON_WARNING: 798 bitmap = wxArtProvider::GetBitmap(wxART_WARNING, wxART_MESSAGE_BOX); 799#ifdef __WXPM__ 800 bitmap.SetId(wxICON_SMALL_WARNING); 801#endif 802 break; 803 804 default: 805 wxFAIL_MSG(_T("incorrect log style")); 806 } 807 808 if (!isPda) 809 sizerAll->Add(new wxStaticBitmap(this, wxID_ANY, bitmap), 0, 810 wxALIGN_CENTRE_VERTICAL); 811 812 const wxString& message = messages.Last(); 813 sizerAll->Add(CreateTextSizer(message), 1, 814 wxALIGN_CENTRE_VERTICAL | wxLEFT | wxRIGHT, MARGIN); 815#ifndef __SMARTPHONE__ 816 sizerAll->Add(sizerButtons, 0, isPda ? wxCENTRE|wxTOP|wxBOTTOM : (wxALIGN_RIGHT | wxLEFT), MARGIN); 817#endif 818 819 sizerTop->Add(sizerAll, 0, wxALL | wxEXPAND, MARGIN); 820 821 SetSizer(sizerTop); 822 823 // see comments in OnDetails() 824 // 825 // Note: Doing this, this way, triggered a nasty bug in 826 // wxTopLevelWindowGTK::GtkOnSize which took -1 literally once 827 // either of maxWidth or maxHeight was set. This symptom has been 828 // fixed there, but it is a problem that remains as long as we allow 829 // unchecked access to the internal size members. We really need to 830 // encapuslate window sizes more cleanly and make it clear when -1 will 831 // be substituted and when it will not. 832 833 wxSize size = sizerTop->Fit(this); 834 m_maxHeight = size.y; 835 SetSizeHints(size.x, size.y, m_maxWidth, m_maxHeight); 836 837#ifndef __SMARTPHONE__ 838 btnOk->SetFocus(); 839#endif 840 841 Centre(); 842 843 if (isPda) 844 { 845 // Move up the screen so that when we expand the dialog, 846 // there's enough space. 847 Move(wxPoint(GetPosition().x, GetPosition().y / 2)); 848 } 849} 850 851void wxLogDialog::CreateDetailsControls() 852{ 853#ifndef __SMARTPHONE__ 854 // create the save button and separator line if possible 855#if wxUSE_FILE 856 m_btnSave = new wxButton(this, wxID_SAVE); 857#endif // wxUSE_FILE 858 859#if wxUSE_STATLINE 860 m_statline = new wxStaticLine(this, wxID_ANY); 861#endif // wxUSE_STATLINE 862 863#endif // __SMARTPHONE__ 864 865 // create the list ctrl now 866 m_listctrl = new wxListCtrl(this, wxID_ANY, 867 wxDefaultPosition, wxDefaultSize, 868 wxSUNKEN_BORDER | 869 wxLC_REPORT | 870 wxLC_NO_HEADER | 871 wxLC_SINGLE_SEL); 872#ifdef __WXWINCE__ 873 // This maks a big aesthetic difference on WinCE but I 874 // don't want to risk problems on other platforms 875 m_listctrl->Hide(); 876#endif 877 878 // no need to translate these strings as they're not shown to the 879 // user anyhow (we use wxLC_NO_HEADER style) 880 m_listctrl->InsertColumn(0, _T("Message")); 881 m_listctrl->InsertColumn(1, _T("Time")); 882 883 // prepare the imagelist 884 static const int ICON_SIZE = 16; 885 wxImageList *imageList = new wxImageList(ICON_SIZE, ICON_SIZE); 886 887 // order should be the same as in the switch below! 888 static const wxChar* icons[] = 889 { 890 wxART_ERROR, 891 wxART_WARNING, 892 wxART_INFORMATION 893 }; 894 895 bool loadedIcons = true; 896 897 for ( size_t icon = 0; icon < WXSIZEOF(icons); icon++ ) 898 { 899 wxBitmap bmp = wxArtProvider::GetBitmap(icons[icon], wxART_MESSAGE_BOX, 900 wxSize(ICON_SIZE, ICON_SIZE)); 901 902 // This may very well fail if there are insufficient colours available. 903 // Degrade gracefully. 904 if ( !bmp.Ok() ) 905 { 906 loadedIcons = false; 907 908 break; 909 } 910 911 imageList->Add(bmp); 912 } 913 914 m_listctrl->SetImageList(imageList, wxIMAGE_LIST_SMALL); 915 916 // and fill it 917 wxString fmt = wxLog::GetTimestamp(); 918 if ( !fmt ) 919 { 920 // default format 921 fmt = _T("%c"); 922 } 923 924 size_t count = m_messages.GetCount(); 925 for ( size_t n = 0; n < count; n++ ) 926 { 927 int image; 928 929 if ( loadedIcons ) 930 { 931 switch ( m_severity[n] ) 932 { 933 case wxLOG_Error: 934 image = 0; 935 break; 936 937 case wxLOG_Warning: 938 image = 1; 939 break; 940 941 default: 942 image = 2; 943 } 944 } 945 else // failed to load images 946 { 947 image = -1; 948 } 949 950 m_listctrl->InsertItem(n, m_messages[n], image); 951 m_listctrl->SetItem(n, 1, TimeStamp(fmt, (time_t)m_times[n])); 952 } 953 954 // let the columns size themselves 955 m_listctrl->SetColumnWidth(0, wxLIST_AUTOSIZE); 956 m_listctrl->SetColumnWidth(1, wxLIST_AUTOSIZE); 957 958 // calculate an approximately nice height for the listctrl 959 int height = GetCharHeight()*(count + 4); 960 961 // but check that the dialog won't fall fown from the screen 962 // 963 // we use GetMinHeight() to get the height of the dialog part without the 964 // details and we consider that the "Save" button below and the separator 965 // line (and the margins around it) take about as much, hence double it 966 int heightMax = wxGetDisplaySize().y - GetPosition().y - 2*GetMinHeight(); 967 968 // we should leave a margin 969 heightMax *= 9; 970 heightMax /= 10; 971 972 m_listctrl->SetSize(wxDefaultCoord, wxMin(height, heightMax)); 973} 974 975void wxLogDialog::OnListSelect(wxListEvent& event) 976{ 977 // we can't just disable the control because this looks ugly under Windows 978 // (wrong bg colour, no scrolling...), but we still want to disable 979 // selecting items - it makes no sense here 980 m_listctrl->SetItemState(event.GetIndex(), 0, wxLIST_STATE_SELECTED); 981} 982 983void wxLogDialog::OnOk(wxCommandEvent& WXUNUSED(event)) 984{ 985 EndModal(wxID_OK); 986} 987 988#if wxUSE_FILE 989 990void wxLogDialog::OnSave(wxCommandEvent& WXUNUSED(event)) 991{ 992#if wxUSE_FILEDLG 993 wxFile file; 994 int rc = OpenLogFile(file, NULL, this); 995 if ( rc == -1 ) 996 { 997 // cancelled 998 return; 999 } 1000 1001 bool ok = rc != 0; 1002 1003 wxString fmt = wxLog::GetTimestamp(); 1004 if ( !fmt ) 1005 { 1006 // default format 1007 fmt = _T("%c"); 1008 } 1009 1010 size_t count = m_messages.GetCount(); 1011 for ( size_t n = 0; ok && (n < count); n++ ) 1012 { 1013 wxString line; 1014 line << TimeStamp(fmt, (time_t)m_times[n]) 1015 << _T(": ") 1016 << m_messages[n] 1017 << wxTextFile::GetEOL(); 1018 1019 ok = file.Write(line); 1020 } 1021 1022 if ( ok ) 1023 ok = file.Close(); 1024 1025 if ( !ok ) 1026 wxLogError(_("Can't save log contents to file.")); 1027#endif // wxUSE_FILEDLG 1028} 1029 1030#endif // wxUSE_FILE 1031 1032void wxLogDialog::OnDetails(wxCommandEvent& WXUNUSED(event)) 1033{ 1034 wxSizer *sizer = GetSizer(); 1035 1036 if ( m_showingDetails ) 1037 { 1038#ifdef __SMARTPHONE__ 1039 SetRightMenu(wxID_MORE, ms_details + EXPAND_SUFFIX); 1040#else 1041 m_btnDetails->SetLabel(ms_details + EXPAND_SUFFIX); 1042#endif 1043 1044 sizer->Detach( m_listctrl ); 1045 1046#ifndef __SMARTPHONE__ 1047 1048#if wxUSE_STATLINE 1049 sizer->Detach( m_statline ); 1050#endif // wxUSE_STATLINE 1051 1052#if wxUSE_FILE 1053 sizer->Detach( m_btnSave ); 1054#endif // wxUSE_FILE 1055 1056#endif // __SMARTPHONE__ 1057 } 1058 else // show details now 1059 { 1060#ifdef __SMARTPHONE__ 1061 SetRightMenu(wxID_MORE, wxString(_T("<< ")) + ms_details); 1062#else 1063 m_btnDetails->SetLabel(wxString(_T("<< ")) + ms_details); 1064#endif 1065 1066 if ( !m_listctrl ) 1067 { 1068 CreateDetailsControls(); 1069 } 1070 1071#if wxUSE_STATLINE && !defined(__SMARTPHONE__) 1072 bool isPda = (wxSystemSettings::GetScreenType() <= wxSYS_SCREEN_PDA); 1073 if (!isPda) 1074 sizer->Add(m_statline, 0, wxEXPAND | (wxALL & ~wxTOP), MARGIN); 1075#endif // wxUSE_STATLINE 1076 1077 sizer->Add(m_listctrl, 1, wxEXPAND | (wxALL & ~wxTOP), MARGIN); 1078 1079 // VZ: this doesn't work as this becomes the initial (and not only 1080 // minimal) listctrl height as well - why? 1081#if 0 1082 // allow the user to make the dialog shorter than its initial height - 1083 // without this it wouldn't work as the list ctrl would have been 1084 // incompressible 1085 sizer->SetItemMinSize(m_listctrl, 100, 3*GetCharHeight()); 1086#endif // 0 1087 1088#if wxUSE_FILE && !defined(__SMARTPHONE__) 1089 sizer->Add(m_btnSave, 0, wxALIGN_RIGHT | (wxALL & ~wxTOP), MARGIN); 1090#endif // wxUSE_FILE 1091 } 1092 1093 m_showingDetails = !m_showingDetails; 1094 1095 // in any case, our size changed - relayout everything and set new hints 1096 // --------------------------------------------------------------------- 1097 1098 // we have to reset min size constraints or Fit() would never reduce the 1099 // dialog size when collapsing it and we have to reset max constraint 1100 // because it wouldn't expand it otherwise 1101 1102 m_minHeight = 1103 m_maxHeight = -1; 1104 1105 // wxSizer::FitSize() is private, otherwise we might use it directly... 1106 wxSize sizeTotal = GetSize(), 1107 sizeClient = GetClientSize(); 1108 1109 wxSize size = sizer->GetMinSize(); 1110 size.x += sizeTotal.x - sizeClient.x; 1111 size.y += sizeTotal.y - sizeClient.y; 1112 1113 // we don't want to allow expanding the dialog in vertical direction as 1114 // this would show the "hidden" details but we can resize the dialog 1115 // vertically while the details are shown 1116 if ( !m_showingDetails ) 1117 m_maxHeight = size.y; 1118 1119 SetSizeHints(size.x, size.y, m_maxWidth, m_maxHeight); 1120 1121#ifdef __WXWINCE__ 1122 if (m_showingDetails) 1123 m_listctrl->Show(); 1124#endif 1125 1126 // don't change the width when expanding/collapsing 1127 SetSize(wxDefaultCoord, size.y); 1128 1129#ifdef __WXGTK__ 1130 // VS: this is necessary in order to force frame redraw under 1131 // WindowMaker or fvwm2 (and probably other broken WMs). 1132 // Otherwise, detailed list wouldn't be displayed. 1133 Show(); 1134#endif // wxGTK 1135} 1136 1137wxLogDialog::~wxLogDialog() 1138{ 1139 if ( m_listctrl ) 1140 { 1141 delete m_listctrl->GetImageList(wxIMAGE_LIST_SMALL); 1142 } 1143} 1144 1145#endif // wxUSE_LOG_DIALOG 1146 1147#if wxUSE_FILE && wxUSE_FILEDLG 1148 1149// pass an uninitialized file object, the function will ask the user for the 1150// filename and try to open it, returns true on success (file was opened), 1151// false if file couldn't be opened/created and -1 if the file selection 1152// dialog was cancelled 1153static int OpenLogFile(wxFile& file, wxString *pFilename, wxWindow *parent) 1154{ 1155 // get the file name 1156 // ----------------- 1157 wxString filename = wxSaveFileSelector(wxT("log"), wxT("txt"), wxT("log.txt"), parent); 1158 if ( !filename ) { 1159 // cancelled 1160 return -1; 1161 } 1162 1163 // open file 1164 // --------- 1165 bool bOk wxDUMMY_INITIALIZE(false); 1166 if ( wxFile::Exists(filename) ) { 1167 bool bAppend = false; 1168 wxString strMsg; 1169 strMsg.Printf(_("Append log to file '%s' (choosing [No] will overwrite it)?"), 1170 filename.c_str()); 1171 switch ( wxMessageBox(strMsg, _("Question"), 1172 wxICON_QUESTION | wxYES_NO | wxCANCEL) ) { 1173 case wxYES: 1174 bAppend = true; 1175 break; 1176 1177 case wxNO: 1178 bAppend = false; 1179 break; 1180 1181 case wxCANCEL: 1182 return -1; 1183 1184 default: 1185 wxFAIL_MSG(_("invalid message box return value")); 1186 } 1187 1188 if ( bAppend ) { 1189 bOk = file.Open(filename, wxFile::write_append); 1190 } 1191 else { 1192 bOk = file.Create(filename, true /* overwrite */); 1193 } 1194 } 1195 else { 1196 bOk = file.Create(filename); 1197 } 1198 1199 if ( pFilename ) 1200 *pFilename = filename; 1201 1202 return bOk; 1203} 1204 1205#endif // wxUSE_FILE 1206 1207#endif // !(wxUSE_LOGGUI || wxUSE_LOGWINDOW) 1208 1209#if wxUSE_LOG && wxUSE_TEXTCTRL 1210 1211// ---------------------------------------------------------------------------- 1212// wxLogTextCtrl implementation 1213// ---------------------------------------------------------------------------- 1214 1215wxLogTextCtrl::wxLogTextCtrl(wxTextCtrl *pTextCtrl) 1216{ 1217 m_pTextCtrl = pTextCtrl; 1218} 1219 1220void wxLogTextCtrl::DoLogString(const wxChar *szString, time_t WXUNUSED(t)) 1221{ 1222 wxString msg; 1223 TimeStamp(&msg); 1224 1225 msg << szString << wxT('\n'); 1226 m_pTextCtrl->AppendText(msg); 1227} 1228 1229#endif // wxUSE_LOG && wxUSE_TEXTCTRL 1230