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: YLayoutBox.cc 00020 00021 Author: Stefan Hundhammer <sh@suse.de> 00022 00023 /-*/ 00024 00025 00026 #include <iomanip> // std::setw() 00027 #include <algorithm> // std::max() 00028 00029 #define YUILogComponent "ui-layout" 00030 #include "YUILog.h" 00031 00032 #include "YLayoutBox.h" 00033 #include "YAlignment.h" 00034 #include "YSpacing.h" 00035 #include "YUI.h" 00036 #include "YApplication.h" 00037 00038 using std::endl; 00039 using std::setw; 00040 using std::max; 00041 using std::boolalpha; 00042 00043 struct YLayoutBoxPrivate 00044 { 00045 /** 00046 * Constructor 00047 **/ 00048 YLayoutBoxPrivate( YUIDimension prim ) 00049 : primary( prim ) 00050 , secondary( prim == YD_HORIZ ? YD_VERT : YD_HORIZ ) 00051 , debugLayout( false ) 00052 {} 00053 00054 // 00055 // Data members 00056 // 00057 00058 YUIDimension primary; 00059 YUIDimension secondary; 00060 bool debugLayout; 00061 }; 00062 00063 00064 00065 00066 YLayoutBox::YLayoutBox( YWidget * parent, YUIDimension primaryDimension ) 00067 : YWidget( parent ) 00068 , priv( new YLayoutBoxPrivate( primaryDimension ) ) 00069 { 00070 YUI_CHECK_NEW( priv ); 00071 setChildrenManager( new YWidgetChildrenManager( this ) ); 00072 } 00073 00074 00075 YLayoutBox::~YLayoutBox() 00076 { 00077 // NOP 00078 } 00079 00080 00081 YUIDimension 00082 YLayoutBox::primary() const 00083 { 00084 return priv->primary; 00085 } 00086 00087 00088 YUIDimension 00089 YLayoutBox::secondary() const 00090 { 00091 return priv->secondary; 00092 } 00093 00094 00095 bool 00096 YLayoutBox::debugLayout() const 00097 { 00098 return priv->debugLayout; 00099 } 00100 00101 void 00102 YLayoutBox::setDebugLayout( bool deb ) 00103 { 00104 priv->debugLayout = deb; 00105 00106 yuiDebug() << "YLayoutBox: Layout debugging: " << boolalpha << deb << endl; 00107 } 00108 00109 00110 int 00111 YLayoutBox::preferredSize( YUIDimension dimension ) 00112 { 00113 if ( dimension == secondary() ) // the easy case first: secondary dimension 00114 { 00115 return childrenMaxPreferredSize( dimension ); 00116 } 00117 else 00118 { 00119 /* 00120 * In the primary dimension things are much more complicated: We want to 00121 * honor any weights specified under all circumstances. So we first 00122 * need to determine the "dominating child" - the widget that determines the 00123 * overall size with respect to its weight in that dimension. Once we 00124 * know that, we need to stretch all other weighted children accordingly 00125 * so the weight ratios are respected. 00126 * 00127 * As a final step, the preferred sizes of all children that don't have 00128 * a weight attached are summed up. 00129 */ 00130 00131 int size = 0L; 00132 00133 // Search for the dominating child 00134 YWidget * dominatingChild = findDominatingChild(); 00135 00136 if ( dominatingChild ) 00137 { 00138 // Calculate size of all weighted widgets. 00139 00140 size = dominatingChild->preferredSize( primary() ) 00141 * childrenTotalWeight( primary() ) 00142 / dominatingChild->weight( primary() ); 00143 00144 // Maintain this order of calculation in order to minimize integer 00145 // rounding errors! 00146 } 00147 00148 00149 // Add up the size of all non-weighted children; 00150 // they will get their respective preferred size. 00151 00152 size += totalNonWeightedChildrenPreferredSize( primary() ); 00153 00154 return size; 00155 } 00156 } 00157 00158 00159 int YLayoutBox::preferredWidth() 00160 { 00161 return preferredSize( YD_HORIZ ); 00162 } 00163 00164 00165 int YLayoutBox::preferredHeight() 00166 { 00167 return preferredSize( YD_VERT ); 00168 } 00169 00170 00171 /* 00172 * Search for the "dominating child" widget. 00173 * 00174 * This is the widget that determines the overall size of the 00175 * container with respect to all children's weights: It is the child 00176 * with the maximum ratio of preferred size and weight. All other 00177 * weighted children need to be stretched accordingly so the weight 00178 * ratios can be maintained. 00179 * 00180 * Returns 0 if there is no dominating child, i.e. if there are only 00181 * non-weighted children. 00182 */ 00183 00184 YWidget * 00185 YLayoutBox::findDominatingChild() 00186 { 00187 YWidget * dominatingChild = 0; 00188 double dominatingRatio = 0.0; 00189 double ratio; 00190 00191 for ( YWidgetListConstIterator it = childrenBegin(); 00192 it != childrenEnd(); 00193 ++it ) 00194 { 00195 YWidget * child = *it; 00196 00197 if ( child->weight( primary() ) != 0 ) // avoid division by zero 00198 { 00199 ratio = ( ( double ) child->preferredSize( primary() ) ) 00200 / child->weight( primary() ); 00201 00202 if ( ratio > dominatingRatio ) // we have a new dominating child 00203 { 00204 dominatingChild = child; 00205 dominatingRatio = ratio; 00206 } 00207 } 00208 } 00209 00210 00211 if ( debugLayout() ) 00212 { 00213 if ( dominatingChild ) 00214 { 00215 yuiDebug() << "Found dominating child: " << dominatingChild 00216 << " - preferred size: " << dominatingChild->preferredSize( primary() ) 00217 << ", weight: " << dominatingChild->weight( primary() ) 00218 << endl; 00219 } 00220 else 00221 { 00222 yuiDebug() << "This layout doesn't have a dominating child." << endl; 00223 } 00224 } 00225 00226 return dominatingChild; 00227 } 00228 00229 00230 int 00231 YLayoutBox::childrenMaxPreferredSize( YUIDimension dimension ) 00232 { 00233 int maxPreferredSize = 0L; 00234 00235 for ( YWidgetListConstIterator it = childrenBegin(); 00236 it != childrenEnd(); 00237 ++it ) 00238 { 00239 maxPreferredSize = std::max( (*it)->preferredSize( dimension ), maxPreferredSize ); 00240 } 00241 00242 return maxPreferredSize; 00243 } 00244 00245 00246 int 00247 YLayoutBox::childrenTotalWeight( YUIDimension dimension ) 00248 { 00249 int totalWeight = 0L; 00250 00251 for ( YWidgetListConstIterator it = childrenBegin(); 00252 it != childrenEnd(); 00253 ++it ) 00254 { 00255 totalWeight += (*it)->weight( dimension ); 00256 } 00257 00258 return totalWeight; 00259 } 00260 00261 00262 int 00263 YLayoutBox::totalNonWeightedChildrenPreferredSize( YUIDimension dimension ) 00264 { 00265 int size = 0L; 00266 00267 for ( YWidgetListConstIterator it = childrenBegin(); 00268 it != childrenEnd(); 00269 ++it ) 00270 { 00271 if ( ! (*it)->hasWeight( dimension ) ) // non-weighted children only 00272 size += (*it)->preferredSize( dimension ); 00273 } 00274 00275 return size; 00276 } 00277 00278 00279 int 00280 YLayoutBox::countNonWeightedChildren( YUIDimension dimension ) 00281 { 00282 int count = 0; 00283 00284 for ( YWidgetListConstIterator it = childrenBegin(); 00285 it != childrenEnd(); 00286 ++it ) 00287 { 00288 if ( ! (*it)->hasWeight( dimension ) ) 00289 count++; 00290 } 00291 00292 return count; 00293 } 00294 00295 00296 int 00297 YLayoutBox::countStretchableChildren( YUIDimension dimension ) 00298 { 00299 int count = 0; 00300 00301 for ( YWidgetListConstIterator it = childrenBegin(); 00302 it != childrenEnd(); 00303 ++it ) 00304 { 00305 if ( ! (*it)->hasWeight( dimension ) && 00306 (*it)->stretchable( dimension ) ) 00307 count++; 00308 } 00309 00310 return count; 00311 } 00312 00313 00314 int 00315 YLayoutBox::countLayoutStretchChildren( YUIDimension dimension ) 00316 { 00317 int count = 0; 00318 00319 for ( YWidgetListConstIterator it = childrenBegin(); 00320 it != childrenEnd(); 00321 ++it ) 00322 { 00323 if ( ! (*it)->hasWeight( dimension ) && 00324 isLayoutStretch( *it, dimension ) ) 00325 count++; 00326 } 00327 00328 return count; 00329 } 00330 00331 00332 bool 00333 YLayoutBox::isLayoutStretch( YWidget * child, YUIDimension dimension ) 00334 { 00335 if ( ! child ) 00336 return false; 00337 00338 YSpacing * spacing = dynamic_cast<YSpacing *> (child); 00339 00340 if ( spacing && spacing->stretchable( dimension ) ) 00341 return true; 00342 else 00343 return false; 00344 } 00345 00346 00347 00348 bool 00349 YLayoutBox::stretchable( YUIDimension dimension ) const 00350 { 00351 for ( YWidgetListConstIterator it = childrenBegin(); 00352 it != childrenEnd(); 00353 ++it ) 00354 { 00355 if ( (*it)->stretchable( dimension ) || 00356 (*it)->hasWeight( dimension ) ) 00357 return true; 00358 } 00359 00360 return false; 00361 } 00362 00363 00364 void 00365 YLayoutBox::setSize( int newWidth, int newHeight ) 00366 { 00367 int count = childrenCount(); 00368 sizeVector widths ( count ); 00369 sizeVector heights ( count ); 00370 posVector x_pos ( count ); 00371 posVector y_pos ( count ); 00372 00373 if ( primary() == YD_HORIZ ) 00374 { 00375 calcPrimaryGeometry ( newWidth, widths, x_pos ); 00376 calcSecondaryGeometry( newHeight, heights, y_pos ); 00377 } 00378 else 00379 { 00380 calcPrimaryGeometry ( newHeight, heights, y_pos ); 00381 calcSecondaryGeometry( newWidth, widths, x_pos ); 00382 } 00383 00384 if ( YUI::app()->reverseLayout() ) 00385 { 00386 // Mirror the widget X geometry for languages with left-to-right 00387 // writing direction (Arabic, Hebrew). 00388 00389 for ( int i = 0; i < childrenCount(); i++ ) 00390 x_pos[i] = newWidth - x_pos[i] - widths[i]; 00391 } 00392 00393 doResize( widths, heights, x_pos, y_pos ); 00394 } 00395 00396 00397 void 00398 YLayoutBox::calcPrimaryGeometry( int newSize, 00399 sizeVector & childSize, 00400 posVector & childPos ) 00401 { 00402 int pos = 0L; 00403 int distributableSize = newSize - totalNonWeightedChildrenPreferredSize( primary() ); 00404 00405 if ( distributableSize >= 0L ) 00406 { 00407 // The (hopefully) normal case: There is enough space. 00408 // The non-weighted children will get their preferred sizes, 00409 // the rest will be distributed among the weighted children 00410 // according to their respective weight ratios. 00411 00412 int nonWeightedExtra = 0L; 00413 int totalWeight = childrenTotalWeight( primary() ); 00414 int rubberBands = 0; 00415 int rubberBandExtra = 0L; 00416 00417 if ( totalWeight <= 0 ) 00418 { 00419 // If there are no weighted children, equally divide the 00420 // extra space among the stretchable children (if any). 00421 // This includes any layout stretch spaces. 00422 00423 int stretchableChildren = countStretchableChildren( primary() ); 00424 00425 if ( stretchableChildren > 0 ) // avoid division by zero 00426 nonWeightedExtra = distributableSize / stretchableChildren; 00427 } 00428 else 00429 { 00430 // If there are weighted children and there are rubber band 00431 // widgets, equally divide any surplus space (i.e. space that 00432 // exceeds the weighted children's preferred sizes with respect to 00433 // their weights) between the rubber bands. 00434 // 00435 // This offers an easy way to make nicely even spaced buttons 00436 // of equal size: Give all buttons a weight of 1 and insert a 00437 // stretch (without weight!) between each. 00438 00439 int surplusSize = newSize - preferredSize( primary() ); 00440 00441 if ( surplusSize > 0L ) 00442 { 00443 rubberBands = countLayoutStretchChildren( primary() ); 00444 00445 if ( rubberBands > 0 ) 00446 { 00447 rubberBandExtra = surplusSize / rubberBands; 00448 distributableSize -= rubberBandExtra * rubberBands; 00449 } 00450 } 00451 } 00452 00453 if ( debugLayout() ) 00454 { 00455 yuiDebug() << "Distributing extra space" << endl; 00456 yuiDebug() << "\tnew size: " << newSize << endl; 00457 yuiDebug() << "\tdistributable size: " << distributableSize << endl; 00458 yuiDebug() << "\trubber band extra: " << rubberBandExtra << endl; 00459 yuiDebug() << "\trubber bands: " << rubberBands << endl; 00460 yuiDebug() << "\ttotal weight: " << totalWeight << endl; 00461 yuiDebug() << "\tnon weighted extra: " << nonWeightedExtra << endl; 00462 } 00463 00464 int i=0; 00465 for ( YWidgetListConstIterator it = childrenBegin(); 00466 it != childrenEnd(); 00467 ++it, i++ ) 00468 { 00469 YWidget * child = *it; 00470 00471 if ( child->hasWeight( primary() ) ) 00472 { 00473 // Weighted children will get their share. 00474 00475 childSize[i] = distributableSize * child->weight( primary() ) / totalWeight; 00476 00477 if ( childSize[i] < child->preferredSize( primary() ) ) 00478 { 00479 yuiDebug() << "Layout running out of space: " 00480 << "Resizing child widget #" << i << " ("<< child 00481 << ") below its preferred size of " << child->preferredSize( primary() ) 00482 << " to " << childSize[i] 00483 << endl; 00484 } 00485 } 00486 else 00487 { 00488 // Non-weighted children will get their preferred size. 00489 00490 childSize[i] = child->preferredSize( primary() ); 00491 00492 00493 if ( child->stretchable( primary() ) ) 00494 { 00495 // If there are only non-weighted children (and only then), 00496 // the stretchable children will get their fair share of the 00497 // extra space. 00498 00499 childSize[i] += nonWeightedExtra; 00500 } 00501 00502 if ( isLayoutStretch( child, primary() ) ) 00503 { 00504 // If there is more than the total preferred size and there 00505 // are rubber bands, distribute surplus space among the 00506 // rubber bands. 00507 00508 childSize[i] += rubberBandExtra; 00509 } 00510 } 00511 00512 childPos[i] = pos; 00513 pos += childSize[i]; 00514 } 00515 } 00516 else // The pathological case: Not enough space. 00517 { 00518 /* 00519 * We're in deep shit. 00520 * 00521 * Not only is there nothing to distribute among the weighted children, 00522 * we also need to resize the non-weighted children below their preferred 00523 * sizes. Let's at least treat them equally bad - divide the lost space 00524 * among them as fair as possible. 00525 */ 00526 00527 int tooSmall = -distributableSize; 00528 int loserCount = 0; 00529 int totalMargins = 0L; 00530 int remainingMargins = 0L; 00531 double marginScale = 0.0; 00532 00533 yuiDebug() << "Not enough space: " << tooSmall << " too small - check the layout!" << endl; 00534 00535 00536 // Maybe some of the children are YAlignments with margins that can be reduced 00537 00538 for ( YWidgetListConstIterator it = childrenBegin(); 00539 it != childrenEnd(); 00540 ++it ) 00541 { 00542 if ( ! (*it)->hasWeight( primary() ) ) // children with weights will get nothing anyway 00543 { 00544 YAlignment * alignment = dynamic_cast<YAlignment *> (*it); 00545 00546 if ( alignment ) 00547 { 00548 totalMargins += alignment->totalMargins( primary() ); 00549 yuiDebug() << "Found alignment with margins" << endl; 00550 } 00551 } 00552 } 00553 00554 00555 if ( totalMargins > tooSmall ) // We can make up for insufficient space just by reducing margins 00556 { 00557 remainingMargins = totalMargins - tooSmall; 00558 tooSmall = 0L; 00559 marginScale = ( (double) remainingMargins ) / totalMargins; 00560 00561 yuiDebug() << "Making up for insufficient space by reducing margins to " 00562 << 100.0 * marginScale << "% - " 00563 << remainingMargins << " left for margins" 00564 << endl; 00565 } 00566 else // Reducing all margins to zero still doesn't solve the problem 00567 { 00568 tooSmall -= totalMargins; 00569 00570 yuiDebug() << "Reducing all margins to 0, but still " << tooSmall << " too small" << endl; 00571 } 00572 00573 00574 // Calculate initial sizes 00575 00576 int i=0; 00577 for ( YWidgetListConstIterator it = childrenBegin(); 00578 it != childrenEnd(); 00579 ++it, i++ ) 00580 { 00581 if ( ! (*it)->hasWeight( primary() ) ) 00582 { 00583 loserCount++; 00584 childSize[i] = (*it)->preferredSize( primary() ); 00585 00586 YAlignment * alignment = dynamic_cast<YAlignment *> (*it); 00587 00588 if ( alignment ) // Alignment widgets may have margins we can reduce 00589 { 00590 int margins = alignment->totalMargins( primary() ); 00591 childSize[i] -= margins; // Strip off original margin 00592 00593 if ( remainingMargins > 0 ) // Anything left to redistribute? 00594 { 00595 margins = (int) marginScale * margins; // Scale down margin 00596 childSize[i] += margins; // Add the scaled-down margin 00597 remainingMargins -= margins; // Deduct from redistributable margin 00598 } 00599 } 00600 } 00601 else 00602 { 00603 // Weighted children will get nothing anyway if there is nothing 00604 // to distribute. 00605 00606 childSize[i] = 0L; 00607 } 00608 } 00609 00610 00611 // Distribute loss 00612 00613 int oldTooSmall = tooSmall; 00614 int oldLoserCount = loserCount; 00615 while ( tooSmall > 0 && loserCount > 0 ) 00616 { 00617 if ( debugLayout() ) 00618 { 00619 yuiWarning() << "Distributing insufficient space of " << tooSmall 00620 << " among " << loserCount << " losers" 00621 << endl; 00622 } 00623 00624 int dividedLoss = std::max( tooSmall / loserCount, 1 ); 00625 00626 int i=0; 00627 for ( YWidgetListConstIterator it = childrenBegin(); 00628 it != childrenEnd() && tooSmall > 0; 00629 ++it, i++ ) 00630 { 00631 if ( childSize[i] < dividedLoss ) 00632 { 00633 // This widget is too small to take its share of the 00634 // loss. We'll have to re-distribute the rest of the 00635 // loss among the others. Arrgh. 00636 00637 if ( childSize[i] > 0L ) 00638 { 00639 tooSmall -= childSize[i]; 00640 childSize[i] = 0L; 00641 loserCount--; 00642 00643 if ( loserCount > 0 ) 00644 dividedLoss = std::max( tooSmall / loserCount, 1 ); 00645 } 00646 } 00647 else 00648 { 00649 childSize[i] -= dividedLoss; 00650 tooSmall -= dividedLoss; 00651 } 00652 00653 if ( debugLayout() ) 00654 { 00655 YWidget * child = *it; 00656 00657 yuiWarning() << "child #" << i <<" ( " << child 00658 << " ) will get " << childSize[i] 00659 << " - " << child->preferredSize( primary() ) - childSize[i] << " too small" 00660 << " (preferred size: "<< child->preferredSize( primary() ) 00661 << ", weight: " << child->weight( primary() ) 00662 << ", stretchable: " << boolalpha << child->stretchable( primary() ) 00663 << "), pos: " << childPos[i] 00664 << endl; 00665 } 00666 } 00667 00668 if ( oldTooSmall == tooSmall && 00669 oldLoserCount == loserCount ) 00670 { 00671 yuiWarning() << "Preventing endless loop while layout space distribution. Break." << endl; 00672 break; 00673 } 00674 00675 oldTooSmall = tooSmall; 00676 oldLoserCount = loserCount; 00677 } 00678 00679 00680 // Calculate postitions 00681 00682 for ( int i = 0, pos=0; i < childrenCount(); i++ ) 00683 { 00684 childPos[i] = pos; 00685 pos += childSize[i]; 00686 00687 } 00688 00689 } 00690 } 00691 00692 00693 void 00694 YLayoutBox::calcSecondaryGeometry( int newSize, 00695 sizeVector & childSize, 00696 posVector & childPos ) 00697 { 00698 int i=0; 00699 for ( YWidgetListConstIterator it = childrenBegin(); 00700 it != childrenEnd(); 00701 ++it, i++ ) 00702 { 00703 YWidget * child = *it; 00704 int preferred = child->preferredSize( secondary() ); 00705 00706 if ( child->stretchable( secondary() ) || newSize < preferred || preferred == 0 ) 00707 // Also checking for preferred == 0 to make HSpacing / VSpacing visible in YDialogSpy: 00708 // Otherwise they would be 0 pixels wide or high, i.e. invisible 00709 { 00710 childSize[i] = newSize; 00711 childPos [i] = 0L; 00712 } 00713 else // child is not stretchable and there is more space than it wants 00714 { 00715 childSize[i] = preferred; 00716 childPos [i] = ( newSize - preferred ) / 2; // center 00717 } 00718 00719 if ( childSize[i] < preferred ) 00720 { 00721 yuiDebug() << "Layout running out of space: " 00722 << "Resizing child widget #" << i 00723 << " (" << child 00724 << ") below its preferred size of " << preferred 00725 << " to " << childSize[i] 00726 << endl; 00727 } 00728 00729 if ( debugLayout() ) 00730 { 00731 ( childSize[i] < preferred ? yuiWarning() : yuiDebug() ) 00732 << "child #" << i 00733 << " (" << child 00734 << ") will get " << childSize[i] 00735 << " (preferred size: " << preferred 00736 << ", weight: " << child->weight( secondary() ) 00737 << ", stretchable: " << boolalpha << child->stretchable( secondary() ) 00738 << "), pos: " << childPos[i] 00739 << endl; 00740 } 00741 } 00742 } 00743 00744 00745 void 00746 YLayoutBox::doResize( sizeVector & width, 00747 sizeVector & height, 00748 posVector & x_pos, 00749 posVector & y_pos ) 00750 { 00751 int i=0; 00752 for ( YWidgetListConstIterator it = childrenBegin(); 00753 it != childrenEnd(); 00754 ++it, i++ ) 00755 { 00756 YWidget * child = *it; 00757 00758 child->setSize( width[i], height[i] ); 00759 moveChild( child, x_pos[i], y_pos[i] ); 00760 00761 if ( debugLayout() ) 00762 { 00763 yuiMilestone() << " x: " << setw( 3 ) << x_pos[i] 00764 << " y: " << setw( 3 ) << y_pos[i] 00765 << " w: " << setw( 3 ) << width[i] 00766 << " h: " << setw( 3 ) << height[i] 00767 << " " << child 00768 << endl; 00769 } 00770 } 00771 } 00772 00773 00774 const char * 00775 YLayoutBox::widgetClass() const 00776 { 00777 return primary() == YD_VERT ? "YVBox" : "YHBox"; 00778 }