Lumiera  0.pre.03
»edit your freedom«
clip-widget.cpp
Go to the documentation of this file.
1 /*
2  ClipWidget - display of a clip in timeline or media bin view
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 
84 #include "stage/gtk-base.hpp"
87 
88 #include "lib/format-string.hpp"
89 
90 #include "lib/util.hpp"
91 
92 #include <utility>
93 
94 
95 
96 using util::_Fmt;
97 using lib::time::TimeVar;
98 using util::unConst;
99 using std::optional;
100 
101 
102 namespace stage {
103 namespace timeline {
104  namespace error = lumiera::error;
105 
106  const int ClipDelegate::defaultOffsetY{0};
107  const string ClipDelegate::defaultName{_("clip")};
108 
109 
110  ClipDelegate::ClipDelegate() { }
112 
113 
114  namespace {// details of concrete clip appearance styles...
115 
118 
119 
120  class ClipData
121  : public ClipDelegate
123  {
124  TimeVar start_;
125  TimeVar dur_;
126 
127  public: /* === Partial implementation of ClipDelegate === */
128  TimeVar&
129  accessStartTime() override
130  {
131  return start_;
132  }
133 
134  TimeVar&
135  accessDuration() override
136  {
137  return this->dur_;
138  }
139 
140 
141  public:
143  : ClipDelegate{}
144  , start_{timings.start()}
145  , dur_{timings.duration()}
146  { }
147 
148  ClipData (ClipData&&) = default;
149  };
150 
151 
157  : public ClipData
158  {
159  WidgetHook& display_;
160  uString clipName_;
161 
162  /* === Interface ClipDelegate === */
163 
164  Appearance
165  currentAppearance() const override
166  {
167  return Appearance::PENDING;
168  }
169 
170  Appearance
172  {
173  return currentAppearance();
174  }
175 
176  cuString
177  getClipName() const override
178  {
179  return clipName_;
180  }
181 
182  void
183  setClipName(cuString& newName) override
184  {
185  clipName_ = newName;
186  }
187 
188 
193  uint
194  calcRequiredHeight() const override
195  {
196  return 0;
197  }
198 
199  uint
200  getVerticalOffset() const override
201  {
202  return 0;
203  }
204 
205  WidgetHook&
206  getCanvas() const override
207  {
208  return unConst(this)->display_;
209  }
210 
211  void
212  updatePosition() override
213  {
214  /* NOOP */
215  }
216 
217 
218  public:
219  DormantClip(WidgetHook& displayAnchor)
220  : ClipData{}
221  , display_{displayAnchor}
222  , clipName_{}
223  { }
224 
226  DormantClip(ClipData&& existing)
227  : ClipData{std::move (existing)}
228  , display_{existing.getCanvas()}
229  , clipName_{existing.getClipName()}
230  { }
231  };
232 
233 
235  : public HookedWidget
236  , public ClipData
237  {
238 
239  /* === Interface ClipDelegate === */
240 
241  Appearance
242  currentAppearance() const override
243  {
245  return Appearance::COMPACT;
246  }
247 
248  Appearance
249  changeAppearance (Appearance desired) override
250  {
252  return currentAppearance();
253  }
254 
255  cuString
256  getClipName() const override
257  {
258  return widget::ElementBoxWidget::getName();
259  }
260 
261  void
262  setClipName(cuString& newName) override
263  {
264  widget::ElementBoxWidget::setName (newName);
265  }
266 
267  uint
268  getVerticalOffset() const override
269  {
270  return defaultOffsetY;
271  }
272 
273  uint
274  calcRequiredHeight() const override
275  {
276  return this->get_height();
277  }
278 
279  WidgetHook&
280  getCanvas() const override
281  {
282  return HookedWidget::getCanvas();
283  }
284 
285  void
286  updatePosition() override
287  {
288  WidgetHook::Pos nominalPos = establishHookPoint(nullptr);
289  this->moveTo (nominalPos.x, nominalPos.y);
290  establishHorizontalExtension();
291  }
292 
293  /* ==== Size and Layout handling ==== */
294 
298  int
300  {
301  int hsiz = getCanvas().getMetric().translateTimeToPixels (accessDuration());
302  return hsiz;
303  }
304 
305 
306  public:
307  ClipWidget(WidgetHook& displayAnchor, TimeSpan const& timings)
308  : HookedWidget{displayAnchor.hookedAt(timings, defaultOffsetY), widget::Kind::CONTENT
312  [this]() { return establishHorizontalExtension(); }
313  )}
314  , ClipData{timings}
315  {
316  show_all();
317  }
318 
320  ClipWidget(ClipData&& existing, WidgetHook* newView =nullptr)
321  : HookedWidget{existing.establishHookPoint(newView) , widget::Kind::CONTENT
323  , widget::name(existing.getClipName())
325  [this]() { return establishHorizontalExtension(); }
326  )}
327  , ClipData{std::move (existing)}
328  {
329  show_all();
330  }
331  };
332 
333 
334 
335  enum Mode { HIDDEN, SUMMARY, INDIVIDUAL };
336 
337  inline Mode
338  classifyAppearance (ClipDelegate::Appearance appearance)
339  {
340  return appearance < ClipDelegate::SYMBOLIC? HIDDEN
341  : appearance < ClipDelegate::ABRIDGED? SUMMARY
342  : INDIVIDUAL;
343  }
344 
345 
347  inline bool
348  canShow (Time start)
349  {
350  return start != Time::NEVER;
351  }
352 
353  inline bool
354  canRepresentAsClip (PDelegate& existing, optional<TimeSpan> const& timing)
355  {
356  return (existing and canShow(existing->accessStartTime()))
357  or (not existing and timing and canShow(timing->start())) ;
358  }
359 
360 
366  inline ClipDelegate*
367  buildDelegateFor (Mode newMode, PDelegate& existingDelegate, WidgetHook* newView, optional<TimeSpan> const& timing)
368  {
369  if (existingDelegate)
370  { // flip existing delegate to another instance for newMode...
371  REQUIRE (INSTANCEOF (ClipData, existingDelegate.get()));
372  ClipData& clipData = static_cast<ClipData&> (*existingDelegate);
373 
374  switch (newMode)
375  {
376  case HIDDEN:
377  return new DormantClip (std::move (clipData));
378  case INDIVIDUAL:
379  return new ClipWidget (std::move (clipData), newView);
380  case SUMMARY:
381  UNIMPLEMENTED ("Summary/Overview presentation style");
382  }
383  }
384  else
385  { // First time: build new delegate from scratch
386  REQUIRE (newMode==HIDDEN or (timing and canShow (timing->start())));
387  REQUIRE (newView);
388 
389  switch (newMode)
390  {
391  case HIDDEN:
392  return new DormantClip{*newView};
393  case INDIVIDUAL:
394  return new ClipWidget{*newView, *timing};
395  case SUMMARY:
396  UNIMPLEMENTED ("Summary/Overview presentation style");
397  }
398  }
399  NOTREACHED("unsupported clip display mode.");
400  }
401  }//(End)clip appearance details.
402 
403 
404 
405  /* === Appearance Style state transitions === */
406 
408  ClipDelegate::selectAppearance (PDelegate& existing, Appearance desired, WidgetHook* newView, optional<TimeSpan> const& timing)
409  {
410  REQUIRE (existing or newView, "need either an existing delegate or also a new View/Canvas");
411 
412  Appearance current = existing? existing->currentAppearance()
413  : Appearance::PENDING;
414  if (not canRepresentAsClip (existing, timing))
415  desired = Appearance::PENDING;
416  // classify all possible appearances into three base presentation modes
417  Mode curMode = classifyAppearance (current);
418  Mode newMode = classifyAppearance (desired);
419 
420  if (newView or newMode != curMode)
421  { // need to switch the clip delegate
422  PDelegate newState (buildDelegateFor (newMode, existing, newView, timing));
423  swap (existing, newState);
424  }
425  ENSURE (existing);
426  return existing->changeAppearance (desired);
427  // fine-tune appearance style within limits of the mode established
428  }
429 
430 
431  WidgetHook::Pos
433  {
434  if (not newView)
435  newView = & getCanvas();
436  return newView->hookedAt (Time{accessStartTime()}, defaultOffsetY);
437  }
438 
439 
440  Gtk::Widget&
442  {
443  if (manager and manager->currentAppearance() >= ClipDelegate::ABRIDGED)
444  return static_cast<ClipWidget&> (*manager);
445 
446  else
447  throw error::State (_Fmt{"Attempt to access the Widget for clip('%s') in presentation state %d. "
448  "This implies an error in the signal wiring logic and state handling."}
449  % string{manager? manager->getClipName() : "<not initialised>"}
450  % int {manager? manager->currentAppearance() : -1}
451  ,LERR_(UIWIRING)
452  );
453  }
454 
455 
456 
457 }}// namespace stage::timeline
static Appearance selectAppearance(PDelegate &existing, Appearance desired=PENDING, WidgetHook *newView=nullptr, optional< TimeSpan > const &timing=nullopt)
request to change the clip delegate&#39;s appearance style, if possible.
a mutable time value, behaving like a plain number, allowing copy and re-accessing ...
Definition: timevalue.hpp:232
static const string defaultName
placeholder name – typically overridden from the model
bool canShow(Time start)
special convention to suppress a clip with start time == Time::NEVER
Appearance currentAppearance() const override
presentation mode and style currently employed
ClipDelegate * buildDelegateFor(Mode newMode, PDelegate &existingDelegate, WidgetHook *newView, optional< TimeSpan > const &timing)
Appearance changeAppearance(Appearance desired) override
alter appearance style, to the degree possible for this delegate.
Appearance currentAppearance() const override
presentation mode and style currently employed
A Clip not directly mapped into presentation, yet present as entity within the timeline framework...
#define INSTANCEOF(CLASS, EXPR)
shortcut for subclass test, intended for assertions only.
Definition: util.hpp:514
Types marked with this mix-in may be moved but not copied.
Definition: nocopy.hpp:49
A widget attached onto a display canvas or similar central presentation context.
Front-end for printf-style string template interpolation.
Widget to render an ID label with associated icon.
virtual Appearance currentAppearance() const =0
presentation mode and style currently employed
Appearance
desired appearance style for the clip
Appearance changeAppearance(Appearance) override
alter appearance style, to the degree possible for this delegate.
A front-end for using printf-style formatting.
static const Duration NIL
constant to indicate "no duration"
Definition: timevalue.hpp:506
Lumiera&#39;s internal time value datatype.
Definition: timevalue.hpp:299
Derived specific exceptions within Lumiera&#39;s exception hierarchy.
Definition: error.hpp:190
WidgetHook::Pos establishHookPoint(WidgetHook *newView)
(re)establish current canvas attachment coordinates, thereby possibly switching to a new canvas imple...
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...
cuString getClipName() const override
human readable rendering of the clip&#39;s name or identity
virtual ~ClipDelegate()
this is an interface
This widget provides the concrete rendering of a clip-like entity.
static const Time NEVER
border condition marker value. NEVER >= any time value
Definition: timevalue.hpp:314
represents moving (or still) image data
ClipWidget(ClipData &&existing, WidgetHook *newView=nullptr)
state switch ctor
static Gtk::Widget & expect_and_expose_Widget(PDelegate &manager)
Wrapper to safely expose the actual clip implementation widget.
int establishHorizontalExtension()
use underlying canvas metric to derive a size constraint, taking into account the duration of the cli...
static const int defaultOffsetY
vertical offset below the track start
A time interval anchored at a specific point in time.
Definition: timevalue.hpp:573
cuString getClipName() const override
human readable rendering of the clip&#39;s name or identity
Widget serves to represent a piece of content (Clip)
A set of basic GTK includes for the UI.
ElementBoxWidget::Config::Qualifier constrained(SizeGetter widthConstraint)
switch in to size-constrained layout mode.
ElementBoxWidget::Config::Qualifier name(string id)
define the name-ID displayed in the caption
uint calcRequiredHeight() const override
This is a mere data record without actual presentation, and thus can not occupy any screen extension...