libyui
3.0.10
|
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 }