libyui  3.9.3
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 #define YUILogComponent "ui"
27 #include "YUILog.h"
28 
29 #include "YDialog.h"
30 #include "YEvent.h"
31 #include "YShortcutManager.h"
32 #include "YPushButton.h"
33 #include "YUI.h"
34 #include "YEventFilter.h"
35 
36 #define VERBOSE_DIALOGS 0
37 #define VERBOSE_DISCARDED_EVENTS 0
38 #define VERBOSE_EVENTS 0
39 
40 typedef std::list<YEventFilter *> YEventFilterList;
41 
42 using std::string;
43 
44 
46 {
47  YDialogPrivate( YDialogType dialogType, YDialogColorMode colorMode )
48  : dialogType( dialogType )
49  , colorMode( colorMode )
50  , shortcutCheckPostponed( false )
51  , defaultButton( 0 )
52  , isOpen( false )
53  , lastEvent( 0 )
54  {}
55 
56  YDialogType dialogType;
57  YDialogColorMode colorMode;
58  bool shortcutCheckPostponed;
59  YPushButton * defaultButton;
60  bool isOpen;
61  YEvent * lastEvent;
62  YEventFilterList eventFilterList;
63 };
64 
65 
66 
67 /**
68  * Helper class: Event filter that handles "Help" buttons.
69  **/
71 {
72 public:
74  : YEventFilter( dialog )
75  {}
76 
77  virtual ~YHelpButtonHandler() {}
78 
79  YEvent * filter( YEvent * event )
80  {
81  if ( event && event->widget() )
82  {
83  YPushButton * button = dynamic_cast<YPushButton *> ( event->widget() );
84 
85  if ( button && button->isHelpButton() )
86  {
87  if ( YDialog::showHelpText( button ) )
88  {
89  event = 0; // consume event
90  }
91  }
92  }
93 
94  return event;
95  }
96 };
97 
98 /**
99  * Helper class: Event filter that handles "ReleaseNotes" buttons.
100  **/
102 {
103 public:
105  : YEventFilter( dialog )
106  {}
107 
108  virtual ~YRelNotesButtonHandler() {}
109 
110  YEvent * filter( YEvent * event )
111  {
112  if ( event && event->widget() )
113  {
114  YPushButton * button = dynamic_cast<YPushButton *> ( event->widget() );
115 
116  if ( button && button->isRelNotesButton() )
117  {
119  {
120  event = 0; // consume event
121  }
122  }
123  }
124 
125  return event;
126  }
127 };
128 
129 
130 
131 
132 YDialog::YDialog( YDialogType dialogType, YDialogColorMode colorMode )
134  , priv( new YDialogPrivate( dialogType, colorMode ) )
135 {
136  YUI_CHECK_NEW( priv );
137 
138  _dialogStack.push( this );
139 
140 #if VERBOSE_DIALOGS
141  yuiDebug() << "New " << this << endl;
142 #endif
143 
144  new YHelpButtonHandler( this );
145  new YRelNotesButtonHandler( this );
146 }
147 
148 
150 {
151 #if VERBOSE_DIALOGS
152  yuiDebug() << "Destroying " << this << endl;
153 #endif
154 
155  // Inform attached classes that this dialog is in the process of being
156  // destroyed. This also happens in the base class destructor, but that
157  // might be too late.
159 
160  if ( priv->lastEvent )
161  deleteEvent( priv->lastEvent );
162 
163  // The base class also deletes all children, but this should be done before
164  // the event filters are deleted to prevent duplicate event filter deletion
165  // from (a) child widget destructors and (b) here.
166  deleteChildren();
167 
168  // Delete the remaining event filters: Those installed by this dialog and
169  // those installed by some child widget that are not deleted yet.
171 
172  if ( ! _dialogStack.empty() && _dialogStack.top() == this )
173  {
174  _dialogStack.pop();
175 
176  if ( ! _dialogStack.empty() )
177  _dialogStack.top()->activate();
178  }
179  else
180  yuiError() << "Not top of dialog stack: " << this << endl;
181 }
182 
183 
184 void
186 {
187  if ( priv->isOpen )
188  return;
189 
190  checkShortcuts();
191  setInitialSize();
192  openInternal(); // Make sure this is only called once!
193 
194  priv->isOpen = true;
195 }
196 
197 
198 bool
200 {
201  return priv->isOpen;
202 }
203 
204 
205 bool
207 {
208  if ( _dialogStack.empty() )
209  {
210  yuiError() << "Dialog stack empty, but dialog existing: " << this << endl;
211  return false;
212  }
213 
214  return _dialogStack.top() == this;
215 }
216 
217 
218 void
220 {
221  while ( ! priv->eventFilterList.empty() )
222  {
223  YEventFilter * filter = priv->eventFilterList.back();
224 
225 #if VERBOSE_DIALOGS
226  yuiDebug() << "Deleting event filter " << hex << filter << dec << endl;
227 #endif
228  delete filter;
229  }
230 }
231 
232 
233 bool
234 YDialog::destroy( bool doThrow )
235 {
236  YUI_CHECK_WIDGET( this );
237 
238  if ( isTopmostDialog() )
239  {
240  delete this;
241 
242  return true;
243  }
244  else
245  {
246  if ( doThrow )
247  YUI_THROW( YUIDialogStackingOrderException() );
248 
249  return false;
250  }
251 }
252 
253 
256 {
257  return priv->dialogType;
258 }
259 
260 
261 bool
263 {
264  switch ( priv->dialogType )
265  {
266  case YMainDialog: return true;
267  case YWizardDialog: return true;
268  case YPopupDialog: return false;
269 
270  // Intentionally omitting the 'default' case so the compiler can
271  // catch unhandled enum values
272  }
273 
274  /*NOTREACHED*/
275  return false;
276 }
277 
278 
279 YDialogColorMode
281 {
282  return priv->colorMode;
283 }
284 
285 
286 void
288 {
289  priv->shortcutCheckPostponed = true;
290 }
291 
292 
293 bool
295 {
296  return priv->shortcutCheckPostponed;
297 }
298 
299 
300 void
302 {
303  if ( priv->shortcutCheckPostponed && ! force )
304  {
305  yuiDebug() << "Shortcut check postponed" << endl;
306  }
307  else
308  {
309 
310  YShortcutManager shortcutManager( this );
311  shortcutManager.checkShortcuts();
312 
313  priv->shortcutCheckPostponed = false;
314  }
315 }
316 
317 
318 YPushButton *
320 {
321  return priv->defaultButton;
322 }
323 
324 
325 void
327 {
328  if ( newDefaultButton && priv->defaultButton ) // already have one?
329  {
330  yuiError() << "Too many `opt(`default) PushButtons: ["
331  << newDefaultButton->label()
332  << "]" << endl;
333  }
334 
335  priv->defaultButton = newDefaultButton;
336 }
337 
338 
339 void
341 {
342 #if VERBOSE_DIALOGS
343  yuiDebug() << "Setting initial size for " << this << endl;
344 #endif
345 
346  // Trigger geometry management
348 }
349 
350 
351 void
353 {
354  yuiDebug() << "Recalculating layout for " << this << endl;
355 
357 }
358 
359 
360 YEvent *
361 YDialog::waitForEvent( int timeout_millisec )
362 {
363  if ( ! isTopmostDialog() )
364  YUI_THROW( YUIDialogStackingOrderException() );
365 
366  if ( timeout_millisec < 0 )
367  timeout_millisec = 0;
368 
369  if ( ! isOpen() )
370  open();
371 
372  if ( shortcutCheckPostponed() )
373  {
374  yuiError() << "Performing missing keyboard shortcut check now in "
375  << this << endl;
376 
377  checkShortcuts( true );
378  }
379 
380  deleteEvent( priv->lastEvent );
381  YEvent * event = 0;
382 
383  do
384  {
385  event = filterInvalidEvents( waitForEventInternal( timeout_millisec ) );
386  event = callEventFilters( event );
387 
388  // If there was no event, if filterInvalidEvents() discarded an invalid
389  // event, or if one of the event filters consumed an event, go back and
390  // get the next event.
391 
392  } while ( ! event );
393 
394  priv->lastEvent = event;
395 
396  return event;
397 }
398 
399 
400 YEvent *
402 {
403  if ( ! isTopmostDialog() )
404  YUI_THROW( YUIDialogStackingOrderException() );
405 
406  if ( ! isOpen() )
407  open();
408 
410 
411  if ( event ) // Optimization (calling with 0 wouldn't hurt)
412  event = callEventFilters( event );
413 
414  priv->lastEvent = event;
415 
416  // Nevermind if filterInvalidEvents() discarded an invalid event.
417  // pollInput() is normally called very often (in a loop), and most of the
418  // times it returns 0 anyway, so there is no need to care for just another
419  // 0 that is returned in this exotic case.
420 
421  return event;
422 }
423 
424 
425 YEvent *
427 {
428  if ( ! event )
429  return 0;
430 
431  YWidgetEvent * widgetEvent = dynamic_cast<YWidgetEvent *> (event);
432 
433  if ( widgetEvent && widgetEvent->widget() )
434  {
435  if ( ! widgetEvent->widget()->isValid() )
436  {
437  /**
438  * Silently discard events from widgets that have become invalid.
439  *
440  * This may legitimately happen if some widget triggered an event yet
441  * nobody cared for that event (i.e. called UserInput() or PollInput() )
442  * and the widget has been destroyed meanwhile.
443  **/
444 
445  // yuiDebug() << "Discarding event for widget that has become invalid" << endl;
446 
447  deleteEvent( widgetEvent );
448  return 0;
449  }
450 
451  if ( widgetEvent->widget()->findDialog() != this )
452  {
453  /**
454  * Silently discard events from all but the current (topmost) dialog.
455  *
456  * This may happen even here even though the specific UI should have
457  * taken care about that: Events may still be in the queue. They might
458  * have been valid (i.e. belonged to the topmost dialog) when they
459  * arrived, but maybe simply nobody has evaluated them.
460  **/
461 
462  // Yes, really yuiDebug() - this may legitimately happen.
463  yuiDebug() << "Discarding event from widget from foreign dialog" << endl;
464 
465 #if VERBOSE_DISCARDED_EVENTS
466  yuiDebug() << "Expected: " << this
467  << ", received: " << widgetEvent->widget()->findDialog()
468  << endl;
469 
470  yuiDebug() << "Event widget: " << widgetEvent->widget() << endl;
471  yuiDebug() << "From:" << endl;
472  widgetEvent->widget()->findDialog()->dumpWidgetTree();
473  yuiDebug() << "Current dialog:" << endl;
474  dumpWidgetTree();
475 #endif
476 
477  activate(); // try to force this dialog to the foreground
478 
479  deleteEvent( widgetEvent );
480  return 0;
481  }
482 
483  }
484 
485  return event;
486 }
487 
488 
489 void
491 {
492  if ( event == priv->lastEvent )
493  priv->lastEvent = 0;
494 
495  if ( event )
496  {
497  if ( event->isValid() )
498  {
499 #if VERBOSE_EVENTS
500  yuiDebug() << "Deleting " << event << endl;
501 #endif
502  delete event;
503  }
504  else
505  {
506  yuiError() << "Attempt to delete invalid event " << event << endl;
507  }
508  }
509 }
510 
511 
512 YDialog *
513 YDialog::currentDialog( bool doThrow )
514 {
515  if ( _dialogStack.empty() )
516  {
517  if ( doThrow )
518  YUI_THROW( YUINoDialogException() );
519  return 0;
520  }
521  else
522  return _dialogStack.top();
523 }
524 
525 
526 bool
528 {
529  if ( _dialogStack.empty() )
530  {
531  if ( doThrow )
532  YUI_THROW( YUINoDialogException() );
533  }
534  else
535  {
536  delete _dialogStack.top();
537  }
538 
539  return ! _dialogStack.empty();
540 }
541 
542 
543 void
545 {
546  while ( ! _dialogStack.empty() )
547  {
548  delete _dialogStack.top();
549  }
550 }
551 
552 
553 void
554 YDialog::deleteTo( YDialog * targetDialog )
555 {
556  YUI_CHECK_WIDGET( targetDialog );
557 
558  while ( ! _dialogStack.empty() )
559  {
560  YDialog * dialog = _dialogStack.top();
561 
562  delete dialog;
563 
564  if ( dialog == targetDialog )
565  return;
566  }
567 
568  // If we ever get here, targetDialog was nowhere in the dialog stack.
569 
570  YUI_THROW( YUIDialogStackingOrderException() );
571 }
572 
573 
574 int
576 {
577  return _dialogStack.size();
578 }
579 
580 
581 void
583 {
584  YUI_CHECK_PTR( eventFilter );
585 
586  if ( find( priv->eventFilterList.begin(), priv->eventFilterList.end(),
587  eventFilter ) != priv->eventFilterList.end() )
588  {
589  yuiError() << "event filter " << std::hex << eventFilter << std::dec
590  << " already added to " << this
591  << endl;
592  }
593  else
594  {
595 #if VERBOSE_DIALOGS
596  yuiDebug() << "Adding event filter " << hex << eventFilter << dec << endl;
597 #endif
598  priv->eventFilterList.push_back( eventFilter );
599  }
600 }
601 
602 
603 void
605 {
606  YUI_CHECK_PTR( eventFilter );
607 
608 #if VERBOSE_DIALOGS
609  yuiDebug() << "Removing event filter " << hex << eventFilter << dec << endl;
610 #endif
611  priv->eventFilterList.remove( eventFilter );
612 }
613 
614 
615 YEvent *
617 {
618  YEventFilterList::const_iterator it = priv->eventFilterList.begin();
619 
620  while ( it != priv->eventFilterList.end() && event )
621  {
622  YEvent * oldEvent = event;
623  event = (*it)->filter( event );
624 
625  if ( oldEvent != event ) // event filter consumed or changed the old event?
626  deleteEvent( oldEvent ); // get rid of the old one
627 
628  ++it;
629  }
630 
631  return event;
632 }
633 
void deleteEvent(YEvent *event)
Delete an event.
Definition: YDialog.cc:490
static bool showHelpText(YWidget *widget)
Show the help text for the specified widget.
void deleteEventFilters()
Delete all (remaining) event filters.
Definition: YDialog.cc:219
YEventFilter(YDialog *dialog=0)
Constructor.
Definition: YEventFilter.cc:44
Helper class: Event filter that handles "Help" buttons.
Definition: YDialog.cc:70
virtual void setDefaultButton(YPushButton *defaultButton)
Set this dialog&#39;s default button (the button that is activated when the user hits [Return] anywhere i...
Definition: YDialog.cc:326
bool isHelpButton() const
Returns &#39;true&#39; if this is a "Help" button.
Definition: YPushButton.cc:127
YDialogType
Type of dialog: Main / Popup / Wizard.
Definition: YTypes.h:66
bool isValid() const
Check if this event is valid.
Definition: YEvent.cc:55
Helper class to manage keyboard shortcuts within one dialog and resolve keyboard shortcut conflicts...
void postponeShortcutCheck()
From now on, postpone keyboard shortcut checks - i.e.
Definition: YDialog.cc:287
static bool deleteTopmostDialog(bool doThrow=true)
Delete the topmost dialog.
Definition: YDialog.cc:527
Abstract base class to filter events.
Definition: YEventFilter.h:62
void dumpWidgetTree(int indentationLevel=0)
Debugging function: Dump the widget tree from here on to the log file.
Definition: YWidget.cc:674
Abstract base class for events to be returned upon UI::UserInput() and related functions.
Definition: YEvent.h:43
void deleteChildren()
Delete all children and remove them from the children manager&#39;s list.
Definition: YWidget.cc:202
YEvent * filterInvalidEvents(YEvent *event)
Filter out invalid events: Return 0 if the event does not belong to this dialog or the unchanged even...
Definition: YDialog.cc:426
bool isTopmostDialog() const
Return &#39;true&#39; if this dialog is the topmost dialog.
Definition: YDialog.cc:206
std::string label() const
Get the label (the text on the button).
Definition: YPushButton.cc:86
void checkShortcuts(bool force=false)
Checks the keyboard shortcuts of widgets in this dialog unless shortcut checks are postponed or &#39;forc...
Definition: YDialog.cc:301
Container widget class that manages one child.
virtual int preferredWidth()
Preferred width of the widget.
void removeEventFilter(YEventFilter *eventFilter)
Remove an event filter.
Definition: YDialog.cc:604
YDialogColorMode colorMode() const
Return this dialog&#39;s color mode.
Definition: YDialog.cc:280
YDialog * findDialog()
Traverse up the widget hierarchy and find the dialog this widget belongs to.
Definition: YWidget.cc:376
YDialog * dialog() const
Return the dialog this event filter belongs to.
Definition: YEventFilter.cc:63
A push button; may have an icon, and a F-key shortcut.
Definition: YPushButton.h:37
bool isValid() const
Checks whether or not this object is valid.
Definition: YWidget.cc:244
virtual void openInternal()=0
Internal open() method.
static void deleteAllDialogs()
Delete all open dialogs.
Definition: YDialog.cc:544
bool isOpen() const
Return &#39;true&#39; if open() has already been called for this dialog.
Definition: YDialog.cc:199
virtual YEvent * pollEventInternal()=0
Check if a user event is pending.
virtual void setSize(int newWidth, int newHeight)
Set the new size of the widget.
static YDialog * currentDialog(bool doThrow=true)
Return the current (topmost) dialog.
Definition: YDialog.cc:513
bool shortcutCheckPostponed() const
Return whether or not shortcut checking is currently postponed.
Definition: YDialog.cc:294
YEvent * callEventFilters(YEvent *event)
Call the installed event filters.
Definition: YDialog.cc:616
void checkShortcuts(bool autoResolve=true)
Check the keyboard shortcuts of all children of this dialog (not for sub-dialogs!).
void setBeingDestroyed()
Set the "being destroyed" flag, i.e.
Definition: YWidget.cc:264
Helper class: Event filter that handles "ReleaseNotes" buttons.
Definition: YDialog.cc:101
void open()
Open a newly created dialog: Finalize it and make it visible on the screen.
Definition: YDialog.cc:185
void addEventFilter(YEventFilter *eventFilter)
Add an event filter.
Definition: YDialog.cc:582
static std::stack< YDialog * > _dialogStack
Stack holding all currently existing dialogs.
Definition: YDialog.h:415
YDialog(YDialogType dialogType, YDialogColorMode colorMode=YDialogNormalColor)
Constructor.
Definition: YDialog.cc:132
void setInitialSize()
Set the initial dialog size, depending on dialogType: YMainDialog dialogs get the UI&#39;s "default main ...
Definition: YDialog.cc:340
static void deleteTo(YDialog *dialog)
Delete all dialogs from the topmost to the one specified.
Definition: YDialog.cc:554
static bool showRelNotesText()
Show the release notes.
YDialogType dialogType() const
Return this dialog&#39;s type (YMainDialog / YPopupDialog /YWizardDialog).
Definition: YDialog.cc:255
virtual void activate()=0
Activate this dialog: Make sure that it is shown as the topmost dialog of this application and that i...
static int openDialogsCount()
Returns the number of currently open dialogs (from 1 on), i.e., the depth of the dialog stack...
Definition: YDialog.cc:575
virtual ~YDialog()
Destructor.
Definition: YDialog.cc:149
void recalcLayout()
Recalculate the layout of the dialog and of all its children after children have been added or remove...
Definition: YDialog.cc:352
YPushButton * defaultButton() const
Return this dialog&#39;s default button: The button that is activated when the user hits [Return] anywher...
Definition: YDialog.cc:319
bool isRelNotesButton() const
Returns &#39;true&#39; if this is a "Release Notes" button.
Definition: YPushButton.cc:139
A window in the desktop environment.
Definition: YDialog.h:47
virtual YEvent * waitForEventInternal(int timeout_millisec)=0
Wait for a user event.
YEvent * filter(YEvent *event)
The heart of the matter: The event filter function.
Definition: YDialog.cc:110
YEvent * pollEvent()
Check if a user event is pending.
Definition: YDialog.cc:401
virtual YWidget * widget() const
Returns the widget that caused this event or 0 if there is none.
Definition: YEvent.h:93
YEvent * filter(YEvent *event)
The heart of the matter: The event filter function.
Definition: YDialog.cc:79
bool destroy(bool doThrow=true)
Close and delete this dialog (and all its children) if it is the topmost dialog.
Definition: YDialog.cc:234
YEvent * waitForEvent(int timeout_millisec=0)
Wait for a user event.
Definition: YDialog.cc:361
virtual YWidget * widget() const
Returns the widget that caused this event.
Definition: YEvent.h:180
virtual int preferredHeight()
Preferred height of the widget.
bool isMainDialog()
Return &#39;true&#39; if this dialog is a dialog of main dialog size: YMainDialog or YWizardDialog.
Definition: YDialog.cc:262