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