libyui  3.0.10
/usr/src/RPM/BUILD/libyui-3.0.10/src/YDialog.cc
00001 /*
00002   Copyright (C) 2000-2012 Novell, Inc
00003   This library is free software; you can redistribute it and/or modify
00004   it under the terms of the GNU Lesser General Public License as
00005   published by the Free Software Foundation; either version 2.1 of the
00006   License, or (at your option) version 3.0 of the License. This library
00007   is distributed in the hope that it will be useful, but WITHOUT ANY
00008   WARRANTY; without even the implied warranty of MERCHANTABILITY or 
00009   FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
00010   License for more details. You should have received a copy of the GNU
00011   Lesser General Public License along with this library; if not, write
00012   to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
00013   Floor, Boston, MA 02110-1301 USA
00014 */
00015 
00016 
00017 /*-/
00018 
00019   File:         YDialog.cc
00020 
00021   Author:       Stefan Hundhammer <sh@suse.de>
00022 
00023 /-*/
00024 
00025 
00026 #include <list>
00027 #include <algorithm>
00028 
00029 #define YUILogComponent "ui"
00030 #include "YUILog.h"
00031 
00032 #include "YDialog.h"
00033 #include "YEvent.h"
00034 #include "YShortcutManager.h"
00035 #include "YPushButton.h"
00036 #include "YButtonBox.h"
00037 
00038 #include "YUI.h"
00039 #include "YWidgetFactory.h"
00040 #include "YLayoutBox.h"
00041 #include "YRichText.h"
00042 #include "YAlignment.h"
00043 #include "YUIException.h"
00044 #include "YEventFilter.h"
00045 
00046 
00047 #define VERBOSE_DIALOGS                 0
00048 #define VERBOSE_DISCARDED_EVENTS        0
00049 #define VERBOSE_EVENTS                  0
00050 
00051 
00052 typedef std::list<YEventFilter *> YEventFilterList;
00053 
00054 
00055 struct YDialogPrivate
00056 {
00057     YDialogPrivate( YDialogType dialogType, YDialogColorMode colorMode )
00058         : dialogType( dialogType )
00059         , colorMode( colorMode )
00060         , shortcutCheckPostponed( false )
00061         , defaultButton( 0 )
00062         , isOpen( false )
00063         , lastEvent( 0 )
00064         {}
00065 
00066     YDialogType         dialogType;
00067     YDialogColorMode    colorMode;
00068     bool                shortcutCheckPostponed;
00069     YPushButton *       defaultButton;
00070     bool                isOpen;
00071     YEvent *            lastEvent;
00072     YEventFilterList    eventFilterList;
00073 };
00074 
00075 
00076 
00077 /**
00078  * Helper class: Event filter that handles "Help" buttons.
00079  **/
00080 class YHelpButtonHandler: public YEventFilter
00081 {
00082 public:
00083     YHelpButtonHandler( YDialog * dialog )
00084         : YEventFilter( dialog )
00085         {}
00086 
00087     virtual ~YHelpButtonHandler() {}
00088 
00089     YEvent * filter( YEvent * event )
00090     {
00091         if ( event && event->widget() )
00092         {
00093             YPushButton * button = dynamic_cast<YPushButton *> ( event->widget() );
00094 
00095             if ( button && button->isHelpButton() )
00096             {
00097                 if ( YDialog::showHelpText( button ) )
00098                 {
00099                     event = 0; // consume event
00100                 }
00101             }
00102         }
00103 
00104         return event;
00105     }
00106 };
00107 
00108 
00109 
00110 
00111 YDialog::YDialog( YDialogType dialogType, YDialogColorMode colorMode )
00112     : YSingleChildContainerWidget( 0 )
00113     , priv( new YDialogPrivate( dialogType, colorMode ) )
00114 {
00115     YUI_CHECK_NEW( priv );
00116 
00117     _dialogStack.push( this );
00118 
00119 #if VERBOSE_DIALOGS
00120     yuiDebug() << "New " << this << std::endl;
00121 #endif
00122 
00123     new YHelpButtonHandler( this );
00124 }
00125 
00126 
00127 YDialog::~YDialog()
00128 {
00129 #if VERBOSE_DIALOGS
00130     yuiDebug() << "Destroying " << this << std::endl;
00131 #endif
00132 
00133     // Inform attached classes that this dialog is in the process of being
00134     // destroyed. This also happens in the base class destructor, but that
00135     // might be too late.
00136     setBeingDestroyed();
00137 
00138     if ( priv->lastEvent )
00139         deleteEvent( priv->lastEvent );
00140 
00141     // The base class also deletes all children, but this should be done before
00142     // the event filters are deleted to prevent duplicate event filter deletion
00143     // from (a) child widget destructors and (b) here.
00144     deleteChildren();
00145 
00146     // Delete the remaining event filters: Those installed by this dialog and
00147     // those installed by some child widget that are not deleted yet.
00148     deleteEventFilters();
00149 
00150     if ( ! _dialogStack.empty() && _dialogStack.top() == this )
00151     {
00152         _dialogStack.pop();
00153 
00154         if ( ! _dialogStack.empty() )
00155             _dialogStack.top()->activate();
00156     }
00157     else
00158         yuiError() << "Not top of dialog stack: " << this << std::endl;
00159 }
00160 
00161 
00162 void
00163 YDialog::open()
00164 {
00165     if ( priv->isOpen )
00166         return;
00167 
00168     checkShortcuts();
00169     setInitialSize();
00170     openInternal();     // Make sure this is only called once!
00171 
00172     priv->isOpen = true;
00173 }
00174 
00175 
00176 bool
00177 YDialog::isOpen() const
00178 {
00179     return priv->isOpen;
00180 }
00181 
00182 
00183 bool
00184 YDialog::isTopmostDialog() const
00185 {
00186     if ( _dialogStack.empty() )
00187     {
00188         yuiError() << "Dialog stack empty, but dialog existing: " << this << std::endl;
00189         return false;
00190     }
00191 
00192     return _dialogStack.top() == this;
00193 }
00194 
00195 
00196 void
00197 YDialog::deleteEventFilters()
00198 {
00199     while ( ! priv->eventFilterList.empty() )
00200     {
00201         YEventFilter * filter = priv->eventFilterList.back();
00202 
00203 #if VERBOSE_DIALOGS
00204         yuiDebug() << "Deleting event filter " << std::std::hex << filter << std::dec << std::endl;
00205 #endif
00206         delete filter;
00207     }
00208 }
00209 
00210 
00211 bool
00212 YDialog::destroy( bool doThrow )
00213 {
00214     YUI_CHECK_WIDGET( this );
00215 
00216     if ( isTopmostDialog() )
00217     {
00218         delete this;
00219 
00220         return true;
00221     }
00222     else
00223     {
00224         if ( doThrow )
00225             YUI_THROW( YUIDialogStackingOrderException() );
00226 
00227         return false;
00228     }
00229 }
00230 
00231 
00232 YDialogType
00233 YDialog::dialogType() const
00234 {
00235     return priv->dialogType;
00236 }
00237 
00238 
00239 bool
00240 YDialog::isMainDialog()
00241 {
00242     switch ( priv->dialogType )
00243     {
00244         case YMainDialog:       return true;
00245         case YWizardDialog:     return true;
00246         case YPopupDialog:      return false;
00247 
00248             // Intentionally omitting the 'default' case so the compiler can
00249             // catch unhandled enum values
00250     }
00251 
00252     /*NOTREACHED*/
00253     return false;
00254 }
00255 
00256 
00257 YDialogColorMode
00258 YDialog::colorMode() const
00259 {
00260     return priv->colorMode;
00261 }
00262 
00263 
00264 void
00265 YDialog::postponeShortcutCheck()
00266 {
00267     priv->shortcutCheckPostponed = true;
00268 }
00269 
00270 
00271 bool
00272 YDialog::shortcutCheckPostponed() const
00273 {
00274     return priv->shortcutCheckPostponed;
00275 }
00276 
00277 
00278 void
00279 YDialog::checkShortcuts( bool force )
00280 {
00281     if ( priv->shortcutCheckPostponed && ! force )
00282     {
00283         yuiDebug() << "Shortcut check postponed" << std::endl;
00284     }
00285     else
00286     {
00287 
00288         YShortcutManager shortcutManager( this );
00289         shortcutManager.checkShortcuts();
00290 
00291         priv->shortcutCheckPostponed = false;
00292     }
00293 }
00294 
00295 
00296 YPushButton *
00297 YDialog::defaultButton() const
00298 {
00299     return priv->defaultButton;
00300 }
00301 
00302 
00303 void
00304 YDialog::setDefaultButton( YPushButton * newDefaultButton )
00305 {
00306     if ( newDefaultButton && priv->defaultButton ) // already have one?
00307     {
00308         yuiError() << "Too many `opt(`default) PushButtons: ["
00309                    << newDefaultButton->label()
00310                    << "]" << std::endl;
00311     }
00312 
00313     priv->defaultButton = newDefaultButton;
00314 }
00315 
00316 
00317 void
00318 YDialog::setInitialSize()
00319 {
00320 #if VERBOSE_DIALOGS
00321     yuiDebug() << "Setting initial size for " << this << std::endl;
00322 #endif
00323 
00324     // Trigger geometry management
00325     setSize( preferredWidth(), preferredHeight() );
00326 }
00327 
00328 
00329 void
00330 YDialog::recalcLayout()
00331 {
00332     yuiDebug() << "Recalculating layout for " << this << std::endl;
00333 
00334     setSize( preferredWidth(), preferredHeight() );
00335 }
00336 
00337 
00338 YEvent *
00339 YDialog::waitForEvent( int timeout_millisec )
00340 {
00341     if ( ! isTopmostDialog() )
00342         YUI_THROW( YUIDialogStackingOrderException() );
00343 
00344     if ( timeout_millisec < 0 )
00345         timeout_millisec = 0;
00346 
00347     if ( ! isOpen() )
00348         open();
00349 
00350     if ( shortcutCheckPostponed() )
00351     {
00352         yuiError() << "Performing missing keyboard shortcut check now in "
00353                    << this << std::endl;
00354 
00355         checkShortcuts( true );
00356     }
00357 
00358     deleteEvent( priv->lastEvent );
00359     YEvent * event = 0;
00360 
00361     do
00362     {
00363         event = filterInvalidEvents( waitForEventInternal( timeout_millisec ) );
00364         event = callEventFilters( event );
00365 
00366         // If there was no event, if filterInvalidEvents() discarded an invalid
00367         // event, or if one of the event filters consumed an event, go back and
00368         // get the next event.
00369 
00370     } while ( ! event );
00371 
00372     priv->lastEvent = event;
00373 
00374     return event;
00375 }
00376 
00377 
00378 YEvent *
00379 YDialog::pollEvent()
00380 {
00381     if ( ! isTopmostDialog() )
00382         YUI_THROW( YUIDialogStackingOrderException() );
00383 
00384     if ( ! isOpen() )
00385         open();
00386 
00387     YEvent * event = filterInvalidEvents( pollEventInternal() );
00388 
00389     if ( event ) // Optimization (calling with 0 wouldn't hurt)
00390         event = callEventFilters( event );
00391 
00392     priv->lastEvent = event;
00393 
00394     // Nevermind if filterInvalidEvents() discarded an invalid event.
00395     // pollInput() is normally called very often (in a loop), and most of the
00396     // times it returns 0 anyway, so there is no need to care for just another
00397     // 0 that is returned in this exotic case.
00398 
00399     return event;
00400 }
00401 
00402 
00403 YEvent *
00404 YDialog::filterInvalidEvents( YEvent * event )
00405 {
00406     if ( ! event )
00407         return 0;
00408 
00409     YWidgetEvent * widgetEvent = dynamic_cast<YWidgetEvent *> (event);
00410 
00411     if ( widgetEvent && widgetEvent->widget() )
00412     {
00413         if ( ! widgetEvent->widget()->isValid() )
00414         {
00415             /**
00416              * Silently discard events from widgets that have become invalid.
00417              *
00418              * This may legitimately happen if some widget triggered an event yet
00419              * nobody cared for that event (i.e. called UserInput() or PollInput() )
00420              * and the widget has been destroyed meanwhile.
00421              **/
00422 
00423             // yuiDebug() << "Discarding event for widget that has become invalid" << std::endl;
00424 
00425             deleteEvent( widgetEvent );
00426             return 0;
00427         }
00428 
00429         if ( widgetEvent->widget()->findDialog() != this )
00430         {
00431             /**
00432              * Silently discard events from all but the current (topmost) dialog.
00433              *
00434              * This may happen even here even though the specific UI should have
00435              * taken care about that: Events may still be in the queue. They might
00436              * have been valid (i.e. belonged to the topmost dialog) when they
00437              * arrived, but maybe simply nobody has evaluated them.
00438              **/
00439 
00440             // Yes, really yuiDebug() - this may legitimately happen.
00441             yuiDebug() << "Discarding event from widget from foreign dialog" << std::endl;
00442 
00443 #if VERBOSE_DISCARDED_EVENTS
00444             yuiDebug() << "Expected: "   << this
00445                        << ", received: " << widgetEvent->widget()->findDialog()
00446                        << std::endl;
00447 
00448             yuiDebug() << "Event widget: "  << widgetEvent->widget() << std::endl;
00449             yuiDebug() << "From:" << std::endl;
00450             widgetEvent->widget()->findDialog()->dumpWidgetTree();
00451             yuiDebug() << "Current dialog:" << std::endl;
00452             dumpWidgetTree();
00453 #endif
00454 
00455             activate(); // try to force this dialog to the foreground
00456 
00457             deleteEvent( widgetEvent );
00458             return 0;
00459         }
00460 
00461     }
00462 
00463     return event;
00464 }
00465 
00466 
00467 void
00468 YDialog::deleteEvent( YEvent * event )
00469 {
00470     if ( event == priv->lastEvent )
00471         priv->lastEvent = 0;
00472 
00473     if ( event )
00474     {
00475         if ( event->isValid() )
00476         {
00477 #if VERBOSE_EVENTS
00478             yuiDebug() << "Deleting " << event << std::endl;
00479 #endif
00480             delete event;
00481         }
00482         else
00483         {
00484             yuiError() << "Attempt to delete invalid event " << event << std::endl;
00485         }
00486     }
00487 }
00488 
00489 
00490 YDialog *
00491 YDialog::currentDialog( bool doThrow )
00492 {
00493     if ( _dialogStack.empty() )
00494     {
00495         if ( doThrow )
00496             YUI_THROW( YUINoDialogException() );
00497         return 0;
00498     }
00499     else
00500         return _dialogStack.top();
00501 }
00502 
00503 
00504 bool
00505 YDialog::deleteTopmostDialog( bool doThrow )
00506 {
00507     if ( _dialogStack.empty() )
00508     {
00509         if ( doThrow )
00510             YUI_THROW( YUINoDialogException() );
00511     }
00512     else
00513     {
00514         delete _dialogStack.top();
00515     }
00516 
00517     return ! _dialogStack.empty();
00518 }
00519 
00520 
00521 void
00522 YDialog::deleteAllDialogs()
00523 {
00524     while ( ! _dialogStack.empty() )
00525     {
00526         delete _dialogStack.top();
00527     }
00528 }
00529 
00530 
00531 void
00532 YDialog::deleteTo( YDialog * targetDialog )
00533 {
00534     YUI_CHECK_WIDGET( targetDialog );
00535 
00536     while ( ! _dialogStack.empty() )
00537     {
00538         YDialog * dialog = _dialogStack.top();
00539 
00540         delete dialog;
00541 
00542         if ( dialog == targetDialog )
00543             return;
00544     }
00545 
00546     // If we ever get here, targetDialog was nowhere in the dialog stack.
00547 
00548     YUI_THROW( YUIDialogStackingOrderException() );
00549 }
00550 
00551 
00552 int
00553 YDialog::openDialogsCount()
00554 {
00555     return _dialogStack.size();
00556 }
00557 
00558 
00559 void
00560 YDialog::addEventFilter( YEventFilter * eventFilter )
00561 {
00562     YUI_CHECK_PTR( eventFilter );
00563 
00564     if ( find( priv->eventFilterList.begin(), priv->eventFilterList.end(),
00565                eventFilter ) != priv->eventFilterList.end() )
00566     {
00567         yuiError() << "event filter " << std::hex << eventFilter << std::dec
00568                    << " already added to " << this
00569                    << std::endl;
00570     }
00571     else
00572     {
00573 #if VERBOSE_DIALOGS
00574         yuiDebug() << "Adding event filter " << std::hex << eventFilter << std::dec << std::endl;
00575 #endif
00576         priv->eventFilterList.push_back( eventFilter );
00577     }
00578 }
00579 
00580 
00581 void
00582 YDialog::removeEventFilter( YEventFilter * eventFilter )
00583 {
00584     YUI_CHECK_PTR( eventFilter );
00585 
00586 #if VERBOSE_DIALOGS
00587     yuiDebug() << "Removing event filter " << std::hex << eventFilter << std::dec << std::endl;
00588 #endif
00589     priv->eventFilterList.remove( eventFilter );
00590 }
00591 
00592 
00593 YEvent *
00594 YDialog::callEventFilters( YEvent * event )
00595 {
00596     YEventFilterList::const_iterator it = priv->eventFilterList.begin();
00597 
00598     while ( it != priv->eventFilterList.end() && event )
00599     {
00600         YEvent * oldEvent = event;
00601         event = (*it)->filter( event );
00602 
00603         if ( oldEvent != event )     // event filter consumed or changed the old event?
00604             deleteEvent( oldEvent ); // get rid of the old one
00605 
00606         ++it;
00607     }
00608 
00609     return event;
00610 }
00611 
00612 
00613 void
00614 YDialog::showText( const std::string & text, bool useRichText )
00615 {
00616 
00617     // set help text dialog size to 80% of topmost dialog, respectively 45x15 (default)
00618 
00619     unsigned int dialogWidth  = 45;
00620     unsigned int dialogHeight = 15;
00621 
00622     if ( ! _dialogStack.empty() )
00623     {
00624         YDialog * dialog = _dialogStack.top();
00625         dialogWidth  = (unsigned int) ( (float) dialog->preferredWidth()  * 0.8 );
00626         dialogHeight = (unsigned int) ( (float) dialog->preferredHeight() * 0.8 );
00627     }
00628 
00629     // limit dialog to a reasonable size
00630     if ( dialogWidth > 80 || dialogHeight > 25 )
00631     {
00632         dialogWidth = 80;
00633         dialogHeight = 25;
00634     }
00635 
00636     try
00637     {
00638         YDialog     * dialog    = YUI::widgetFactory()->createPopupDialog();
00639         YAlignment  * minSize   = YUI::widgetFactory()->createMinSize( dialog, dialogWidth, dialogHeight );
00640         YLayoutBox  * vbox      = YUI::widgetFactory()->createVBox( minSize );
00641         YUI::widgetFactory()->createRichText( vbox, text, ! useRichText );
00642         YButtonBox  * buttonBox = YUI::widgetFactory()->createButtonBox( vbox );
00643         YPushButton * okButton  = YUI::widgetFactory()->createPushButton( buttonBox, "&OK" );
00644         okButton->setRole( YOKButton );
00645         okButton->setDefaultButton();
00646 
00647         dialog->waitForEvent();
00648         dialog->destroy();
00649     }
00650     catch ( YUIException exception )
00651     {
00652         // Don't let the application die just because help couldn't be displayed.
00653 
00654         YUI_CAUGHT( exception );
00655     }
00656 }
00657 
00658 
00659 bool
00660 YDialog::showHelpText( YWidget * widget )
00661 {
00662     std::string helpText;
00663 
00664     while ( widget )
00665     {
00666         if ( ! widget->helpText().empty() )
00667         {
00668             yuiDebug() << "Found help text for " << widget << std::endl;
00669             helpText = widget->helpText();
00670         }
00671 
00672         widget = widget->parent();
00673     }
00674 
00675     if ( ! helpText.empty() )
00676     {
00677         yuiMilestone() << "Showing help text" << std::endl;
00678         showText( helpText, true );
00679 
00680         yuiMilestone() << "Help dialog closed" << std::endl;
00681     }
00682     else // No help text
00683     {
00684         yuiWarning() << "No help text" << std::endl;
00685     }
00686 
00687     return ! helpText.empty();
00688 }
 All Classes Functions Variables Enumerations Friends