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