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