]> git.saurik.com Git - wxWidgets.git/blobdiff - samples/thread/thread.cpp
Support using GetTextExtent() with empty string to get descent in wxOSX.
[wxWidgets.git] / samples / thread / thread.cpp
index b176ef8c01b2e339b5198734770b888f0350a1ac..7948c35a473f92c83e452af84ec19c45ec5152ee 100644 (file)
@@ -1,18 +1,21 @@
 /////////////////////////////////////////////////////////////////////////////
 // Name:        thread.cpp
 /////////////////////////////////////////////////////////////////////////////
 // Name:        thread.cpp
-// Purpose:     wxWindows thread sample
-// Author:      Julian Smart(minimal)/Guilhem Lavaux(thread test)
+// Purpose:     wxWidgets thread sample
+// Author:      Guilhem Lavaux, Vadim Zeitlin
 // Modified by:
 // Created:     06/16/98
 // RCS-ID:      $Id$
 // Modified by:
 // Created:     06/16/98
 // RCS-ID:      $Id$
-// Copyright:   (c) Julian Smart, Markus Holzem, Guilhem Lavaux
-// Licence:     wxWindows license
+// Copyright:   (c) 1998-2009 wxWidgets team
+// Licence:     wxWindows licence
 /////////////////////////////////////////////////////////////////////////////
 
 /////////////////////////////////////////////////////////////////////////////
 
-/*
-    TODO: use worker threads to update progress controls instead of writing
-          messages - it will be more visual
- */
+// ============================================================================
+// declarations
+// ============================================================================
+
+// ----------------------------------------------------------------------------
+// headers
+// ----------------------------------------------------------------------------
 
 // For compilers that support precompilation, includes "wx/wx.h".
 #include "wx/wxprec.h"
 
 // For compilers that support precompilation, includes "wx/wx.h".
 #include "wx/wxprec.h"
 
 #include "wx/thread.h"
 #include "wx/dynarray.h"
 
 #include "wx/thread.h"
 #include "wx/dynarray.h"
-#include "wx/time.h"
-
+#include "wx/numdlg.h"
 #include "wx/progdlg.h"
 
 #include "wx/progdlg.h"
 
+// ----------------------------------------------------------------------------
+// resources
+// ----------------------------------------------------------------------------
+
+#include "../sample.xpm"
+
+// ----------------------------------------------------------------------------
+// private classes
+// ----------------------------------------------------------------------------
+
+// define this to use wxExecute in the exec tests, otherwise just use system
 #define USE_EXECUTE
 #define USE_EXECUTE
+
 #ifdef USE_EXECUTE
     #define EXEC(cmd) wxExecute((cmd), wxEXEC_SYNC)
 #else
 #ifdef USE_EXECUTE
     #define EXEC(cmd) wxExecute((cmd), wxEXEC_SYNC)
 #else
 #endif
 
 class MyThread;
 #endif
 
 class MyThread;
-WX_DEFINE_ARRAY(wxThread *, wxArrayThread);
+WX_DEFINE_ARRAY_PTR(wxThread *, wxArrayThread);
+
+// ----------------------------------------------------------------------------
+// the application object
+// ----------------------------------------------------------------------------
 
 
-// Define a new application type
 class MyApp : public wxApp
 {
 public:
 class MyApp : public wxApp
 {
 public:
+    MyApp();
+    virtual ~MyApp(){};
+
     virtual bool OnInit();
 
     virtual bool OnInit();
 
-public:
+    // critical section protects access to all of the fields below
+    wxCriticalSection m_critsect;
+
     // all the threads currently alive - as soon as the thread terminates, it's
     // removed from the array
     wxArrayThread m_threads;
 
     // all the threads currently alive - as soon as the thread terminates, it's
     // removed from the array
     wxArrayThread m_threads;
 
-    // crit section protects access to all of the arrays below
-    wxCriticalSection m_critsect;
+    // semaphore used to wait for the threads to exit, see MyFrame::OnQuit()
+    wxSemaphore m_semAllDone;
+
+    // indicates that we're shutting down and all threads should exit
+    bool m_shuttingDown;
 };
 
 };
 
-// Create a new application object
-IMPLEMENT_APP(MyApp)
+// ----------------------------------------------------------------------------
+// the main application frame
+// ----------------------------------------------------------------------------
 
 
-// Define a new frame type
-class MyFrame: public wxFrame
+class MyFrame : public wxFrame,
+                private wxLog
 {
 public:
     // ctor
 {
 public:
     // ctor
-    MyFrame(wxFrame *frame, const wxString& title, int x, int y, int w, int h);
-
-    // operations
-    void WriteText(const wxString& text) { m_txtctrl->WriteText(text); }
+    MyFrame(const wxString& title);
+    virtual ~MyFrame();
 
     // accessors for MyWorkerThread (called in its context!)
     bool Cancelled();
 
 
     // accessors for MyWorkerThread (called in its context!)
     bool Cancelled();
 
-    // callbacks
+protected:
+    virtual void DoLogRecord(wxLogLevel level,
+                             const wxString& msg,
+                             const wxLogRecordInfo& info);
+
+private:
+    // event handlers
+    // --------------
+
     void OnQuit(wxCommandEvent& event);
     void OnClear(wxCommandEvent& event);
 
     void OnQuit(wxCommandEvent& event);
     void OnClear(wxCommandEvent& event);
 
@@ -87,43 +119,72 @@ public:
     void OnResumeThread(wxCommandEvent& event);
 
     void OnStartWorker(wxCommandEvent& event);
     void OnResumeThread(wxCommandEvent& event);
 
     void OnStartWorker(wxCommandEvent& event);
-    void OnWorkerEvent(wxCommandEvent& event);
-    void OnUpdateWorker(wxUpdateUIEvent& event);
-
     void OnExecMain(wxCommandEvent& event);
     void OnExecMain(wxCommandEvent& event);
-    void OnExecThread(wxCommandEvent& event);
+    void OnStartGUIThread(wxCommandEvent& event);
 
     void OnShowCPUs(wxCommandEvent& event);
     void OnAbout(wxCommandEvent& event);
 
     void OnIdle(wxIdleEvent &event);
 
     void OnShowCPUs(wxCommandEvent& event);
     void OnAbout(wxCommandEvent& event);
 
     void OnIdle(wxIdleEvent &event);
+    void OnWorkerEvent(wxThreadEvent& event);
+    void OnUpdateWorker(wxUpdateUIEvent& event);
+
+
+    // logging helper
+    void DoLogLine(wxTextCtrl *text,
+                   const wxString& timestr,
+                   const wxString& threadstr,
+                   const wxString& msg);
+
+
+    // thread helper functions
+    // -----------------------
 
 
-private:
     // helper function - creates a new thread (but doesn't run it)
     MyThread *CreateThread();
 
     // helper function - creates a new thread (but doesn't run it)
     MyThread *CreateThread();
 
+    // update display in our status bar: called during idle handling
+    void UpdateThreadStatus();
+
+
+    // internal variables
+    // ------------------
+
     // just some place to put our messages in
     wxTextCtrl *m_txtctrl;
 
     // just some place to put our messages in
     wxTextCtrl *m_txtctrl;
 
+    // old log target, we replace it with one using m_txtctrl during this
+    // frame life time
+    wxLog *m_oldLogger;
+
+    // the array of pending messages to be displayed and the critical section
+    // protecting it
+    wxArrayString m_messages;
+    wxCriticalSection m_csMessages;
+
     // remember the number of running threads and total number of threads
     // remember the number of running threads and total number of threads
-    size_t m_nRunning, m_nCount;
+    size_t m_nRunning,
+           m_nCount;
 
     // the progress dialog which we show while worker thread is running
     wxProgressDialog *m_dlgProgress;
 
     // was the worker thread cancelled by user?
     bool m_cancelled;
 
     // the progress dialog which we show while worker thread is running
     wxProgressDialog *m_dlgProgress;
 
     // was the worker thread cancelled by user?
     bool m_cancelled;
-
-    // protects m_cancelled
-    wxCriticalSection m_critsectWork;
+    wxCriticalSection m_csCancelled;        // protects m_cancelled
 
     DECLARE_EVENT_TABLE()
 };
 
 
     DECLARE_EVENT_TABLE()
 };
 
+// ----------------------------------------------------------------------------
+// constants
+// ----------------------------------------------------------------------------
+
 // ID for the menu commands
 enum
 {
 // ID for the menu commands
 enum
 {
-    THREAD_QUIT          = 1,
+    THREAD_QUIT  = wxID_EXIT,
+    THREAD_ABOUT = wxID_ABOUT,
     THREAD_TEXT          = 101,
     THREAD_CLEAR,
     THREAD_START_THREAD  = 201,
     THREAD_TEXT          = 101,
     THREAD_CLEAR,
     THREAD_START_THREAD  = 201,
@@ -131,101 +192,36 @@ enum
     THREAD_STOP_THREAD,
     THREAD_PAUSE_THREAD,
     THREAD_RESUME_THREAD,
     THREAD_STOP_THREAD,
     THREAD_PAUSE_THREAD,
     THREAD_RESUME_THREAD,
-    THREAD_START_WORKER,
 
 
+    THREAD_START_WORKER,
     THREAD_EXEC_MAIN,
     THREAD_EXEC_MAIN,
-    THREAD_EXEC_THREAD,
+    THREAD_START_GUI_THREAD,
 
     THREAD_SHOWCPUS,
 
     THREAD_SHOWCPUS,
-    THREAD_ABOUT,
 
 
-    WORKER_EVENT    // this one gets sent from the worker thread
+    WORKER_EVENT = wxID_HIGHEST+1,   // this one gets sent from MyWorkerThread
+    GUITHREAD_EVENT                  // this one gets sent from MyGUIThread
 };
 
 // ----------------------------------------------------------------------------
 };
 
 // ----------------------------------------------------------------------------
-// GUI thread
+// a simple thread
 // ----------------------------------------------------------------------------
 
 class MyThread : public wxThread
 {
 public:
 // ----------------------------------------------------------------------------
 
 class MyThread : public wxThread
 {
 public:
-    MyThread(MyFrame *frame);
+    MyThread();
+    virtual ~MyThread();
 
     // thread execution starts here
     virtual void *Entry();
 
 
     // thread execution starts here
     virtual void *Entry();
 
-    // called when the thread exits - whether it terminates normally or is
-    // stopped with Delete() (but not when it is Kill()ed!)
-    virtual void OnExit();
-
-    // write something to the text control
-    void WriteText(const wxString& text);
-
 public:
 public:
-    size_t   m_count;
-    MyFrame *m_frame;
+    unsigned m_count;
 };
 
 };
 
-MyThread::MyThread(MyFrame *frame)
-        : wxThread()
-{
-    m_count = 0;
-    m_frame = frame;
-}
-
-void MyThread::WriteText(const wxString& text)
-{
-    wxString msg;
-
-    // before doing any GUI calls we must ensure that this thread is the only
-    // one doing it!
-
-    wxMutexGuiEnter();
-
-    msg << text;
-    m_frame->WriteText(msg);
-
-    wxMutexGuiLeave();
-}
-
-void MyThread::OnExit()
-{
-    wxCriticalSectionLocker locker(wxGetApp().m_critsect);
-
-    wxGetApp().m_threads.Remove(this);
-}
-
-void *MyThread::Entry()
-{
-    wxString text;
-
-    text.Printf(wxT("Thread 0x%x started (priority = %d).\n"),
-                GetId(), GetPriority());
-    WriteText(text);
-    // wxLogMessage(text); -- test wxLog thread safeness
-
-    for ( m_count = 0; m_count < 10; m_count++ )
-    {
-        // check if we were asked to exit
-        if ( TestDestroy() )
-            break;
-
-        text.Printf(wxT("[%u] Thread 0x%x here.\n"), m_count, GetId());
-        WriteText(text);
-
-        // wxSleep() can't be called from non-GUI thread!
-        wxThread::Sleep(1000);
-    }
-
-    text.Printf(wxT("Thread 0x%x finished.\n"), GetId());
-    WriteText(text);
-    // wxLogMessage(text); -- test wxLog thread safeness
-
-    return NULL;
-}
-
 // ----------------------------------------------------------------------------
 // ----------------------------------------------------------------------------
-// worker thread
+// worker thread
 // ----------------------------------------------------------------------------
 
 class MyWorkerThread : public wxThread
 // ----------------------------------------------------------------------------
 
 class MyWorkerThread : public wxThread
@@ -242,70 +238,90 @@ public:
 
 public:
     MyFrame *m_frame;
 
 public:
     MyFrame *m_frame;
-    size_t   m_count;
+    unsigned m_count;
 };
 
 };
 
-MyWorkerThread::MyWorkerThread(MyFrame *frame)
-        : wxThread()
-{
-    m_frame = frame;
-    m_count = 0;
-}
+// ----------------------------------------------------------------------------
+// a thread which executes GUI calls using wxMutexGuiEnter/Leave
+// ----------------------------------------------------------------------------
 
 
-void MyWorkerThread::OnExit()
-{
-}
+#define GUITHREAD_BMP_SIZE          300
+#define GUITHREAD_NUM_UPDATES       50
+class MyImageDialog;
 
 
-void *MyWorkerThread::Entry()
+class MyGUIThread : public wxThread
 {
 {
-    for ( m_count = 0; !m_frame->Cancelled() && (m_count < 100); m_count++ )
+public:
+    MyGUIThread(MyImageDialog *dlg) : wxThread(wxTHREAD_JOINABLE)
     {
     {
-        // check if we were asked to exit
-        if ( TestDestroy() )
-            break;
-
-        // create any type of command event here
-        wxCommandEvent event( wxEVT_COMMAND_MENU_SELECTED, WORKER_EVENT );
-        event.SetInt( m_count );
-
-        // send in a thread-safe way
-        wxPostEvent( m_frame, event );
-
-        // wxSleep() can't be called from non-main thread!
-        wxThread::Sleep(200);
+        m_dlg = dlg;
     }
 
     }
 
-    wxCommandEvent event( wxEVT_COMMAND_MENU_SELECTED, WORKER_EVENT );
-    event.SetInt(-1); // that's all
-    wxPostEvent( m_frame, event );
+    virtual ExitCode Entry();
 
 
-    return NULL;
-}
+private:
+    MyImageDialog *m_dlg;
+};
 
 // ----------------------------------------------------------------------------
 
 // ----------------------------------------------------------------------------
-// a thread which simply calls wxExecute
+// an helper dialog used by MyFrame::OnStartGUIThread
 // ----------------------------------------------------------------------------
 
 // ----------------------------------------------------------------------------
 
-class MyExecThread : public wxThread
+class MyImageDialog: public wxDialog
 {
 public:
 {
 public:
-    MyExecThread(const wxChar *command) : wxThread(wxTHREAD_JOINABLE),
-                                          m_command(command)
-    {
-        Create();
-    }
+    // ctor
+    MyImageDialog(wxFrame *frame);
+    ~MyImageDialog();
 
 
-    virtual ExitCode Entry()
-    {
-        return (ExitCode)EXEC(m_command);
-    }
+    // stuff used by MyGUIThread:
+    wxBitmap m_bmp;    // the bitmap drawn by MyGUIThread
+    wxCriticalSection m_csBmp;        // protects m_bmp
 
 private:
 
 private:
-    wxString m_command;
+    void OnGUIThreadEvent(wxThreadEvent& event);
+    void OnPaint(wxPaintEvent&);
+
+    MyGUIThread m_thread;
+    int m_nCurrentProgress;
+
+    DECLARE_EVENT_TABLE()
 };
 
 };
 
-// ----------------------------------------------------------------------------
+// ============================================================================
 // implementation
 // implementation
+// ============================================================================
+
+// ----------------------------------------------------------------------------
+// the application class
+// ----------------------------------------------------------------------------
+
+// Create a new application object
+IMPLEMENT_APP(MyApp)
+
+MyApp::MyApp()
+{
+    m_shuttingDown = false;
+}
+
+// `Main program' equivalent, creating windows and returning main app frame
+bool MyApp::OnInit()
+{
+    if ( !wxApp::OnInit() )
+        return false;
+
+    // uncomment this to get some debugging messages from the trace code
+    // on the console (or just set WXTRACE env variable to include "thread")
+    wxLog::AddTraceMask("thread");
+
+    // Create the main frame window
+    new MyFrame("wxWidgets threads sample");
+
+    return true;
+}
+
+// ----------------------------------------------------------------------------
+// MyFrame
 // ----------------------------------------------------------------------------
 
 BEGIN_EVENT_TABLE(MyFrame, wxFrame)
 // ----------------------------------------------------------------------------
 
 BEGIN_EVENT_TABLE(MyFrame, wxFrame)
@@ -317,90 +333,160 @@ BEGIN_EVENT_TABLE(MyFrame, wxFrame)
     EVT_MENU(THREAD_PAUSE_THREAD, MyFrame::OnPauseThread)
     EVT_MENU(THREAD_RESUME_THREAD, MyFrame::OnResumeThread)
 
     EVT_MENU(THREAD_PAUSE_THREAD, MyFrame::OnPauseThread)
     EVT_MENU(THREAD_RESUME_THREAD, MyFrame::OnResumeThread)
 
+    EVT_MENU(THREAD_START_WORKER, MyFrame::OnStartWorker)
     EVT_MENU(THREAD_EXEC_MAIN, MyFrame::OnExecMain)
     EVT_MENU(THREAD_EXEC_MAIN, MyFrame::OnExecMain)
-    EVT_MENU(THREAD_EXEC_THREAD, MyFrame::OnExecThread)
+    EVT_MENU(THREAD_START_GUI_THREAD, MyFrame::OnStartGUIThread)
 
     EVT_MENU(THREAD_SHOWCPUS, MyFrame::OnShowCPUs)
     EVT_MENU(THREAD_ABOUT, MyFrame::OnAbout)
 
     EVT_UPDATE_UI(THREAD_START_WORKER, MyFrame::OnUpdateWorker)
 
     EVT_MENU(THREAD_SHOWCPUS, MyFrame::OnShowCPUs)
     EVT_MENU(THREAD_ABOUT, MyFrame::OnAbout)
 
     EVT_UPDATE_UI(THREAD_START_WORKER, MyFrame::OnUpdateWorker)
-    EVT_MENU(THREAD_START_WORKER, MyFrame::OnStartWorker)
-    EVT_MENU(WORKER_EVENT, MyFrame::OnWorkerEvent)
-
+    EVT_THREAD(WORKER_EVENT, MyFrame::OnWorkerEvent)
     EVT_IDLE(MyFrame::OnIdle)
 END_EVENT_TABLE()
 
     EVT_IDLE(MyFrame::OnIdle)
 END_EVENT_TABLE()
 
-// `Main program' equivalent, creating windows and returning main app frame
-bool MyApp::OnInit()
+// My frame constructor
+MyFrame::MyFrame(const wxString& title)
+       : wxFrame(NULL, wxID_ANY, title)
 {
 {
-    // uncomment this to get some debugging messages from the trace code
-    // on the console (or just set WXTRACE env variable to include "thread")
-    //wxLog::AddTraceMask("thread");
+    m_oldLogger = wxLog::GetActiveTarget();
 
 
-    // Create the main frame window
-    MyFrame *frame = new MyFrame((wxFrame *)NULL, "wxWindows threads sample",
-                                 50, 50, 450, 340);
+    SetIcon(wxICON(sample));
 
     // Make a menubar
     wxMenuBar *menuBar = new wxMenuBar;
 
     wxMenu *menuFile = new wxMenu;
 
     // Make a menubar
     wxMenuBar *menuBar = new wxMenuBar;
 
     wxMenu *menuFile = new wxMenu;
-    menuFile->Append(THREAD_CLEAR, "&Clear log\tCtrl-L");
+    menuFile->Append(THREAD_CLEAR, wxT("&Clear log\tCtrl-L"));
     menuFile->AppendSeparator();
     menuFile->AppendSeparator();
-    menuFile->Append(THREAD_QUIT, "E&xit\tAlt-X");
-    menuBar->Append(menuFile, "&File");
+    menuFile->Append(THREAD_QUIT, wxT("E&xit\tAlt-X"));
+    menuBar->Append(menuFile, wxT("&File"));
 
     wxMenu *menuThread = new wxMenu;
 
     wxMenu *menuThread = new wxMenu;
-    menuThread->Append(THREAD_START_THREAD, "&Start a new thread\tCtrl-N");
-    menuThread->Append(THREAD_START_THREADS, "Start &many threads at once");
-    menuThread->Append(THREAD_STOP_THREAD, "S&top a running thread\tCtrl-S");
+    menuThread->Append(THREAD_START_THREAD, wxT("&Start a new thread\tCtrl-N"));
+    menuThread->Append(THREAD_START_THREADS, wxT("Start &many threads at once"));
+    menuThread->Append(THREAD_STOP_THREAD, wxT("S&top the last spawned thread\tCtrl-S"));
     menuThread->AppendSeparator();
     menuThread->AppendSeparator();
-    menuThread->Append(THREAD_PAUSE_THREAD, "&Pause a running thread\tCtrl-P");
-    menuThread->Append(THREAD_RESUME_THREAD, "&Resume suspended thread\tCtrl-R");
+    menuThread->Append(THREAD_PAUSE_THREAD, wxT("&Pause the last spawned running thread\tCtrl-P"));
+    menuThread->Append(THREAD_RESUME_THREAD, wxT("&Resume the first suspended thread\tCtrl-R"));
     menuThread->AppendSeparator();
     menuThread->AppendSeparator();
-    menuThread->Append(THREAD_START_WORKER, "Start &worker thread\tCtrl-W");
-    menuBar->Append(menuThread, "&Thread");
-
-    wxMenu *menuExec = new wxMenu;
-    menuExec->Append(THREAD_EXEC_MAIN, "&Launch a program from main thread\tF5");
-    menuExec->Append(THREAD_EXEC_THREAD, "L&aunch a program from a thread\tCtrl-F5");
-    menuBar->Append(menuExec, "&Execute");
+    menuThread->Append(THREAD_START_WORKER, wxT("Start a &worker thread\tCtrl-W"));
+    menuThread->Append(THREAD_EXEC_MAIN, wxT("&Launch a program from main thread\tF5"));
+    menuThread->Append(THREAD_START_GUI_THREAD, wxT("Launch a &GUI thread\tF6"));
+    menuBar->Append(menuThread, wxT("&Thread"));
 
     wxMenu *menuHelp = new wxMenu;
 
     wxMenu *menuHelp = new wxMenu;
-    menuHelp->Append(THREAD_SHOWCPUS, "&Show CPU count");
+    menuHelp->Append(THREAD_SHOWCPUS, wxT("&Show CPU count"));
     menuHelp->AppendSeparator();
     menuHelp->AppendSeparator();
-    menuHelp->Append(THREAD_ABOUT, "&About...");
-    menuBar->Append(menuHelp, "&Help");
+    menuHelp->Append(THREAD_ABOUT, wxT("&About"));
+    menuBar->Append(menuHelp, wxT("&Help"));
 
 
-    frame->SetMenuBar(menuBar);
+    SetMenuBar(menuBar);
 
 
-    // Show the frame
-    frame->Show(TRUE);
+    m_nRunning = m_nCount = 0;
 
 
-    SetTopWindow(frame);
+    m_dlgProgress = NULL;
 
 
-    return TRUE;
+#if wxUSE_STATUSBAR
+    CreateStatusBar(2);
+#endif // wxUSE_STATUSBAR
+
+    // create the logging text control and a header showing the meaning of the
+    // different columns
+    wxTextCtrl *header = new wxTextCtrl(this, wxID_ANY, "",
+                                        wxDefaultPosition, wxDefaultSize,
+                                        wxTE_READONLY);
+    DoLogLine(header, "  Time", " Thread", "Message");
+    m_txtctrl = new wxTextCtrl(this, wxID_ANY, "",
+                               wxDefaultPosition, wxDefaultSize,
+                               wxTE_MULTILINE | wxTE_READONLY);
+    wxLog::SetActiveTarget(this);
+
+    // use fixed width font to align output in nice columns
+    wxFont font(wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_TELETYPE,
+                wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
+    header->SetFont(font);
+    m_txtctrl->SetFont(font);
+
+    m_txtctrl->SetFocus();
+
+    // layout and show the frame
+    wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
+    sizer->Add(header, wxSizerFlags().Expand());
+    sizer->Add(m_txtctrl, wxSizerFlags(1).Expand());
+    SetSizer(sizer);
+
+    SetSize(600, 350);
+    Show();
 }
 
 }
 
-// My frame constructor
-MyFrame::MyFrame(wxFrame *frame, const wxString& title,
-                 int x, int y, int w, int h)
-       : wxFrame(frame, -1, title, wxPoint(x, y), wxSize(w, h))
+MyFrame::~MyFrame()
 {
 {
-    m_nRunning = m_nCount = 0;
+    wxLog::SetActiveTarget(m_oldLogger);
 
 
-    m_dlgProgress = (wxProgressDialog *)NULL;
+    // NB: although the OS will terminate all the threads anyhow when the main
+    //     one exits, it's good practice to do it ourselves -- even if it's not
+    //     completely trivial in this example
 
 
-    CreateStatusBar(2);
+    // tell all the threads to terminate: note that they can't terminate while
+    // we're deleting them because they will block in their OnExit() -- this is
+    // important as otherwise we might access invalid array elements
 
 
-    m_txtctrl = new wxTextCtrl(this, -1, "", wxPoint(0, 0), wxSize(0, 0),
-                               wxTE_MULTILINE | wxTE_READONLY);
+    {
+        wxCriticalSectionLocker locker(wxGetApp().m_critsect);
+
+        // check if we have any threads running first
+        const wxArrayThread& threads = wxGetApp().m_threads;
+        size_t count = threads.GetCount();
 
 
+        if ( !count )
+            return;
+
+        // set the flag indicating that all threads should exit
+        wxGetApp().m_shuttingDown = true;
+    }
+
+    // now wait for them to really terminate
+    wxGetApp().m_semAllDone.Wait();
+}
+
+void
+MyFrame::DoLogLine(wxTextCtrl *text,
+                   const wxString& timestr,
+                   const wxString& threadstr,
+                   const wxString& msg)
+{
+    text->AppendText(wxString::Format("%9s %10s %s", timestr, threadstr, msg));
+}
+
+void
+MyFrame::DoLogRecord(wxLogLevel level,
+                     const wxString& msg,
+                     const wxLogRecordInfo& info)
+{
+    // let the default GUI logger treat warnings and errors as they should be
+    // more noticeable than just another line in the log window and also trace
+    // messages as there may be too many of them
+    if ( level <= wxLOG_Warning || level == wxLOG_Trace )
+    {
+        m_oldLogger->LogRecord(level, msg, info);
+        return;
+    }
+
+    DoLogLine
+    (
+        m_txtctrl,
+        wxDateTime(info.timestamp).FormatISOTime(),
+        info.threadId == wxThread::GetMainId()
+            ? wxString("main")
+            : wxString::Format("%lx", info.threadId),
+        msg + "\n"
+    );
 }
 
 MyThread *MyFrame::CreateThread()
 {
 }
 
 MyThread *MyFrame::CreateThread()
 {
-    MyThread *thread = new MyThread(this);
+    MyThread *thread = new MyThread;
 
     if ( thread->Create() != wxTHREAD_NO_ERROR )
     {
 
     if ( thread->Create() != wxTHREAD_NO_ERROR )
     {
@@ -413,12 +499,46 @@ MyThread *MyFrame::CreateThread()
     return thread;
 }
 
     return thread;
 }
 
+void MyFrame::UpdateThreadStatus()
+{
+    wxCriticalSectionLocker enter(wxGetApp().m_critsect);
+
+    // update the counts of running/total threads
+    size_t nRunning = 0,
+           nCount = wxGetApp().m_threads.Count();
+    for ( size_t n = 0; n < nCount; n++ )
+    {
+        if ( wxGetApp().m_threads[n]->IsRunning() )
+            nRunning++;
+    }
+
+    if ( nCount != m_nCount || nRunning != m_nRunning )
+    {
+        m_nRunning = nRunning;
+        m_nCount = nCount;
+
+        wxLogStatus(this, wxT("%u threads total, %u running."), unsigned(nCount), unsigned(nRunning));
+    }
+    //else: avoid flicker - don't print anything
+}
+
+bool MyFrame::Cancelled()
+{
+    wxCriticalSectionLocker lock(m_csCancelled);
+
+    return m_cancelled;
+}
+
+// ----------------------------------------------------------------------------
+// MyFrame - event handlers
+// ----------------------------------------------------------------------------
+
 void MyFrame::OnStartThreads(wxCommandEvent& WXUNUSED(event) )
 {
 void MyFrame::OnStartThreads(wxCommandEvent& WXUNUSED(event) )
 {
-    static long s_num = 10;
+    static long s_num;
 
 
-    s_num = wxGetNumberFromUser("How many threads to start: ", "",
-                                "wxThread sample", s_num, 1, 10000, this);
+    s_num = wxGetNumberFromUser(wxT("How many threads to start: "), wxT(""),
+                                wxT("wxThread sample"), s_num, 1, 10000, this);
     if ( s_num == -1 )
     {
         s_num = 10;
     if ( s_num == -1 )
     {
         s_num = 10;
@@ -426,7 +546,7 @@ void MyFrame::OnStartThreads(wxCommandEvent& WXUNUSED(event) )
         return;
     }
 
         return;
     }
 
-    size_t count = (size_t)s_num, n;
+    unsigned count = unsigned(s_num), n;
 
     wxArrayThread threads;
 
 
     wxArrayThread threads;
 
@@ -439,18 +559,20 @@ void MyFrame::OnStartThreads(wxCommandEvent& WXUNUSED(event) )
         // have the lowest priority, the second - the highest, all the rest
         // the normal one
         if ( n == 0 )
         // have the lowest priority, the second - the highest, all the rest
         // the normal one
         if ( n == 0 )
-            thr->SetPriority(WXTHREAD_MIN_PRIORITY);
+            thr->SetPriority(wxPRIORITY_MIN);
         else if ( n == 1 )
         else if ( n == 1 )
-            thr->SetPriority(WXTHREAD_MAX_PRIORITY);
+            thr->SetPriority(wxPRIORITY_MAX);
         else
         else
-            thr->SetPriority(WXTHREAD_DEFAULT_PRIORITY);
+            thr->SetPriority(wxPRIORITY_DEFAULT);
 
         threads.Add(thr);
     }
 
 
         threads.Add(thr);
     }
 
+#if wxUSE_STATUSBAR
     wxString msg;
     msg.Printf(wxT("%d new threads created."), count);
     SetStatusText(msg, 1);
     wxString msg;
     msg.Printf(wxT("%d new threads created."), count);
     SetStatusText(msg, 1);
+#endif // wxUSE_STATUSBAR
 
     // ...and then start them
     for ( n = 0; n < count; n++ )
 
     // ...and then start them
     for ( n = 0; n < count; n++ )
@@ -468,32 +590,37 @@ void MyFrame::OnStartThread(wxCommandEvent& WXUNUSED(event) )
         wxLogError(wxT("Can't start thread!"));
     }
 
         wxLogError(wxT("Can't start thread!"));
     }
 
-    SetStatusText("New thread started.", 1);
+#if wxUSE_STATUSBAR
+    SetStatusText(wxT("New thread started."), 1);
+#endif // wxUSE_STATUSBAR
 }
 
 void MyFrame::OnStopThread(wxCommandEvent& WXUNUSED(event) )
 {
 }
 
 void MyFrame::OnStopThread(wxCommandEvent& WXUNUSED(event) )
 {
-    wxGetApp().m_critsect.Enter();
+    wxThread* toDelete = NULL;
+    {
+    wxCriticalSectionLocker enter(wxGetApp().m_critsect);
 
     // stop the last thread
     if ( wxGetApp().m_threads.IsEmpty() )
     {
         wxLogError(wxT("No thread to stop!"));
 
     // stop the last thread
     if ( wxGetApp().m_threads.IsEmpty() )
     {
         wxLogError(wxT("No thread to stop!"));
-
-        wxGetApp().m_critsect.Leave();
     }
     else
     {
     }
     else
     {
-        wxThread *thread = wxGetApp().m_threads.Last();
-
-        // it's important to leave critical section before calling Delete()
-        // because delete will (implicitly) call OnExit() which also tries
-        // to enter the same crit section - would dead lock.
-        wxGetApp().m_critsect.Leave();
+        toDelete = wxGetApp().m_threads.Last();
+    }
+    }
 
 
-        thread->Delete();
+    if ( toDelete )
+    {
+        // This can still crash if the thread gets to delete itself
+        // in the mean time.
+        toDelete->Delete();
 
 
-        SetStatusText("Thread stopped.", 1);
+#if wxUSE_STATUSBAR
+        SetStatusText(wxT("Last thread stopped."), 1);
+#endif // wxUSE_STATUSBAR
     }
 }
 
     }
 }
 
@@ -514,7 +641,9 @@ void MyFrame::OnResumeThread(wxCommandEvent& WXUNUSED(event) )
     {
         wxGetApp().m_threads[n]->Resume();
 
     {
         wxGetApp().m_threads[n]->Resume();
 
-        SetStatusText("Thread resumed.", 1);
+#if wxUSE_STATUSBAR
+        SetStatusText(wxT("Thread resumed."), 1);
+#endif // wxUSE_STATUSBAR
     }
 }
 
     }
 }
 
@@ -535,56 +664,39 @@ void MyFrame::OnPauseThread(wxCommandEvent& WXUNUSED(event) )
     {
         wxGetApp().m_threads[n]->Pause();
 
     {
         wxGetApp().m_threads[n]->Pause();
 
-        SetStatusText("Thread paused.", 1);
+#if wxUSE_STATUSBAR
+        SetStatusText(wxT("Thread paused."), 1);
+#endif // wxUSE_STATUSBAR
     }
 }
 
     }
 }
 
-// set the frame title indicating the current number of threads
-void MyFrame::OnIdle(wxIdleEvent &event)
+void MyFrame::OnIdle(wxIdleEvent& event)
 {
 {
-    // update the counts of running/total threads
-    size_t nRunning = 0,
-           nCount = wxGetApp().m_threads.Count();
-    for ( size_t n = 0; n < nCount; n++ )
-    {
-        if ( wxGetApp().m_threads[n]->IsRunning() )
-            nRunning++;
-    }
-
-    if ( nCount != m_nCount || nRunning != m_nRunning )
-    {
-        m_nRunning = nRunning;
-        m_nCount = nCount;
+    UpdateThreadStatus();
 
 
-        wxLogStatus(this, wxT("%u threads total, %u running."), nCount, nRunning);
-    }
-    //else: avoid flicker - don't print anything
+    event.Skip();
 }
 
 void MyFrame::OnQuit(wxCommandEvent& WXUNUSED(event) )
 {
 }
 
 void MyFrame::OnQuit(wxCommandEvent& WXUNUSED(event) )
 {
-    const wxArrayThread& threads = wxGetApp().m_threads;
-    while ( !threads.IsEmpty() )
-    {
-        threads[0]->Delete();
-    }
-
-    Close(TRUE);
+    Close(true);
 }
 
 void MyFrame::OnExecMain(wxCommandEvent& WXUNUSED(event))
 {
 }
 
 void MyFrame::OnExecMain(wxCommandEvent& WXUNUSED(event))
 {
-    wxLogMessage(wxT("The exit code from the main program is %ld"),
-                 EXEC("/bin/echo \"main program\""));
-}
-
-void MyFrame::OnExecThread(wxCommandEvent& WXUNUSED(event))
-{
-    MyExecThread thread(wxT("/bin/echo \"child thread\""));
-    thread.Run();
+    wxString cmd = wxGetTextFromUser("Please enter the command to execute",
+                                     "Enter command",
+#ifdef __WXMSW__
+                                     "notepad",
+#else
+                                     "/bin/echo \"Message from another process\"",
+#endif
+                                     this);
+    if (cmd.IsEmpty())
+        return;     // user clicked cancel
 
 
-    wxLogMessage(wxT("The exit code from a child thread is %ld"),
-                 (long)thread.Wait());
+    wxLogMessage(wxT("The exit code from the main program is %ld"),
+                 EXEC(cmd));
 }
 
 void MyFrame::OnShowCPUs(wxCommandEvent& WXUNUSED(event))
 }
 
 void MyFrame::OnShowCPUs(wxCommandEvent& WXUNUSED(event))
@@ -595,31 +707,32 @@ void MyFrame::OnShowCPUs(wxCommandEvent& WXUNUSED(event))
     switch ( nCPUs )
     {
         case -1:
     switch ( nCPUs )
     {
         case -1:
-            msg = "Unknown number of CPUs";
+            msg = wxT("Unknown number of CPUs");
             break;
 
         case 0:
             break;
 
         case 0:
-            msg = "WARNING: you're running without any CPUs!";
+            msg = wxT("WARNING: you're running without any CPUs!");
             break;
 
         case 1:
             break;
 
         case 1:
-            msg = "This system only has one CPU.";
+            msg = wxT("This system only has one CPU.");
             break;
 
         default:
             msg.Printf(wxT("This system has %d CPUs"), nCPUs);
     }
             break;
 
         default:
             msg.Printf(wxT("This system has %d CPUs"), nCPUs);
     }
-            
+
     wxLogMessage(msg);
 }
 
 void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event) )
 {
     wxLogMessage(msg);
 }
 
 void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event) )
 {
-    wxMessageDialog dialog(this, "wxWindows multithreaded application sample\n"
-                                 "(c) 1998 Julian Smart, Guilhem Lavaux\n"
-                                 "(c) 1999 Vadim Zeitlin\n"
-                                 "(c) 2000 Robert Roebling",
-                           "About wxThread sample",
+    wxMessageDialog dialog(this,
+                           wxT("wxWidgets multithreaded application sample\n")
+                           wxT("(c) 1998 Julian Smart, Guilhem Lavaux\n")
+                           wxT("(c) 2000 Robert Roebling\n")
+                           wxT("(c) 1999,2009 Vadim Zeitlin"),
+                           wxT("About wxThread sample"),
                            wxOK | wxICON_INFORMATION);
 
     dialog.ShowModal();
                            wxOK | wxICON_INFORMATION);
 
     dialog.ShowModal();
@@ -642,12 +755,13 @@ void MyFrame::OnStartWorker(wxCommandEvent& WXUNUSED(event))
     if ( thread->Create() != wxTHREAD_NO_ERROR )
     {
         wxLogError(wxT("Can't create thread!"));
     if ( thread->Create() != wxTHREAD_NO_ERROR )
     {
         wxLogError(wxT("Can't create thread!"));
+        return;
     }
 
     m_dlgProgress = new wxProgressDialog
                         (
     }
 
     m_dlgProgress = new wxProgressDialog
                         (
-                         "Progress dialog",
-                         "Wait until the thread terminates or press [Cancel]",
+                         wxT("Progress dialog"),
+                         wxT("Wait until the thread terminates or press [Cancel]"),
                          100,
                          this,
                          wxPD_CAN_ABORT |
                          100,
                          this,
                          wxPD_CAN_ABORT |
@@ -658,18 +772,13 @@ void MyFrame::OnStartWorker(wxCommandEvent& WXUNUSED(event))
                         );
 
     // thread is not running yet, no need for crit sect
                         );
 
     // thread is not running yet, no need for crit sect
-    m_cancelled = FALSE;
+    m_cancelled = false;
 
     thread->Run();
 }
 
 
     thread->Run();
 }
 
-void MyFrame::OnWorkerEvent(wxCommandEvent& event)
+void MyFrame::OnWorkerEvent(wxThreadEvent& event)
 {
 {
-#if 0
-    WriteText( "Got message from worker thread: " );
-    WriteText( event.GetString() );
-    WriteText( "\n" );
-#else
     int n = event.GetInt();
     if ( n == -1 )
     {
     int n = event.GetInt();
     if ( n == -1 )
     {
@@ -685,17 +794,285 @@ void MyFrame::OnWorkerEvent(wxCommandEvent& event)
     {
         if ( !m_dlgProgress->Update(n) )
         {
     {
         if ( !m_dlgProgress->Update(n) )
         {
-            wxCriticalSectionLocker lock(m_critsectWork);
+            wxCriticalSectionLocker lock(m_csCancelled);
 
 
-            m_cancelled = TRUE;
+            m_cancelled = true;
         }
     }
         }
     }
+}
+
+void MyFrame::OnStartGUIThread(wxCommandEvent& WXUNUSED(event))
+{
+    // we use this to check that disabling logging only affects the main thread
+    // but the messages from the worker thread will still be logged
+    wxLogNull noLog;
+    wxLogMessage("You shouldn't see this message because of wxLogNull");
+
+    MyImageDialog dlg(this);
+
+    dlg.ShowModal();
+}
+
+
+// ----------------------------------------------------------------------------
+// MyImageDialog
+// ----------------------------------------------------------------------------
+
+BEGIN_EVENT_TABLE(MyImageDialog, wxDialog)
+    EVT_THREAD(GUITHREAD_EVENT, MyImageDialog::OnGUIThreadEvent)
+    EVT_PAINT(MyImageDialog::OnPaint)
+END_EVENT_TABLE()
+
+MyImageDialog::MyImageDialog(wxFrame *parent)
+    : wxDialog(parent, wxID_ANY, "Image created by a secondary thread",
+               wxDefaultPosition, wxSize(GUITHREAD_BMP_SIZE,GUITHREAD_BMP_SIZE)*1.5, wxDEFAULT_DIALOG_STYLE),
+      m_thread(this)
+{
+    m_nCurrentProgress = 0;
+
+    CentreOnScreen();
+
+    // NOTE: no need to lock m_csBmp until the thread isn't started:
+
+    // create the bitmap
+    if (!m_bmp.Create(GUITHREAD_BMP_SIZE,GUITHREAD_BMP_SIZE) || !m_bmp.IsOk())
+    {
+        wxLogError("Couldn't create the bitmap!");
+        return;
+    }
+
+    // clean it
+    wxMemoryDC dc(m_bmp);
+    dc.SetBackground(*wxBLACK_BRUSH);
+    dc.Clear();
+
+    // draw the bitmap from a secondary thread
+    if ( m_thread.Create() != wxTHREAD_NO_ERROR ||
+         m_thread.Run() != wxTHREAD_NO_ERROR )
+    {
+        wxLogError(wxT("Can't create/run thread!"));
+        return;
+    }
+}
+
+MyImageDialog::~MyImageDialog()
+{
+    // in case our thread is still running and for some reason we are destroyed,
+    // do wait for the thread to complete as it assumes that its MyImageDialog
+    // pointer is always valid
+    m_thread.Delete();
+}
+
+void MyImageDialog::OnGUIThreadEvent(wxThreadEvent& event)
+{
+    m_nCurrentProgress = int(((float)event.GetInt()*100)/GUITHREAD_NUM_UPDATES);
+
+    Refresh();
+}
+
+void MyImageDialog::OnPaint(wxPaintEvent& WXUNUSED(evt))
+{
+    wxPaintDC dc(this);
+
+    const wxSize& sz = dc.GetSize();
+
+    {
+        // paint the bitmap
+        wxCriticalSectionLocker locker(m_csBmp);
+        dc.DrawBitmap(m_bmp, (sz.GetWidth()-GUITHREAD_BMP_SIZE)/2,
+                             (sz.GetHeight()-GUITHREAD_BMP_SIZE)/2);
+    }
+
+    // paint a sort of progress bar with a 10px border:
+    dc.SetBrush(*wxRED_BRUSH);
+    dc.DrawRectangle(10,10, m_nCurrentProgress*(sz.GetWidth()-20)/100,30);
+    dc.SetTextForeground(*wxBLUE);
+    dc.DrawText(wxString::Format("%d%%", m_nCurrentProgress),
+                (sz.GetWidth()-dc.GetCharWidth()*2)/2,
+                25-dc.GetCharHeight()/2);
+}
+
+// ----------------------------------------------------------------------------
+// MyThread
+// ----------------------------------------------------------------------------
+
+MyThread::MyThread()
+        : wxThread()
+{
+    m_count = 0;
+}
+
+MyThread::~MyThread()
+{
+    wxCriticalSectionLocker locker(wxGetApp().m_critsect);
+
+    wxArrayThread& threads = wxGetApp().m_threads;
+    threads.Remove(this);
+
+    if ( threads.IsEmpty() )
+    {
+        // signal the main thread that there are no more threads left if it is
+        // waiting for us
+        if ( wxGetApp().m_shuttingDown )
+        {
+            wxGetApp().m_shuttingDown = false;
+
+            wxGetApp().m_semAllDone.Post();
+        }
+    }
+}
+
+wxThread::ExitCode MyThread::Entry()
+{
+    wxLogMessage("Thread started (priority = %u).", GetPriority());
+
+    for ( m_count = 0; m_count < 10; m_count++ )
+    {
+        // check if the application is shutting down: in this case all threads
+        // should stop a.s.a.p.
+        {
+            wxCriticalSectionLocker locker(wxGetApp().m_critsect);
+            if ( wxGetApp().m_shuttingDown )
+                return NULL;
+        }
+
+        // check if just this thread was asked to exit
+        if ( TestDestroy() )
+            break;
+
+        wxLogMessage("Thread progress: %u", m_count);
+
+        // wxSleep() can't be called from non-GUI thread!
+        wxThread::Sleep(1000);
+    }
+
+    wxLogMessage("Thread finished.");
+
+    return NULL;
+}
+
+
+// ----------------------------------------------------------------------------
+// MyWorkerThread
+// ----------------------------------------------------------------------------
+
+// define this symbol to 1 to test if the YieldFor() call in the wxProgressDialog::Update
+// function provokes a race condition in which the second wxThreadEvent posted by
+// MyWorkerThread::Entry is processed by the YieldFor() call of wxProgressDialog::Update
+// and results in the destruction of the progress dialog itself, resulting in a crash later.
+#define TEST_YIELD_RACE_CONDITION       0
+
+MyWorkerThread::MyWorkerThread(MyFrame *frame)
+        : wxThread()
+{
+    m_frame = frame;
+    m_count = 0;
+}
+
+void MyWorkerThread::OnExit()
+{
+}
+
+wxThread::ExitCode MyWorkerThread::Entry()
+{
+#if TEST_YIELD_RACE_CONDITION
+    if ( TestDestroy() )
+        return NULL;
+
+    wxThreadEvent event( wxEVT_THREAD, WORKER_EVENT );
+
+    event.SetInt( 50 );
+    wxQueueEvent( m_frame, event.Clone() );
+
+    event.SetInt(-1);
+    wxQueueEvent( m_frame, event.Clone() );
+#else
+    for ( m_count = 0; !m_frame->Cancelled() && (m_count < 100); m_count++ )
+    {
+        // check if we were asked to exit
+        if ( TestDestroy() )
+            break;
+
+        // create any type of command event here
+        wxThreadEvent event( wxEVT_THREAD, WORKER_EVENT );
+        event.SetInt( m_count );
+
+        // send in a thread-safe way
+        wxQueueEvent( m_frame, event.Clone() );
+
+        wxMilliSleep(200);
+    }
+
+    wxThreadEvent event( wxEVT_THREAD, WORKER_EVENT );
+    event.SetInt(-1); // that's all
+    wxQueueEvent( m_frame, event.Clone() );
 #endif
 #endif
+
+    return NULL;
 }
 
 }
 
-bool MyFrame::Cancelled()
+
+// ----------------------------------------------------------------------------
+// MyGUIThread
+// ----------------------------------------------------------------------------
+
+wxThread::ExitCode MyGUIThread::Entry()
 {
 {
-    wxCriticalSectionLocker lock(m_critsectWork);
+    // uncomment this to check that disabling logging here does disable it for
+    // this thread -- but not the main one if you also comment out wxLogNull
+    // line in MyFrame::OnStartGUIThread()
+    //wxLogNull noLog;
 
 
-    return m_cancelled;
+    // this goes to the main window
+    wxLogMessage("GUI thread starting");
+
+    // use a thread-specific log target for this thread to show that its
+    // messages don't appear in the main window while it runs
+    wxLogBuffer logBuf;
+    wxLog::SetThreadActiveTarget(&logBuf);
+
+    for (int i=0; i<GUITHREAD_NUM_UPDATES && !TestDestroy(); i++)
+    {
+        // inform the GUI toolkit that we're going to use GUI functions
+        // from a secondary thread:
+        wxMutexGuiEnter();
+
+        {
+            wxCriticalSectionLocker lock(m_dlg->m_csBmp);
+
+            // draw some more stuff on the bitmap
+            wxMemoryDC dc(m_dlg->m_bmp);
+            dc.SetBrush((i%2)==0 ? *wxBLUE_BRUSH : *wxGREEN_BRUSH);
+            dc.DrawRectangle(rand()%GUITHREAD_BMP_SIZE, rand()%GUITHREAD_BMP_SIZE, 30, 30);
+
+            // simulate long drawing time:
+            wxMilliSleep(200);
+        }
+
+        // if we don't release the GUI mutex the MyImageDialog won't be able to refresh
+        wxMutexGuiLeave();
+
+        // notify the dialog that another piece of our masterpiece is complete:
+        wxThreadEvent event( wxEVT_THREAD, GUITHREAD_EVENT );
+        event.SetInt(i+1);
+        wxQueueEvent( m_dlg, event.Clone() );
+
+        if ( !((i + 1) % 10) )
+        {
+            // this message will go to the buffer
+            wxLogMessage("Step #%d.", i + 1);
+        }
+
+        // give the main thread the time to refresh before we lock the GUI mutex again
+        // FIXME: find a better way to do this!
+        wxMilliSleep(100);
+    }
+
+    // now remove the thread-specific thread target
+    wxLog::SetThreadActiveTarget(NULL);
+
+    // so that this goes to the main window again
+    wxLogMessage("GUI thread finished.");
+
+    return (ExitCode)0;
 }
 }