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