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