LUMIERA.clone/src/gui/model/tangible.hpp
Ichthyostega 685a9b84ee Library: replace boost::noncopyable by our own library solution
Benefits
 - get rid of yet another pervasive Boost dependency
 - define additional more fine grained policies (move only, clonable)
2018-03-24 05:35:13 +01:00

253 lines
11 KiB
C++

/*
TANGIBLE.hpp - a tangible element of the user interface
Copyright (C) Lumiera.org
2015, 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 tangible.hpp
** Abstraction: a tangible element of the User Interface.
** This is a generic foundation for any elements of more than local relevance
** within the Lumiera UI.<br/> Any such element is connected to the [UI-Bus](ui-bus.hpp).
**
** ## rationale
**
** Simple user interfaces can be built by wiring up the actions right within the
** code processing the trigger of actions. This leads to core functionality littered
** and tangled with presentation code. The next step towards a more sane architecture
** would be to code a forwarding call into every UI action, invoking some core facade
** in turn. This approach works, but is repetitive and thus lures the lazy programmer
** into taking shortcuts. Since we can foresee the Lumiera UI to become quite challenging
** in itself, we prefer to introduce a **mediating backbone**, impersonating the role
** of the _Model_ and the _Controler_ in the [MVC-Pattern] in common UI architecture.
**
** The MVC-Pattern as such is fine, and probably the best we know for construction of
** user interfaces. But it doesn't scale well towards the integration into a larger and
** more structured system. There is a tension between the Controller in the UI and other
** parts of an application, which as well need to be _in control._ And, even more important,
** there is a tension between the demands of UI elements for support by a model, and the
** demands to be placed on a core domain model of a large scale application. This tension is
** resolved by enacting these roles while transforming the requests and demands into _Messages._
**
** This way, we separate between immediate local control of UI state and the more global,
** generic concerns of interaction control and command binding. The immediately tangible
** "mechanics" of the UI shall be implemented in a conventional way, right within the
** concrete widget (or controller) code. But, since any widget concerned with more than
** local behaviour will inherit from \ref Tangible, the embedded [UI-Bus terminal](\ref ctrl::BusTerm)
** `Tangible::uiBus_`
** can be used for interaction with core services.
**
** ## the generic interface element API
**
** The _generic interface element_ based on [Tangible](\ref model::Tangible) covers a set of behaviour
** common to all elements of the interface. This behaviour is targeted towards the _integration_
** with the core application. Beyond that, there are still several concerns regarding presentation,
** like a common styling. These are addressed the conventional way, through a common \ref StyleManager.
** The following discussion focuses on the aspects of integration with the core.
**
** For one reason or another, any element in the UI can appear and go away.
** This lifecycle behaviour corresponds to attachment and deregistration at the UI-Bus
**
** In regular, operative state, an interface element may initiate _actions_, which translate
** into _commands_ at the session interface. To complicate matters, there might be higher-level,
** cooperative _gestures_ implemented within the interface, leading to actions being formed
** similar to sentences of spoken language, with the help of a _focus concept_ -- anyway, this
** means, in the end, there is a _subject_ and a _predicate_. These need to be bound in order
** to form an _action_. And some interface element takes on or relates to the role of the
** underlying, the subject, the **tangible element**.
**
** @todo Some actions are very common and can be represented by a shorthand.
** An example would be to tweak some property, which means to mutate the attribute of a
** model element known beforehand. Such tweaks are often caused by direct interaction,
** and thus have the tendency to appear in flushes, which we want to batch in order to
** remove some load from the lower layers.
**
** And then there are manipulations that _alter presentation state:_ Scrolling, canvas dragging,
** expanding and collapsing, moving by focus or manipulation of a similar presentation control.
** These manipulations in itself do not constitute an action. But there typically is some widget
** or controller, which is responsible for the touched presentation state. If this entity judges
** the state change to be relevant and persistent, it may [send](\ref BusTerm::note()) a
** **state mark** into the UI-Bus -- expecting this marked state to be remembered.
** In turn this means the bus terminal might feed a state mark back into the
** tangible element, expecting this state to be restored.
**
** A special case of state marking is the presentation of _transient feedback._
** Such feedback is pushed from "somewhere" towards given elements, which react through an
** implementation dependent visual state change (flushing, colour change, marker icon).
** If such state marking is to be persistent, the interface element has in turn to send
** a specific state mark. An example would be a permanent error flag with an explanatory
** text showed in mouse over.
**
** And finally, there are the _essential updates_ -- any changes in the model _for real._
** These are sent as notifications just to some relevant top level element, expecting this element
** to request a [diff](tree-diff.hpp) and to mutate contents into shape recursively.
**
** ## Interactions
**
** - **lifecycle**: connect to an existing term, supply the [EntryID](\ref Tangible::ID) of the new element.
** This interaction also implies, that the element automatically detaches itself at end of life.
** - **act**: send a \ref GenNode representing the action
** - **note**: _send_ a GenNode representing the _state mark_
** - **mark**: _receive_ a GenNode representing the _feedback,_ a replayed _state mark_ or _generic message._
** - **diff**: ask to retrieve a diff, which
** - either is an incremental status update
** - or is a from-scratch reconfiguration
**
** Beside these basic interactions, the generic element also exposes some common signal slots
** - slotExpand() prompts the element to transition into expanded / unfolded state.
** If this state is to be sticky, the element answers with a _state mark_
** - slotReveal() prompts the element to bring the indicated child into sight.
** Typically, this request will "bubble up" recursively.
** These slots are defined to be `sigc::trackable` for automated disconnection
** see [sigc-track] for an explanation.
**
** [MVC-Pattern]: http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
** [sigc-track]: http://issues.lumiera.org/ticket/940#comment:3 "Ticket #940"
**
** @see \ref AbstractTangible_test
** @see \ref BusTerm_test
**
*/
#ifndef GUI_MODEL_TANGIBLE_H
#define GUI_MODEL_TANGIBLE_H
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
#include "gui/ctrl/bus-term.hpp"
#include "lib/diff/diff-mutable.hpp"
#include "lib/idi/entry-id.hpp"
#include "lib/symbol.hpp"
#include <sigc++/trackable.h>
#include <utility>
#include <string>
namespace gui {
namespace model {
using std::string;
using lib::Symbol;
/**
* Interface common to all UI elements of relevance for the Lumiera application.
* Any non-local and tangible interface interaction will at some point pass through
* this foundation element, which forms the joint and attachment to the UI backbone,
* which is the [UI-Bus](ui-bus.hpp). Any tangible element acquires a distinct identity
* and has to be formed starting from an already existing bus nexus.
* @see [explanation of the basic interactions](tangible.hpp)
* @warning Tangible is `NonCopyable` for good reason: the UI-Bus Nexus adds a direct
* reference into the routing table, tied to the given Tangible's ID (identity.
* Consequently you must not store tangibles in STL containers, since these
* might re-allocate and thus change the location in memory.
*/
class Tangible
: public sigc::trackable
, public lib::diff::DiffMutable
, util::NonCopyable
{
public:
using ID = ctrl::BusTerm::ID;
using LuidH = lib::hash::LuidH;
protected:
using Rec = lib::diff::Rec;
using GenNode = lib::diff::GenNode;
ctrl::BusTerm uiBus_;
Tangible(ID identity, ctrl::BusTerm& nexus)
: uiBus_{nexus.attach(identity, *this)}
{ }
public:
virtual ~Tangible(); ///< this is an interface
operator ID() const { return uiBus_.getID();}
operator LuidH() const { return uiBus_.getID().getHash(); }
void reset();
void clearMsg();
void clearErr();
template<typename...ARGS>
void invoke (Symbol cmdID, ARGS&&...);
void invoke (Symbol cmdID, Rec&& arguments);
void slotExpand();
void slotCollapse();
void slotReveal(ID child);
void markFlash();
void markMsg (string message);
void markErr (string error);
void mark(GenNode const&);
protected:
virtual bool doReset() =0;
virtual bool doClearMsg() =0;
virtual bool doClearErr() =0;
virtual bool doExpand (bool yes) =0;
virtual void doReveal (ID child) =0;
virtual void doRevealYourself () =0;
virtual bool doMsg (string) =0;
virtual bool doErr (string) =0;
virtual void doFlash() =0;
virtual void doMark(GenNode const&) =0;
public:
virtual void buildMutator (lib::diff::TreeMutator::Handle) =0;
/** override default size traits for diff application.
* @remarks this value here is hard coded, base on what
* can be expected for diff application to UI elements.
*/
friend constexpr size_t
treeMutatorSize (const Tangible*)
{
return 256;
}
};
/** convenience shortcut to issue a command with several arguments */
template<typename...ARGS>
inline void
Tangible::invoke (Symbol cmdID, ARGS&&... args)
{
using GenNodeIL = std::initializer_list<GenNode>;
invoke (cmdID,
Rec(Rec::TYPE_NIL_SYM
,GenNodeIL{}
,GenNodeIL {std::forward<ARGS> (args)...}));
} // not typed, no attributes, all arguments as children
}} // namespace gui::model
#endif /*GUI_MODEL_TANGIBLE_H*/