lumiera_/src/gui/timeline/timeline-controller.cpp
Ichthyostega fa6ba76f85 investigate insidious ill-guided conversion
As it turns out, using the functional-notation form conversion
with *parentheses* will fall back on a C-style (wild, re-interpret) cast
when the target type is *not* a class. As in the case in question here, where
it is a const& to a class. To the contrary, using *curly braces* will always
attempt to go through a constructor, and thus fail as expected, when there is
no conversion path available.

I wasn't aware of that pitfall. I noticed it since the recently introduced
class TimelineGui lacked a conversion operator to BareEntryID const& and just
happily used the TimelineGui object itself and did a reinterpret_cast into BareEntryID
2018-10-12 23:42:56 +02:00

172 lines
7.8 KiB
C++

/*
TimelineController - coordinate operation of timeline display
Copyright (C) Lumiera.org
2016, Hermann Vosseler <Ichthyostega@web.de>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* *****************************************************/
/** @file timeline-controller.cpp
** Implementation details of timeline operation management and control.
** - we build a binding to allow TimelineController to handle mutation messages
** on behalf of "the timeline". While the setup of a Timeline is quite flexible
** at the session level, here, when it comes to UI presentation, it can be
** boiled down to
** + a name
** + a single mandatory root track (which in turn could hold nested tracks)
** - thus we get a rather simple mapping, with some fixed attributes and no
** flexible child collection. The root track is implemented as TrackPresenter.
**
** @todo as of 10/2018 timeline display in the UI is rebuilt to match the architecture
** @see TimelineWidget
**
*/
#include "gui/gtk-base.hpp"
#include "include/ui-protocol.hpp"
#include "gui/timeline/timeline-controller.hpp"
#include "gui/timeline/track-presenter.hpp"
#include "gui/timeline/marker-widget.hpp"
//#include "gui/workspace/workspace-window.hpp"
//#include "gui/ui-bus.hpp"
//#include "lib/format-string.hpp"
//#include "lib/format-cout.hpp"
//#include "lib/util.hpp"
//#include <algorithm>
//#include <cstdlib>
//using util::_Fmt;
using lib::diff::TreeMutator;
using lib::diff::collection;
using std::make_unique;
//using std::shared_ptr;
//using std::weak_ptr;
//using util::contains;
//using Gtk::Widget;
//using sigc::mem_fun;
//using sigc::ptr_fun;
//using std::cout;
//using std::endl;
namespace gui {
namespace timeline {
//const int TimelineWidget::TrackPadding = 1;
//const int TimelineWidget::HeaderWidth = 150;
//const int TimelineWidget::HeaderIndentWidth = 10;
TimelineController::TimelineController (ID identity, ID trackID, ctrl::BusTerm& nexus)
: Controller{identity, nexus}
, markers_{}
, fork_{new TrackPresenter{trackID, nexus}}
, name_{identity.getSym()} // fallback initialise name from human-readable ID symbol
{
UNIMPLEMENTED ("how to make the controller operative...");
}
TimelineController::~TimelineController()
{
}
/**
* @internal this method is invoked by the UI-Bus when dispatching a MutationMessage...
* @remarks this is likely the first occasion a casual reader sees such a binding function,
* thus some explanations might be helpful. This is part of the »diff framework«: we use
* messages to _communicate changes on structured data._ We might as well just use a common
* object model, but we refrain from doing so, to avoid tight coupling, here between the
* core logic and the structures in the UI. Rather we assume that both sides share a
* roughly compatible understanding regarding the structure of the session model.
* Exchanging just diff messages allows us to use private implementation data structures
* in the UI as we see fit, without the danger of breaking anything in the core. And vice
* versa. You may see this as yet another way of data binding between model and view.
* The TreeMutator helps to accomplish this binding between a generic structure description,
* in our case based on GenNode elements, and the private data structure, here the private
* object fields and the collection of child objects within TimelineController. To ease this
* essentially "mechanic" and repetitive task, the TreeMutator offers some standard building
* blocks, plus a builder DSL, allowing just to fill in the flexible parts with some lambdas.
* Yet still, the technical details of getting this right can be tricky, especially since
* it is very important to set up those bindings in the right order. Basically we build
* a stack of decorators, so what is mentioned last will be checked first. Effectively
* this creates a structure of "onion layers", where each layer handles just one aspect
* of the binding. This works together with the convention that the diff message must
* mention all changes regarding one group (or kind) of elements together and completely.
* This is kind of an _object description protocol_, meaning that the diff has to mention
* the metadata (the object type) first, followed by the "attributes" (fields) and finally
* nested child objects. And nested elements can be handled with a nested diff, which
* recurses into some nested scope. In the example here, we are prepared to deal with
* two kinds of nested scope:
* - the _fork_ (that is the tree of tracks) is a nested structure
* - we hold a collection of marker child objects, each of which can be entered
* as a nested scope.
* For both cases we prepare a way to build a _nested mutator_, and in both cases this
* is simply achieved by relying on the common interface of all those "elements", which
* is gui::model::Tangible and just happens to require each such "tangible" to offer
* a mutation building method, just like this one here. Simple recursive programming.
* @see DiffComplexApplication_test
*/
void
TimelineController::buildMutator (TreeMutator::Handle buffer)
{
using PMarker = unique_ptr<MarkerWidget>;
buffer.create (
TreeMutator::build()
.attach (collection(markers_)
.isApplicableIf ([&](GenNode const& spec) -> bool
{ // »Selector« : require object-like sub scope
return spec.data.isNested();
})
.matchElement ([&](GenNode const& spec, PMarker const& elm) -> bool
{ // »Matcher« : how to know we're dealing with the right object
return spec.idi == ID{*elm};
})
.constructFrom ([&](GenNode const& spec) -> PMarker
{ // »Constructor« : what to do when the diff mentions a new entity
return make_unique<MarkerWidget>(spec.idi, this->uiBus_);
})
.buildChildMutator ([&](PMarker& target, GenNode::ID const& subID, TreeMutator::Handle buff) -> bool
{ // »Mutator« : how to apply the diff recursively to a nested scope
if (ID{*target} != subID) return false; // - require match on already existing child object
target->buildMutator (buff); // - delegate to child to build nested TreeMutator
return true;
}))
.mutateAttrib(ATTR_fork, [&](TreeMutator::Handle buff)
{ // »Attribute Mutator« : how enter an object field as nested scope
REQUIRE (fork_);
fork_->buildMutator(buff);
})
.change(ATTR_name, [&](string val)
{ // »Attribute Setter« : how assign a new value to some object field
name_ = val;
}));
}
}}// namespace gui::timeline