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)
5  2016, Hermann Vosseler <Ichthyostega@web.de>
6 
7   **Lumiera** is free software; you can redistribute it and/or modify it
8   under the terms of the GNU General Public License as published by the
9   Free Software Foundation; either version 2 of the License, or (at your
10   option) any later version. See the file COPYING for further details.
11 
12 * *****************************************************************/
13 
14 
44 #include "stage/gtk-base.hpp"
46 #include "stage/style-scheme.hpp"
47 
48 #include "lib/util.hpp"
49 
50 
51 
52 namespace stage {
53 namespace widget {
54 
55 
56  namespace {//Implementation helpers...
69  inline void
70  queryNaturalSize (Gtk::Widget const& widget, Gtk::Requisition& natSize)
71  {
72  Gtk::Requisition minDummy;
73  widget.get_preferred_size (minDummy, natSize);
74  }
75 
76  inline int
77  queryNaturalHeight (Gtk::Widget const& widget)
78  {
79  int minDummy{0}, natHeight{0};
80  widget.get_preferred_height(minDummy, natHeight);
81  return natHeight;
82  }
83 
84  inline int
85  queryNaturalWidth (Gtk::Widget const& widget)
86  {
87  int minDummy{0}, natWidth{0};
88  widget.get_preferred_width(minDummy, natWidth);
89  return natWidth;
90  }
91 
93  Gtk::Requisition ICON_SIZ{};
94 
98  const double HYSTERESIS = 1.6;
99 
100  inline void
101  initIconSizeHeuristic (Gtk::Widget const& icon)
102  {
103  if (ICON_SIZ.width > 0) return;
104  queryNaturalSize (icon, ICON_SIZ);
105  }
106 
107  }//(End)helpers
108 
109 
110 
111 
112  IDLabel::~IDLabel() { }
113  ElementBoxWidget::~ElementBoxWidget() { }
114 
115 
116  Literal
117  ElementBoxWidget::Config::getIconID() const
118  {
120  return ICON_placement;
121  }
122 
123  Literal
124  ElementBoxWidget::Config::getMenuSymb() const
125  {
127  return ICON_arrow_hand_menu;
128  }
129 
130  Gtk::IconSize
131  ElementBoxWidget::Config::getIconSize() const
132  {
134  return Gtk::ICON_SIZE_MENU;
135  }
136 
137 
140  {
141  Strategy strategy;
142  if (widthConstraint_)
143  strategy.getWidth = move(widthConstraint_);
144  if (heightConstraint_)
145  strategy.getHeight = move(heightConstraint_);
146  return strategy;
147  }
148 
149 
150  IDLabel::IDLabel (Literal iconID, Literal menuSymb, Gtk::IconSize siz)
151  : Gtk::Box{Gtk::ORIENTATION_HORIZONTAL}
152  , imgIcon_{Gtk::StockID{iconID}, siz}
153  , imgMenu_{Gtk::StockID{menuSymb}, siz}
154  {
155  icon_.set_image(imgIcon_);
156  menu_.set_image(imgMenu_);
157  this->add(icon_);
158  this->add(menu_);
159  this->add(name_);
160  this->set_name(ID_idlabel);
161  this->get_style_context()->add_class(CLASS_background);
162  this->get_style_context()->add_class(CLASS_idlabel);
163  icon_.get_style_context()->add_class(CLASS_idlabel_icon);
164  menu_.get_style_context()->add_class(CLASS_idlabel_menu);
165  name_.get_style_context()->add_class(CLASS_idlabel_name);
166  name_.set_hexpand(true);
167 
168  this->show_all();
169  initIconSizeHeuristic (icon_);
170  }
171 
172 
173  void
174  IDLabel::setCaption(cuString& idCaption)
175  {
176  name_.set_text(idCaption);
177  // can not retrieve size information from hidden widgets...
178  this->show_all(); // Note: size constraint handling will trigger again
179  // cache required full display size (for size constrained layout)
180  queryNaturalSize (*this, labelFullSize_);
181  }
182 
183  cuString
184  IDLabel::getCaption() const
185  {
186  return name_.get_text();
187  }
188 
189 
190 
192  : EventBox{}
193  , strategy_{config.buildLayoutStrategy(*this)}
194  , label_{config.getIconID()
195  ,config.getMenuSymb()
196  ,config.getIconSize()}
197  , frame_{}
198  {
199  set_name (ID_element);
200  get_style_context()->add_class (CLASS_background); // Style to ensure an opaque backdrop
201  get_style_context()->add_class (CLASS_elementbox);
202  label_.get_style_context()->add_class (CLASS_elementbox_idlabel);
203 
204  frame_.set_label_align (0.0, 0.0);
205  frame_.set_label_widget(label_);
206  this->add (frame_);
207 
208  this->show_all();
209  label_.setCaption (config.getName());
210  }
211 
212 
213  void
214  ElementBoxWidget::setName (cuString& nameID)
215  {
216  label_.setCaption (nameID);
217  }
218 
219  cuString
220  ElementBoxWidget::getName() const
221  {
222  return label_.getCaption();
223  }
224 
231  Gtk::SizeRequestMode
233  {
234  return Gtk::SizeRequestMode::SIZE_REQUEST_HEIGHT_FOR_WIDTH;
235  }
236 
259  void
260  ElementBoxWidget::get_preferred_width_vfunc (int& minimum_width, int& natural_width) const
261  {
262  if (strategy_.is_size_constrained())
263  minimum_width = natural_width = strategy_.getWidth();
264  else
265  _Base::get_preferred_width_vfunc (minimum_width,natural_width);
266  }
267 
272  void
273  ElementBoxWidget::get_preferred_height_vfunc (int& minimum_height, int& natural_height) const
274  {
275  if (strategy_.shall_control_height())
276  minimum_height = natural_height = strategy_.getHeight();
277  else
278  _Base::get_preferred_height_vfunc (minimum_height,natural_height);
279  }
280 
281  void
282  ElementBoxWidget::get_preferred_height_for_width_vfunc (int width, int& minimum_height, int& natural_height) const
283  {
284  if (strategy_.is_size_constrained() and strategy_.shall_control_height())
285  minimum_height = natural_height = strategy_.getHeight();
286  else
287  _Base::get_preferred_height_for_width_vfunc (width, minimum_height,natural_height);
288  }
289 
301  void
302  ElementBoxWidget::on_size_allocate (Gtk::Allocation& availableSize)
303  {
304  if (strategy_.is_size_constrained())
305  imposeSizeConstraint (availableSize.get_width(), availableSize.get_height());
306  _Base::on_size_allocate(availableSize);
307  }
308 
309 
318  void
319  ElementBoxWidget::imposeSizeConstraint (int widthC, int heightC)
320  {
321  label_.imposeSizeConstraint (widthC, heightC);
322  }
323 
330  void
331  IDLabel::imposeSizeConstraint (int widthC, int heightC)
332  {
333  // short circuit: need to perform precise checks?
334  if ( labelFullSize_.width > widthC
335  or labelFullSize_.height > heightC
336  )
337  this->adaptSize(widthC, heightC);
338  }
339 
340 
341  namespace {//IDLabel layout management internals....
342 
346  inline int
347  reduce(Gtk::Button& icon)
348  {
349  int widthReduction{0};
350  if (icon.get_visible())
351  {
352  widthReduction = queryNaturalWidth (icon);
353  icon.hide();
354  }
355  return widthReduction;
356  }
357 
359  inline int
360  reduce(Gtk::Label& label, int goal)
361  {
362  ASSERT (goal >=0);
363  int reduction{0};
364  if (label.get_visible())
365  {
366  int width = queryNaturalWidth (label);
370  if (reduction < goal)
371  {//shortening alone does not suffice
372  label.hide();
373  reduction = width;
374  }
375  }
376  return reduction;
377  }
378 
386  template<class FUN>
387  inline bool
388  maybeShow(Gtk::Button& icon, int w, int h, FUN& reCheck)
389  {
390  if (icon.is_visible()) return true; // nothing can be done here
391  bool success{false};
392  if (w >= ICON_SIZ.width * HYSTERESIS and h >= ICON_SIZ.height)
393  {
394  icon.show();
395  if (not (success=reCheck()))
396  icon.hide();
397  }
398  return success;
399  }
400 
401  template<class FUN>
402  inline bool
403  maybeShow(Gtk::Label& label, int w, int h, FUN& reCheck)
404  {
405  bool success{false};
406  // use icon dimensions as as heuristics to determine
407  // if attempting to show the label is worth trying...
408  if (w >= ICON_SIZ.width * HYSTERESIS and h >= ICON_SIZ.height)
409  {
410  label.show();
411  int width = queryNaturalWidth (label);
412  int goal = width - w;
413  if (goal > 0) // too large, yet might fit if shortened
414  reduce (label, goal);
415  if (not (success=reCheck()))
416  label.hide();
417  }
418  return success;
419  }
420 
421 
422  }//(End)Layout helpers
423 
424 
435  void
436  IDLabel::adaptSize (int widthC, int heightC)
437  {
438  // first determine if vertical extension is problematic
439  int currH = queryNaturalHeight (*this);
440  if (currH > heightC)
441  {//hide all child widgets,
442  // not much options left...
443  name_.hide();
444  menu_.hide();
445  icon_.hide();
446  return;
447  }
448 
449  // now test if we need to reduce or can expand
450  int currW = queryNaturalWidth (*this);
451  if (currW > widthC)
452  {//reduce to comply
453  int goal = currW - widthC;
454  ASSERT (goal > 0);
455  if ((goal -= reduce(name_, goal)) <= 0) return;
456  if ((goal -= reduce(menu_) ) <= 0) return;
457  if ((goal -= reduce(icon_) ) <= 0) return;
458  currW = queryNaturalWidth(*this);
459  goal = currW - widthC;
460  ENSURE (goal <= 0, "IDLabel layout management floundered. "
461  "Removed all content, yet remaining width %d > %d"
462  , currW, widthC);
463  }
464  else
465  {//maybe some headroom left to show more?
466  int headroom = widthC - currW;
467  auto reCheck = [&]() -> bool
468  {// WARNING: side effect assignment
469  currW = queryNaturalWidth (*this);
470  currH = queryNaturalHeight(*this);
471  headroom = widthC - currW;
472  return currH <= heightC
473  and currW <= widthC;
474  };
475 
476  if (not maybeShow (icon_, headroom, heightC, reCheck)) return;
477  if (not maybeShow (menu_, headroom, heightC, reCheck)) return;
478  if (not maybeShow (name_, headroom, heightC, reCheck)) return;
479  }
480  }
481 
482 
483 
484 }}// 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:76
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:37
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