Ported new double idle handlers for pending events
[wxWidgets.git] / src / gtk1 / app.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: app.cpp
3 // Purpose:
4 // Author: Robert Roebling
5 // Id: $Id$
6 // Copyright: (c) 1998 Robert Roebling, Julian Smart
7 // Licence: wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
9
10 #ifdef __GNUG__
11 #pragma implementation "app.h"
12 #endif
13
14 #include "wx/app.h"
15 #include "wx/gdicmn.h"
16 #include "wx/utils.h"
17 #include "wx/intl.h"
18 #include "wx/log.h"
19 #include "wx/memory.h"
20 #include "wx/font.h"
21 #include "wx/settings.h"
22 #include "wx/dialog.h"
23
24 #if wxUSE_WX_RESOURCES
25 #include "wx/resource.h"
26 #endif
27
28 #include "wx/module.h"
29 #include "wx/image.h"
30
31 #if wxUSE_THREADS
32 #include "wx/thread.h"
33 #endif
34
35 #include <unistd.h>
36
37 #include <glib.h>
38 #include <gdk/gdk.h>
39 #include <gtk/gtk.h>
40
41 #include "wx/gtk/win_gtk.h"
42
43 //-----------------------------------------------------------------------------
44 // global data
45 //-----------------------------------------------------------------------------
46
47 wxApp *wxTheApp = (wxApp *) NULL;
48 wxAppInitializerFunction wxAppBase::m_appInitFn = (wxAppInitializerFunction) NULL;
49
50 extern bool g_isIdle;
51
52 bool g_mainThreadLocked = FALSE;
53 gint g_pendingTag = 0;
54
55 GtkWidget *wxRootWindow = (GtkWidget*) NULL;
56
57 //-----------------------------------------------------------------------------
58 // local functions
59 //-----------------------------------------------------------------------------
60
61 /* forward declaration */
62 gint wxapp_idle_callback( gpointer WXUNUSED(data) );
63 gint wxapp_pending_callback( gpointer WXUNUSED(data) );
64 void wxapp_install_idle_handler();
65
66 #if wxUSE_THREADS
67 gint wxapp_wakeup_timerout_callback( gpointer WXUNUSED(data) );
68 #endif
69
70 //-----------------------------------------------------------------------------
71 // wxExit
72 //-----------------------------------------------------------------------------
73
74 void wxExit()
75 {
76 gtk_main_quit();
77 }
78
79 //-----------------------------------------------------------------------------
80 // wxYield
81 //-----------------------------------------------------------------------------
82
83 bool wxYield()
84 {
85 bool has_idle = (wxTheApp->m_idleTag != 0);
86
87 if (has_idle)
88 {
89 /* We need to temporarily remove idle callbacks or the loop will
90 never finish. */
91 gtk_idle_remove( wxTheApp->m_idleTag );
92 wxTheApp->m_idleTag = 0;
93 }
94
95 while (gtk_events_pending())
96 gtk_main_iteration();
97
98 if (has_idle)
99 {
100 /* re-add idle handler (very low priority) */
101 wxTheApp->m_idleTag = gtk_idle_add_priority( 1000, wxapp_idle_callback, (gpointer) NULL );
102 }
103
104 // disable log flushing from here because a call to wxYield() shouldn't
105 // normally result in message boxes popping up &c
106 wxLog::Suspend();
107
108 /* it's necessary to call ProcessIdle() to update the frames sizes which
109 might have been changed (it also will update other things set from
110 OnUpdateUI() which is a nice (and desired) side effect) */
111 while (wxTheApp->ProcessIdle()) { }
112
113 // let the logs be flashed again
114 wxLog::Resume();
115
116 return TRUE;
117 }
118
119 //-----------------------------------------------------------------------------
120 // wxWakeUpIdle
121 //-----------------------------------------------------------------------------
122
123 void wxWakeUpIdle()
124 {
125 #if wxUSE_THREADS
126 if (!wxThread::IsMain())
127 wxMutexGuiEnter();
128 #endif
129
130 if (g_isIdle)
131 wxapp_install_idle_handler();
132
133 #if wxUSE_THREADS
134 if (!wxThread::IsMain())
135 wxMutexGuiLeave();
136 #endif
137 }
138
139 //-----------------------------------------------------------------------------
140 // local functions
141 //-----------------------------------------------------------------------------
142
143 gint wxapp_pending_callback( gpointer WXUNUSED(data) )
144 {
145 if (!wxTheApp) return TRUE;
146
147 // when getting called from GDK's time-out handler
148 // we are no longer within GDK's grab on the GUI
149 // thread so we must lock it here ourselves
150 gdk_threads_enter();
151
152 // Sent idle event to all who request them
153 wxTheApp->ProcessPendingEvents();
154
155 g_pendingTag = 0;
156
157 // Release lock again
158 gdk_threads_leave();
159
160 // Return FALSE to indicate that no more idle events are
161 // to be sent (single shot instead of continuous stream)
162 return FALSE;
163 }
164
165 gint wxapp_idle_callback( gpointer WXUNUSED(data) )
166 {
167 if (!wxTheApp) return TRUE;
168
169 // when getting called from GDK's time-out handler
170 // we are no longer within GDK's grab on the GUI
171 // thread so we must lock it here ourselves
172 gdk_threads_enter();
173
174 // Sent idle event to all who request them
175 while (wxTheApp->ProcessIdle()) { }
176
177 /* Indicate that we are now in idle mode - even so deeply
178 in idle mode that we don't get any idle events anymore.
179 this is like wxMSW where an idle event is sent only
180 once each time after the event queue has been completely
181 emptied */
182 g_isIdle = TRUE;
183 wxTheApp->m_idleTag = 0;
184
185 // Release lock again
186 gdk_threads_leave();
187
188 // Return FALSE to indicate that no more idle events are
189 // to be sent (single shot instead of continuous stream)
190 return FALSE;
191 }
192
193 void wxapp_install_idle_handler()
194 {
195 wxASSERT_MSG( wxTheApp->m_idleTag == 0, wxT("attempt to install idle handler twice") );
196
197 if (g_pendingTag == 0)
198 g_pendingTag = gtk_idle_add_priority( 900, wxapp_pending_callback, (gpointer) NULL );
199
200 /* This routine gets called by all event handlers
201 indicating that the idle is over. It may also
202 get called from other thread for sending events
203 to the main thread (and processing these in
204 idle time). Very low priority. */
205
206 wxTheApp->m_idleTag = gtk_idle_add_priority( 1000, wxapp_idle_callback, (gpointer) NULL );
207
208 g_isIdle = FALSE;
209 }
210
211 #if wxUSE_THREADS
212
213 static int g_threadUninstallLevel = 0;
214
215 void wxapp_install_thread_wakeup()
216 {
217 g_threadUninstallLevel++;
218
219 if (g_threadUninstallLevel != 1) return;
220
221 if (wxTheApp->m_wakeUpTimerTag) return;
222
223 wxTheApp->m_wakeUpTimerTag = gtk_timeout_add( 50, wxapp_wakeup_timerout_callback, (gpointer) NULL );
224 }
225
226 void wxapp_uninstall_thread_wakeup()
227 {
228 g_threadUninstallLevel--;
229
230 if (g_threadUninstallLevel != 0) return;
231
232 if (!wxTheApp->m_wakeUpTimerTag) return;
233
234 gtk_timeout_remove( wxTheApp->m_wakeUpTimerTag );
235 wxTheApp->m_wakeUpTimerTag = 0;
236 }
237
238 gint wxapp_wakeup_timerout_callback( gpointer WXUNUSED(data) )
239 {
240 // when getting called from GDK's time-out handler
241 // we are no longer within GDK's grab on the GUI
242 // thread so we must lock it here ourselves
243 gdk_threads_enter();
244
245 wxapp_uninstall_thread_wakeup();
246
247 // unblock other threads wishing to do some GUI things
248 wxMutexGuiLeave();
249
250 g_mainThreadLocked = TRUE;
251
252 // wake up other threads
253 wxUsleep( 1 );
254
255 // block other thread again
256 wxMutexGuiEnter();
257
258 g_mainThreadLocked = FALSE;
259
260 wxapp_install_thread_wakeup();
261
262 // release lock again
263 gdk_threads_leave();
264
265 return TRUE;
266 }
267
268 #endif // wxUSE_THREADS
269
270 //-----------------------------------------------------------------------------
271 // wxApp
272 //-----------------------------------------------------------------------------
273
274 IMPLEMENT_DYNAMIC_CLASS(wxApp,wxEvtHandler)
275
276 BEGIN_EVENT_TABLE(wxApp, wxEvtHandler)
277 EVT_IDLE(wxApp::OnIdle)
278 END_EVENT_TABLE()
279
280 wxApp::wxApp()
281 {
282 wxTheApp = this;
283
284 m_topWindow = (wxWindow *) NULL;
285 m_exitOnFrameDelete = TRUE;
286
287 m_idleTag = gtk_idle_add_priority( 1000, wxapp_idle_callback, (gpointer) NULL );
288
289 #if wxUSE_THREADS
290 m_wakeUpTimerTag = 0;
291 wxapp_install_thread_wakeup();
292 #endif
293
294 m_colorCube = (unsigned char*) NULL;
295
296 m_useBestVisual = FALSE;
297 }
298
299 wxApp::~wxApp()
300 {
301 if (m_idleTag) gtk_idle_remove( m_idleTag );
302
303 #if wxUSE_THREADS
304 wxapp_uninstall_thread_wakeup();
305 #endif
306
307 if (m_colorCube) free(m_colorCube);
308 }
309
310 bool wxApp::OnInitGui()
311 {
312 GdkVisual *visual = gdk_visual_get_system();
313
314 /* on some machines, the default visual is just 256 colours, so
315 we make sure we get the best. this can sometimes be wasteful,
316 of course, but what do these guys pay $30.000 for? */
317
318 if ((gdk_visual_get_best() != gdk_visual_get_system()) &&
319 (m_useBestVisual))
320 {
321 #ifdef __WXGTK20__
322 /* seems gtk_widget_set_default_visual no longer exists? */
323 GdkVisual* vis = gtk_widget_get_default_visual();
324 #else
325 GdkVisual* vis = gdk_visual_get_best();
326 gtk_widget_set_default_visual( vis );
327 #endif
328
329 GdkColormap *colormap = gdk_colormap_new( vis, FALSE );
330 gtk_widget_set_default_colormap( colormap );
331
332 visual = vis;
333 }
334
335 /* Nothing to do for 15, 16, 24, 32 bit displays */
336 if (visual->depth > 8) return TRUE;
337
338 /* initialize color cube for 8-bit color reduction dithering */
339
340 GdkColormap *cmap = gtk_widget_get_default_colormap();
341
342 m_colorCube = (unsigned char*)malloc(32 * 32 * 32);
343
344 for (int r = 0; r < 32; r++)
345 {
346 for (int g = 0; g < 32; g++)
347 {
348 for (int b = 0; b < 32; b++)
349 {
350 int rr = (r << 3) | (r >> 2);
351 int gg = (g << 3) | (g >> 2);
352 int bb = (b << 3) | (b >> 2);
353
354 int index = -1;
355
356 GdkColor *colors = cmap->colors;
357 if (colors)
358 {
359 int max = 3 * 65536;
360
361 for (int i = 0; i < cmap->size; i++)
362 {
363 int rdiff = ((rr << 8) - colors[i].red);
364 int gdiff = ((gg << 8) - colors[i].green);
365 int bdiff = ((bb << 8) - colors[i].blue);
366 int sum = ABS (rdiff) + ABS (gdiff) + ABS (bdiff);
367 if (sum < max)
368 {
369 index = i; max = sum;
370 }
371 }
372 }
373 else
374 {
375 #if (GTK_MINOR_VERSION > 0)
376 /* assume 8-bit true or static colors. this really
377 exists. */
378 GdkVisual* vis = gdk_colormap_get_visual( cmap );
379 index = (r >> (5 - vis->red_prec)) << vis->red_shift;
380 index |= (g >> (5 - vis->green_prec)) << vis->green_shift;
381 index |= (b >> (5 - vis->blue_prec)) << vis->blue_shift;
382 #else
383 wxFAIL_MSG( wxT("Unsupported graphics hardware") );
384 #endif
385 }
386 m_colorCube[ (r*1024) + (g*32) + b ] = index;
387 }
388 }
389 }
390
391 return TRUE;
392 }
393
394 bool wxApp::ProcessIdle()
395 {
396 wxIdleEvent event;
397 event.SetEventObject( this );
398 ProcessEvent( event );
399
400 return event.MoreRequested();
401 }
402
403 void wxApp::OnIdle( wxIdleEvent &event )
404 {
405 static bool s_inOnIdle = FALSE;
406
407 /* Avoid recursion (via ProcessEvent default case) */
408 if (s_inOnIdle)
409 return;
410
411 s_inOnIdle = TRUE;
412
413 /* Resend in the main thread events which have been prepared in other
414 threads */
415 ProcessPendingEvents();
416
417 /* 'Garbage' collection of windows deleted with Close(). */
418 DeletePendingObjects();
419
420 /* Send OnIdle events to all windows */
421 bool needMore = SendIdleEvents();
422
423 if (needMore)
424 event.RequestMore(TRUE);
425
426 s_inOnIdle = FALSE;
427
428 /* flush the logged messages if any */
429 #if wxUSE_LOG
430 wxLog::FlushActive();
431 #endif // wxUSE_LOG
432 }
433
434 bool wxApp::SendIdleEvents()
435 {
436 bool needMore = FALSE;
437
438 wxWindowList::Node* node = wxTopLevelWindows.GetFirst();
439 while (node)
440 {
441 wxWindow* win = node->GetData();
442 if (SendIdleEvents(win))
443 needMore = TRUE;
444 node = node->GetNext();
445 }
446
447 return needMore;
448 }
449
450 bool wxApp::SendIdleEvents( wxWindow* win )
451 {
452 bool needMore = FALSE;
453
454 wxIdleEvent event;
455 event.SetEventObject(win);
456
457 win->GetEventHandler()->ProcessEvent(event);
458
459 win->OnInternalIdle();
460
461 if (event.MoreRequested())
462 needMore = TRUE;
463
464 wxNode* node = win->GetChildren().First();
465 while (node)
466 {
467 wxWindow* win = (wxWindow*) node->Data();
468 if (SendIdleEvents(win))
469 needMore = TRUE;
470
471 node = node->Next();
472 }
473 return needMore ;
474 }
475
476 int wxApp::MainLoop()
477 {
478 gtk_main();
479 return 0;
480 }
481
482 void wxApp::ExitMainLoop()
483 {
484 if (gtk_main_level() > 0)
485 gtk_main_quit();
486 }
487
488 bool wxApp::Initialized()
489 {
490 return m_initialized;
491 }
492
493 bool wxApp::Pending()
494 {
495 return (gtk_events_pending() > 0);
496 }
497
498 void wxApp::Dispatch()
499 {
500 gtk_main_iteration();
501 }
502
503 void wxApp::DeletePendingObjects()
504 {
505 wxNode *node = wxPendingDelete.First();
506 while (node)
507 {
508 wxObject *obj = (wxObject *)node->Data();
509
510 delete obj;
511
512 if (wxPendingDelete.Find(obj))
513 delete node;
514
515 node = wxPendingDelete.First();
516 }
517 }
518
519 bool wxApp::Initialize()
520 {
521 wxBuffer = new wxChar[BUFSIZ + 512];
522
523 wxClassInfo::InitializeClasses();
524
525 wxSystemSettings::Init();
526
527 // GL: I'm annoyed ... I don't know where to put this and I don't want to
528 // create a module for that as it's part of the core.
529 #if wxUSE_THREADS
530 wxPendingEvents = new wxList();
531 wxPendingEventsLocker = new wxCriticalSection();
532 #endif
533
534 wxTheColourDatabase = new wxColourDatabase( wxKEY_STRING );
535 wxTheColourDatabase->Initialize();
536
537 wxInitializeStockLists();
538 wxInitializeStockObjects();
539
540 #if wxUSE_WX_RESOURCES
541 wxInitializeResourceSystem();
542 #endif
543
544 wxModule::RegisterModules();
545 if (!wxModule::InitializeModules()) return FALSE;
546
547 return TRUE;
548 }
549
550 void wxApp::CleanUp()
551 {
552 wxModule::CleanUpModules();
553
554 #if wxUSE_WX_RESOURCES
555 wxCleanUpResourceSystem();
556 #endif
557
558 if (wxTheColourDatabase)
559 delete wxTheColourDatabase;
560
561 wxTheColourDatabase = (wxColourDatabase*) NULL;
562
563 wxDeleteStockObjects();
564
565 wxDeleteStockLists();
566
567 delete wxTheApp;
568 wxTheApp = (wxApp*) NULL;
569
570 // GL: I'm annoyed ... I don't know where to put this and I don't want to
571 // create a module for that as it's part of the core.
572 #if wxUSE_THREADS
573 delete wxPendingEvents;
574 delete wxPendingEventsLocker;
575 #endif
576
577 wxSystemSettings::Done();
578
579 delete[] wxBuffer;
580
581 wxClassInfo::CleanUpClasses();
582
583 // check for memory leaks
584 #if (defined(__WXDEBUG__) && wxUSE_MEMORY_TRACING) || wxUSE_DEBUG_CONTEXT
585 if (wxDebugContext::CountObjectsLeft(TRUE) > 0)
586 {
587 wxLogDebug(wxT("There were memory leaks.\n"));
588 wxDebugContext::Dump();
589 wxDebugContext::PrintStatistics();
590 }
591 #endif // Debug
592
593 #if wxUSE_LOG
594 // do this as the very last thing because everything else can log messages
595 wxLog::DontCreateOnDemand();
596
597 wxLog *oldLog = wxLog::SetActiveTarget( (wxLog*) NULL );
598 if (oldLog)
599 delete oldLog;
600 #endif // wxUSE_LOG
601 }
602
603 //-----------------------------------------------------------------------------
604 // wxEntry
605 //-----------------------------------------------------------------------------
606
607
608 int wxEntryStart( int argc, char *argv[] )
609 {
610 #if wxUSE_THREADS
611 /* GTK 1.2 up to version 1.2.3 has broken threads */
612 #ifdef __VMS__
613 if ((vms_gtk_major_version() == 1) &&
614 (vms_gtk_minor_version() == 2) &&
615 (vms_gtk_micro_version() < 4))
616 #else
617 if ((gtk_major_version == 1) &&
618 (gtk_minor_version == 2) &&
619 (gtk_micro_version < 4))
620 #endif
621 {
622 printf( "wxWindows warning: GUI threading disabled due to outdated GTK version\n" );
623 }
624 else
625 {
626 g_thread_init(NULL);
627 }
628 #endif
629
630 gtk_set_locale();
631
632 #if defined(__WXGTK20__)
633 // gtk+ 2.0 supports Unicode through UTF-8 strings
634 wxConvCurrent = &wxConvUTF8;
635 #elif wxUSE_WCHAR_T
636 if (!wxOKlibc()) wxConvCurrent = &wxConvLocal;
637 #else
638 if (!wxOKlibc()) wxConvCurrent = (wxMBConv*) NULL;
639 #endif
640
641 gdk_threads_enter();
642
643 gtk_init( &argc, &argv );
644
645 wxSetDetectableAutoRepeat( TRUE );
646
647 if (!wxApp::Initialize())
648 {
649 gdk_threads_leave();
650 return -1;
651 }
652
653 return 0;
654 }
655
656
657 int wxEntryInitGui()
658 {
659 int retValue = 0;
660
661 if ( !wxTheApp->OnInitGui() )
662 retValue = -1;
663
664 wxRootWindow = gtk_window_new( GTK_WINDOW_TOPLEVEL );
665 gtk_widget_realize( wxRootWindow );
666
667 return retValue;
668 }
669
670
671 void wxEntryCleanup()
672 {
673 #if wxUSE_LOG
674 // flush the logged messages if any
675 wxLog *log = wxLog::GetActiveTarget();
676 if (log != NULL && log->HasPendingMessages())
677 log->Flush();
678
679 // continuing to use user defined log target is unsafe from now on because
680 // some resources may be already unavailable, so replace it by something
681 // more safe
682 wxLog *oldlog = wxLog::SetActiveTarget(new wxLogStderr);
683 if ( oldlog )
684 delete oldlog;
685 #endif // wxUSE_LOG
686
687 wxApp::CleanUp();
688
689 gdk_threads_leave();
690 }
691
692
693
694 int wxEntry( int argc, char *argv[] )
695 {
696 #if (defined(__WXDEBUG__) && wxUSE_MEMORY_TRACING) || wxUSE_DEBUG_CONTEXT
697 // This seems to be necessary since there are 'rogue'
698 // objects present at this point (perhaps global objects?)
699 // Setting a checkpoint will ignore them as far as the
700 // memory checking facility is concerned.
701 // Of course you may argue that memory allocated in globals should be
702 // checked, but this is a reasonable compromise.
703 wxDebugContext::SetCheckpoint();
704 #endif
705 int err = wxEntryStart(argc, argv);
706 if (err)
707 return err;
708
709 if (!wxTheApp)
710 {
711 wxCHECK_MSG( wxApp::GetInitializerFunction(), -1,
712 wxT("wxWindows error: No initializer - use IMPLEMENT_APP macro.\n") );
713
714 wxAppInitializerFunction app_ini = wxApp::GetInitializerFunction();
715
716 wxObject *test_app = app_ini();
717
718 wxTheApp = (wxApp*) test_app;
719 }
720
721 wxCHECK_MSG( wxTheApp, -1, wxT("wxWindows error: no application object") );
722
723 wxTheApp->argc = argc;
724 #if wxUSE_UNICODE
725 wxTheApp->argv = new wxChar*[argc+1];
726 int mb_argc = 0;
727 while (mb_argc < argc)
728 {
729 wxTheApp->argv[mb_argc] = wxStrdup(wxConvLibc.cMB2WX(argv[mb_argc]));
730 mb_argc++;
731 }
732 wxTheApp->argv[mb_argc] = (wxChar *)NULL;
733 #else
734 wxTheApp->argv = argv;
735 #endif
736
737 wxString name(wxFileNameFromPath(argv[0]));
738 wxStripExtension( name );
739 wxTheApp->SetAppName( name );
740
741 int retValue;
742 retValue = wxEntryInitGui();
743
744 // Here frames insert themselves automatically into wxTopLevelWindows by
745 // getting created in OnInit().
746 if ( retValue == 0 )
747 {
748 if ( !wxTheApp->OnInit() )
749 retValue = -1;
750 }
751
752 if ( retValue == 0 )
753 {
754 /* delete pending toplevel windows (typically a single
755 dialog) so that, if there isn't any left, we don't
756 call OnRun() */
757 wxTheApp->DeletePendingObjects();
758
759 wxTheApp->m_initialized = wxTopLevelWindows.GetCount() != 0;
760
761 if (wxTheApp->Initialized())
762 {
763 wxTheApp->OnRun();
764
765 wxWindow *topWindow = wxTheApp->GetTopWindow();
766 if (topWindow)
767 {
768 /* Forcibly delete the window. */
769 if (topWindow->IsKindOf(CLASSINFO(wxFrame)) ||
770 topWindow->IsKindOf(CLASSINFO(wxDialog)) )
771 {
772 topWindow->Close( TRUE );
773 wxTheApp->DeletePendingObjects();
774 }
775 else
776 {
777 delete topWindow;
778 wxTheApp->SetTopWindow( (wxWindow*) NULL );
779 }
780 }
781
782 retValue = wxTheApp->OnExit();
783 }
784 }
785
786 wxEntryCleanup();
787
788 return retValue;
789 }
790
791 #include "wx/gtk/info.xpm"
792 #include "wx/gtk/error.xpm"
793 #include "wx/gtk/question.xpm"
794 #include "wx/gtk/warning.xpm"
795
796 wxIcon
797 wxApp::GetStdIcon(int which) const
798 {
799 switch(which)
800 {
801 case wxICON_INFORMATION:
802 return wxIcon(info_xpm);
803
804 case wxICON_QUESTION:
805 return wxIcon(question_xpm);
806
807 case wxICON_EXCLAMATION:
808 return wxIcon(warning_xpm);
809
810 default:
811 wxFAIL_MSG(wxT("requested non existent standard icon"));
812 // still fall through
813
814 case wxICON_HAND:
815 return wxIcon(error_xpm);
816 }
817 }