libyui  3.0.10
/usr/src/RPM/BUILD/libyui-3.0.10/src/YLayoutBox.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:         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 }
 All Classes Functions Variables Enumerations Friends