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