libyui  3.0.10
 All Classes Functions Variables Enumerations Friends
YDialog.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: YDialog.cc
20 
21  Author: Stefan Hundhammer <sh@suse.de>
22 
23 /-*/
24 
25 
26 #include <list>
27 #include <algorithm>
28 
29 #define YUILogComponent "ui"
30 #include "YUILog.h"
31 
32 #include "YDialog.h"
33 #include "YEvent.h"
34 #include "YShortcutManager.h"
35 #include "YPushButton.h"
36 #include "YButtonBox.h"
37 
38 #include "YUI.h"
39 #include "YWidgetFactory.h"
40 #include "YLayoutBox.h"
41 #include "YRichText.h"
42 #include "YAlignment.h"
43 #include "YUIException.h"
44 #include "YEventFilter.h"
45 
46 
47 #define VERBOSE_DIALOGS 0
48 #define VERBOSE_DISCARDED_EVENTS 0
49 #define VERBOSE_EVENTS 0
50 
51 
52 typedef std::list<YEventFilter *> YEventFilterList;
53 
54 
56 {
57  YDialogPrivate( YDialogType dialogType, YDialogColorMode colorMode )
58  : dialogType( dialogType )
59  , colorMode( colorMode )
60  , shortcutCheckPostponed( false )
61  , defaultButton( 0 )
62  , isOpen( false )
63  , lastEvent( 0 )
64  {}
65 
66  YDialogType dialogType;
67  YDialogColorMode colorMode;
68  bool shortcutCheckPostponed;
69  YPushButton * defaultButton;
70  bool isOpen;
71  YEvent * lastEvent;
72  YEventFilterList eventFilterList;
73 };
74 
75 
76 
77 /**
78  * Helper class: Event filter that handles "Help" buttons.
79  **/
81 {
82 public:
84  : YEventFilter( dialog )
85  {}
86 
87  virtual ~YHelpButtonHandler() {}
88 
89  YEvent * filter( YEvent * event )
90  {
91  if ( event && event->widget() )
92  {
93  YPushButton * button = dynamic_cast<YPushButton *> ( event->widget() );
94 
95  if ( button && button->isHelpButton() )
96  {
97  if ( YDialog::showHelpText( button ) )
98  {
99  event = 0; // consume event
100  }
101  }
102  }
103 
104  return event;
105  }
106 };
107 
108 
109 
110 
111 YDialog::YDialog( YDialogType dialogType, YDialogColorMode colorMode )
113  , priv( new YDialogPrivate( dialogType, colorMode ) )
114 {
115  YUI_CHECK_NEW( priv );
116 
117  _dialogStack.push( this );
118 
119 #if VERBOSE_DIALOGS
120  yuiDebug() << "New " << this << std::endl;
121 #endif
122 
123  new YHelpButtonHandler( this );
124 }
125 
126 
128 {
129 #if VERBOSE_DIALOGS
130  yuiDebug() << "Destroying " << this << std::endl;
131 #endif
132 
133  // Inform attached classes that this dialog is in the process of being
134  // destroyed. This also happens in the base class destructor, but that
135  // might be too late.
137 
138  if ( priv->lastEvent )
139  deleteEvent( priv->lastEvent );
140 
141  // The base class also deletes all children, but this should be done before
142  // the event filters are deleted to prevent duplicate event filter deletion
143  // from (a) child widget destructors and (b) here.
144  deleteChildren();
145 
146  // Delete the remaining event filters: Those installed by this dialog and
147  // those installed by some child widget that are not deleted yet.
149 
150  if ( ! _dialogStack.empty() && _dialogStack.top() == this )
151  {
152  _dialogStack.pop();
153 
154  if ( ! _dialogStack.empty() )
155  _dialogStack.top()->activate();
156  }
157  else
158  yuiError() << "Not top of dialog stack: " << this << std::endl;
159 }
160 
161 
162 void
164 {
165  if ( priv->isOpen )
166  return;
167 
168  checkShortcuts();
169  setInitialSize();
170  openInternal(); // Make sure this is only called once!
171 
172  priv->isOpen = true;
173 }
174 
175 
176 bool
178 {
179  return priv->isOpen;
180 }
181 
182 
183 bool
185 {
186  if ( _dialogStack.empty() )
187  {
188  yuiError() << "Dialog stack empty, but dialog existing: " << this << std::endl;
189  return false;
190  }
191 
192  return _dialogStack.top() == this;
193 }
194 
195 
196 void
198 {
199  while ( ! priv->eventFilterList.empty() )
200  {
201  YEventFilter * filter = priv->eventFilterList.back();
202 
203 #if VERBOSE_DIALOGS
204  yuiDebug() << "Deleting event filter " << std::std::hex << filter << std::dec << std::endl;
205 #endif
206  delete filter;
207  }
208 }
209 
210 
211 bool
212 YDialog::destroy( bool doThrow )
213 {
214  YUI_CHECK_WIDGET( this );
215 
216  if ( isTopmostDialog() )
217  {
218  delete this;
219 
220  return true;
221  }
222  else
223  {
224  if ( doThrow )
225  YUI_THROW( YUIDialogStackingOrderException() );
226 
227  return false;
228  }
229 }
230 
231 
232 YDialogType
234 {
235  return priv->dialogType;
236 }
237 
238 
239 bool
241 {
242  switch ( priv->dialogType )
243  {
244  case YMainDialog: return true;
245  case YWizardDialog: return true;
246  case YPopupDialog: return false;
247 
248  // Intentionally omitting the 'default' case so the compiler can
249  // catch unhandled enum values
250  }
251 
252  /*NOTREACHED*/
253  return false;
254 }
255 
256 
257 YDialogColorMode
259 {
260  return priv->colorMode;
261 }
262 
263 
264 void
266 {
267  priv->shortcutCheckPostponed = true;
268 }
269 
270 
271 bool
273 {
274  return priv->shortcutCheckPostponed;
275 }
276 
277 
278 void
280 {
281  if ( priv->shortcutCheckPostponed && ! force )
282  {
283  yuiDebug() << "Shortcut check postponed" << std::endl;
284  }
285  else
286  {
287 
288  YShortcutManager shortcutManager( this );
289  shortcutManager.checkShortcuts();
290 
291  priv->shortcutCheckPostponed = false;
292  }
293 }
294 
295 
296 YPushButton *
298 {
299  return priv->defaultButton;
300 }
301 
302 
303 void
305 {
306  if ( newDefaultButton && priv->defaultButton ) // already have one?
307  {
308  yuiError() << "Too many `opt(`default) PushButtons: ["
309  << newDefaultButton->label()
310  << "]" << std::endl;
311  }
312 
313  priv->defaultButton = newDefaultButton;
314 }
315 
316 
317 void
319 {
320 #if VERBOSE_DIALOGS
321  yuiDebug() << "Setting initial size for " << this << std::endl;
322 #endif
323 
324  // Trigger geometry management
326 }
327 
328 
329 void
331 {
332  yuiDebug() << "Recalculating layout for " << this << std::endl;
333 
335 }
336 
337 
338 YEvent *
339 YDialog::waitForEvent( int timeout_millisec )
340 {
341  if ( ! isTopmostDialog() )
342  YUI_THROW( YUIDialogStackingOrderException() );
343 
344  if ( timeout_millisec < 0 )
345  timeout_millisec = 0;
346 
347  if ( ! isOpen() )
348  open();
349 
350  if ( shortcutCheckPostponed() )
351  {
352  yuiError() << "Performing missing keyboard shortcut check now in "
353  << this << std::endl;
354 
355  checkShortcuts( true );
356  }
357 
358  deleteEvent( priv->lastEvent );
359  YEvent * event = 0;
360 
361  do
362  {
363  event = filterInvalidEvents( waitForEventInternal( timeout_millisec ) );
364  event = callEventFilters( event );
365 
366  // If there was no event, if filterInvalidEvents() discarded an invalid
367  // event, or if one of the event filters consumed an event, go back and
368  // get the next event.
369 
370  } while ( ! event );
371 
372  priv->lastEvent = event;
373 
374  return event;
375 }
376 
377 
378 YEvent *
380 {
381  if ( ! isTopmostDialog() )
382  YUI_THROW( YUIDialogStackingOrderException() );
383 
384  if ( ! isOpen() )
385  open();
386 
388 
389  if ( event ) // Optimization (calling with 0 wouldn't hurt)
390  event = callEventFilters( event );
391 
392  priv->lastEvent = event;
393 
394  // Nevermind if filterInvalidEvents() discarded an invalid event.
395  // pollInput() is normally called very often (in a loop), and most of the
396  // times it returns 0 anyway, so there is no need to care for just another
397  // 0 that is returned in this exotic case.
398 
399  return event;
400 }
401 
402 
403 YEvent *
405 {
406  if ( ! event )
407  return 0;
408 
409  YWidgetEvent * widgetEvent = dynamic_cast<YWidgetEvent *> (event);
410 
411  if ( widgetEvent && widgetEvent->widget() )
412  {
413  if ( ! widgetEvent->widget()->isValid() )
414  {
415  /**
416  * Silently discard events from widgets that have become invalid.
417  *
418  * This may legitimately happen if some widget triggered an event yet
419  * nobody cared for that event (i.e. called UserInput() or PollInput() )
420  * and the widget has been destroyed meanwhile.
421  **/
422 
423  // yuiDebug() << "Discarding event for widget that has become invalid" << std::endl;
424 
425  deleteEvent( widgetEvent );
426  return 0;
427  }
428 
429  if ( widgetEvent->widget()->findDialog() != this )
430  {
431  /**
432  * Silently discard events from all but the current (topmost) dialog.
433  *
434  * This may happen even here even though the specific UI should have
435  * taken care about that: Events may still be in the queue. They might
436  * have been valid (i.e. belonged to the topmost dialog) when they
437  * arrived, but maybe simply nobody has evaluated them.
438  **/
439 
440  // Yes, really yuiDebug() - this may legitimately happen.
441  yuiDebug() << "Discarding event from widget from foreign dialog" << std::endl;
442 
443 #if VERBOSE_DISCARDED_EVENTS
444  yuiDebug() << "Expected: " << this
445  << ", received: " << widgetEvent->widget()->findDialog()
446  << std::endl;
447 
448  yuiDebug() << "Event widget: " << widgetEvent->widget() << std::endl;
449  yuiDebug() << "From:" << std::endl;
450  widgetEvent->widget()->findDialog()->dumpWidgetTree();
451  yuiDebug() << "Current dialog:" << std::endl;
452  dumpWidgetTree();
453 #endif
454 
455  activate(); // try to force this dialog to the foreground
456 
457  deleteEvent( widgetEvent );
458  return 0;
459  }
460 
461  }
462 
463  return event;
464 }
465 
466 
467 void
469 {
470  if ( event == priv->lastEvent )
471  priv->lastEvent = 0;
472 
473  if ( event )
474  {
475  if ( event->isValid() )
476  {
477 #if VERBOSE_EVENTS
478  yuiDebug() << "Deleting " << event << std::endl;
479 #endif
480  delete event;
481  }
482  else
483  {
484  yuiError() << "Attempt to delete invalid event " << event << std::endl;
485  }
486  }
487 }
488 
489 
490 YDialog *
491 YDialog::currentDialog( bool doThrow )
492 {
493  if ( _dialogStack.empty() )
494  {
495  if ( doThrow )
496  YUI_THROW( YUINoDialogException() );
497  return 0;
498  }
499  else
500  return _dialogStack.top();
501 }
502 
503 
504 bool
506 {
507  if ( _dialogStack.empty() )
508  {
509  if ( doThrow )
510  YUI_THROW( YUINoDialogException() );
511  }
512  else
513  {
514  delete _dialogStack.top();
515  }
516 
517  return ! _dialogStack.empty();
518 }
519 
520 
521 void
523 {
524  while ( ! _dialogStack.empty() )
525  {
526  delete _dialogStack.top();
527  }
528 }
529 
530 
531 void
532 YDialog::deleteTo( YDialog * targetDialog )
533 {
534  YUI_CHECK_WIDGET( targetDialog );
535 
536  while ( ! _dialogStack.empty() )
537  {
538  YDialog * dialog = _dialogStack.top();
539 
540  delete dialog;
541 
542  if ( dialog == targetDialog )
543  return;
544  }
545 
546  // If we ever get here, targetDialog was nowhere in the dialog stack.
547 
548  YUI_THROW( YUIDialogStackingOrderException() );
549 }
550 
551 
552 int
554 {
555  return _dialogStack.size();
556 }
557 
558 
559 void
561 {
562  YUI_CHECK_PTR( eventFilter );
563 
564  if ( find( priv->eventFilterList.begin(), priv->eventFilterList.end(),
565  eventFilter ) != priv->eventFilterList.end() )
566  {
567  yuiError() << "event filter " << std::hex << eventFilter << std::dec
568  << " already added to " << this
569  << std::endl;
570  }
571  else
572  {
573 #if VERBOSE_DIALOGS
574  yuiDebug() << "Adding event filter " << std::hex << eventFilter << std::dec << std::endl;
575 #endif
576  priv->eventFilterList.push_back( eventFilter );
577  }
578 }
579 
580 
581 void
583 {
584  YUI_CHECK_PTR( eventFilter );
585 
586 #if VERBOSE_DIALOGS
587  yuiDebug() << "Removing event filter " << std::hex << eventFilter << std::dec << std::endl;
588 #endif
589  priv->eventFilterList.remove( eventFilter );
590 }
591 
592 
593 YEvent *
595 {
596  YEventFilterList::const_iterator it = priv->eventFilterList.begin();
597 
598  while ( it != priv->eventFilterList.end() && event )
599  {
600  YEvent * oldEvent = event;
601  event = (*it)->filter( event );
602 
603  if ( oldEvent != event ) // event filter consumed or changed the old event?
604  deleteEvent( oldEvent ); // get rid of the old one
605 
606  ++it;
607  }
608 
609  return event;
610 }
611 
612 
613 void
614 YDialog::showText( const std::string & text, bool useRichText )
615 {
616 
617  // set help text dialog size to 80% of topmost dialog, respectively 45x15 (default)
618 
619  unsigned int dialogWidth = 45;
620  unsigned int dialogHeight = 15;
621 
622  if ( ! _dialogStack.empty() )
623  {
624  YDialog * dialog = _dialogStack.top();
625  dialogWidth = (unsigned int) ( (float) dialog->preferredWidth() * 0.8 );
626  dialogHeight = (unsigned int) ( (float) dialog->preferredHeight() * 0.8 );
627  }
628 
629  // limit dialog to a reasonable size
630  if ( dialogWidth > 80 || dialogHeight > 25 )
631  {
632  dialogWidth = 80;
633  dialogHeight = 25;
634  }
635 
636  try
637  {
638  YDialog * dialog = YUI::widgetFactory()->createPopupDialog();
639  YAlignment * minSize = YUI::widgetFactory()->createMinSize( dialog, dialogWidth, dialogHeight );
640  YLayoutBox * vbox = YUI::widgetFactory()->createVBox( minSize );
641  YUI::widgetFactory()->createRichText( vbox, text, ! useRichText );
642  YButtonBox * buttonBox = YUI::widgetFactory()->createButtonBox( vbox );
643  YPushButton * okButton = YUI::widgetFactory()->createPushButton( buttonBox, "&OK" );
644  okButton->setRole( YOKButton );
645  okButton->setDefaultButton();
646 
647  dialog->waitForEvent();
648  dialog->destroy();
649  }
650  catch ( YUIException exception )
651  {
652  // Don't let the application die just because help couldn't be displayed.
653 
654  YUI_CAUGHT( exception );
655  }
656 }
657 
658 
659 bool
661 {
662  std::string helpText;
663 
664  while ( widget )
665  {
666  if ( ! widget->helpText().empty() )
667  {
668  yuiDebug() << "Found help text for " << widget << std::endl;
669  helpText = widget->helpText();
670  }
671 
672  widget = widget->parent();
673  }
674 
675  if ( ! helpText.empty() )
676  {
677  yuiMilestone() << "Showing help text" << std::endl;
678  showText( helpText, true );
679 
680  yuiMilestone() << "Help dialog closed" << std::endl;
681  }
682  else // No help text
683  {
684  yuiWarning() << "No help text" << std::endl;
685  }
686 
687  return ! helpText.empty();
688 }