]> icculus.org git repositories - duncan/yast2-qt4.git/blob - src/YQUI_core.cc
restart qt4 porting
[duncan/yast2-qt4.git] / src / YQUI_core.cc
1 /*---------------------------------------------------------------------\
2 |                                                                      |
3 |                      __   __    ____ _____ ____                      |
4 |                      \ \ / /_ _/ ___|_   _|___ \                     |
5 |                       \ V / _` \___ \ | |   __) |                    |
6 |                        | | (_| |___) || |  / __/                     |
7 |                        |_|\__,_|____/ |_| |_____|                    |
8 |                                                                      |
9 |                               core system                            |
10 |                                                        (C) SuSE GmbH |
11 \----------------------------------------------------------------------/
12
13   File:         YQUI_core.cc
14
15   Author:       Stefan Hundhammer <sh@suse.de>
16
17 /-*/
18
19 #include <rpc/types.h>          // MAXHOSTNAMELEN
20 #include <dlfcn.h>
21 #include <libintl.h>
22
23 #include <qcursor.h>
24 #include <qmessagebox.h>
25 #include <qsocketnotifier.h>
26 #include <qvbox.h>
27 #include <qwidgetstack.h>
28
29 #include <ycp/YCPTerm.h>
30 #include <ycp/YCPCode.h>
31
32 #define y2log_component "qt-ui"
33 #include <ycp/y2log.h>
34
35 #include "YQUI.h"
36 #include "YQApplication.h"
37 #include "YQWidgetFactory.h"
38 #include "YQOptionalWidgetFactory.h"
39 #include "YEvent.h"
40 #include "YUISymbols.h"
41 #include "YQEBunny.h"
42 #include "utf8.h"
43
44 #include "YQDialog.h"
45 #include "QY2Settings.h"
46
47
48 #define BUSY_CURSOR_TIMEOUT     200     // milliseconds
49 #define KYAST_EMBEDDING         0
50
51
52 YQUI * YQUI::_ui = 0;
53
54
55 static void qMessageHandler( QtMsgType type, const char * msg );
56
57 YQUI::YQUI( int argc, char **argv, bool with_threads, const char * macro_file )
58     : QObject()
59     , YUI( with_threads )
60     , _main_win( NULL )
61     , _main_dialog_id(0)
62     , _do_exit_loop( false )
63     , _wm_close_blocked( false )
64     , _auto_activate_dialogs( true )
65 {
66     y2debug( "YQUI constructor start" );
67
68     _ui                         = this;
69     _fatal_error                = false;
70     _have_wm                    = true;
71     _fullscreen                 = false;
72     _decorate_toplevel_window   = true;
73     _usingVisionImpairedPalette = false;
74     _leftHandedMouse            = false;
75     _askedForLeftHandedMouse    = false;
76     screenShotNameTemplate      = "";
77
78     qInstallMsgHandler( qMessageHandler );
79
80     y2debug( "Creating QApplication" );
81     new QApplication( argc, argv );
82
83     _normalPalette = qApp->palette();
84
85     // Qt keeps track to a global QApplication in qApp.
86     CHECK_PTR( qApp );
87
88     qApp->installEventFilter( this );
89     processCommandLineArgs( argc, argv );
90     calcDefaultSize();
91
92
93     // Create main window for `opt(`defaultsize) dialogs.
94     //
95     // We have to use something else than QWidgetStack since QWidgetStack
96     // doesn't accept a WFlags arg which we badly need here.
97
98     WFlags wflags = WType_TopLevel;
99
100     if ( ! _decorate_toplevel_window )
101     {
102         y2debug( "Suppressing WM decorations for toplevel window" );
103         wflags |= WStyle_Customize | WStyle_NoBorder;
104     }
105
106     // if we have a window already, delete it
107     if (_main_win)
108         delete _main_win;
109
110     _main_win = new QVBox( 0, 0, wflags ); // parent, name, wflags
111
112
113     // Create widget stack for `opt(`defaultsize) dialogs
114
115     _widget_stack = new QWidgetStack( _main_win );
116     _widget_stack->setFocusPolicy( QWidget::StrongFocus );
117     qApp->setMainWidget( _main_win );
118     _main_win->installEventFilter( this );
119     _main_win->resize( _default_size );
120
121     if ( _fullscreen || ! _have_wm )
122         _main_win->move( 0, 0 );
123
124
125     // Set window title
126
127     QString title( "YaST2" );
128     char hostname[ MAXHOSTNAMELEN+1 ];
129
130     if ( gethostname( hostname, sizeof( hostname )-1 ) == 0 )
131     {
132         hostname[ sizeof( hostname ) -1 ] = '\0'; // make sure it's terminated
133
134         if ( strlen( hostname ) > 0 )
135         {
136             if ( ( strcmp( hostname, "(none)" ) != 0 &&
137                    strcmp( hostname, "linux"  ) != 0 ) )
138             {
139                 title += "@";
140                 title += hostname;
141             }
142         }
143     }
144
145     _main_win->setCaption( title );
146
147
148     // Hide the main window for now. The first call to UI::OpenDialog() on an
149     // `opt(`defaultSize) dialog will trigger a showDialog() call that shows
150     // the main window - there is nothing to display yet.
151
152     _main_win->hide();
153
154
155     // Ugly hack as a workaround of bug #121872 (Segfault at program exit
156     // if no Qt style defined):
157     //
158     // Qt does not seem to be designed for use in plugin libs. It loads some
159     // add-on libs dynamically with dlopen() and unloads them at program exit
160     // (QGPluginManager). Unfortunately, since they all depend on the Qt master
161     // lib (libqt-mt) themselves, when they are unloading the last call to
162     // dlclose() for them causes the last reference to libqt-mt to vanish as
163     // well. Since libqt-mt is already in the process of destruction there is
164     // no more reference from the caller of libqt-mt, and the GLIBC decides
165     // that libqt-mt is not needed any more (zero references) and unmaps
166     // libqt-mt. When the static destructor of libqt-mt that triggered the
167     // cleanup in QGPluginManager returns, the code it is to return to is
168     // already unmapped, causing a segfault.
169     //
170     // Workaround: Keep one more reference to libqt-mt open - dlopen() it here
171     // and make sure there is no corresponding dlclose().
172
173     QString qt_lib_name = QString( QTLIBDIR "/libqt-mt.so.%1" ).arg( QT_VERSION >> 16 );;
174     void * qt_lib = dlopen( (const char *) qt_lib_name, RTLD_GLOBAL );
175     y2milestone( "Forcing %s open %s", (const char *) qt_lib_name,
176                  qt_lib ? "successful" : "failed" );
177
178
179     //  Init other stuff
180
181     qApp->setFont( yqApp()->currentFont() );
182     busyCursor();
183
184     connect( & _user_input_timer,       SIGNAL( timeout()          ),
185              this,                      SLOT  ( userInputTimeout() ) );
186
187     connect( & _busy_cursor_timer,      SIGNAL( timeout()       ),
188              this,                      SLOT  ( busyCursor()    ) );
189
190     if ( macro_file )
191         playMacro( macro_file );
192
193
194     topmostConstructorHasFinished();
195
196     y2debug( "YQUI constructor end" );
197 }
198
199
200 YQApplication *
201 YQUI::yqApp()
202 {
203     return static_cast<YQApplication *>( app() );
204 }
205
206
207 void YQUI::processCommandLineArgs( int argc, char **argv )
208 {
209     if ( argv )
210     {
211         for( int i=0; i < argc; i++ )
212         {
213             QString opt = argv[i];
214
215             y2milestone ("Qt argument: %s", argv[i]);
216
217             // Normalize command line option - accept "--xy" as well as "-xy"
218
219             if ( opt.startsWith( "--" ) )
220                 opt.remove(0, 1);
221
222             if      ( opt == QString( "-no-wm"          ) )     _have_wm                        = false;
223             else if ( opt == QString( "-fullscreen"     ) )     _fullscreen                     = true;
224             else if ( opt == QString( "-noborder"       ) )     _decorate_toplevel_window       = false;
225             else if ( opt == QString( "-auto-font"      ) )     yqApp()->setAutoFonts( true );
226             else if ( opt == QString( "-auto-fonts"     ) )     yqApp()->setAutoFonts( true );
227             // --macro is handled by YUI_component
228             else if ( opt == QString( "-help"  ) )
229             {
230                 fprintf( stderr,
231                          "Command line options for the YaST2 Qt UI:\n"
232                          "\n"
233                          "--nothreads   run without additional UI threads\n"
234                          "--no-wm       assume no window manager is running\n"
235                          "--fullscreen  use full screen for `opt(`defaultsize) dialogs\n"
236                          "--noborder    no window manager border for `opt(`defaultsize) dialogs\n"
237                          "--auto-fonts  automatically pick fonts, disregard Qt standard settings\n"
238                          "--help        this help text\n"
239                          "\n"
240                          "--macro <macro-file>        play a macro right on startup\n"
241                          "\n"
242                          "-no-wm, -noborder etc. are accepted as well as --no-wm, --noborder\n"
243                          "to maintain backwards compatibility.\n"
244                          "\n"
245                          );
246
247                 raiseFatalError();
248             }
249         }
250     }
251
252     // Qt handles command line option "-reverse" for Arabic / Hebrew
253 }
254
255
256
257 YQUI::~YQUI()
258 {
259     y2debug("Closing down Qt UI.");
260
261     normalCursor();
262
263     // Intentionally NOT calling dlclose() to libqt-mt
264     // (see constructor for explanation)
265 }
266
267
268
269 YWidgetFactory *
270 YQUI::createWidgetFactory()
271 {
272     YQWidgetFactory * factory = new YQWidgetFactory();
273     YUI_CHECK_NEW( factory );
274
275     return factory;
276 }
277
278
279
280 YOptionalWidgetFactory *
281 YQUI::createOptionalWidgetFactory()
282 {
283     YQOptionalWidgetFactory * factory = new YQOptionalWidgetFactory();
284     YUI_CHECK_NEW( factory );
285
286     return factory;
287 }
288
289
290 YApplication *
291 YQUI::createApplication()
292 {
293     YQApplication * app = new YQApplication();
294     YUI_CHECK_NEW( app );
295
296     return app;
297 }
298
299
300 void YQUI::calcDefaultSize()
301 {
302     QSize primaryScreenSize     = qApp->desktop()->screenGeometry( qApp->desktop()->primaryScreen() ).size();
303     QSize availableSize         = qApp->desktop()->availableGeometry().size();
304
305     if ( _fullscreen )
306     {
307         _default_size = availableSize;
308
309         y2milestone( "-fullscreen: using %dx%d for `opt(`defaultsize)",
310                      _default_size.width(), _default_size.height() );
311     }
312     else if ( _have_wm )
313     {
314         // Get _default_size via -geometry command line option (if set)
315
316         QWidget * dummy = new QWidget();
317         dummy->hide();
318         qApp->setMainWidget( dummy );
319         _default_size = dummy->size();
320
321
322         // Set min defaultsize or figure one out if -geometry was not used
323
324         if ( _default_size.width()  < 800 ||
325              _default_size.height() < 600   )
326         {
327             if ( primaryScreenSize.width() >= 1024 && primaryScreenSize.height() >= 768  )
328             {
329                 // Scale down to 70% of screen size
330
331                 _default_size.setWidth ( max( (int) (availableSize.width()  * 0.7), 800 ) );
332                 _default_size.setHeight( max( (int) (availableSize.height() * 0.7), 600 ) );
333             }
334             else
335             {
336                 _default_size = availableSize;
337             }
338         }
339         else
340         {
341             y2milestone( "Forced size (via -geometry): %dx%d",
342                          _default_size.width(), _default_size.height() );
343         }
344     }
345     else        // ! _have_wm
346     {
347         _default_size = primaryScreenSize;
348     }
349
350
351     y2milestone( "Default size: %dx%d", _default_size.width(), _default_size.height() );
352 }
353
354
355
356 void YQUI::internalError( const char * msg )
357 {
358     normalCursor();
359     int button = QMessageBox::critical( 0, "YaST2 Internal Error", msg,
360                                         QMessageBox::Abort | QMessageBox::Default,
361                                         0 ); // button1
362     busyCursor();
363
364     if ( button == QMessageBox::Abort )
365     {
366         raiseFatalError();
367         abort();
368
369         // exit() leaves a process running (WFM?), so this really seems to be
370         // the only way to make sure we are really going down.
371     }
372 }
373
374
375 void YQUI::idleLoop( int fd_ycp )
376 {
377     _leave_idle_loop = false;
378
379     // process Qt events until fd_ycp is readable.
380     QSocketNotifier * notifier = new QSocketNotifier( fd_ycp, QSocketNotifier::Read );
381     QObject::connect( notifier, SIGNAL( activated    ( int ) ),
382                       this,     SLOT  ( leaveIdleLoop( int ) ) );
383
384     notifier->setEnabled( true );
385
386     while ( !_leave_idle_loop )
387         qApp->processOneEvent();
388
389     delete notifier;
390 }
391
392
393 void YQUI::leaveIdleLoop( int )
394 {
395     _leave_idle_loop = true;
396 }
397
398
399 void YQUI::sendEvent( YEvent * event )
400 {
401     if ( event )
402     {
403         _event_handler.sendEvent( event );
404
405         if ( _do_exit_loop )
406             qApp->exit_loop();
407     }
408 }
409
410
411 YEvent * YQUI::userInput( unsigned long timeout_millisec )
412 {
413     YEvent *    event  = 0;
414     YQDialog *  dialog = dynamic_cast<YQDialog *> ( YDialog::currentDialog( false ) );
415
416     if ( _user_input_timer.isActive() )
417         _user_input_timer.stop();
418
419     if ( dialog )
420     {
421         if ( timeout_millisec > 0 )
422             _user_input_timer.start( timeout_millisec, true ); // single shot
423
424         dialog->activate( true );
425
426         if ( qApp->focusWidget() )
427             qApp->focusWidget()->setFocus();
428
429         normalCursor();
430         _do_exit_loop = true; // should exit_loop() be called in sendEvent()?
431
432         while ( ! pendingEvent() )
433         {
434             qApp->enter_loop();
435         }
436
437         _do_exit_loop = false;
438         event = _event_handler.consumePendingEvent();
439         dialog->activate( false );
440
441         // Display a busy cursor, but only if there is no other activity within
442         // BUSY_CURSOR_TIMEOUT milliseconds (avoid cursor flicker)
443
444         _busy_cursor_timer.start( BUSY_CURSOR_TIMEOUT, true ); // single shot
445     }
446
447     if ( _user_input_timer.isActive() )
448         _user_input_timer.stop();
449
450     return event;
451 }
452
453
454 YEvent * YQUI::pollInput()
455 {
456     YEvent * event = 0;
457
458     if ( _user_input_timer.isActive() )
459         _user_input_timer.stop();
460
461     if ( ! pendingEvent() )
462     {
463         YQDialog * dialog = dynamic_cast<YQDialog *> ( YDialog::currentDialog( false ) );
464
465         if ( dialog )
466         {
467             dialog->activate( true );
468             qApp->processEvents();
469             event = _event_handler.consumePendingEvent();
470             dialog->activate( false );
471         }
472     }
473
474     if ( pendingEvent() )
475         event = _event_handler.consumePendingEvent();
476
477     return event;
478 }
479
480
481 void YQUI::userInputTimeout()
482 {
483     if ( ! pendingEvent() )
484         sendEvent( new YTimeoutEvent() );
485 }
486
487
488 YDialog * YQUI::createDialog( YWidgetOpt & opt )
489 {
490     bool has_defaultsize = opt.hasDefaultSize.value();
491     QWidget * qt_parent = _main_win;
492
493     // Popup dialogs get the topmost other popup dialog as their parent since
494     // some window managers (e.g., fvwm2 as used in the inst-sys) otherwise
495     // tend to confuse the stacking order of popup dialogs.
496     //
497     // This _popup_stack handling would be better placed in showDialog(), but we
498     // need the parent here for QWidget creation. libyui guarantees that each
499     // createDialog() will be followed by showDialog() for the same dialog
500     // without any chance for other dialogs to get in between.
501
502     if ( ! has_defaultsize && ! _popup_stack.empty() )
503         qt_parent = _popup_stack.back();
504
505     YQDialog * dialog = new YQDialog( opt, qt_parent, has_defaultsize );
506     CHECK_PTR( dialog );
507
508     if ( ! has_defaultsize )
509         _popup_stack.push_back( (QWidget *) dialog->widgetRep() );
510
511     return dialog;
512 }
513
514
515 void YQUI::showDialog( YDialog * dialog )
516 {
517     QWidget * qw = (QWidget *) dialog->widgetRep();
518
519     if ( ! qw )
520     {
521         y2error( "No widgetRep() for dialog" );
522         return;
523     }
524
525     if ( dialog->hasDefaultSize() )
526     {
527         _widget_stack->addWidget  ( qw, ++_main_dialog_id );
528         _widget_stack->raiseWidget( qw ); // maybe this is not necessary (?)
529
530         if ( ! _main_win->isVisible() )
531         {
532             // y2milestone( "Showing main window" );
533             _main_win->resize( _default_size );
534
535             if ( ! _have_wm )
536                 _main_win->move( 0, 0 );
537
538             _main_win->show();
539             qw->setFocus();
540         }
541     }
542     else        // non-defaultsize dialog
543     {
544         qw->show();
545     }
546
547     ( (YQDialog *) dialog)->ensureOnlyOneDefaultButton();
548     qApp->processEvents();
549 }
550
551
552 void YQUI::closeDialog( YDialog * dialog )
553 {
554     QWidget * qw = (QWidget *) dialog->widgetRep();
555
556     if ( ! qw )
557     {
558         y2error( "No widgetRep() for dialog" );
559         return;
560     }
561
562     if ( dialog->hasDefaultSize() )
563     {
564         _widget_stack->removeWidget( qw );
565
566         if ( --_main_dialog_id < 1 )    // nothing left on the stack
567         {
568             // y2milestone( "Hiding main window" );
569             _main_win->hide();
570             _main_dialog_id = 0;        // this should not be necessary - but better be safe than sorry
571         }
572         else
573         {
574             // FIXME: This is anti-social behaviour - do this only in the inst-sys
575             _widget_stack->raiseWidget( _main_dialog_id );
576         }
577     }
578     else        // non-defaultsize dialog
579     {
580         qw->hide();
581
582         // Clean up the popup stack. libyui guarantees that a dialog will be
583         // deleted after closeDialog() so it is safe to pop that dialog from
584         // the popup stack here.
585
586         if ( ! _popup_stack.empty() && _popup_stack.back() == qw )
587             _popup_stack.pop_back();
588         else
589             y2error( "Popup dialog stack corrupted!" );
590     }
591 }
592
593
594 void YQUI::easterEgg()
595 {
596     y2milestone( "Starting easter egg..." );
597
598
599     YQEasterBunny::layEgg();
600     y2milestone( "Done." );
601
602 #if 0
603     // desktop()->repaint() has no effect - we need to do it the hard way.
604     system( "/usr/X11R6/bin/xrefresh" );
605 #endif
606 }
607
608
609 QString YQUI::productName() const
610 {
611     return fromUTF8( YUI::productName() );
612 }
613
614
615 void
616 YQUI::setTextdomain( const char * domain )
617 {
618     bindtextdomain( domain, LOCALEDIR );
619     bind_textdomain_codeset( domain, "utf8" );
620     textdomain( domain );
621
622     // Make change known.
623     {
624         extern int _nl_msg_cat_cntr;
625         ++_nl_msg_cat_cntr;
626     }
627 }
628
629
630
631 static void
632 qMessageHandler( QtMsgType type, const char * msg )
633 {
634     switch (type)
635     {
636         case QtDebugMsg:
637             y2debug ("qt-debug: %s\n", msg);
638             break;
639         case QtWarningMsg:
640             y2warning ("qt-warning: %s\n", msg);
641             break;
642         case QtFatalMsg:
643             y2internal ("qt-fatal: %s\n", msg);
644             exit (1);           // qt does the same
645     }
646 }
647
648
649
650 #include "YQUI.moc"