libyui  3.10.0
YButtonBox.cc
1 /*
2  Copyright (C) 2000-2012 Novell, Inc
3  This library is free software; you can redistribute it and/or modify
4  it under the terms of the GNU Lesser General Public License as
5  published by the Free Software Foundation; either version 2.1 of the
6  License, or (at your option) version 3.0 of the License. This library
7  is distributed in the hope that it will be useful, but WITHOUT ANY
8  WARRANTY; without even the implied warranty of MERCHANTABILITY or
9  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
10  License for more details. You should have received a copy of the GNU
11  Lesser General Public License along with this library; if not, write
12  to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
13  Floor, Boston, MA 02110-1301 USA
14 */
15 
16 
17 /*-/
18 
19  File: YButtonBox.cc
20 
21  Author: Stefan Hundhammer <sh@suse.de>
22 
23 /-*/
24 
25 
26 #include <algorithm> // max()
27 #include <vector>
28 #include <list>
29 
30 #define YUILogComponent "ui"
31 #include "YUILog.h"
32 
33 #include "YButtonBox.h"
34 #include "YPushButton.h"
35 #include "YUI.h"
36 #include "YApplication.h"
37 
38 using std::string;
39 using std::vector;
40 
41 
42 YButtonBoxLayoutPolicy YButtonBox::_layoutPolicy = kdeLayoutPolicy();
43 YButtonBoxMargins YButtonBox::_defaultMargins;
44 
45 
47 {
48  /**
49  * Constructor
50  **/
52  : sanityCheckRelaxed( false )
53  , margins( YButtonBox::_defaultMargins )
54  {}
55 
56  //
57  // Data members
58  //
59 
60  bool sanityCheckRelaxed;
61  YButtonBoxMargins margins;
62 };
63 
64 
65 
66 
68  : YWidget( parent )
69  , priv( new YButtonBoxPrivate() )
70 {
71  YUI_CHECK_NEW( priv );
73 }
74 
75 
77 {
78  // NOP
79 }
80 
81 
82 void
84 {
85  _layoutPolicy = layoutPolicy;
86 }
87 
88 
91 {
92  return _layoutPolicy;
93 }
94 
95 
98 {
100 
101  policy.buttonOrder = YKDEButtonOrder;
102  policy.equalSizeButtons = false;
103  policy.alignment[ YD_HORIZ ] = YAlignCenter;
104  policy.alignment[ YD_VERT ] = YAlignBegin; // Align top
105 
106  return policy;
107 }
108 
109 
112 {
113  YButtonBoxLayoutPolicy policy;
114 
115  policy.buttonOrder = YGnomeButtonOrder;
116  policy.equalSizeButtons = true;
117  policy.alignment[ YD_HORIZ ] = YAlignEnd; // Align right
118  policy.alignment[ YD_VERT ] = YAlignBegin; // Align top
119  policy.addExcessSpaceToHelpButtonExtraMargin = true;
120 
121  return policy;
122 }
123 
124 
125 void
127 {
128  _defaultMargins = margins;
129 }
130 
131 
134 {
135  return _defaultMargins;
136 }
137 
138 
139 void
141 {
142  priv->margins = margins;
143 }
144 
145 
148 {
149  return priv->margins;
150 }
151 
152 
153 void
154 YButtonBox::setSize( int newWidth, int newHeight )
155 {
156  sanityCheck();
157  doLayout( newWidth, newHeight );
158 }
159 
160 
161 void
162 YButtonBox::doLayout( int width, int height )
163 {
164  vector<YPushButton *> buttons = buttonsByButtonOrder();
165 
166  if ( buttons.empty() )
167  return;
168 
169  YPushButton * helpButton = findButton( YHelpButton );
170 
171  int prefWidth = preferredWidth();
172  int prefHeight = preferredHeight();
173  YButtonBoxMargins margins = priv->margins;
174  bool equalSizeButtons = _layoutPolicy.equalSizeButtons;
175 
176 
177  //
178  // Horizontal layout
179  //
180 
181  if ( width < prefWidth ) // Not enough horizontal space
182  {
183  if ( equalSizeButtons )
184  {
185  int buttonWidthWithoutMargins = maxChildSize( YD_HORIZ ) * buttons.size();
186 
187  if ( width < buttonWidthWithoutMargins )
188  {
189  // The missing width can't be compensated by reducing margins and spacings.
190  // Try not enforcing the same width:
191  //
192  // If one button is very much larger than most others, that one
193  // button will greatly distort the overall layout. If we simply cut
194  // some pixels off every button, for sure that one very large
195  // button will become unreadable. So let's try first with buttons
196  // getting just the size they really need.
197  //
198  // Of course, we might still have cut some pixels off all buttons
199  // if that also is too wide, but in that case we can't do very much
200  // anyway.
201 
202  equalSizeButtons = false;
203  prefWidth = preferredWidth( equalSizeButtons );
204  }
205  }
206  }
207 
208  int widthLoss = 0;
209 
210  if ( width < prefWidth ) // Not enough horizontal space
211  {
212  // Try reducing margins
213 
214  int missing = prefWidth - width;
215 
216  if ( missing <= margins.left + margins.right )
217  {
218  margins.left -= missing / 2;
219  margins.right -= missing / 2;
220  missing = 0;
221  }
222  else
223  {
224  missing -= margins.left;
225  missing -= margins.right;
226  margins.left = 0;
227  margins.right = 0;
228  }
229 
230  if ( missing > 0 && buttons.size() > 1 )
231  {
232  // Try reducing spacing
233 
234  int totalSpacing = ( buttons.size() - 1 ) * margins.spacing;
235 
236  if ( missing <= totalSpacing )
237  {
238  totalSpacing -= missing;
239  margins.spacing = totalSpacing / ( buttons.size() - 1 );
240  missing = 0;
241  }
242  else
243  {
244  missing -= totalSpacing;
245  margins.spacing = 0;
246  }
247  }
248 
249  if ( missing > 0 && helpButton )
250  {
251  // Try reducing help button extra spacing
252 
253  if ( missing <= margins.helpButtonExtraSpacing )
254  {
255  margins.helpButtonExtraSpacing -= missing;
256  missing = 0;
257  }
258  else
259  {
260  missing -= margins.helpButtonExtraSpacing;
261  margins.helpButtonExtraSpacing = 0;
262  }
263  }
264 
265 
266  // Distribute missing width among all buttons
267 
268  if ( missing > 0 )
269  widthLoss = missing / buttons.size();
270  }
271 
272  if ( width > prefWidth ) // Excess horizontal space
273  {
274  int excessWidth = width - prefWidth;
275 
276  if ( _layoutPolicy.addExcessSpaceToHelpButtonExtraMargin && helpButton )
277  {
278  margins.helpButtonExtraSpacing += excessWidth;
279  }
280  else
281  {
282  switch ( _layoutPolicy.alignment[ YD_HORIZ ] )
283  {
284  case YAlignCenter:
285  margins.left += excessWidth / 2;
286  margins.right += excessWidth / 2;
287  break;
288 
289  case YAlignBegin:
290  case YAlignUnchanged:
291  margins.right += excessWidth;
292  break;
293 
294  case YAlignEnd:
295  margins.left += excessWidth;
296  break;
297  }
298  }
299  }
300 
301 
302  //
303  // Vertical layout
304  //
305 
306  int buttonHeight = maxChildSize( YD_VERT );
307 
308  if ( height < prefHeight ) // Not enough vertical space
309  {
310  // Reduce margins
311 
312  int missing = prefHeight - height;
313 
314  if ( missing < margins.top + margins.bottom )
315  {
316  margins.top -= missing / 2;
317  margins.bottom -= missing / 2;
318  }
319  else
320  {
321  margins.top = 0;
322  margins.bottom = 0;
323  }
324  }
325 
326  if ( height < buttonHeight )
327  {
328  buttonHeight = height;
329  }
330 
331  int y_pos = margins.top;
332 
333  if ( height > prefHeight ) // Excess vertical space
334  {
335  // Distribute excess vertical space
336 
337  int excessHeight = height - buttonHeight;
338  excessHeight -= margins.top;
339  excessHeight -= margins.bottom;
340 
341  switch ( _layoutPolicy.alignment[ YD_VERT ] )
342  {
343  case YAlignBegin: // Align top
344  case YAlignUnchanged:
345  break;
346 
347  case YAlignCenter:
348  y_pos += excessHeight / 2;
349  break;
350 
351  case YAlignEnd: // Align bottom
352  y_pos += excessHeight;
353  break;
354  }
355  }
356 
357 
358  //
359  // Set child widget positions and sizes from left to right
360  //
361 
362  int x_pos = margins.left;
363  int buttonWidth = 0;
364 
365  if ( equalSizeButtons )
366  {
367  buttonWidth = maxChildSize( YD_HORIZ );
368  buttonWidth -= widthLoss;
369  }
370 
371  bool reverseLayout = YUI::app()->reverseLayout();
372 
373  for ( vector<YPushButton *>::iterator it = buttons.begin();
374  it != buttons.end();
375  ++it )
376  {
377  YPushButton * button = *it;
378 
379  // Extra spacing left of [Help] button
380  // (Only if this isn't the first button)
381 
382  if ( button == helpButton && button != buttons.front() )
383  x_pos += margins.helpButtonExtraSpacing;
384 
385  if ( ! equalSizeButtons )
386  {
387  buttonWidth = button->preferredWidth();
388  buttonWidth -= widthLoss;
389  }
390 
391  button->setSize( buttonWidth, buttonHeight );
392 
393  if ( reverseLayout )
394  moveChild( button, width - x_pos - buttonWidth, y_pos );
395  else
396  moveChild( button, x_pos, y_pos );
397 
398  x_pos += buttonWidth;
399  x_pos += margins.spacing;
400 
401 
402  // Extra spacing right of [Help] button
403 
404  if ( button == helpButton )
405  x_pos += margins.helpButtonExtraSpacing;
406  }
407 }
408 
409 
410 vector<YPushButton *>
412 {
413  vector<YPushButton *> specialButtons( YMaxButtonRole, (YPushButton *) 0 );
414  vector<YPushButton *> customButtons;
415 
416  for ( YWidgetListConstIterator it = childrenBegin();
417  it != childrenEnd();
418  ++it )
419  {
420  YPushButton * button = dynamic_cast<YPushButton *> (*it);
421 
422  if ( ! button )
423  YUI_THROW( YUIInvalidChildException<YWidget>( this, *it ) );
424 
425  switch ( button->role() )
426  {
427  case YOKButton:
428  case YCancelButton:
429  case YApplyButton:
430  case YHelpButton:
431  case YRelNotesButton:
432 
433  if ( specialButtons[ button->role() ] ) // Only one of each of those is allowed
434  {
435  string msg = "Multiple buttons with that role [";
436  msg += button->debugLabel();
437  msg += "]";
438  YUI_THROW( YUIButtonRoleMismatchException( msg ) );
439  }
440  else
441  {
442  specialButtons[ button->role() ] = button;
443  }
444  break;
445 
446  case YCustomButton:
447  customButtons.push_back( button );
448  break;
449 
450  case YMaxButtonRole:
451  YUI_THROW( YUIButtonRoleMismatchException( "Invalid button role" ) );
452  break;
453  }
454  }
455 
456  vector<YPushButton *> buttons;
457 
458  if ( _layoutPolicy.buttonOrder == YKDEButtonOrder )
459  {
460  if ( specialButtons[ YOKButton ] ) buttons.push_back( specialButtons[ YOKButton ] );
461  if ( specialButtons[ YApplyButton ] ) buttons.push_back( specialButtons[ YApplyButton ] );
462  if ( specialButtons[ YCancelButton ] ) buttons.push_back( specialButtons[ YCancelButton ] );
463 
464  buttons.insert( buttons.end(), customButtons.begin(), customButtons.end() );
465 
466  if ( specialButtons[ YHelpButton ] ) buttons.push_back( specialButtons[ YHelpButton ] );
467  }
468  else // YGnomeButtonOrder
469  {
470  if ( specialButtons[ YHelpButton ] ) buttons.push_back( specialButtons[ YHelpButton ] );
471 
472  buttons.insert( buttons.end(), customButtons.begin(), customButtons.end() );
473 
474  if ( specialButtons[ YApplyButton ] ) buttons.push_back( specialButtons[ YApplyButton ] );
475  if ( specialButtons[ YCancelButton ] ) buttons.push_back( specialButtons[ YCancelButton ] );
476  if ( specialButtons[ YOKButton ] ) buttons.push_back( specialButtons[ YOKButton ] );
477  }
478 
479  return buttons;
480 }
481 
482 
483 
484 int
485 YButtonBox::preferredWidth( bool equalSizeButtons )
486 {
487  if ( childrenCount() < 1 )
488  return 0;
489 
490  int width = ( childrenCount() - 1 ) * priv->margins.spacing;
491 
492  if ( equalSizeButtons )
493  width += maxChildSize( YD_HORIZ ) * childrenCount();
494  else
495  width += totalChildrenWidth();
496 
497  width += priv->margins.left;
498  width += priv->margins.right;
499 
500  if ( priv->margins.helpButtonExtraSpacing )
501  {
502  if ( findButton( YHelpButton ) )
503  width += priv->margins.helpButtonExtraSpacing;
504  }
505 
506  return width;
507 }
508 
509 
510 int
512 {
513  return preferredWidth( _layoutPolicy.equalSizeButtons );
514 }
515 
516 
517 int
519 {
520  int height = maxChildSize( YD_VERT );
521  height += priv->margins.top;
522  height += priv->margins.bottom;
523 
524  return height;
525 }
526 
527 
528 int
529 YButtonBox::maxChildSize( YUIDimension dim ) const
530 {
531  int maxSize = 0;
532 
533  for ( YWidgetListConstIterator it = childrenBegin();
534  it != childrenEnd();
535  ++it )
536  {
537  maxSize = std::max( maxSize, (*it)->preferredSize( dim ) );
538  }
539 
540  return maxSize;
541 }
542 
543 
544 int
546 {
547  int totalWidth = 0;
548 
549  for ( YWidgetListConstIterator it = childrenBegin();
550  it != childrenEnd();
551  ++it )
552  {
553  totalWidth += (*it)->preferredWidth();
554  }
555 
556  return totalWidth;
557 }
558 
559 
560 bool
561 YButtonBox::stretchable( YUIDimension dimension ) const
562 {
563  switch ( dimension )
564  {
565  case YD_HORIZ: return true;
566  case YD_VERT : return false;
567 
568  default:
569  YUI_THROW( YUIInvalidDimensionException() );
570  return 0;
571  }
572 }
573 
574 
575 YPushButton *
576 YButtonBox::findButton( YButtonRole role )
577 {
578  for ( YWidgetListConstIterator it = childrenBegin();
579  it != childrenEnd();
580  ++it )
581  {
582  YPushButton * button = dynamic_cast<YPushButton *> (*it);
583 
584  if ( button && button->role() == role )
585  return button;
586  }
587 
588  return 0;
589 }
590 
591 
592 void
594 {
595  priv->sanityCheckRelaxed = relaxed;
596 }
597 
598 
599 bool
601 {
602  return priv->sanityCheckRelaxed;
603 }
604 
605 
606 void
608 {
609  YPushButton * okButton = 0;
610  YPushButton * cancelButton = 0;
611 
612  for ( YWidgetListConstIterator it = childrenBegin();
613  it != childrenEnd();
614  ++it )
615  {
616  YPushButton * button = dynamic_cast<YPushButton *> (*it);
617 
618  if ( ! button )
619  YUI_THROW( YUIInvalidChildException<YWidget>( this, *it ) );
620 
621  switch ( button->role() )
622  {
623  case YOKButton:
624 
625  if ( okButton )
626  YUI_THROW( YUIButtonRoleMismatchException( "Multiple buttons with role [OK]" ) );
627  else
628  okButton = button;
629  break;
630 
631 
632  case YCancelButton:
633 
634  if ( cancelButton )
635  YUI_THROW( YUIButtonRoleMismatchException( "Multiple buttons with role [Cancel]" ) );
636  else
637  cancelButton = button;
638  break;
639 
640 
641  default:
642  break;
643  }
644  }
645 
646  if ( childrenCount() > 1 && ! sanityCheckRelaxed() )
647  {
648  if ( ! okButton || ! cancelButton )
649  YUI_THROW( YUIButtonRoleMismatchException( "Button role mismatch: Must have both [OK] and [Cancel] roles" ) );
650  }
651 }
YButtonBox::findButton
YPushButton * findButton(YButtonRole role)
Search the child widgets for the first button with the specified role.
Definition: YButtonBox.cc:576
YButtonBox::setMargins
virtual void setMargins(const YButtonBoxMargins &margins)
Set the margins for this YButtonBox.
Definition: YButtonBox.cc:140
YButtonBox::gnomeLayoutPolicy
static YButtonBoxLayoutPolicy gnomeLayoutPolicy()
Predefined layout policy for GNOME-like behaviour.
Definition: YButtonBox.cc:111
YWidget
Abstract base class of all UI widgets.
Definition: YWidget.h:55
YWidget::preferredWidth
virtual int preferredWidth()=0
Preferred width of the widget.
YWidget::childrenEnd
YWidgetListIterator childrenEnd() const
Return an interator that points after the last child.
Definition: YWidget.h:218
YButtonBoxMargins
Helper class: Margins for YButtonBox widgets.
Definition: YButtonBox.h:66
YWidget::setSize
virtual void setSize(int newWidth, int newHeight)=0
Set the new size of the widget.
YButtonBox::setSize
virtual void setSize(int newWidth, int newHeight)
Sets the size of the YButtonBox.
Definition: YButtonBox.cc:154
YButtonBox::preferredHeight
virtual int preferredHeight()
Preferred height of the widget.
Definition: YButtonBox.cc:518
YWidget::setChildrenManager
void setChildrenManager(YWidgetChildrenManager *manager)
Sets a new children manager for this widget.
Definition: YWidget.cc:166
YButtonBox::sanityCheckRelaxed
bool sanityCheckRelaxed() const
Return 'true' if sanity checks are currently relaxed, 'false' if not.
Definition: YButtonBox.cc:600
YButtonBox::moveChild
virtual void moveChild(YWidget *child, int newX, int newY)=0
Move a child to a new position.
YUIInvalidChildException
Exception class for "invalid child".
Definition: YUIException.h:713
YButtonBox::setLayoutPolicy
static void setLayoutPolicy(const YButtonBoxLayoutPolicy &layoutPolicy)
Set the button policy for all future YButtonBox widgets: Button order, alignment if there is any exce...
Definition: YButtonBox.cc:83
YWidget::childrenCount
int childrenCount() const
Returns the current number of children.
Definition: YWidget.h:251
YButtonBox::margins
YButtonBoxMargins margins() const
Return the margins of this YButtonBox.
Definition: YButtonBox.cc:147
YButtonBox::setSanityCheckRelaxed
void setSanityCheckRelaxed(bool relax=true)
Relax the sanity check done in sanityCheck(): Do not enforce that there has to be a YOKButton and a Y...
Definition: YButtonBox.cc:593
YButtonBox::preferredWidth
virtual int preferredWidth()
Preferred width of the widget.
Definition: YButtonBox.cc:511
YButtonBox::~YButtonBox
virtual ~YButtonBox()
Destructor.
Definition: YButtonBox.cc:76
YButtonBox::buttonsByButtonOrder
virtual std::vector< YPushButton * > buttonsByButtonOrder()
Return the button children sorted (left to right) by the current button order (from the layout policy...
Definition: YButtonBox.cc:411
YButtonBox::maxChildSize
int maxChildSize(YUIDimension dim) const
Return the (preferred) size of the biggest child widget in the specified dimension.
Definition: YButtonBox.cc:529
YButtonBoxLayoutPolicy
Helper class: Layout policy for YButtonBox widgets.
Definition: YButtonBox.h:42
YUIInvalidDimensionException
Exception class for "value other than YD_HORIZ or YD_VERT used for dimension".
Definition: YUIException.h:793
YButtonBox::YButtonBox
YButtonBox(YWidget *parent)
Constructor.
Definition: YButtonBox.cc:67
YPushButton::role
YButtonRole role() const
Return the role of this button.
Definition: YPushButton.cc:178
YApplication::reverseLayout
bool reverseLayout() const
Returns 'true' if widget geometry should be reversed for languages that have right-to-left writing di...
Definition: YApplication.cc:156
YButtonBox::stretchable
virtual bool stretchable(YUIDimension dimension) const
Returns the stretchability of the YButtonBox.
Definition: YButtonBox.cc:561
YUIButtonRoleMismatchException
Exception class for "wrong button roles in YButtonBox".
Definition: YUIException.h:906
YButtonBoxPrivate::YButtonBoxPrivate
YButtonBoxPrivate()
Constructor.
Definition: YButtonBox.cc:51
YButtonBox
Container widget for dialog buttons that abstracts the button order depending on the desktop environm...
Definition: YButtonBox.h:149
YButtonBox::setDefaultMargins
static void setDefaultMargins(const YButtonBoxMargins &margins)
Set the default margins for all future YButtonBox widgets.
Definition: YButtonBox.cc:126
YButtonBox::sanityCheck
void sanityCheck()
Sanity check: Check if all child widgets have the correct widget class and if the button roles are as...
Definition: YButtonBox.cc:607
YButtonBox::kdeLayoutPolicy
static YButtonBoxLayoutPolicy kdeLayoutPolicy()
Predefined layout policy for KDE-like behaviour.
Definition: YButtonBox.cc:97
YUI::app
static YApplication * app()
Return the global YApplication object.
Definition: YUI.cc:162
YButtonBox::defaultMargins
static YButtonBoxMargins defaultMargins()
Return the default margins for all future YButtonBox widgets.
Definition: YButtonBox.cc:133
YWidget::childrenBegin
YWidgetListIterator childrenBegin() const
Return an iterator that points to the first child or to childrenEnd() if there are no children.
Definition: YWidget.h:212
YButtonBox::totalChildrenWidth
int totalChildrenWidth() const
Return the sum of the (preferred) widths of all child widgets.
Definition: YButtonBox.cc:545
YButtonBox::layoutPolicy
static YButtonBoxLayoutPolicy layoutPolicy()
Return the layout policy.
Definition: YButtonBox.cc:90
YWidget::debugLabel
virtual std::string debugLabel() const
Returns a descriptive label of this widget instance.
Definition: YWidget.cc:223
YButtonBox::doLayout
virtual void doLayout(int width, int height)
Lay out the button box and its children (its buttons).
Definition: YButtonBox.cc:162
YChildrenManager
Abstract base template class for children management, such as child widgets.
Definition: YChildrenManager.h:38
YButtonBoxPrivate
Definition: YButtonBox.cc:47
YPushButton
A push button; may have an icon, and a F-key shortcut.
Definition: YPushButton.h:38