]> git.saurik.com Git - wxWidgets.git/blob - src/gtk1/app.cpp
enabled _vsnprintf for mingw with norlander headers
[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 wxResourceCache *wxTheResourceCache;
51 extern bool g_isIdle;
52
53 unsigned char g_palette[64*3] =
54 {
55 0x0, 0x0, 0x0,
56 0xff, 0xff, 0xff,
57 0xff, 0x0, 0x0,
58 0xff, 0xff, 0x0,
59 0x0, 0xff, 0x0,
60 0x0, 0x0, 0xff,
61 0x0, 0xff, 0xff,
62 0x99, 0x99, 0x99,
63 0xff, 0x88, 0x0,
64 0x88, 0x0, 0x0,
65 0x0, 0x88, 0x88,
66 0x88, 0x88, 0x0,
67 0xff, 0xcc, 0x97,
68 0xbb, 0xbb, 0xbb,
69 0x9f, 0x6b, 0x42,
70 0x55, 0x55, 0x55,
71 0xdd, 0xdd, 0xdd,
72 0x77, 0x77, 0x77,
73 0x33, 0x33, 0x33,
74 0xcc, 0x0, 0x0,
75 0xff, 0x44, 0x0,
76 0xff, 0xcc, 0x0,
77 0xcc, 0xcc, 0x0,
78 0x60, 0x60, 0x0,
79 0x0, 0x43, 0x0,
80 0x0, 0x7f, 0x0,
81 0x0, 0xcc, 0x0,
82 0x0, 0x44, 0x44,
83 0x0, 0x0, 0x44,
84 0x0, 0x0, 0x88,
85 0xef, 0xb1, 0x7b,
86 0xdf, 0x98, 0x5f,
87 0xbf, 0x87, 0x56,
88 0x7f, 0x57, 0x26,
89 0x5f, 0x39, 0xc,
90 0x3f, 0x1c, 0x0,
91 0x21, 0x0, 0x0,
92 0x0, 0x43, 0x87,
93 0x2d, 0x70, 0xaf,
94 0x5a, 0x9e, 0xd7,
95 0x87, 0xcc, 0xff,
96 0xff, 0xe0, 0xba,
97 0x21, 0x43, 0xf,
98 0x3d, 0x5d, 0x25,
99 0x59, 0x78, 0x3a,
100 0x75, 0x93, 0x4f,
101 0x91, 0xae, 0x64,
102 0xad, 0xc8, 0x7a,
103 0xf0, 0xa8, 0xef,
104 0xd0, 0x88, 0xd0,
105 0xaf, 0x66, 0xaf,
106 0x8e, 0x44, 0x8e,
107 0x6d, 0x22, 0x6d,
108 0x4b, 0x0, 0x4b,
109 0xff, 0xc0, 0xbc,
110 0xff, 0x93, 0x91,
111 0xff, 0x66, 0x67,
112 0xd8, 0xf2, 0xbf,
113 0xff, 0xc9, 0x68,
114 0xff, 0x96, 0x67,
115 0xa5, 0x60, 0xff,
116 0x51, 0xff, 0x99,
117 0x3f, 0xa5, 0x63,
118 0x98, 0x90, 0x67
119 };
120
121 //-----------------------------------------------------------------------------
122 // local functions
123 //-----------------------------------------------------------------------------
124
125 extern void wxFlushResources();
126
127 /* forward declaration */
128 gint wxapp_idle_callback( gpointer WXUNUSED(data) );
129 void wxapp_install_idle_handler();
130 static gint wxapp_wakeup_timerout_callback( gpointer WXUNUSED(data) );
131
132 //-----------------------------------------------------------------------------
133 // wxExit
134 //-----------------------------------------------------------------------------
135
136 void wxExit()
137 {
138 gtk_main_quit();
139 }
140
141 //-----------------------------------------------------------------------------
142 // wxYield
143 //-----------------------------------------------------------------------------
144
145 bool wxYield()
146 {
147 bool has_idle = (wxTheApp->m_idleTag != 0);
148
149 if (has_idle)
150 {
151 /* We need to temporarily remove idle callbacks or the loop will
152 never finish. */
153 gtk_idle_remove( wxTheApp->m_idleTag );
154 wxTheApp->m_idleTag = 0;
155 }
156
157 while (gtk_events_pending())
158 gtk_main_iteration();
159
160 /* it's necessary to call ProcessIdle() to update the frames sizes which
161 might have been changed (it also will update other things set from
162 OnUpdateUI() which is a nice (and desired) side effect) */
163 while (wxTheApp->ProcessIdle()) { }
164
165 if (has_idle)
166 {
167 /* re-add idle handler */
168 wxTheApp->m_idleTag = gtk_idle_add( wxapp_idle_callback, (gpointer) NULL );
169 }
170
171 return TRUE;
172 }
173
174 //-----------------------------------------------------------------------------
175 // wxWakeUpIdle
176 //-----------------------------------------------------------------------------
177
178 void wxWakeUpIdle()
179 {
180 if (g_isIdle)
181 wxapp_install_idle_handler();
182 }
183
184 //-----------------------------------------------------------------------------
185 // local functions
186 //-----------------------------------------------------------------------------
187
188 gint wxapp_idle_callback( gpointer WXUNUSED(data) )
189 {
190 if (!wxTheApp) return TRUE;
191
192 #if (GTK_MINOR_VERSION > 0)
193 /* when getting called from GDK's idle handler we
194 are no longer within GDK's grab on the GUI
195 thread so we must lock it here ourselves */
196 GDK_THREADS_ENTER ();
197 #endif
198
199 /* sent idle event to all who request them */
200 while (wxTheApp->ProcessIdle()) { }
201
202 /* we don't want any more idle events until the next event is
203 sent to wxGTK */
204 gtk_idle_remove( wxTheApp->m_idleTag );
205 wxTheApp->m_idleTag = 0;
206
207 /* indicate that we are now in idle mode - even so deeply
208 in idle mode that we don't get any idle events anymore.
209 this is like wxMSW where an idle event is sent only
210 once each time after the event queue has been completely
211 emptied */
212 g_isIdle = TRUE;
213
214 #if (GTK_MINOR_VERSION > 0)
215 /* release lock again */
216 GDK_THREADS_LEAVE ();
217 #endif
218
219 return TRUE;
220 }
221
222 void wxapp_install_idle_handler()
223 {
224 wxASSERT_MSG( wxTheApp->m_idleTag == 0, wxT("attempt to install idle handler twice") );
225
226 /* this routine gets called by all event handlers
227 indicating that the idle is over. */
228
229 wxTheApp->m_idleTag = gtk_idle_add( wxapp_idle_callback, (gpointer) NULL );
230
231 g_isIdle = FALSE;
232 }
233
234 #if wxUSE_THREADS
235
236 void wxapp_install_thread_wakeup()
237 {
238 if (wxTheApp->m_wakeUpTimerTag) return;
239
240 wxTheApp->m_wakeUpTimerTag = gtk_timeout_add( 100, wxapp_wakeup_timerout_callback, (gpointer) NULL );
241 }
242
243 void wxapp_uninstall_thread_wakeup()
244 {
245 if (!wxTheApp->m_wakeUpTimerTag) return;
246
247 gtk_timeout_remove( wxTheApp->m_wakeUpTimerTag );
248 wxTheApp->m_wakeUpTimerTag = 0;
249 }
250
251 static gint wxapp_wakeup_timerout_callback( gpointer WXUNUSED(data) )
252 {
253 wxapp_uninstall_thread_wakeup();
254
255 #if (GTK_MINOR_VERSION > 0)
256 // when getting called from GDK's time-out handler
257 // we are no longer within GDK's grab on the GUI
258 // thread so we must lock it here ourselves
259 GDK_THREADS_ENTER ();
260 #endif
261
262 // unblock other threads wishing to do some GUI things
263 wxMutexGuiLeave();
264
265 // wake up other threads
266 wxUsleep( 1 );
267
268 // block other thread again
269 wxMutexGuiEnter();
270
271 #if (GTK_MINOR_VERSION > 0)
272 // release lock again
273 GDK_THREADS_LEAVE ();
274 #endif
275
276 wxapp_install_thread_wakeup();
277
278 return TRUE;
279 }
280
281 #endif // wxUSE_THREADS
282
283 //-----------------------------------------------------------------------------
284 // wxApp
285 //-----------------------------------------------------------------------------
286
287 IMPLEMENT_DYNAMIC_CLASS(wxApp,wxEvtHandler)
288
289 BEGIN_EVENT_TABLE(wxApp, wxEvtHandler)
290 EVT_IDLE(wxApp::OnIdle)
291 END_EVENT_TABLE()
292
293 wxApp::wxApp()
294 {
295 wxTheApp = this;
296
297 m_topWindow = (wxWindow *) NULL;
298 m_exitOnFrameDelete = TRUE;
299
300 m_idleTag = gtk_idle_add( wxapp_idle_callback, (gpointer) NULL );
301
302 #if wxUSE_THREADS
303 m_wakeUpTimerTag = 0;
304 wxapp_install_thread_wakeup();
305 #endif
306
307 m_colorCube = (unsigned char*) NULL;
308 }
309
310 wxApp::~wxApp()
311 {
312 if (m_idleTag) gtk_idle_remove( m_idleTag );
313
314 #if wxUSE_THREADS
315 wxapp_uninstall_thread_wakeup();
316 #endif
317
318 if (m_colorCube) free(m_colorCube);
319 }
320
321 bool wxApp::OnInitGui()
322 {
323 GdkVisual *visual = gdk_visual_get_system();
324
325 /* on some machines, the default visual is just 256 colours, so
326 we make sure we get the best. this can sometimes be wasteful,
327 of course, but what do these guys pay $30.000 for? */
328 /*
329 if (gdk_visual_get_best() != gdk_visual_get_system())
330 {
331 GdkVisual* vis = gdk_visual_get_best();
332 gtk_widget_set_default_visual( vis );
333
334 GdkColormap *colormap = gdk_colormap_new( vis, FALSE );
335 gtk_widget_set_default_colormap( colormap );
336
337 visual = vis;
338 }
339 */
340
341 /* Nothing to do for 15, 16, 24, 32 bit displays */
342 if (visual->depth > 8) return TRUE;
343
344 /* this initiates the standard palette as defined by GdkImlib
345 in the GNOME libraries. it ensures that all GNOME applications
346 use the same 64 colormap entries on 8-bit displays so you
347 can use several rather graphics-heavy applications at the
348 same time.
349 NOTE: this doesn't really seem to work this way... */
350
351 /*
352 GdkColormap *cmap = gdk_colormap_new( gdk_visual_get_system(), TRUE );
353
354 for (int i = 0; i < 64; i++)
355 {
356 GdkColor col;
357 col.red = g_palette[i*3 + 0] << 8;
358 col.green = g_palette[i*3 + 1] << 8;
359 col.blue = g_palette[i*3 + 2] << 8;
360 col.pixel = 0;
361
362 gdk_color_alloc( cmap, &col );
363 }
364
365 gtk_widget_set_default_colormap( cmap );
366 */
367
368 /* initialize color cube for 8-bit color reduction dithering */
369
370 GdkColormap *cmap = gtk_widget_get_default_colormap();
371
372 m_colorCube = (unsigned char*)malloc(32 * 32 * 32);
373
374 for (int r = 0; r < 32; r++)
375 {
376 for (int g = 0; g < 32; g++)
377 {
378 for (int b = 0; b < 32; b++)
379 {
380 int rr = (r << 3) | (r >> 2);
381 int gg = (g << 3) | (g >> 2);
382 int bb = (b << 3) | (b >> 2);
383
384 int index = -1;
385
386 GdkColor *colors = cmap->colors;
387 if (colors)
388 {
389 int max = 3 * 65536;
390
391 for (int i = 0; i < cmap->size; i++)
392 {
393 int rdiff = ((rr << 8) - colors[i].red);
394 int gdiff = ((gg << 8) - colors[i].green);
395 int bdiff = ((bb << 8) - colors[i].blue);
396 int sum = ABS (rdiff) + ABS (gdiff) + ABS (bdiff);
397 if (sum < max)
398 {
399 index = i; max = sum;
400 }
401 }
402 }
403 else
404 {
405 #if (GTK_MINOR_VERSION > 0)
406 /* assume 8-bit true or static colors. this really
407 exists. */
408 GdkVisual* vis = gdk_colormap_get_visual( cmap );
409 index = (r >> (5 - vis->red_prec)) << vis->red_shift;
410 index |= (g >> (5 - vis->green_prec)) << vis->green_shift;
411 index |= (b >> (5 - vis->blue_prec)) << vis->blue_shift;
412 #else
413 wxFAIL_MSG( wxT("Unsupported graphics hardware") );
414 #endif
415 }
416 m_colorCube[ (r*1024) + (g*32) + b ] = index;
417 }
418 }
419 }
420
421 return TRUE;
422 }
423
424 bool wxApp::ProcessIdle()
425 {
426 wxIdleEvent event;
427 event.SetEventObject( this );
428 ProcessEvent( event );
429
430 return event.MoreRequested();
431 }
432
433 void wxApp::OnIdle( wxIdleEvent &event )
434 {
435 static bool s_inOnIdle = FALSE;
436
437 /* Avoid recursion (via ProcessEvent default case) */
438 if (s_inOnIdle)
439 return;
440
441 s_inOnIdle = TRUE;
442
443 /* Resend in the main thread events which have been prepared in other
444 threads */
445 ProcessPendingEvents();
446
447 /* 'Garbage' collection of windows deleted with Close(). */
448 DeletePendingObjects();
449
450 /* flush the logged messages if any */
451 #if wxUSE_LOG
452 wxLog *log = wxLog::GetActiveTarget();
453 if (log != NULL && log->HasPendingMessages())
454 log->Flush();
455 #endif // wxUSE_LOG
456
457 /* Send OnIdle events to all windows */
458 bool needMore = SendIdleEvents();
459
460 if (needMore)
461 event.RequestMore(TRUE);
462
463 s_inOnIdle = FALSE;
464 }
465
466 bool wxApp::SendIdleEvents()
467 {
468 bool needMore = FALSE;
469
470 wxWindowList::Node* node = wxTopLevelWindows.GetFirst();
471 while (node)
472 {
473 wxWindow* win = node->GetData();
474 if (SendIdleEvents(win))
475 needMore = TRUE;
476 node = node->GetNext();
477 }
478
479 return needMore;
480 }
481
482 bool wxApp::SendIdleEvents( wxWindow* win )
483 {
484 bool needMore = FALSE;
485
486 wxIdleEvent event;
487 event.SetEventObject(win);
488
489 win->ProcessEvent(event);
490
491 win->OnInternalIdle();
492
493 if (event.MoreRequested())
494 needMore = TRUE;
495
496 wxNode* node = win->GetChildren().First();
497 while (node)
498 {
499 wxWindow* win = (wxWindow*) node->Data();
500 if (SendIdleEvents(win))
501 needMore = TRUE;
502
503 node = node->Next();
504 }
505 return needMore ;
506 }
507
508 int wxApp::MainLoop()
509 {
510 gtk_main();
511 return 0;
512 }
513
514 void wxApp::ExitMainLoop()
515 {
516 gtk_main_quit();
517 }
518
519 bool wxApp::Initialized()
520 {
521 return m_initialized;
522 }
523
524 bool wxApp::Pending()
525 {
526 return (gtk_events_pending() > 0);
527 }
528
529 void wxApp::Dispatch()
530 {
531 gtk_main_iteration();
532 }
533
534 void wxApp::DeletePendingObjects()
535 {
536 wxNode *node = wxPendingDelete.First();
537 while (node)
538 {
539 wxObject *obj = (wxObject *)node->Data();
540
541 delete obj;
542
543 if (wxPendingDelete.Find(obj))
544 delete node;
545
546 node = wxPendingDelete.First();
547 }
548 }
549
550 bool wxApp::Initialize()
551 {
552 wxBuffer = new wxChar[BUFSIZ + 512];
553
554 wxClassInfo::InitializeClasses();
555
556 wxSystemSettings::Init();
557
558 // GL: I'm annoyed ... I don't know where to put this and I don't want to
559 // create a module for that as it's part of the core.
560 #if wxUSE_THREADS
561 wxPendingEvents = new wxList();
562 wxPendingEventsLocker = new wxCriticalSection();
563 #endif
564
565 /*
566 wxTheFontNameDirectory = new wxFontNameDirectory;
567 wxTheFontNameDirectory->Initialize();
568 */
569
570 wxTheColourDatabase = new wxColourDatabase( wxKEY_STRING );
571 wxTheColourDatabase->Initialize();
572
573 wxInitializeStockLists();
574 wxInitializeStockObjects();
575
576 #if wxUSE_WX_RESOURCES
577 wxTheResourceCache = new wxResourceCache( wxKEY_STRING );
578
579 wxInitializeResourceSystem();
580 #endif
581
582 wxModule::RegisterModules();
583 if (!wxModule::InitializeModules()) return FALSE;
584
585 return TRUE;
586 }
587
588 void wxApp::CleanUp()
589 {
590 wxModule::CleanUpModules();
591
592 #if wxUSE_WX_RESOURCES
593 wxFlushResources();
594
595 if (wxTheResourceCache)
596 delete wxTheResourceCache;
597 wxTheResourceCache = (wxResourceCache*) NULL;
598
599 wxCleanUpResourceSystem();
600 #endif
601
602 if (wxTheColourDatabase)
603 delete wxTheColourDatabase;
604 wxTheColourDatabase = (wxColourDatabase*) NULL;
605
606 /*
607 if (wxTheFontNameDirectory) delete wxTheFontNameDirectory;
608 wxTheFontNameDirectory = (wxFontNameDirectory*) NULL;
609 */
610
611 wxDeleteStockObjects();
612
613 wxDeleteStockLists();
614
615 delete wxTheApp;
616 wxTheApp = (wxApp*) NULL;
617
618 // GL: I'm annoyed ... I don't know where to put this and I don't want to
619 // create a module for that as it's part of the core.
620 #if wxUSE_THREADS
621 delete wxPendingEvents;
622 delete wxPendingEventsLocker;
623 #endif
624
625 wxSystemSettings::Done();
626
627 delete[] wxBuffer;
628
629 wxClassInfo::CleanUpClasses();
630
631 // check for memory leaks
632 #if (defined(__WXDEBUG__) && wxUSE_MEMORY_TRACING) || wxUSE_DEBUG_CONTEXT
633 if (wxDebugContext::CountObjectsLeft() > 0)
634 {
635 wxLogDebug(wxT("There were memory leaks.\n"));
636 wxDebugContext::Dump();
637 wxDebugContext::PrintStatistics();
638 }
639 #endif // Debug
640
641 #if wxUSE_LOG
642 // do this as the very last thing because everything else can log messages
643 wxLog::DontCreateOnDemand();
644
645 wxLog *oldLog = wxLog::SetActiveTarget( (wxLog*) NULL );
646 if (oldLog)
647 delete oldLog;
648 #endif // wxUSE_LOG
649 }
650
651 //-----------------------------------------------------------------------------
652 // wxEntry
653 //-----------------------------------------------------------------------------
654
655 int wxEntry( int argc, char *argv[] )
656 {
657 gtk_set_locale();
658
659 #if wxUSE_WCHAR_T
660 if (!wxOKlibc()) wxConvCurrent = &wxConvLocal;
661 #else
662 if (!wxOKlibc()) wxConvCurrent = (wxMBConv*) NULL;
663 #endif
664
665 gtk_init( &argc, &argv );
666
667 wxSetDetectableAutoRepeat( TRUE );
668
669 if (!wxApp::Initialize())
670 return -1;
671
672 if (!wxTheApp)
673 {
674 wxCHECK_MSG( wxApp::GetInitializerFunction(), -1,
675 wxT("wxWindows error: No initializer - use IMPLEMENT_APP macro.\n") );
676
677 wxAppInitializerFunction app_ini = wxApp::GetInitializerFunction();
678
679 wxObject *test_app = app_ini();
680
681 wxTheApp = (wxApp*) test_app;
682 }
683
684 wxCHECK_MSG( wxTheApp, -1, wxT("wxWindows error: no application object") );
685
686 wxTheApp->argc = argc;
687 #if wxUSE_UNICODE
688 wxTheApp->argv = new wxChar*[argc+1];
689 int mb_argc = 0;
690 while (mb_argc < argc) {
691 wxTheApp->argv[mb_argc] = wxStrdup(wxConvLibc.cMB2WX(argv[mb_argc]));
692 mb_argc++;
693 }
694 wxTheApp->argv[mb_argc] = (wxChar *)NULL;
695 #else
696 wxTheApp->argv = argv;
697 #endif
698
699 wxString name(wxFileNameFromPath(argv[0]));
700 wxStripExtension( name );
701 wxTheApp->SetAppName( name );
702
703 int retValue = 0;
704
705 if ( !wxTheApp->OnInitGui() )
706 retValue = -1;
707
708 // Here frames insert themselves automatically into wxTopLevelWindows by
709 // getting created in OnInit().
710 if ( retValue == 0 )
711 {
712 if ( !wxTheApp->OnInit() )
713 retValue = -1;
714 }
715
716 if ( retValue == 0 )
717 {
718 /* delete pending toplevel windows (typically a single
719 dialog) so that, if there isn't any left, we don't
720 call OnRun() */
721 wxTheApp->DeletePendingObjects();
722
723 wxTheApp->m_initialized = wxTopLevelWindows.GetCount() != 0;
724
725 if (wxTheApp->Initialized())
726 {
727 retValue = wxTheApp->OnRun();
728
729 wxWindow *topWindow = wxTheApp->GetTopWindow();
730 if (topWindow)
731 {
732 /* Forcibly delete the window. */
733 if (topWindow->IsKindOf(CLASSINFO(wxFrame)) ||
734 topWindow->IsKindOf(CLASSINFO(wxDialog)) )
735 {
736 topWindow->Close( TRUE );
737 wxTheApp->DeletePendingObjects();
738 }
739 else
740 {
741 delete topWindow;
742 wxTheApp->SetTopWindow( (wxWindow*) NULL );
743 }
744 }
745 wxTheApp->OnExit();
746 }
747 }
748
749 #if wxUSE_LOG
750 // flush the logged messages if any
751 wxLog *log = wxLog::GetActiveTarget();
752 if (log != NULL && log->HasPendingMessages())
753 log->Flush();
754
755 // continuing to use user defined log target is unsafe from now on because
756 // some resources may be already unavailable, so replace it by something
757 // more safe
758 wxLog *oldlog = wxLog::SetActiveTarget(new wxLogStderr);
759 if ( oldlog )
760 delete oldlog;
761 #endif // wxUSE_LOG
762
763 wxApp::CleanUp();
764
765 return retValue;
766 }
767
768 #include "wx/gtk/info.xpm"
769 #include "wx/gtk/error.xpm"
770 #include "wx/gtk/question.xpm"
771 #include "wx/gtk/warning.xpm"
772
773 wxIcon
774 wxApp::GetStdIcon(int which) const
775 {
776 switch(which)
777 {
778 case wxICON_INFORMATION:
779 return wxIcon(info_xpm);
780
781 case wxICON_QUESTION:
782 return wxIcon(question_xpm);
783
784 case wxICON_EXCLAMATION:
785 return wxIcon(warning_xpm);
786
787 default:
788 wxFAIL_MSG(wxT("requested non existent standard icon"));
789 // still fall through
790
791 case wxICON_HAND:
792 return wxIcon(error_xpm);
793 }
794 }