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