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