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