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