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