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