libyui-qt  2.43.3
 All Classes Functions Variables
YQUI.cc
1 /*
2  Copyright (C) 2000-2012 Novell, Inc
3  This library is free software; you can redistribute it and/or modify
4  it under the terms of the GNU Lesser General Public License as
5  published by the Free Software Foundation; either version 2.1 of the
6  License, or (at your option) version 3.0 of the License. This library
7  is distributed in the hope that it will be useful, but WITHOUT ANY
8  WARRANTY; without even the implied warranty of MERCHANTABILITY or
9  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
10  License for more details. You should have received a copy of the GNU
11  Lesser General Public License along with this library; if not, write
12  to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
13  Floor, Boston, MA 02110-1301 USA
14 */
15 
16 
17 /*-/
18 
19  File: YQUI.cc
20 
21  Author: Stefan Hundhammer <sh@suse.de>
22 
23 /-*/
24 
25 #include <rpc/types.h> // MAXHOSTNAMELEN
26 #include <dlfcn.h>
27 #include <libintl.h>
28 #include <algorithm>
29 #include <stdio.h>
30 
31 #include <QWidget>
32 #include <QThread>
33 #include <QSocketNotifier>
34 #include <QDesktopWidget>
35 #include <QEvent>
36 #include <QCursor>
37 #include <QLocale>
38 
39 
40 #define YUILogComponent "qt-ui"
41 #include <yui/YUILog.h>
42 #include <yui/Libyui_config.h>
43 
44 #include "YQUI.h"
45 
46 #include <yui/YEvent.h>
47 #include <yui/YCommandLine.h>
48 #include <yui/YButtonBox.h>
49 #include <yui/YUISymbols.h>
50 
51 #include "QY2Styler.h"
52 #include "YQApplication.h"
53 #include "YQDialog.h"
54 #include "YQWidgetFactory.h"
55 #include "YQOptionalWidgetFactory.h"
56 
57 #include "YQi18n.h"
58 #include "utf8.h"
59 
60 // Include low-level X headers AFTER Qt headers:
61 // X.h pollutes the global namespace (!!!) with pretty useless #defines
62 // like "Above", "Below" etc. that clash with some Qt headers.
63 #include <X11/Xlib.h>
64 
65 
66 using std::max;
67 
68 #define BUSY_CURSOR_TIMEOUT 200 // milliseconds
69 #define VERBOSE_EVENT_LOOP 0
70 
71 
72 
73 static void qMessageHandler( QtMsgType type, const char * msg );
74 YQUI * YQUI::_ui = 0;
75 
76 
77 YUI * createUI( bool withThreads )
78 {
79  if ( ! YQUI::ui() )
80  {
81  YQUI * ui = new YQUI( withThreads );
82 
83  if ( ui && ! withThreads )
84  ui->initUI();
85  }
86 
87  return YQUI::ui();
88 }
89 
90 
91 YQUI::YQUI( bool withThreads )
92  : YUI( withThreads )
93 #if 0
94  , _main_win( NULL )
95 #endif
96  , _do_exit_loop( false )
97 {
98  yuiDebug() << "YQUI constructor start" << std::endl;
99  yuiMilestone() << "This is libyui-qt " << VERSION << std::endl;
100 
101  _ui = this;
102  _uiInitialized = false;
103  _fatalError = false;
104  _fullscreen = false;
105  _usingVisionImpairedPalette = false;
106  _noborder = false;
107  screenShotNameTemplate = "";
108  _blockedLevel = 0;
109 
110  qInstallMsgHandler( qMessageHandler );
111 
112  yuiDebug() << "YQUI constructor finished" << std::endl;
113 
114  topmostConstructorHasFinished();
115 }
116 
117 
119 {
120  if ( _uiInitialized )
121  return;
122 
123  _uiInitialized = true;
124  yuiDebug() << "Initializing Qt part" << std::endl;
125 
126  YCommandLine cmdLine; // Retrieve command line args from /proc/<pid>/cmdline
127  std::string progName;
128 
129  if ( cmdLine.argc() > 0 )
130  {
131  progName = cmdLine[0];
132  std::size_t lastSlashPos = progName.find_last_of( '/' );
133 
134  if ( lastSlashPos != std::string::npos )
135  progName = progName.substr( lastSlashPos+1 );
136 
137  // Qt will display argv[0] as the window manager title.
138  // For YaST2, display "YaST2" instead of "y2base".
139  // For other applications, leave argv[0] alone.
140 
141  if ( progName == "y2base" )
142  cmdLine.replace( 0, "YaST2" );
143  }
144 
145  _ui_argc = cmdLine.argc();
146  char ** argv = cmdLine.argv();
147 
148  // Probe X11 display for better error handling if it can't be opened
149  probeX11Display( cmdLine );
150 
151  yuiDebug() << "Creating QApplication" << std::endl;
152  new QApplication( _ui_argc, argv );
153  Q_CHECK_PTR( qApp );
154  // Qt keeps track to a global QApplication in qApp.
155 
156  _signalReceiver = new YQUISignalReceiver();
157  _busyCursorTimer = new QTimer( _signalReceiver );
158  _busyCursorTimer->setSingleShot( true );
159 
160  _normalPalette = qApp->palette();
161  (void) QY2Styler::styler(); // Make sure QY2Styler singleton is created
162 
163  setButtonOrderFromEnvironment();
164  processCommandLineArgs( _ui_argc, argv );
165  calcDefaultSize();
166 
167  _do_exit_loop = false;
168 
169 #if 0
170  // Create main window for `opt(`defaultsize) dialogs.
171  //
172  // We have to use something else than QWidgetStack since QWidgetStack
173  // doesn't accept a WFlags arg which we badly need here.
174 
175  _main_win = new QWidget( 0, Qt::Window ); // parent, wflags
176  _main_win->setFocusPolicy( Qt::StrongFocus );
177  _main_win->setObjectName( "main_window" );
178 
179  _main_win->resize( _defaultSize );
180 
181  if ( _fullscreen )
182  _main_win->move( 0, 0 );
183 #endif
184 
185 
186  //
187  // Set application title (used by YQDialog and YQWizard)
188  //
189 
190  // for YaST2, display "YaST2" instead of "y2base"
191  if ( progName == "y2base" )
192  _applicationTitle = QString( "YaST2" );
193  else
194  _applicationTitle = fromUTF8( progName );
195 
196  // read x11 display from commandline or environment variable
197  int displayArgPos = cmdLine.find( "-display" );
198  QString displayName;
199 
200  if ( displayArgPos > 0 && displayArgPos+1 < cmdLine.argc() )
201  displayName = cmdLine[ displayArgPos+1 ].c_str();
202  else
203  displayName = getenv( "DISPLAY" );
204 
205  // identify hostname
206  char hostname[ MAXHOSTNAMELEN+1 ];
207  if ( gethostname( hostname, sizeof( hostname )-1 ) == 0 )
208  hostname[ sizeof( hostname ) -1 ] = '\0'; // make sure it's terminated
209  else
210  hostname[0] = '\0';
211 
212  // add hostname to the window title if it's not a local display
213  if ( !displayName.startsWith( ":" ) && strlen( hostname ) > 0 )
214  {
215  _applicationTitle += QString( "@" );
216  _applicationTitle += fromUTF8( hostname );
217  }
218 
219 
220 #if 0
221  // Hide the main window for now. The first call to UI::OpenDialog() on an
222  // `opt(`defaultSize) dialog will trigger a dialog->open() call that shows
223  // the main window - there is nothing to display yet.
224 
225  _main_win->hide();
226 #endif
227 
228  YButtonBoxMargins buttonBoxMargins;
229  buttonBoxMargins.left = 8;
230  buttonBoxMargins.right = 8;
231  buttonBoxMargins.top = 6;
232  buttonBoxMargins.bottom = 6;
233 
234  buttonBoxMargins.spacing = 4;
235  buttonBoxMargins.helpButtonExtraSpacing = 16;
236  YButtonBox::setDefaultMargins( buttonBoxMargins );
237 
238 
239 
240  // Ugly hack as a workaround of bug #121872 (Segfault at program exit
241  // if no Qt style defined):
242  //
243  // Qt does not seem to be designed for use in plugin libs. It loads some
244  // add-on libs dynamically with dlopen() and unloads them at program exit
245  // (QGPluginManager). Unfortunately, since they all depend on the Qt master
246  // lib (libqt-mt) themselves, when they are unloading the last call to
247  // dlclose() for them causes the last reference to libqt-mt to vanish as
248  // well. Since libqt-mt is already in the process of destruction there is
249  // no more reference from the caller of libqt-mt, and the GLIBC decides
250  // that libqt-mt is not needed any more (zero references) and unmaps
251  // libqt-mt. When the static destructor of libqt-mt that triggered the
252  // cleanup in QGPluginManager returns, the code it is to return to is
253  // already unmapped, causing a segfault.
254  //
255  // Workaround: Keep one more reference to libqt-mt open - dlopen() it here
256  // and make sure there is no corresponding dlclose().
257 
258  QString qt_lib_name = QString( QTLIBDIR "/libQtGui.so.%1" ).arg( QT_VERSION >> 16 );;
259  void * qt_lib = dlopen( qt_lib_name.toUtf8().constData(), RTLD_LAZY | RTLD_GLOBAL );
260  if (qt_lib)
261  yuiMilestone() << "Forcing " << qt_lib_name.toUtf8().constData() << " open successful" << std::endl;
262  else
263  yuiError() << "Forcing " << qt_lib_name.toUtf8().constData() << " open failed" << std::endl;
264 
265  // Init other stuff
266 
267  qApp->setFont( yqApp()->currentFont() );
268  busyCursor();
269 
270 
271  QObject::connect( _busyCursorTimer, SIGNAL( timeout() ),
272  _signalReceiver, SLOT ( slotBusyCursor() ) );
273 
274  yuiMilestone() << "YQUI initialized. Thread ID: 0x"
275  << hex << QThread::currentThreadId () << dec
276  << std::endl;
277 
278  qApp->processEvents();
279 }
280 
281 
284 {
285  return static_cast<YQApplication *>( app() );
286 }
287 
288 
289 void YQUI::processCommandLineArgs( int argc, char **argv )
290 {
291  if ( argv )
292  {
293  for( int i=0; i < argc; i++ )
294  {
295  QString opt = argv[i];
296 
297  yuiMilestone() << "Qt argument: " << argv[i] << std::endl;
298 
299  // Normalize command line option - accept "--xy" as well as "-xy"
300 
301  if ( opt.startsWith( "--" ) )
302  opt.remove(0, 1);
303 
304  if ( opt == QString( "-fullscreen" ) ) _fullscreen = true;
305  else if ( opt == QString( "-noborder" ) ) _noborder = true;
306  else if ( opt == QString( "-auto-font" ) ) yqApp()->setAutoFonts( true );
307  else if ( opt == QString( "-auto-fonts" ) ) yqApp()->setAutoFonts( true );
308  else if ( opt == QString( "-gnome-button-order" ) ) YButtonBox::setLayoutPolicy( YButtonBox::gnomeLayoutPolicy() );
309  else if ( opt == QString( "-kde-button-order" ) ) YButtonBox::setLayoutPolicy( YButtonBox::kdeLayoutPolicy() );
310  // --macro is handled by YUI_component
311  else if ( opt == QString( "-help" ) )
312  {
313  fprintf( stderr,
314  "Command line options for the YaST2 Qt UI:\n"
315  "\n"
316  "--nothreads run without additional UI threads\n"
317  "--fullscreen use full screen for `opt(`defaultsize) dialogs\n"
318  "--noborder no window manager border for `opt(`defaultsize) dialogs\n"
319  "--auto-fonts automatically pick fonts, disregard Qt standard settings\n"
320  "--help this help text\n"
321  "\n"
322  "--macro <macro-file> play a macro right on startup\n"
323  "\n"
324  "-no-wm, -noborder etc. are accepted as well as --no-wm, --noborder\n"
325  "to maintain backwards compatibility.\n"
326  "\n"
327  );
328 
329  raiseFatalError();
330  }
331  }
332  }
333 
334  // Qt handles command line option "-reverse" for Arabic / Hebrew
335 }
336 
337 
338 
340 {
341  yuiDebug() <<"Closing down Qt UI." << std::endl;
342 
343  // Intentionally NOT calling dlclose() to libqt-mt
344  // (see constructor for explanation)
345 
346  if ( qApp ) // might already be reset to 0 internally from Qt
347  {
348  qApp->exit();
349  qApp->deleteLater();
350  }
351 
352  delete _signalReceiver;
353 }
354 
355 
356 
357 YWidgetFactory *
359 {
360  YQWidgetFactory * factory = new YQWidgetFactory();
361  YUI_CHECK_NEW( factory );
362 
363  return factory;
364 }
365 
366 
367 
368 YOptionalWidgetFactory *
370 {
372  YUI_CHECK_NEW( factory );
373 
374  return factory;
375 }
376 
377 
378 YApplication *
379 YQUI::createApplication()
380 {
381  YQApplication * app = new YQApplication();
382  YUI_CHECK_NEW( app );
383 
384  return app;
385 }
386 
387 
389 {
390  QSize primaryScreenSize = qApp->desktop()->screenGeometry( qApp->desktop()->primaryScreen() ).size();
391  QSize availableSize = qApp->desktop()->availableGeometry( qApp->desktop()->primaryScreen() ).size();
392 
393  if ( _fullscreen )
394  {
395  _defaultSize = availableSize;
396 
397  yuiMilestone() << "-fullscreen: using "
398  << _defaultSize.width() << " x " << _defaultSize.height()
399  << "for `opt(`defaultsize)"
400  << std::endl;
401  }
402  else
403  {
404  // Get _defaultSize via -geometry command line option (if set)
405 
406  // Set min defaultsize or figure one out if -geometry was not used
407 
408  if ( _defaultSize.width() < 800 ||
409  _defaultSize.height() < 600 )
410  {
411  if ( primaryScreenSize.width() >= 1024 && primaryScreenSize.height() >= 768 )
412  {
413  // Scale down to 70% of screen size
414 
415  _defaultSize.setWidth ( max( (int) (availableSize.width() * 0.7), 800 ) );
416  _defaultSize.setHeight( max( (int) (availableSize.height() * 0.7), 600 ) );
417  }
418  else
419  {
420  _defaultSize = availableSize;
421  }
422  }
423  else
424  {
425  yuiMilestone() << "Forced size (via -geometry): "
426  << _defaultSize.width() << " x " << _defaultSize.height()
427  << std::endl;
428  }
429  }
430 
431  yuiMilestone() << "Default size: "
432  << _defaultSize.width() << " x " << _defaultSize.height()
433  << std::endl;
434 }
435 
436 
437 void YQUI::idleLoop( int fd_ycp )
438 {
439  initUI();
440 
441  _received_ycp_command = false;
442  QSocketNotifier * notifier = new QSocketNotifier( fd_ycp, QSocketNotifier::Read );
443  QObject::connect( notifier, SIGNAL( activated( int ) ),
444  _signalReceiver, SLOT ( slotReceivedYCPCommand() ) );
445 
446  notifier->setEnabled( true );
447 
448 
449  //
450  // Process Qt events until fd_ycp is readable
451  //
452 
453 #if VERBOSE_EVENT_LOOP
454  yuiDebug() << "Entering idle loop" << std::endl;
455 #endif
456 
457  QEventLoop eventLoop( qApp );
458 
459  while ( !_received_ycp_command )
460  eventLoop.processEvents( QEventLoop::ExcludeUserInputEvents | QEventLoop::WaitForMoreEvents );
461 
462 #if VERBOSE_EVENT_LOOP
463  yuiDebug() << "Leaving idle loop" << std::endl;
464 #endif
465 
466  delete notifier;
467 }
468 
469 
471 {
472  _received_ycp_command = true;
473 }
474 
475 
476 void YQUI::sendEvent( YEvent * event )
477 {
478  if ( event )
479  {
480  _eventHandler.sendEvent( event );
481  YQDialog * dialog = (YQDialog *) YDialog::currentDialog( false ); // don't throw
482 
483  if ( dialog )
484  {
485  if ( dialog->eventLoop()->isRunning() )
486  dialog->eventLoop()->exit( 0 );
487  }
488  else
489  {
490  yuiError() << "No dialog" << std::endl;
491  }
492  }
493 }
494 
495 
496 void YQUI::setTextdomain( const char * domain )
497 {
498  bindtextdomain( domain, YSettings::localeDir().c_str() );
499  bind_textdomain_codeset( domain, "utf8" );
500  textdomain( domain );
501 
502  // Make change known.
503  {
504  extern int _nl_msg_cat_cntr;
505  ++_nl_msg_cat_cntr;
506  }
507 }
508 
509 
510 void YQUI::blockEvents( bool block )
511 {
512  initUI();
513 
514  if ( block )
515  {
516  if ( ++_blockedLevel == 1 )
517  {
518  _eventHandler.blockEvents( true );
519 
520  YQDialog * dialog = (YQDialog *) YDialog::currentDialog( false ); // don't throw
521 
522  if ( dialog && dialog->eventLoop()->isRunning() )
523  {
524  yuiWarning() << "blocking events in active event loop of " << dialog << std::endl;
525  dialog->eventLoop()->exit();
526  }
527  }
528  }
529  else
530  {
531  if ( --_blockedLevel == 0 )
532  {
533  _eventHandler.blockEvents( false );
534 
535  YQDialog * dialog = (YQDialog *) YDialog::currentDialog( false ); // don't throw
536 
537  if ( dialog )
538  dialog->eventLoop()->wakeUp();
539  }
540  }
541 }
542 
543 
545 {
546  initUI();
547  _blockedLevel = 0;
548  _eventHandler.blockEvents( false );
549 }
550 
551 
553 {
554  return _eventHandler.eventsBlocked();
555 }
556 
557 
559 {
560  qApp->setOverrideCursor( Qt::BusyCursor );
561 }
562 
563 
565 {
566  if ( _busyCursorTimer->isActive() )
567  _busyCursorTimer->stop();
568 
569  while ( qApp->overrideCursor() )
570  qApp->restoreOverrideCursor();
571 }
572 
573 
575 {
576  // Display a busy cursor, but only if there is no other activity within
577  // BUSY_CURSOR_TIMEOUT milliseconds: Avoid cursor flicker.
578 
579  _busyCursorTimer->start( BUSY_CURSOR_TIMEOUT ); // single shot
580 }
581 
582 
583 int YQUI::defaultSize(YUIDimension dim) const
584 {
585  return dim == YD_HORIZ ? _defaultSize.width() : _defaultSize.height();
586 }
587 
588 
589 void YQUI::probeX11Display( const YCommandLine & cmdLine )
590 {
591  int displayArgPos = cmdLine.find( "-display" );
592  std::string displayNameStr;
593 
594  if ( displayArgPos > 0 && displayArgPos+1 < cmdLine.argc() )
595  {
596  displayNameStr = cmdLine[ displayArgPos+1 ];
597  yuiMilestone() << "Using X11 display \"" << displayNameStr << "\"" << std::endl;
598  }
599 
600  const char * displayName = ( displayNameStr.empty() ? 0 : displayNameStr.c_str() );
601  Display * display = XOpenDisplay( displayName );
602 
603  if ( display )
604  {
605  yuiDebug() << "Probing X11 display successful" << std::endl;
606  XCloseDisplay( display );
607  }
608  else
609  {
610  string msg = "Can't open display " + displayNameStr;
611  YUI_THROW( YUIException( msg ) );
612  }
613 }
614 
615 
616 void YQUI::deleteNotify( YWidget * widget )
617 {
618  _eventHandler.deletePendingEventsFor( widget );
619 }
620 
621 
623 {
624  if ( _usingVisionImpairedPalette )
625  {
626  qApp->setPalette( normalPalette()); // informWidgets
627 
628  _usingVisionImpairedPalette = false;
629  }
630  else
631  {
632  qApp->setPalette( visionImpairedPalette() ); // informWidgets
633 
634  _usingVisionImpairedPalette = true;
635  }
636 }
637 
638 
639 QPalette
641 {
642  const QColor dark ( 0x20, 0x20, 0x20 );
643  QPalette pal;
644 
645  // for the active window (the one with the keyboard focus)
646  pal.setColor( QPalette::Active, QPalette::Background, Qt::black );
647  pal.setColor( QPalette::Active, QPalette::Foreground, Qt::cyan );
648  pal.setColor( QPalette::Active, QPalette::Text, Qt::cyan );
649  pal.setColor( QPalette::Active, QPalette::Base, dark );
650  pal.setColor( QPalette::Active, QPalette::Button, dark );
651  pal.setColor( QPalette::Active, QPalette::ButtonText, Qt::green );
652  pal.setColor( QPalette::Active, QPalette::Highlight, Qt::yellow );
653  pal.setColor( QPalette::Active, QPalette::HighlightedText, Qt::black );
654 
655  // for other windows (those that don't have the keyboard focus)
656  pal.setColor( QPalette::Inactive, QPalette::Background, Qt::black );
657  pal.setColor( QPalette::Inactive, QPalette::Foreground, Qt::cyan );
658  pal.setColor( QPalette::Inactive, QPalette::Text, Qt::cyan );
659  pal.setColor( QPalette::Inactive, QPalette::Base, dark );
660  pal.setColor( QPalette::Inactive, QPalette::Button, dark );
661  pal.setColor( QPalette::Inactive, QPalette::ButtonText, Qt::green );
662 
663  // for disabled widgets
664  pal.setColor( QPalette::Disabled, QPalette::Background, Qt::black );
665  pal.setColor( QPalette::Disabled, QPalette::Foreground, Qt::gray );
666  pal.setColor( QPalette::Disabled, QPalette::Text, Qt::gray );
667  pal.setColor( QPalette::Disabled, QPalette::Base, dark );
668  pal.setColor( QPalette::Disabled, QPalette::Button, dark );
669  pal.setColor( QPalette::Disabled, QPalette::ButtonText, Qt::gray );
670 
671  return pal;
672 }
673 
674 
675 // FIXME: Does this still do anything now that YQUI is no longer a QObject?
677 {
678  yuiMilestone() << "Closing application" << std::endl;
679  sendEvent( new YCancelEvent() );
680  return true;
681 }
682 
683 
684 
685 
686 YQUISignalReceiver::YQUISignalReceiver()
687  : QObject()
688 {
689 }
690 
691 
692 void YQUISignalReceiver::slotBusyCursor()
693 {
694  YQUI::ui()->busyCursor();
695 }
696 
697 
698 void YQUISignalReceiver::slotReceivedYCPCommand()
699 {
701 }
702 
703 
704 
705 static void
706 qMessageHandler( QtMsgType type, const char * msg )
707 {
708  switch (type)
709  {
710  case QtDebugMsg:
711  yuiMilestone() << "<libqt-debug> " << msg << std::endl;
712  break;
713 
714  case QtWarningMsg:
715  yuiWarning() << "<libqt-warning> " << msg << std::endl;
716  break;
717 
718  case QtCriticalMsg:
719  yuiError() << "<libqt-critical>" << msg << std::endl;
720  break;
721 
722  case QtFatalMsg:
723  yuiError() << "<libqt-fatal> " << msg << std::endl;
724  abort();
725  exit(1); // Qt does the same
726  }
727 
728  if ( QString( msg ).contains( "Fatal IO error", Qt::CaseInsensitive ) &&
729  QString( msg ).contains( "client killed", Qt::CaseInsensitive ) )
730  yuiError() << "Client killed. Possibly caused by X server shutdown or crash." << std::endl;
731 }
732 
733 
734 
735 #include "YQUI.moc"