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