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