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