Lumiera  0.pre.03
»edit your freedom«
element-box-widget.cpp
Go to the documentation of this file.
1 /*
2  ElementBoxWidget - fundamental UI building block to represent a placed element
3 
4  Copyright (C) Lumiera.org
5  2016, Hermann Vosseler <Ichthyostega@web.de>
6 
7  This program is free software; you can redistribute it and/or
8  modify it under the terms of the GNU General Public License as
9  published by the Free Software Foundation; either version 2 of
10  the License, or (at your option) any later version.
11 
12  This program is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  GNU General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License
18  along with this program; if not, write to the Free Software
19  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 
21 * *****************************************************/
22 
23 
53 #include "stage/gtk-base.hpp"
55 #include "stage/style-scheme.hpp"
56 
57 #include "lib/util.hpp"
58 
59 
60 
61 namespace stage {
62 namespace widget {
63 
64 
65  namespace {//Implementation helpers...
78  inline void
79  queryNaturalSize (Gtk::Widget const& widget, Gtk::Requisition& natSize)
80  {
81  Gtk::Requisition minDummy;
82  widget.get_preferred_size (minDummy, natSize);
83  }
84 
85  inline int
86  queryNaturalHeight (Gtk::Widget const& widget)
87  {
88  int minDummy{0}, natHeight{0};
89  widget.get_preferred_height(minDummy, natHeight);
90  return natHeight;
91  }
92 
93  inline int
94  queryNaturalWidth (Gtk::Widget const& widget)
95  {
96  int minDummy{0}, natWidth{0};
97  widget.get_preferred_width(minDummy, natWidth);
98  return natWidth;
99  }
100 
102  Gtk::Requisition ICON_SIZ{};
103 
107  const double HYSTERESIS = 1.6;
108 
109  inline void
110  initIconSizeHeuristic (Gtk::Widget const& icon)
111  {
112  if (ICON_SIZ.width > 0) return;
113  queryNaturalSize (icon, ICON_SIZ);
114  }
115 
116  }//(End)helpers
117 
118 
119 
120 
121  IDLabel::~IDLabel() { }
122  ElementBoxWidget::~ElementBoxWidget() { }
123 
124 
125  Literal
126  ElementBoxWidget::Config::getIconID() const
127  {
129  return ICON_placement;
130  }
131 
132  Literal
133  ElementBoxWidget::Config::getMenuSymb() const
134  {
136  return ICON_arrow_hand_menu;
137  }
138 
139  Gtk::IconSize
140  ElementBoxWidget::Config::getIconSize() const
141  {
143  return Gtk::ICON_SIZE_MENU;
144  }
145 
146 
149  {
150  Strategy strategy;
151  if (widthConstraint_)
152  strategy.getWidth = move(widthConstraint_);
153  if (heightConstraint_)
154  strategy.getHeight = move(heightConstraint_);
155  return strategy;
156  }
157 
158 
159  IDLabel::IDLabel (Literal iconID, Literal menuSymb, Gtk::IconSize siz)
160  : Gtk::Box{Gtk::ORIENTATION_HORIZONTAL}
161  , imgIcon_{Gtk::StockID{iconID}, siz}
162  , imgMenu_{Gtk::StockID{menuSymb}, siz}
163  {
164  icon_.set_image(imgIcon_);
165  menu_.set_image(imgMenu_);
166  this->add(icon_);
167  this->add(menu_);
168  this->add(name_);
169  this->set_name(ID_idlabel);
170  this->get_style_context()->add_class(CLASS_background);
171  this->get_style_context()->add_class(CLASS_idlabel);
172  icon_.get_style_context()->add_class(CLASS_idlabel_icon);
173  menu_.get_style_context()->add_class(CLASS_idlabel_menu);
174  name_.get_style_context()->add_class(CLASS_idlabel_name);
175  name_.set_hexpand(true);
176 
177  this->show_all();
178  initIconSizeHeuristic (icon_);
179  }
180 
181 
182  void
183  IDLabel::setCaption(cuString& idCaption)
184  {
185  name_.set_text(idCaption);
186  // can not retrieve size information from hidden widgets...
187  this->show_all(); // Note: size constraint handling will trigger again
188  // cache required full display size (for size constrained layout)
189  queryNaturalSize (*this, labelFullSize_);
190  }
191 
192  cuString
193  IDLabel::getCaption() const
194  {
195  return name_.get_text();
196  }
197 
198 
199 
201  : EventBox{}
202  , strategy_{config.buildLayoutStrategy(*this)}
203  , label_{config.getIconID()
204  ,config.getMenuSymb()
205  ,config.getIconSize()}
206  , frame_{}
207  {
208  set_name (ID_element);
209  get_style_context()->add_class (CLASS_background); // Style to ensure an opaque backdrop
210  get_style_context()->add_class (CLASS_elementbox);
211  label_.get_style_context()->add_class (CLASS_elementbox_idlabel);
212 
213  frame_.set_label_align (0.0, 0.0);
214  frame_.set_label_widget(label_);
215  this->add (frame_);
216 
217  this->show_all();
218  label_.setCaption (config.getName());
219  }
220 
221 
222  void
223  ElementBoxWidget::setName (cuString& nameID)
224  {
225  label_.setCaption (nameID);
226  }
227 
228  cuString
229  ElementBoxWidget::getName() const
230  {
231  return label_.getCaption();
232  }
233 
240  Gtk::SizeRequestMode
242  {
243  return Gtk::SizeRequestMode::SIZE_REQUEST_HEIGHT_FOR_WIDTH;
244  }
245 
268  void
269  ElementBoxWidget::get_preferred_width_vfunc (int& minimum_width, int& natural_width) const
270  {
271  if (strategy_.is_size_constrained())
272  minimum_width = natural_width = strategy_.getWidth();
273  else
274  _Base::get_preferred_width_vfunc (minimum_width,natural_width);
275  }
276 
281  void
282  ElementBoxWidget::get_preferred_height_vfunc (int& minimum_height, int& natural_height) const
283  {
284  if (strategy_.shall_control_height())
285  minimum_height = natural_height = strategy_.getHeight();
286  else
287  _Base::get_preferred_height_vfunc (minimum_height,natural_height);
288  }
289 
290  void
291  ElementBoxWidget::get_preferred_height_for_width_vfunc (int width, int& minimum_height, int& natural_height) const
292  {
293  if (strategy_.is_size_constrained() and strategy_.shall_control_height())
294  minimum_height = natural_height = strategy_.getHeight();
295  else
296  _Base::get_preferred_height_for_width_vfunc (width, minimum_height,natural_height);
297  }
298 
310  void
311  ElementBoxWidget::on_size_allocate (Gtk::Allocation& availableSize)
312  {
313  if (strategy_.is_size_constrained())
314  imposeSizeConstraint (availableSize.get_width(), availableSize.get_height());
315  _Base::on_size_allocate(availableSize);
316  }
317 
318 
327  void
328  ElementBoxWidget::imposeSizeConstraint (int widthC, int heightC)
329  {
330  label_.imposeSizeConstraint (widthC, heightC);
331  }
332 
339  void
340  IDLabel::imposeSizeConstraint (int widthC, int heightC)
341  {
342  // short circuit: need to perform precise checks?
343  if ( labelFullSize_.width > widthC
344  or labelFullSize_.height > heightC
345  )
346  this->adaptSize(widthC, heightC);
347  }
348 
349 
350  namespace {//IDLabel layout management internals....
351 
355  inline int
356  reduce(Gtk::Button& icon)
357  {
358  int widthReduction{0};
359  if (icon.get_visible())
360  {
361  widthReduction = queryNaturalWidth (icon);
362  icon.hide();
363  }
364  return widthReduction;
365  }
366 
368  inline int
369  reduce(Gtk::Label& label, int goal)
370  {
371  ASSERT (goal >=0);
372  int reduction{0};
373  if (label.get_visible())
374  {
375  int width = queryNaturalWidth (label);
379  if (reduction < goal)
380  {//shortening alone does not suffice
381  label.hide();
382  reduction = width;
383  }
384  }
385  return reduction;
386  }
387 
395  template<class FUN>
396  inline bool
397  maybeShow(Gtk::Button& icon, int w, int h, FUN& reCheck)
398  {
399  if (icon.is_visible()) return true; // nothing can be done here
400  bool success{false};
401  if (w >= ICON_SIZ.width * HYSTERESIS and h >= ICON_SIZ.height)
402  {
403  icon.show();
404  if (not (success=reCheck()))
405  icon.hide();
406  }
407  return success;
408  }
409 
410  template<class FUN>
411  inline bool
412  maybeShow(Gtk::Label& label, int w, int h, FUN& reCheck)
413  {
414  bool success{false};
415  // use icon dimensions as as heuristics to determine
416  // if attempting to show the label is worth trying...
417  if (w >= ICON_SIZ.width * HYSTERESIS and h >= ICON_SIZ.height)
418  {
419  label.show();
420  int width = queryNaturalWidth (label);
421  int goal = width - w;
422  if (goal > 0) // too large, yet might fit if shortened
423  reduce (label, goal);
424  if (not (success=reCheck()))
425  label.hide();
426  }
427  return success;
428  }
429 
430 
431  }//(End)Layout helpers
432 
433 
444  void
445  IDLabel::adaptSize (int widthC, int heightC)
446  {
447  // first determine if vertical extension is problematic
448  int currH = queryNaturalHeight (*this);
449  if (currH > heightC)
450  {//hide all child widgets,
451  // not much options left...
452  name_.hide();
453  menu_.hide();
454  icon_.hide();
455  return;
456  }
457 
458  // now test if we need to reduce or can expand
459  int currW = queryNaturalWidth (*this);
460  if (currW > widthC)
461  {//reduce to comply
462  int goal = currW - widthC;
463  ASSERT (goal > 0);
464  if ((goal -= reduce(name_, goal)) <= 0) return;
465  if ((goal -= reduce(menu_) ) <= 0) return;
466  if ((goal -= reduce(icon_) ) <= 0) return;
467  currW = queryNaturalWidth(*this);
468  goal = currW - widthC;
469  ENSURE (goal <= 0, "IDLabel layout management floundered. "
470  "Removed all content, yet remaining width %d > %d"
471  , currW, widthC);
472  }
473  else
474  {//maybe some headroom left to show more?
475  int headroom = widthC - currW;
476  auto reCheck = [&]() -> bool
477  {// WARNING: side effect assignment
478  currW = queryNaturalWidth (*this);
479  currH = queryNaturalHeight(*this);
480  headroom = widthC - currW;
481  return currH <= heightC
482  and currW <= widthC;
483  };
484 
485  if (not maybeShow (icon_, headroom, heightC, reCheck)) return;
486  if (not maybeShow (menu_, headroom, heightC, reCheck)) return;
487  if (not maybeShow (name_, headroom, heightC, reCheck)) return;
488  }
489  }
490 
491 
492 
493 }}// namespace stage::widget
void on_size_allocate(Gtk::Allocation &) override
Tap into the notification of screen space allocation to possibly enforce size constraints.
cuString CLASS_background
opaque backdrop
cuString CLASS_elementbox_idlabel
only present on IDLabel widget within ElementBoxWidget
void adaptSize(int, int)
Multi-step procedure to keep this IDLabel widget within the given screen size constraints.
inline string literal This is a marker type to indicate that
Definition: symbol.hpp:85
A basic building block of the Lumiera UI.
Widget to render an ID label with associated icon.
ElementBoxWidget(Kind kind, Type type, QS ...qualifiers)
setup an ElementBoxWidget with suitable presentation style.
Gtk::Requisition ICON_SIZ
point of reference for layout computations
void queryNaturalSize(Gtk::Widget const &widget, Gtk::Requisition &natSize)
Helper to retrieve what GTK effectively uses as minimal extension of a widget.
const double HYSTERESIS
excess factor used to prevent "layout flickering"
void imposeSizeConstraint(int, int)
Ensure the child widgets can be represented and possibly adjust or hide content, in case the extensio...
void imposeSizeConstraint(int, int)
Ensure the IDLabel stays within a given size constraint.
Lumiera GTK UI implementation root.
Definition: guifacade.cpp:46
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...
void get_preferred_height_vfunc(int &, int &) const override
void get_preferred_width_vfunc(int &, int &) const override
Layout preferences are delegated through the Strategy.
bool maybeShow(Gtk::Button &icon, int w, int h, FUN &reCheck)
attempt to use available space to show more content
Gtk::SizeRequestMode get_request_mode_vfunc() const final
Layout trend for ElementBoxWidget is nailed down (final) to "height-for-width".
Definition of access keys for uniform UI styling.
A set of basic GTK includes for the UI.
Strategy buildLayoutStrategy(ElementBoxWidget &)
decide upon the presentation strategy