1 /*---------------------------------------------------------------------\
3 | __ __ ____ _____ ____ |
4 | \ \ / /_ _/ ___|_ _|___ \ |
5 | \ V / _` \___ \ | | __) | |
6 | | | (_| |___) || | / __/ |
7 | |_|\__,_|____/ |_| |_____| |
11 \----------------------------------------------------------------------/
15 Author: Stefan Hundhammer <sh@suse.de>
20 #include <rpc/types.h> // MAXHOSTNAMELEN
25 #include <qmessagebox.h>
26 #include <Qt/qsocketnotifier.h>
27 #include <QStackedWidget>
28 #include <QDesktopWidget>
31 #include <ycp/YCPTerm.h>
32 #include <ycp/YCPCode.h>
34 #define y2log_component "qt-ui"
35 #include <ycp/y2log.h>
38 #include "YQApplication.h"
39 #include "YQWidgetFactory.h"
40 #include "YQOptionalWidgetFactory.h"
42 #include "YUISymbols.h"
47 #include "QY2Settings.h"
50 #define BUSY_CURSOR_TIMEOUT 200 // milliseconds
54 static void qMessageHandler( QtMsgType type, const char * msg );
56 YQUI::YQUI( int argc, char **argv, bool with_threads, const char * macro_file )
61 , _wm_close_blocked( false )
62 , _auto_activate_dialogs( true )
64 y2milestone( "YQUI constructor start" );
70 _decorate_toplevel_window = true;
71 _usingVisionImpairedPalette = false;
72 _leftHandedMouse = false;
73 _askedForLeftHandedMouse = false;
74 screenShotNameTemplate = "";
76 qInstallMsgHandler( qMessageHandler );
78 // Copy command line arguments for QApplication
80 _ui_argv = (char **) malloc( (argc+1) * sizeof( const char * ) );
83 for ( int i=0; i < argc; i++ )
84 _ui_argv[i+1] = strdup( argv[i] );
86 _ui_argv[0] = strdup( "YaST2" );
89 topmostConstructorHasFinished();
98 Qt thinks the first QObject defines the main thread, but
99 the main thread in this case is the ycp thread, not UI
101 //extern void qt_set_current_thread_to_main_thread();
102 //qt_set_current_thread_to_main_thread();
106 new QApplication( _ui_argc, _ui_argv);
107 _busy_cursor_timer = new QTimer( qApp );
108 _busy_cursor_timer->setSingleShot( true );
110 _user_input_timer.setSingleShot( true );
112 _normalPalette = qApp->palette();
114 // Qt keeps track to a global QApplication in qApp.
117 qApp->installEventFilter( this );
118 processCommandLineArgs( _ui_argc, _ui_argv );
121 // Event loop object. Required since a YaST2 UI needs to react to commands
122 // from the YCP command stream as well as to X11 / Qt events.
123 _eventLoop = new QEventLoop( qApp );
124 _do_exit_loop = false;
126 // Create main window for `opt(`defaultsize) dialogs.
128 // We have to use something else than QWidgetStack since QWidgetStack
129 // doesn't accept a WFlags arg which we badly need here.
131 Qt::WFlags wflags = Qt::Window;
133 if ( ! _decorate_toplevel_window )
135 y2debug( "Suppressing WM decorations for toplevel window" );
136 wflags |= Qt::FramelessWindowHint;
139 _main_win = new Q3VBox( 0, 0, wflags ); // parent, name, wflags
140 _main_win->setFocusPolicy( Qt::StrongFocus );
142 // Create widget stack for `opt(`defaultsize) dialogs
144 _widget_stack = new QStackedWidget( _main_win );
145 _widget_stack->setFocusPolicy( Qt::StrongFocus );
147 _main_win->installEventFilter( this );
150 _main_win->resize( _default_size );
152 if ( _fullscreen || ! _have_wm )
153 _main_win->move( 0, 0 );
158 QString title( "YaST2" );
159 char hostname[ MAXHOSTNAMELEN+1 ];
161 if ( gethostname( hostname, sizeof( hostname )-1 ) == 0 )
163 hostname[ sizeof( hostname ) -1 ] = '\0'; // make sure it's terminated
165 if ( strlen( hostname ) > 0 )
167 if ( ( strcmp( hostname, "(none)" ) != 0 &&
168 strcmp( hostname, "linux" ) != 0 ) )
176 _main_win->setCaption( title );
179 // Hide the main window for now. The first call to UI::OpenDialog() on an
180 // `opt(`defaultSize) dialog will trigger a showDialog() call that shows
181 // the main window - there is nothing to display yet.
186 // Ugly hack as a workaround of bug #121872 (Segfault at program exit
187 // if no Qt style defined):
189 // Qt does not seem to be designed for use in plugin libs. It loads some
190 // add-on libs dynamically with dlopen() and unloads them at program exit
191 // (QGPluginManager). Unfortunately, since they all depend on the Qt master
192 // lib (libqt-mt) themselves, when they are unloading the last call to
193 // dlclose() for them causes the last reference to libqt-mt to vanish as
194 // well. Since libqt-mt is already in the process of destruction there is
195 // no more reference from the caller of libqt-mt, and the GLIBC decides
196 // that libqt-mt is not needed any more (zero references) and unmaps
197 // libqt-mt. When the static destructor of libqt-mt that triggered the
198 // cleanup in QGPluginManager returns, the code it is to return to is
199 // already unmapped, causing a segfault.
201 // Workaround: Keep one more reference to libqt-mt open - dlopen() it here
202 // and make sure there is no corresponding dlclose().
204 QString qt_lib_name = QString( QTLIBDIR "/libQtGui.so.%1" ).arg( QT_VERSION >> 16 );;
205 void * qt_lib = dlopen( qt_lib_name.utf8().constData(), RTLD_GLOBAL );
206 y2milestone( "Forcing %s open %s", qt_lib_name.utf8().constData(),
207 qt_lib ? "successful" : "failed" );
211 qApp->setFont( yqApp()->currentFont() );
214 connect( & _user_input_timer, SIGNAL( timeout() ),
215 this, SLOT ( userInputTimeout() ) );
217 connect( _busy_cursor_timer, SIGNAL( timeout() ),
218 this, SLOT ( busyCursor() ) );
222 // playMacro( macro_file );
224 y2milestone( "YQUI constructor end %ld", QThread::currentThreadId () );
225 qApp->processEvents();
232 return static_cast<YQApplication *>( app() );
236 void YQUI::processCommandLineArgs( int argc, char **argv )
240 for( int i=0; i < argc; i++ )
242 QString opt = argv[i];
244 y2milestone ("Qt argument: %s", argv[i]);
246 // Normalize command line option - accept "--xy" as well as "-xy"
248 if ( opt.startsWith( "--" ) )
251 if ( opt == QString( "-no-wm" ) ) _have_wm = false;
252 else if ( opt == QString( "-fullscreen" ) ) _fullscreen = true;
253 else if ( opt == QString( "-noborder" ) ) _decorate_toplevel_window = false;
254 else if ( opt == QString( "-auto-font" ) ) yqApp()->setAutoFonts( true );
255 else if ( opt == QString( "-auto-fonts" ) ) yqApp()->setAutoFonts( true );
256 // --macro is handled by YUI_component
257 else if ( opt == QString( "-help" ) )
260 "Command line options for the YaST2 Qt UI:\n"
262 "--nothreads run without additional UI threads\n"
263 "--no-wm assume no window manager is running\n"
264 "--fullscreen use full screen for `opt(`defaultsize) dialogs\n"
265 "--noborder no window manager border for `opt(`defaultsize) dialogs\n"
266 "--auto-fonts automatically pick fonts, disregard Qt standard settings\n"
267 "--help this help text\n"
269 "--macro <macro-file> play a macro right on startup\n"
271 "-no-wm, -noborder etc. are accepted as well as --no-wm, --noborder\n"
272 "to maintain backwards compatibility.\n"
281 // Qt handles command line option "-reverse" for Arabic / Hebrew
288 y2debug("Closing down Qt UI.");
292 // Intentionally NOT calling dlclose() to libqt-mt
293 // (see constructor for explanation)
299 YQUI::createWidgetFactory()
301 YQWidgetFactory * factory = new YQWidgetFactory();
302 YUI_CHECK_NEW( factory );
309 YOptionalWidgetFactory *
310 YQUI::createOptionalWidgetFactory()
312 YQOptionalWidgetFactory * factory = new YQOptionalWidgetFactory();
313 YUI_CHECK_NEW( factory );
320 YQUI::createApplication()
322 YQApplication * app = new YQApplication();
323 YUI_CHECK_NEW( app );
329 void YQUI::calcDefaultSize()
331 QSize primaryScreenSize = qApp->desktop()->screenGeometry( qApp->desktop()->primaryScreen() ).size();
332 QSize availableSize = qApp->desktop()->availableGeometry().size();
336 _default_size = availableSize;
338 y2milestone( "-fullscreen: using %dx%d for `opt(`defaultsize)",
339 _default_size.width(), _default_size.height() );
343 // Get _default_size via -geometry command line option (if set)
345 // NOTE not needed in qt4
346 // QWidget * dummy = new QWidget();
348 // qApp->setMainWidget( dummy );
349 // _default_size = dummy->size();
352 // Set min defaultsize or figure one out if -geometry was not used
354 if ( _default_size.width() < 800 ||
355 _default_size.height() < 600 )
357 if ( primaryScreenSize.width() >= 1024 && primaryScreenSize.height() >= 768 )
359 // Scale down to 70% of screen size
361 _default_size.setWidth ( max( (int) (availableSize.width() * 0.7), 800 ) );
362 _default_size.setHeight( max( (int) (availableSize.height() * 0.7), 600 ) );
366 _default_size = availableSize;
371 y2milestone( "Forced size (via -geometry): %dx%d",
372 _default_size.width(), _default_size.height() );
377 _default_size = primaryScreenSize;
381 y2milestone( "Default size: %dx%d", _default_size.width(), _default_size.height() );
386 void YQUI::internalError( const char * msg )
389 int button = QMessageBox::critical( 0, "YaST2 Internal Error", msg,
390 QMessageBox::Abort | QMessageBox::Default,
394 if ( button == QMessageBox::Abort )
399 // exit() leaves a process running (WFM?), so this really seems to be
400 // the only way to make sure we are really going down.
404 void YQUI::idleLoop( int fd_ycp )
408 // runs in main thread
409 _eventLoop->wakeUp();
414 FD_SET(fd_ycp, &rfds);
418 int retval = select(fd_ycp+1, &rfds, NULL, NULL, NULL);
425 void YQUI::sendEvent( YEvent * event )
429 _event_handler.sendEvent( event );
432 _eventLoop->exit( 1 );
437 YEvent * YQUI::userInput( unsigned long timeout_millisec )
441 // y2milestone( "userInput %ld", timeout_millisec );
444 YQDialog * dialog = dynamic_cast<YQDialog *> ( YDialog::currentDialog( false ) );
446 if ( _user_input_timer.isActive() )
447 _user_input_timer.stop();
451 if ( timeout_millisec > 0 )
452 _user_input_timer.start( timeout_millisec ); // single shot
454 dialog->activate( true );
456 if ( qApp->focusWidget() )
457 qApp->focusWidget()->setFocus();
460 _do_exit_loop = true; // should exit_loop() be called in sendEvent()?
462 while ( ! pendingEvent() )
464 _eventLoop->processEvents(QEventLoop::WaitForMoreEvents);
467 _do_exit_loop = false;
469 event = _event_handler.consumePendingEvent();
470 dialog->activate( false );
472 // Display a busy cursor, but only if there is no other activity within
473 // BUSY_CURSOR_TIMEOUT milliseconds (avoid cursor flicker)
475 _busy_cursor_timer->start( BUSY_CURSOR_TIMEOUT ); // single shot
478 if ( _user_input_timer.isActive() )
479 _user_input_timer.stop();
485 YEvent * YQUI::pollInput()
489 if ( _user_input_timer.isActive() )
490 _user_input_timer.stop();
492 if ( ! pendingEvent() )
494 YQDialog * dialog = dynamic_cast<YQDialog *> ( YDialog::currentDialog( false ) );
498 dialog->activate( true );
499 //qApp->processEvents();
500 event = _event_handler.consumePendingEvent();
501 dialog->activate( false );
505 if ( pendingEvent() )
506 event = _event_handler.consumePendingEvent();
512 void YQUI::userInputTimeout()
514 if ( ! pendingEvent() )
515 sendEvent( new YTimeoutEvent() );
519 YDialog * YQUI::createDialog( YWidgetOpt & opt )
523 y2milestone( "createDialog %ld", QThread::currentThreadId () );
524 bool has_defaultsize = opt.hasDefaultSize.value();
525 QWidget * qt_parent =
526 has_defaultsize ? _widget_stack : _main_win;
528 // FIXME: Probably obsolete
529 if ( ! has_defaultsize && ! _popup_stack.empty() )
530 qt_parent = _popup_stack.back();
532 YQDialog * dialog = new YQDialog( opt, qt_parent, has_defaultsize );
533 Q_CHECK_PTR( dialog );
535 if ( ! has_defaultsize )
536 _popup_stack.push_back( (QWidget *) dialog->widgetRep() );
542 void YQUI::showDialog( YDialog * dialog )
544 QWidget * qw = (QWidget *) dialog->widgetRep();
548 y2error( "No widgetRep() for dialog" );
552 if ( dialog->hasDefaultSize() )
554 _widget_stack->addWidget ( qw );
555 _widget_stack->setCurrentWidget( qw );
557 if ( ! _main_win->isVisible() )
559 // y2milestone( "Showing main window" );
560 _main_win->resize( _default_size );
563 _main_win->move( 0, 0 );
569 else // non-defaultsize dialog
574 ( (YQDialog *) dialog)->ensureOnlyOneDefaultButton();
576 //qApp->processEvents();
580 void YQUI::closeDialog( YDialog * dialog )
582 QWidget * qw = (QWidget *) dialog->widgetRep();
586 y2error( "No widgetRep() for dialog" );
590 if ( dialog->hasDefaultSize() )
592 _widget_stack->removeWidget( qw );
594 else // non-defaultsize dialog
598 // Clean up the popup stack. libyui guarantees that a dialog will be
599 // deleted after closeDialog() so it is safe to pop that dialog from
600 // the popup stack here.
602 if ( ! _popup_stack.empty() && _popup_stack.back() == qw )
603 _popup_stack.pop_back();
605 y2error( "Popup dialog stack corrupted!" );
610 void YQUI::easterEgg()
612 y2milestone( "Starting easter egg..." );
615 YQEasterBunny::layEgg();
616 y2milestone( "Done." );
619 // desktop()->repaint() has no effect - we need to do it the hard way.
620 system( "/usr/X11R6/bin/xrefresh" );
625 QString YQUI::productName() const
627 return fromUTF8( YUI::productName() );
632 YQUI::setTextdomain( const char * domain )
634 bindtextdomain( domain, LOCALEDIR );
635 bind_textdomain_codeset( domain, "utf8" );
636 textdomain( domain );
638 // Make change known.
640 extern int _nl_msg_cat_cntr;
648 qMessageHandler( QtMsgType type, const char * msg )
653 y2milestone ("qt-debug: %s\n", msg);
656 y2warning ("qt-warning: %s\n", msg);
660 y2warning ("qt-critical: %s\n", msg);
663 y2internal ("qt-fatal: %s\n", msg);
664 exit (1); // qt does the same