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: YButtonBox.cc 00020 00021 Author: Stefan Hundhammer <sh@suse.de> 00022 00023 /-*/ 00024 00025 00026 #include <algorithm> // max() 00027 #include <vector> 00028 #include <list> 00029 00030 #define YUILogComponent "ui" 00031 #include "YUILog.h" 00032 00033 #include "YButtonBox.h" 00034 #include "YPushButton.h" 00035 #include "YUI.h" 00036 #include "YApplication.h" 00037 00038 using std::max; 00039 00040 00041 YButtonBoxLayoutPolicy YButtonBox::_layoutPolicy = kdeLayoutPolicy(); 00042 YButtonBoxMargins YButtonBox::_defaultMargins; 00043 00044 00045 struct YButtonBoxPrivate 00046 { 00047 /** 00048 * Constructor 00049 **/ 00050 YButtonBoxPrivate() 00051 : sanityCheckRelaxed( false ) 00052 , margins( YButtonBox::_defaultMargins ) 00053 {} 00054 00055 // 00056 // Data members 00057 // 00058 00059 bool sanityCheckRelaxed; 00060 YButtonBoxMargins margins; 00061 }; 00062 00063 00064 00065 00066 YButtonBox::YButtonBox( YWidget * parent ) 00067 : YWidget( parent ) 00068 , priv( new YButtonBoxPrivate() ) 00069 { 00070 YUI_CHECK_NEW( priv ); 00071 setChildrenManager( new YWidgetChildrenManager( this ) ); 00072 } 00073 00074 00075 YButtonBox::~YButtonBox() 00076 { 00077 // NOP 00078 } 00079 00080 00081 void 00082 YButtonBox::setLayoutPolicy( const YButtonBoxLayoutPolicy & layoutPolicy ) 00083 { 00084 _layoutPolicy = layoutPolicy; 00085 } 00086 00087 00088 YButtonBoxLayoutPolicy 00089 YButtonBox::layoutPolicy() 00090 { 00091 return _layoutPolicy; 00092 } 00093 00094 00095 YButtonBoxLayoutPolicy 00096 YButtonBox::kdeLayoutPolicy() 00097 { 00098 YButtonBoxLayoutPolicy policy; 00099 00100 policy.buttonOrder = YKDEButtonOrder; 00101 policy.equalSizeButtons = false; 00102 policy.alignment[ YD_HORIZ ] = YAlignCenter; 00103 policy.alignment[ YD_VERT ] = YAlignBegin; // Align top 00104 00105 return policy; 00106 } 00107 00108 00109 YButtonBoxLayoutPolicy 00110 YButtonBox::gnomeLayoutPolicy() 00111 { 00112 YButtonBoxLayoutPolicy policy; 00113 00114 policy.buttonOrder = YGnomeButtonOrder; 00115 policy.equalSizeButtons = true; 00116 policy.alignment[ YD_HORIZ ] = YAlignEnd; // Align right 00117 policy.alignment[ YD_VERT ] = YAlignBegin; // Align top 00118 policy.addExcessSpaceToHelpButtonExtraMargin = true; 00119 00120 return policy; 00121 } 00122 00123 00124 void 00125 YButtonBox::setDefaultMargins( const YButtonBoxMargins & margins ) 00126 { 00127 _defaultMargins = margins; 00128 } 00129 00130 00131 YButtonBoxMargins 00132 YButtonBox::defaultMargins() 00133 { 00134 return _defaultMargins; 00135 } 00136 00137 00138 void 00139 YButtonBox::setMargins( const YButtonBoxMargins & margins ) 00140 { 00141 priv->margins = margins; 00142 } 00143 00144 00145 YButtonBoxMargins 00146 YButtonBox::margins() const 00147 { 00148 return priv->margins; 00149 } 00150 00151 00152 void 00153 YButtonBox::setSize( int newWidth, int newHeight ) 00154 { 00155 sanityCheck(); 00156 doLayout( newWidth, newHeight ); 00157 } 00158 00159 00160 void 00161 YButtonBox::doLayout( int width, int height ) 00162 { 00163 std::vector<YPushButton *> buttons = buttonsByButtonOrder(); 00164 00165 if ( buttons.empty() ) 00166 return; 00167 00168 YPushButton * helpButton = findButton( YHelpButton ); 00169 00170 int prefWidth = preferredWidth(); 00171 int prefHeight = preferredHeight(); 00172 YButtonBoxMargins margins = priv->margins; 00173 bool equalSizeButtons = _layoutPolicy.equalSizeButtons; 00174 00175 00176 // 00177 // Horizontal layout 00178 // 00179 00180 if ( width < prefWidth ) // Not enough horizontal space 00181 { 00182 if ( equalSizeButtons ) 00183 { 00184 int buttonWidthWithoutMargins = maxChildSize( YD_HORIZ ) * buttons.size(); 00185 00186 if ( width < buttonWidthWithoutMargins ) 00187 { 00188 // The missing width can't be compensated by reducing margins and spacings. 00189 // Try not enforcing the same width: 00190 // 00191 // If one button is very much larger than most others, that one 00192 // button will greatly distort the overall layout. If we simply cut 00193 // some pixels off every button, for sure that one very large 00194 // button will become unreadable. So let's try first with buttons 00195 // getting just the size they really need. 00196 // 00197 // Of course, we might still have cut some pixels off all buttons 00198 // if that also is too wide, but in that case we can't do very much 00199 // anyway. 00200 00201 equalSizeButtons = false; 00202 prefWidth = preferredWidth( equalSizeButtons ); 00203 } 00204 } 00205 } 00206 00207 int widthLoss = 0; 00208 00209 if ( width < prefWidth ) // Not enough horizontal space 00210 { 00211 // Try reducing margins 00212 00213 int missing = prefWidth - width; 00214 00215 if ( missing <= margins.left + margins.right ) 00216 { 00217 margins.left -= missing / 2; 00218 margins.right -= missing / 2; 00219 missing = 0; 00220 } 00221 else 00222 { 00223 missing -= margins.left; 00224 missing -= margins.right; 00225 margins.left = 0; 00226 margins.right = 0; 00227 } 00228 00229 if ( missing > 0 && buttons.size() > 1 ) 00230 { 00231 // Try reducing spacing 00232 00233 int totalSpacing = ( buttons.size() - 1 ) * margins.spacing; 00234 00235 if ( missing <= totalSpacing ) 00236 { 00237 totalSpacing -= missing; 00238 margins.spacing = totalSpacing / ( buttons.size() - 1 ); 00239 missing = 0; 00240 } 00241 else 00242 { 00243 missing -= totalSpacing; 00244 margins.spacing = 0; 00245 } 00246 } 00247 00248 if ( missing > 0 && helpButton ) 00249 { 00250 // Try reducing help button extra spacing 00251 00252 if ( missing <= margins.helpButtonExtraSpacing ) 00253 { 00254 margins.helpButtonExtraSpacing -= missing; 00255 missing = 0; 00256 } 00257 else 00258 { 00259 missing -= margins.helpButtonExtraSpacing; 00260 margins.helpButtonExtraSpacing = 0; 00261 } 00262 } 00263 00264 00265 // Distribute missing width among all buttons 00266 00267 if ( missing > 0 ) 00268 widthLoss = missing / buttons.size(); 00269 } 00270 00271 if ( width > prefWidth ) // Excess horizontal space 00272 { 00273 int excessWidth = width - prefWidth; 00274 00275 if ( _layoutPolicy.addExcessSpaceToHelpButtonExtraMargin && helpButton ) 00276 { 00277 margins.helpButtonExtraSpacing += excessWidth; 00278 } 00279 else 00280 { 00281 switch ( _layoutPolicy.alignment[ YD_HORIZ ] ) 00282 { 00283 case YAlignCenter: 00284 margins.left += excessWidth / 2; 00285 margins.right += excessWidth / 2; 00286 break; 00287 00288 case YAlignBegin: 00289 case YAlignUnchanged: 00290 margins.right += excessWidth; 00291 break; 00292 00293 case YAlignEnd: 00294 margins.left += excessWidth; 00295 break; 00296 } 00297 } 00298 } 00299 00300 00301 // 00302 // Vertical layout 00303 // 00304 00305 int buttonHeight = maxChildSize( YD_VERT ); 00306 00307 if ( height < prefHeight ) // Not enough vertical space 00308 { 00309 // Reduce margins 00310 00311 int missing = prefHeight - height; 00312 00313 if ( missing < margins.top + margins.bottom ) 00314 { 00315 margins.top -= missing / 2; 00316 margins.bottom -= missing / 2; 00317 } 00318 else 00319 { 00320 margins.top = 0; 00321 margins.bottom = 0; 00322 } 00323 } 00324 00325 if ( height < buttonHeight ) 00326 { 00327 buttonHeight = height; 00328 } 00329 00330 int y_pos = margins.top; 00331 00332 if ( height > prefHeight ) // Excess vertical space 00333 { 00334 // Distribute excess vertical space 00335 00336 int excessHeight = height - buttonHeight; 00337 excessHeight -= margins.top; 00338 excessHeight -= margins.bottom; 00339 00340 switch ( _layoutPolicy.alignment[ YD_VERT ] ) 00341 { 00342 case YAlignBegin: // Align top 00343 case YAlignUnchanged: 00344 break; 00345 00346 case YAlignCenter: 00347 y_pos += excessHeight / 2; 00348 break; 00349 00350 case YAlignEnd: // Align bottom 00351 y_pos += excessHeight; 00352 break; 00353 } 00354 } 00355 00356 00357 // 00358 // Set child widget positions and sizes from left to right 00359 // 00360 00361 int x_pos = margins.left; 00362 int buttonWidth = 0; 00363 00364 if ( equalSizeButtons ) 00365 { 00366 buttonWidth = maxChildSize( YD_HORIZ ); 00367 buttonWidth -= widthLoss; 00368 } 00369 00370 bool reverseLayout = YUI::app()->reverseLayout(); 00371 00372 for ( std::vector<YPushButton *>::iterator it = buttons.begin(); 00373 it != buttons.end(); 00374 ++it ) 00375 { 00376 YPushButton * button = *it; 00377 00378 // Extra spacing left of [Help] button 00379 // (Only if this isn't the first button) 00380 00381 if ( button == helpButton && button != buttons.front() ) 00382 x_pos += margins.helpButtonExtraSpacing; 00383 00384 if ( ! equalSizeButtons ) 00385 { 00386 buttonWidth = button->preferredWidth(); 00387 buttonWidth -= widthLoss; 00388 } 00389 00390 button->setSize( buttonWidth, buttonHeight ); 00391 00392 if ( reverseLayout ) 00393 moveChild( button, width - x_pos - buttonWidth, y_pos ); 00394 else 00395 moveChild( button, x_pos, y_pos ); 00396 00397 x_pos += buttonWidth; 00398 x_pos += margins.spacing; 00399 00400 00401 // Extra spacing right of [Help] button 00402 00403 if ( button == helpButton ) 00404 x_pos += margins.helpButtonExtraSpacing; 00405 } 00406 } 00407 00408 00409 std::vector<YPushButton *> 00410 YButtonBox::buttonsByButtonOrder() 00411 { 00412 std::vector<YPushButton *> specialButtons( YMaxButtonRole, (YPushButton *) 0 ); 00413 std::vector<YPushButton *> customButtons; 00414 00415 for ( YWidgetListConstIterator it = childrenBegin(); 00416 it != childrenEnd(); 00417 ++it ) 00418 { 00419 YPushButton * button = dynamic_cast<YPushButton *> (*it); 00420 00421 if ( ! button ) 00422 YUI_THROW( YUIInvalidChildException<YWidget>( this, *it ) ); 00423 00424 switch ( button->role() ) 00425 { 00426 case YOKButton: 00427 case YCancelButton: 00428 case YApplyButton: 00429 case YHelpButton: 00430 00431 if ( specialButtons[ button->role() ] ) // Only one of each of those is allowed 00432 { 00433 std::string msg = "Multiple buttons with that role ["; 00434 msg += button->debugLabel(); 00435 msg += "]"; 00436 YUI_THROW( YUIButtonRoleMismatchException( msg ) ); 00437 } 00438 else 00439 { 00440 specialButtons[ button->role() ] = button; 00441 } 00442 break; 00443 00444 case YCustomButton: 00445 customButtons.push_back( button ); 00446 break; 00447 00448 case YMaxButtonRole: 00449 YUI_THROW( YUIButtonRoleMismatchException( "Invalid button role" ) ); 00450 break; 00451 } 00452 } 00453 00454 std::vector<YPushButton *> buttons; 00455 00456 if ( _layoutPolicy.buttonOrder == YKDEButtonOrder ) 00457 { 00458 if ( specialButtons[ YOKButton ] ) buttons.push_back( specialButtons[ YOKButton ] ); 00459 if ( specialButtons[ YApplyButton ] ) buttons.push_back( specialButtons[ YApplyButton ] ); 00460 if ( specialButtons[ YCancelButton ] ) buttons.push_back( specialButtons[ YCancelButton ] ); 00461 00462 buttons.insert( buttons.end(), customButtons.begin(), customButtons.end() ); 00463 00464 if ( specialButtons[ YHelpButton ] ) buttons.push_back( specialButtons[ YHelpButton ] ); 00465 } 00466 else // YGnomeButtonOrder 00467 { 00468 if ( specialButtons[ YHelpButton ] ) buttons.push_back( specialButtons[ YHelpButton ] ); 00469 00470 buttons.insert( buttons.end(), customButtons.begin(), customButtons.end() ); 00471 00472 if ( specialButtons[ YApplyButton ] ) buttons.push_back( specialButtons[ YApplyButton ] ); 00473 if ( specialButtons[ YCancelButton ] ) buttons.push_back( specialButtons[ YCancelButton ] ); 00474 if ( specialButtons[ YOKButton ] ) buttons.push_back( specialButtons[ YOKButton ] ); 00475 } 00476 00477 return buttons; 00478 } 00479 00480 00481 00482 int 00483 YButtonBox::preferredWidth( bool equalSizeButtons ) 00484 { 00485 if ( childrenCount() < 1 ) 00486 return 0; 00487 00488 int width = ( childrenCount() - 1 ) * priv->margins.spacing; 00489 00490 if ( equalSizeButtons ) 00491 width += maxChildSize( YD_HORIZ ) * childrenCount(); 00492 else 00493 width += totalChildrenWidth(); 00494 00495 width += priv->margins.left; 00496 width += priv->margins.right; 00497 00498 if ( priv->margins.helpButtonExtraSpacing ) 00499 { 00500 if ( findButton( YHelpButton ) ) 00501 width += priv->margins.helpButtonExtraSpacing; 00502 } 00503 00504 return width; 00505 } 00506 00507 00508 int 00509 YButtonBox::preferredWidth() 00510 { 00511 return preferredWidth( _layoutPolicy.equalSizeButtons ); 00512 } 00513 00514 00515 int 00516 YButtonBox::preferredHeight() 00517 { 00518 int height = maxChildSize( YD_VERT ); 00519 height += priv->margins.top; 00520 height += priv->margins.bottom; 00521 00522 return height; 00523 } 00524 00525 00526 int 00527 YButtonBox::maxChildSize( YUIDimension dim ) const 00528 { 00529 int maxSize = 0; 00530 00531 for ( YWidgetListConstIterator it = childrenBegin(); 00532 it != childrenEnd(); 00533 ++it ) 00534 { 00535 maxSize = max( maxSize, (*it)->preferredSize( dim ) ); 00536 } 00537 00538 return maxSize; 00539 } 00540 00541 00542 int 00543 YButtonBox::totalChildrenWidth() const 00544 { 00545 int totalWidth = 0; 00546 00547 for ( YWidgetListConstIterator it = childrenBegin(); 00548 it != childrenEnd(); 00549 ++it ) 00550 { 00551 totalWidth += (*it)->preferredWidth(); 00552 } 00553 00554 return totalWidth; 00555 } 00556 00557 00558 bool 00559 YButtonBox::stretchable( YUIDimension dimension ) const 00560 { 00561 switch ( dimension ) 00562 { 00563 case YD_HORIZ: return true; 00564 case YD_VERT : return false; 00565 00566 default: 00567 YUI_THROW( YUIInvalidDimensionException() ); 00568 return 0; 00569 } 00570 } 00571 00572 00573 YPushButton * 00574 YButtonBox::findButton( YButtonRole role ) 00575 { 00576 for ( YWidgetListConstIterator it = childrenBegin(); 00577 it != childrenEnd(); 00578 ++it ) 00579 { 00580 YPushButton * button = dynamic_cast<YPushButton *> (*it); 00581 00582 if ( button && button->role() == role ) 00583 return button; 00584 } 00585 00586 return 0; 00587 } 00588 00589 00590 void 00591 YButtonBox::setSanityCheckRelaxed( bool relaxed ) 00592 { 00593 priv->sanityCheckRelaxed = relaxed; 00594 } 00595 00596 00597 bool 00598 YButtonBox::sanityCheckRelaxed() const 00599 { 00600 return priv->sanityCheckRelaxed; 00601 } 00602 00603 00604 void 00605 YButtonBox::sanityCheck() 00606 { 00607 YPushButton * okButton = 0; 00608 YPushButton * cancelButton = 0; 00609 00610 for ( YWidgetListConstIterator it = childrenBegin(); 00611 it != childrenEnd(); 00612 ++it ) 00613 { 00614 YPushButton * button = dynamic_cast<YPushButton *> (*it); 00615 00616 if ( ! button ) 00617 YUI_THROW( YUIInvalidChildException<YWidget>( this, *it ) ); 00618 00619 switch ( button->role() ) 00620 { 00621 case YOKButton: 00622 00623 if ( okButton ) 00624 YUI_THROW( YUIButtonRoleMismatchException( "Multiple buttons with role [OK]" ) ); 00625 else 00626 okButton = button; 00627 break; 00628 00629 00630 case YCancelButton: 00631 00632 if ( cancelButton ) 00633 YUI_THROW( YUIButtonRoleMismatchException( "Multiple buttons with role [Cancel]" ) ); 00634 else 00635 cancelButton = button; 00636 break; 00637 00638 00639 default: 00640 break; 00641 } 00642 } 00643 00644 if ( childrenCount() > 1 && ! sanityCheckRelaxed() ) 00645 { 00646 if ( ! okButton || ! cancelButton ) 00647 YUI_THROW( YUIButtonRoleMismatchException( "Button role mismatch: Must have both [OK] and [Cancel] roles" ) ); 00648 } 00649 }