GCC-7: integrate recent clean-up and refactoring work (lib::Depend)

# Conflicts:
#	src/lib/error-exception.cpp
#	src/lib/error.hpp
#	src/lib/opaque-holder.hpp
#	src/lib/wrapper.hpp
#	src/proc/mobject/session/sess-manager-impl.hpp
This commit is contained in:
Fischlurch 2018-04-27 02:23:20 +02:00
commit f06038828c
414 changed files with 34590 additions and 7553 deletions

View file

@ -82,8 +82,6 @@ def configure(env):
if not conf.CheckCXXHeader('boost/config.hpp'):
problems.append('We need the C++ boost-libraries.')
else:
if not conf.CheckCXXHeader('boost/noncopyable.hpp'):
problems.append('We need boost::noncopyable')
if not conf.CheckCXXHeader('boost/lexical_cast.hpp'):
problems.append('We need boost::lexical_cast')
if not conf.CheckCXXHeader('boost/format.hpp'):

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View file

@ -0,0 +1,258 @@
Layers -- Subsystems -- Lifecycle
=================================
:Author: Hermann Voßeler
:Email: <Ichthyostega@web.de>
:Date: 2018
:Toc:
WARNING: under construction -- [red]#some parts to be filled in#
Terminology
-----------
Layer::
A conceptual realm within the application to group related concerns and define an ordering.
Layers are above/below each other and may depend _solely_ on lower layers. The Application
may be operated in a partial layer configuration with only some lower layers. Each layer
deals with distinct topics and has its own style. In Lumiera, we distinguish three layers
+
* User Interface -> Interaction
* Proc-Layer -> Processing
* Backend -> Data manipulation
Subsystem::
A runtime entity which acts as anchor point and framework to maintain a well defined lifecycle.
While layers are conceptual realms, subsystems can actually be started and stopped and their
dependencies are represented as data structure. A subsystem typically starts one or several
primary components, which might spawn a dedicated thread and instantiate further components
and services.
Service::
A component within some subsystem is termed _Service_
+
--
* when it exposes an interfaces with an associated contract
(informal rules about usage pattern and expectations)
* when it accepts invocations from arbitrary other components
without specific wiring or hard coded knowledge
--
+
The service lifecycle is tied to the lifecycle of the related subsystem; whenever the subsystem is ``up and running'',
any contained services can be accessed and used. Within Lumiera, there is no _service broker_ or any similar kind
of elaborate _service discovery_ -- rather, services are accessed *by name*, where ``name'' is the _type name_
of the service interface.
Dependency::
A relation at implementation level and thus a local property of an individual component. A dependency
is something we need in order to complete the task at hand, yet a dependency lies beyond that task and
relates to concerns outside the scope and theme of this actual task. Which means, a dependency is not
introduced by the task or part of the task, rather the task is the reason why some entity dealing with
it needs to _pull_ the dependency, in order to be able to handle the task. So essentially, dependencies
are accessed on-demand. Dependencies might be other components or services, and typically the user
(consumer) of a dependency just relies on the corresponding interface and remains agnostic with respect
to the dependency's actual implementation or lifecycle details.
Subsystems
----------
As a coherent part of the application, a subsystem can be started into running state. Several subsystems
can reside within a single layer, which leads to rather tight coupling. We do not define boundaries between
subsystems in a strict way (as we do with layers) -- rather some component is associated with a subsystem
when it relies on services of the subsystem to be ``just available''. However, the grouping into subsystems
is often also a thematic one, and related to a specific abstraction level. To give an example, the Player
deals with _calculation streams,_ while the engine handles individual _render jobs,_ which together form
a calculation stream. So there is a considerable grey area in between. Any code related with defining and
then dispatching frame jobs needs at least some knowledge about the presence of calculation streams; yet
it depends and relies on the scheduling service of the engine. In the end, it remains a question of
architecture to keep those dependency chains ordered in a way to form a one-way route: when we start
the engine, it must not instantiate a component which _requires the player_ in order to be operative.
Yet we can not start the player without having started the engine beforehand; if we do, its services
will throw exceptions due to missing dependencies on first use.
However, subsystems as such are not dynamically configured. This was a clear cut design decision (and the
result of a heated debate within the Lumiera team at that time). We do _not expect_ to load just some plug-in
dynamically, inserted via an UI-action at runtime, which then installs a new subsystem and hooks it into the
existing architecture. The flexibility lies in what we can do with the _contents_ of the session -- yet the
application itself is not conceived as set of Lego(TM) bricks. Rather, we identify a small number of coherent
parts of the application, each with its own theme, style, relations and contingencies.
Engine
~~~~~~
_tbw_
Player
~~~~~~
_tbw_
Session
~~~~~~~
_tbw_
User Interface
~~~~~~~~~~~~~~
_tbw_
Script Runner
~~~~~~~~~~~~~
_tbw_
Net Node
~~~~~~~~
_tbw_
Lifecycle
---------
Dependencies and abstraction through interfaces are ways to deal with complexity getting out of hand.
When done well, we can avoid adding _accidental complexity_ -- yet essential complexity as such can not
be removed, but with the help of abstractions it can be raised to another level.footnote:[Irony tags here.
There is a lot of hostility towards abstractions, because it is quite natural to conflate the abstraction
with the essential complexity it has to deal with. It seems compelling to kill the abstraction, in the
hope to kill the complexities as well -- an attitude rather effective, in practice...].
When components express their external needs in the form of dependency on an interface, the immediate tangling
at the code level is resolved, however, someone needs to implement that interface, and this other entity needs
to be _available_. It is now an architecture challenge to get those dependency chains ordered. A way to
circumvent this problem is to rely on a _lifecycle_ with several _phases._
This is the idea behind the subsystems and the subsystem runner.
. First we define an ordering between the subsystems. The most basic subsystem (the Engine) is started first.
. Within a subsystem, components may be mutually dependent. However, we establish a new rule, dictating that
during the _startup phase_ only local operations within a single component are allowed. The component need
to be written in a way that it does not need the help of anything ``remote'' in order to get its inner
workings up and ready. The component may rely on its members and on other services it created, _owns and
manages._ And sometimes we do need to rely on a more low-level service in another subsystem or in the
application core.footnote:[A typical example would be the reliance on threading, locking or application
configuration.] -- which then creates a hard dependency on _architecture level_
. Moreover, we ensure that all operational activity is generated by actual work tasks, and that such tasks
in turn may be initiated _solely through official interfaces._ Such interfaces are to be _opened explicitly_
at the end of the startup phase.
. In operational mode, any part of the system can now assume for its dependencies to be ``just available''.
. And finally we establish a further rule to _disallow extended clean-up._ Everything must be written in a
way such that when a task is done, it is _really done_ and settled. (Obviously there are some fine points
to consider here, like caching or elaborate buffer and I/O management). The rationale is that after leaving
the operational phase at the end of `main()` the application is able to unwind in any arbitrary way.
The problem with emergencies
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This concept has a weak spot however: A catastrophic failure might cause any subsystem to break down immediately.
The handler within the subsystem's primary component will hopefully detect the corresponding exception and signal
emergency to the subsystem runner. However, the working services of that subsystem are already gone at that point.
And even while other subsystems get the (emergency) shutdown trigger, some working parts may be already failing
catastrophically due to their dependencies being dragged away suddenly.
Lumiera is not written for exceptional resilience or high availability. Our attitude towards such failures can
be summarised as ``Let it crash''. And this is another rationale for the ruling against extended clean-up.
Any valuable work done by the user should be accepted and recorded persistently right away. Actions on the
session are logged, like in a database. The user may still save snapshots, but basically any actual change
is immediately recorded persistently. And thus we may crash without remorse.
Static initialisation and shutdown
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A lot of fine points can be made about when precisely static objects in C\++ will be initialised or destroyed.
However, anything beyond the scope of `main()` is not meant to be used for regular application code. Extended
initialisation, dependency management and decommissioning -- when actually necessary -- should be part of the
application code proper.footnote:[this is established ``best practice'' for good reasons. The interplay of
static lifespan, various translation units and even dynamically loaded libraries together with shared access
becomes intricate and insidious quite easily. And since in theory any static function could use some static
variable residing in another translation unit, it is always possible to construct a situation where objects
are accessed after being destroyed. Typically such objects do not even look especially ``dead'', since the
static storage remains in place and still holds possibly sane values. Static (global) variables, like raw
pointers, allow to subvert the deterministic automatic memory management, which otherwise is one of the
greatest strengths of C++. Whenever we find ourselves developing extended collaborative logic based on
several statics, we should consider to transform this logic into regular objects, which are easier to
test and better to reason about. If it really can not be avoided to use such units of logic from a
static context, it should at least be packaged as a single object, plus we should ensure this logic
can only be accessed through a regular (non static) object as front-end. Packaged this way, the
most common and dangerous pitfalls with statics can be avoided.] And since Lumiera indeed allows
for link:{ldoc}/technical/library/Dependencies.html[[lazily initialised dependencies], we
establish the policy that *destructors must not rely on dependencies*. In fact, they should
not do any tangible work at all, beyond releasing other resources.
Lifecycle Events
~~~~~~~~~~~~~~~~
The Application as a whole conducts a well defined lifecycle; whenever transitioning to the next phase,
a _Lifecycle Event_ is issued. Components may register a notification hook with the central _Lifecycle Manager_
(see 'include/lifecycle.h') to be invoked whenever a specific event is emitted. The process of registration
can be simplified by planting a static variable of type `lumiera::LifecycleHook`.
WARNING: A callback enrolled this way needs to be callable at the respective point in the lifecycle,
otherwise the application will crash.
`ON_BASIC_INIT`::
Invoked as early as possible, somewhere in the static initialisation phase prior to entering `main()`.
In order to install a callback hook for this event, the client must plant a static variable somewhere.
`AppState`::
This is the Lumiera »Application Object«. It is a singleton, and should be used by `main()` solely.
While not a lifecycle event in itself, it serves to bring up some very fundamental application services:
+
--
- the plug-in loader
- the application configuration
--
+
After starting those services within the `AppState::init()` function,
the event `ON_GLOBAL_INIT` is emitted.
`ON_GLOBAL_INIT`::
When this event occurs, the start-up phase of the application has commenced. The command line was already
parsed and the basic application configuration is loaded, but the subsystems are not yet initialised.
`Subsys::start()`::
By evaluation of the command line, the application object determines what subsystems actually need to
be started; those will receive the `start()` call, prompting them to enter their startup phase, to
instantiate all service objects and open their business façade when ready
`ON_SESSION_START`::
When this hook is activated, the session implementation facilities are available and the corresponding
interfaces are already opened and accessible, but the session itself is completely pristine and empty.
Basic setup of the session can be performed at that point. Afterwards, the session contents will be
populated.
`ON_SESSION_INIT`::
At this point, all specific session content and configuration has already be loaded. Any subsystems
in need to build some indices or to establish additional wiring to keep track of the session's content
should register here.
`ON_SESSION_READY`::
Lifecycle hook to perform post loading tasks, which require an already completely usable and configured
session to be in place. When activated, the session is completely restored according to the defaulted or
persisted definition, and any access interfaces are already opened and enabled. Scripts and the GUI might
even be accessing the session in parallel already. Subsystems intending to perform additional processing
should register here, when requiring fully functional client side APIs. Examples would be statistics gathering,
validation or auto-correction of the session's contents.
`ON_SESSION_CLOSE`::
This event indicates to commence any activity relying on an opened and fully operative session.
When invoked, the session is still in fully operative state, all interfaces are open and the render engine
is available. However, after issuing this event, the session shutdown sequence will be initiated, by detaching
the engine interfaces and signalling the scheduler to cease running render jobs.
`ON_SESSION_END`::
This is the point to perform any state saving, deregistration or de-activation necessary before
an existing session can be brought down. When invoked, the session is still fully valid and functional,
but the GUI/external access has already been closed. Rendering tasks might be running beyond this point,
since the low-level session data is maintained by reference count.
`Subsys::triggerShutdown()`::
While not a clear cut lifecycle event, this call prompts any subsystem to close external interfaces
and cease any activity. Especially the GUI will signal the UI toolkit set to end the event loop and
then to destroy all windows and widgets.
`ON_GLOBAL_SHUTDOWN`::
Issued when the control flow is about to leave `main()` regularly to proceed into the shutdown and
unwinding phase. All subsystems have already signalled termination at that point. So this is the right
point to perform any non-trivial clean-up, since, on a language level, all service objects (especially
the singletons) are still alive, but all actual application activity has ceased.
`ON_EMERGENCY_EXIT`::
As notification of emergency shutdown, this event is issued _instead of_ `ON_GLOBAL_SHUTDOWN`, whenever
some subsystem collapsed irregularly with a top-level exception.
NOTE: all lifecycle hooks installed on those events are _blocking_. This is intentionally so, since any
lifecycle event is a breaking point, after which some assumptions can or can not be made further on.
However, care should be taken not to block unconditionally from within such a callback, since this
would freeze the whole application. Moreover, implementers should be careful not to make too much
assumptions regarding the actual thread of invocation; we only affirm that it will be _that specific_
thread responsible for bringing the global lifecycle ahead at this point.

View file

@ -1,6 +1,12 @@
Design Documents: Lumiera Architecture
======================================
// Menu : sort children
// Menu : prepend child Subsystems
// Menu : prepend child ArchitectureSummary
// Menu : attach child TimeQuant after TimeUsage
This section will feature some rather high-level considerations regarding
the architecture of the application.

View file

@ -0,0 +1,137 @@
Interaction Control
===================
:Author: Hermann Voßeler
:Email: <Ichthyostega@web.de>
:Date: Summer 2017
This overarching topic is where the arrangement of our interface components meets considerations about
https://en.wikipedia.org/wiki/Interaction_design[Interaction Design]. The interface programming allows us
to react on events and trigger behaviour, and it allows us to arrange building blocks within a layout framework.
And beyond that, there needs to be some kind of coherency in the way matters are arranged -- this is the realm
of conventions and guidelines. Yet in any more than trivial UI application, there is an intermediate and implicit
level of understanding, where things just ``happen to happen'', which can not fully be derived from first principles.
It is fine to have a convention to put the »OK« button right -- but how do we get at trimming a clip?
How are we assumed to get at trimming a clip? if we work with the mouse? or the keyboard? or with a pen?
or with a hardware controller we don't even know yet? We could deal with such on a case-by-case base
(as the so called reasonable people do) or we could aim at an abstract intermediary space, with the ability
to assimilate the practical situation yet to come.
interface has a spatial quality::
the elements within an user interface are arranged in a way that parallels our experience when working
in real world space. With the addition of some minor dose of __ ``hyper'' __ -- allowing for cross connections
and shortcuts beyond spatial logic...
locality of work spaces::
but the arrangement of the interface interactions is not amorphous,
rather it is segregated into cohesive clusters of closely interrelated actions.
We move between these clusters of activity the same way as we move between several
clearly segregated rooms confined within a building.
context and focus of activity::
most of what we could do _in theory,_ is not relevant _right now_ most of the time.
Yet only when the inner logic of what we're about to do coincides with the things at hand _right now,_
only then we might feel _enabled_ to perform our work.
shift of perspective::
and while we work, the focus moves along. +
Some things are closer, other things are remote and require us to move and re-orient and reshape our perspective,
should we choose to turn towards them.
the ability to arrange what is relevant::
all day long, we do the same stuff again and again, and this makes us observe and gradually understand matters.
As we reveal the inner nature of what we're doing, we desire to arrange _close at hand_ what belongs together,
and to expunge the superficial and distracting.
Foundation Concepts
-------------------
The primary insight is _that we build upon a spatial metaphor_ --
and thus we start out by defining various kinds of _locations._
We express interactions as _happening somewhere..._
Work Site::
a distinct, coherent place where some ongoing work is done +
the [blue]#Work Site# might move along with the work, but we also may leave it temporarily
to visit some other [blue]#Work Site#
the Spot::
the [blue]#Spot# is _where we currently are_ -- taken both in the sense of a location and a spotlight.
Thus a [blue]#Spot# is potentially at some [blue]#Work Site#, but it can be navigated to another one
Focus::
the concrete realisation of the [blue]#Spot# within a given [blue]#Control System#
Control System::
a practical technical realisation of an human-computer-interface,
like keyboard input/navigation, mouse, pen, hardware controller, touch
Focus Goal::
an _order_ or _instruction_ to bring something _into focus,_
which also means to move the [blue]#Spot# to the designated location.
UI Frame::
the overall interface is arranged into independent top-level segments of equal importance.
Practically speaking, we may have multiple top-level windows residing on multiple desktops...
Perspective::
a set of concrete configuration parameters defining the contents and arrangement within one [blue]#UI Frame#.
The [blue]#Perspective# defines which views are opened and arranged at what position and within which docking panel
Focus Path::
concrete coordinates to reach a specific [blue]#Work Site#. +
The [blue]#Focus Path# specifies the [blue]#UI Frame# (top-level window), the perspective, and then some canonical
path to navigate down a component hierarchy in order to reach the anchor point of the new [blue]#Work Site#
the Spot Locator::
navigating means to move the [blue]#Spot Locator#, in order to move the spot from [blue]#Work Site# to
[blue]#Work Site#.
The [blue]#Spot Locator# is relocated by loading a new [blue]#Focus Path# leading to another [blue]#Work Site#
The concept of a __[blue]#Focus Goal#__ has several ramifications:
For one it implies that there is something akin the __ ``current Control System'',__ which also could be the
_currently active Control System(s)._ Simply because Focus, as the realisation of the _abstract notion_ of the Spot,
is always tied to a Control System able to implement it. And when we're able to define generic location coordinates
and then __ ``move there'',__ with the help of the `SpotLocator`, we draw the conclusion that there must be a Focus
(implementation), somehow getting shifted towards that location.
Like e.g. the desired entity to gain the keyboard focus.
And, beyond that, the second thing we may conclude is that there need to be some degree of leeway in the way
such a Focus Goal can be reached. Since the inner logic of Control Systems can be quite drastically different
from each other, we are well advised to leave it to the actual Control System how _actually to fulfil the focus goal._
To point out an obvious example: it is not a good idea to move the mouse pointer forcibly onto a screen element.
Rather, we must use the established mechanisms of switching, scrolling and unfolding to bring the desired
target element into the visible area, leaving the last step to the user, which must actively move the mouse
onto the target. And we must give good visual clues as to what happened, and what we expect from the user
(namely to direct her attention onto the element brought into focus).
Building the framework
----------------------
To create such a system is an ambitious goal for sure.
We can not reach it in a single step, since it entails the formation of a whole intermediary layer,
on top of the _usual UI mechanics,_ yet below the concrete UI interactions.
Especially, we'd need to clarify the meaning of _Perspective,_ we need to decide on the relation of top level frame,
individual view, layout, focus and _current location within the UI._
On a second thought, building such a system implies we'll have to live with an intermediary state of evolution,
where parts of the new framework are already in place without interfering with common conventional usage
of the interface as-is.
UI coordinates
~~~~~~~~~~~~~~
Especially the focus navigation entails the use of some kind of ubiquitous coordinate system within the user interface.
In fact this is more of a topological navigation, since these coordinates describe the decisions and forks taken on
navigation down the _Focus Path._
* [optional] top-level Window (UI frame)
* [optional] Perspective
* Panel
* View-ID
* [optional] Group or Tab
* local path: `component.component.component`...

View file

@ -58,7 +58,8 @@ Languages and Tools
occasionally we check using Clang]
- GCC *4.9* or Clang *3.5* should be fine footnote:[basically we try to use just the stock language.
On rare occasions in the past, we _did_ use some GCC extensions, like `typeof()`, but we care for
workarounds, in case this becomes a problem].
workarounds, in case this becomes a problem. Incidentally, `typeof()` is obsoleted by the new
C++ standard, which provides `decltype()` for this purpose.].
* BOOST (listed below are the Debian package names)
- libboost-dev (at least *1.55*)

View file

@ -163,7 +163,11 @@ of arbitrary type to the constructor of type `TY`. Note the following details
that a rvalue reference can only bind to _something unnamed_. This is a safety feature; moving destroys
the contents at the source location. Thus if we really want to move something known by name (like e.g.
the function arguments in this example), we need to indicate so by an explicit call.
- `std::forward` needs an explicit type parameter to be able to deduce the right target type in any case
- `std::forward` needs an explicit type parameter to be able to deduce the right target type in any case.
It is _crucial to pass this type parameter in the correct way,_ as demonstrated above. Because, if we
invoke this function for _copying_ (i.e. with a lvalue reference), this type parameter will _carry on_
this refrerence. Only a rvalue reference is stripped. This is the only way ``downstream'' receivers can
tell the copy or move case apart.
- note how we're allowed to ``wrap'' a function call around the unpacking of the _argument pack_ (`args`):
the three dots `...` are _outside_ the parenthesis, and thus the `std::forward` is applied to each of
the arguments individually
@ -221,3 +225,70 @@ Thus -- as of 9/2014 -- the _rules of the game_ are as folows
to variadic templates and `std::tuple`
New Capabilities
----------------
As the support for the new language standard matures, we're able to extend our capabilities to write code
more succinctly and clearer to read. Sometimes we have to be careful in order to use these new powers the right way...
Variadic Templates
~~~~~~~~~~~~~~~~~~
Programming with variadic template arguments can be surprisingly tricky and the resulting code design tends to be confusing.
This is caused by the fact that a _»variadic argument pack«_ is not a type proper. Rather, it is a syntactic (not lexical)
macro. It is thus not possible to ``grab'' the type from variadic arguments and pass it directly to some metaprogramming
helper or metafunction. The only way to dissect and evaluate such types is kind of _going backwards_: pass them to some
helper function or template with explicit specialisations, thereby performing a pattern match.
A typical example: Let's assume we're about to write a function or constructor to accept flexible arguments, but we
want to ensure those arguments to conform to some standards. Maybe we can handle only a small selection of types
sensibly within the given context -- and thus we want to check and produce a compilation failure otherwise.
Unfortunately, this is not possible, at least not directly. If we want to get at the argument types one by one, we need
to fabricate some variadic template helper type, and then add an explicit specialisation to it, for the sole purpose of
writing a pattern match `<Arg a, Args...>`. Another common technique is to fabricate a `std::tuple<ARGS...>` and then
to use the `std::get<i> (std::tuple<ARGS...>)` to grab individual types. Such might be bewildering for the casual
reader, since we never intend to create a storage tuple anywhere.
NOTE: we might consider to reshape our typelist to be a real `lib::meta::Types<TYPES...>` and outfit it with some
of the most frequently needed accessors. At least the name ``Types'' is somewhat more indicative than ``tuple''.
Type inference
~~~~~~~~~~~~~~
Generally speaking, the use of `auto` and type inference has the potential to significantly improve readability of
the code. Indeed, this new language feature is what removed most of the clumsiness regarding use of the Lumiera
Forward Iterator concept. There are some notable pitfalls to consider, though
- the `decltype(...)` operator is kind-of ``overloaded'', insofar it supports two distinct usages
* to find out the type of a given _name_
* to deduce the type of a given _expression_
- and the `auto` type inference does not just pick up what `decltype` would see -- rather, it applies its own
preferences and transformations (which does make much sense from a pragmatic perspective)
* a definition with `auto` type specification always prefers to create an object or data element. This is very much
in contrast to the usual behaviour of the C++ language, which by default prefers a reference as result type of
expression evaluation.
* return type deduction always _decays_ the inferred type (similar as `std::decay<T>::type` does). Which means,
functions and arrays become pointers, and a _reference_ becomes a _value_. Given the danger of returning a
dangling reference to local storage, the latter is a sensible default, but certainly not what you'd expect
when just passing the result from a delegate function (or lambda!).
Generic Lambdas
~~~~~~~~~~~~~~~
A generic lambda (i.e. using `auto` on some argument) produces _a template_, not a regular `operator()` function.
Whenever such a lambda is invoked, this template is instantiated. This has several ramifications. For one, the
notorious techniques for detecting a function work well on conventional lambdas, but _fail on generic lambdas_.
And it is not possible to ``probe'' a template with SFINAE: If you instantiate a template, and the result is not
valid, you've produced a _compilation failure_, not a _substitution failure._ Which means, the compilation aborts,
instead of just trying the next argument substitution (as you'd might expect). Another consequence is that you
can not _just accept a generic lambda as argument and put it into a `std::function` (which otherwise is the
idiomatic way of ``just accepting anything function-like''). In order to place a generic lambda into a `std::function`,
you must decide upon the concrete argument and return type(s) and thus instantiate the template at that point.
The only remedy to get around these serious limitations will be when C++ starts to support *Concepts* -- which
can be expected to replace the usage of unbounded type inference in many situations: for example, it would then
be possible to accept just ``an Iterator'' or ``a STL container''...

View file

@ -48,7 +48,7 @@ The Boost library *functional-hash* provided the foundation for the definition n
into the new C++ standard. Yet the boost library provides some additional facilities not
part of the standard. Thus we're bound to choose
* either including +<tr1/functional>+ and +using std::tr1::hash+
* either including +<functional>+ and +using std::hash+
* or including +<boost/functional-hash>+ and +using boost::hash+
The boost version additionally provides pre defined hash functors for STL containers holding

View file

@ -54,41 +54,6 @@ source code comment near the static assertion statement to help solving the actu
Relevant Bosst extensions
~~~~~~~~~~~~~~~~~~~~~~~~~
.noncopyable
Inheriting from `boost::noncoypable` inhibits any copy, assignment and copy construction. It's a highly
recommended practice _by default to use that for every new class you create_ -- unless you know for sure
your class is going to have _value semantics_. The C++ language has kind of a ``fixation'' on value
semantics, passing objects by value, and the language adds a lot of magic on that behalf. Which might lead
to surprising results if you aren't aware of the fine details.
.type traits
Boost provides a very elaborate collection of type trait templates, allowing to ``detect'' several
properties of a given type at compile time. Since C++ has no reflection and only a very weak introspection
feature (RTTI, run time type information), using these type traits is often indispensable.
.enable_if
a simple but ingenious metaprogramming trick, allowing to control precisely in which cases the compiler
shall pick a specific class or function template specialisation. Basically this allows to control the
code generation, based on some type traits or other (metaprogramming) predicates you provide. Again,
since C++ is lacking introspection features, we're frequently forced to resort to metaprogramming
techniques, i.e to influence the way the compiler translates our code from within that very code.
.metaprogramming library
A very elaborate, and sometimes mind-bending library and framework. While heavily used within
Boost to build the more advanced features, it seems too complicated and esoteric for general purpose
and everyday use. Code written using the MPL tends to be very abstract and almost unreadable for
people without math background. In Lumiera, we _try to avoid using MPL directly._ Instead, we
supplement some metaprogramming helpers (type lists and tuples), written in a simple LISP style,
which -- hopefully -- should be decipherable without having to learn an abstract academic
terminology and framework.
.lambda
In a similar vein, the `boost::lambda` library might be worth exploring a bit, yet doesn't add
much value in practical use. It is stunning to see how this library adds the capability to define
real _lambda expressions_ on-the-fly, but since C++ was never designed for language extensions of
that kind, using lambda in practice is surprisingly tricky, sometimes even obscure and rarely
not worth the hassle. (An notable exception might be when it comes to defining parser combinators)
.operators
The `boost::operators` library allows to build families of types/objects with consistent
algebraic properties. Especially, it eases building _equality comparable_, _totally ordered_,
@ -109,6 +74,15 @@ quite some compilation overhead and size impact (-> see our own
link:http://git.lumiera.org/gitweb?p=lumiera/ichthyo;a=blob;f=src/lib/format-string.hpp;h=716aa0e3d23f09269973b7659910d74b3ee334ea;hb=37384f1b681f5bbfa7dc4d50b8588ed801fbddb3[formatting front-end]
to reduce this overhead)
.metaprogramming library
A very elaborate, and sometimes mind-bending library and framework. While heavily used within
Boost to build the more advanced features, it seems too complicated and esoteric for general purpose
and everyday use. Code written using the MPL tends to be very abstract and almost unreadable for
people without math background. In Lumiera, we _try to avoid using MPL directly._ Instead, we
supplement some metaprogramming helpers (type lists and tuples), written in a simple LISP style,
which -- hopefully -- should be decipherable without having to learn an abstract academic
terminology and framework.
.variant and any
These library provide a nice option for building data structures able to hold a mixture of
multiple types, especially types not directly related to each other. `boost::variant` is a

View file

@ -0,0 +1,226 @@
Singletons and Dependency Handling
==================================
:Date: 2018
:Toc:
We encounter _dependencies as an issue at implementation level:_ In order to deal with some task at hand,
sometimes we need to arrange matters way beyond the scope of that task. We could just thoughtlessly reach out and
settle those extraneous concerns -- yet this kind of pragmatism has a price tag: we are now mutually dependent
with internals of some other part of the system we do not even care much about. A more prudent choice would be
to let ``that other part'' provide a service for us, focussed to what we actually need right here to get _our_
work done. In essence, we create a dependency to resolve issues of coupling and to reduce complexity
(»divide et impera«).
Unfortunately this solution created a new problem: how do we get at our dependencies? We can not just step ahead
and create them or manage them, because then we'd be ``back to square one''. Rather someone else has to care.
Someone _needs to connect us with those dependencies,_ so we can use them. This is a special meta-service known
as _Dependency Injection_. A dedicated part of the application wires all other components, so each component
can focus on its specific concern and abstract away everything else. Dependency Injection can be seen as
application of the principle »Inversion Of Control«: each part is sovereign within its own realm, but becomes
a client (asks for help) for anything beyond that.
However, in the Lumiera code base, we refrain from building or using a full-blown Dependency Injection Container.
A lot of FUD has been spread regarding Dependency Injection and Singletons, to the point that a majority of developers
confuses and conflates the Inversion-of-Control principle (which is essential) with the use of a DI-Container. Nowadays,
you can not even utter the word ``Singleton'' without everyone yelling out ``Evil! Evil!'' -- while most of these people
at the same time feel just comfortable living in the metadata hell.
Not Singletons as such are problematic -- rather, the _coupling_ of the Singleton class _itself_ with the instantiation
and lifecycle mechanism is what creates the problems. This situation is similar to the use of _global variables,_ which
likewise are not evil as such; the problems arise from an imperative, operation driven and data centric mindset,
combined with hostility towards any abstraction. In C++ such problems can be mitigated by use of a generic
_Singleton Factory_ -- which can be augmented into a _Dependency Factory_ for those rare cases where we actually need
more instance and lifecycle management beyond lazy initialisation. Client code indicates the dependence on some other
service by planting an instance of that Dependency Factory (for Lumiera this is `lib::Depend<TY>`) and remains unaware
if the instance is created lazily in singleton style (which is the default) or has been reconfigured to expose
a service instance explicitly created by some subsystem lifecycle. The __essence of a ``dependency'' __ of this kind is
that we **access a service _by name_**. And this service name or service ID is in our case a _type name._
Requirements
------------
Our *DependencyFactory* satisfies the following requirements
- client code is able to access some service _by-name_ -- where the name is actually
the _type name_ of the service interface.
- client code remains agnostic with regard to the lifecycle or backing context of the service it relies on.
- in the simplest (and most prominent case), _nothing_ has to be done at all by anyone to manage that lifecycle. +
By default, the Dependency Factory creates a *singleton* instance lazily (heap allocated) on demand and it ensures
thread-safe initialisation and access.
- we establish a policy to *disallow any significant functionality during application shutdown*.
After leaving `main()`, only trivial dtors are invoked and possibly a few resource handles are dropped.
No filesystem writes, no clean-up and reorganisation, not even any logging is allowed. For this reason,
we established a link:{ldoc}/design/architecture/Subsystems.html[Subsystem] concept with explicit shutdown hooks,
which are invoked beforehand.
- the Dependency Factory can be re-configured for individual services (type names) to refer to an explicitly installed
service instance. In those cases, access while the service is not available will raise an exception.
There is a simple one-shot mechanism to reconfigure Dependency Factory and create a link to an actual
service implementation, including automatic deregistration.
Configuration
~~~~~~~~~~~~~
The DependencyFactory and thus the behaviour of dependency injection can be reconfigured, ad hoc, at runtime. +
Deliberately, we do not enforce global consistency statically (since that would lead to one central static configuration).
However, a runtime sanity check is performed to ensure configuration actually happens prior to any use, which means any
invocation to retrieve (and thus lazily create) the service instance. The following flavours can be configured:
default::
a singleton instance of the designated type is created lazily, on first access
- define an instance for access (preferably static): `Depend<Blah> theBla;`
- access the singleton instance as `theBla().doIt()`
singleton subclass::
`DependInject<Blah>::useSingleton<SubBlah>();` +
causes the dependency factory `Depend<Bla>` to create a `SubBlah` singleton instance from now on
attach to service::
`DependInject<Blah>::ServiceInstance<SubBlah> service{p1, p2, p3};`
- build and manage an instance of `SubBlah` in heap memory immediately (not lazily)
- configure the dependency factory to return a reference _to this instance_
- the instantiated `ServiceInstance<SubBlah>` object itself acts as lifecycle handle (and managing smart-ptr)
- when it is destroyed, the dependency factory is automatically cleared, and further access will trigger an error
support for test mocking::
`DependInject<Blah>::Local<SubBlah> mock;` +
- temporarily shadows whatever configuration resides within the dependency factory
- the next access will create a (non singleton) `SubBlah` instance in heap memory and return a `Blah&`
- the instantiated mock handle object again acts as lifecycle handle and smart-ptr
to access the `SubBlah` instance like `mock->doItSpecial()`
- when this handle goes out of scope, the original configuration of the dependency factory is restored
custom constructors::
both the subclass singleton configuration and the test mock support optionally accept a functor
or lambda argument with signature `SubBlah*()`. The contract is for this construction functor
to return a heap allocated object, which will be owned and managed by the DependencyFactory.
Especially this enables use of subclasses with non default ctor and / or binding to some
additional hidden context. Please note _that this closure will be invoked later, on-demand._
We consider the usage pattern of dependencies a question of architecture rather --
such can not be solved by any mechanism at implementation level. For this reason,
Lumiera's Dependency Factory prevents reconfiguration after use, but does nothing beyond such basic sanity checks.
Performance considerations
~~~~~~~~~~~~~~~~~~~~~~~~~~
We acknowledge that such a dependency or service will be accessed frequently and even from rather performance critical
parts of the application. We have to optimise for low overhead on access, while initialisation happens only once and
can be arbitrarily expensive. It is more important that configuration, setup and initialisation code remains readable.
And it is important to place such configuration at a location within the code where the related concerns are treated --
which is not at the usage site, and which is likewise not within some global central core application setup. At which
point precisely initialisation happens is a question of architecture -- lazy initialisation can be used to avoid
expensive setup of rarely used services, or it can be employed to simplify the bootstrap of complex subsystems,
or to break service dependency cycles. All of this builds on the assumption that the global application structure
is fixed and finite and well-known -- we assume we are in full control about when and how parts of the application
start and stop.
Our requirements on (optional) reconfigurability have some impact on the implementation technique though,
since we need access to the instance pointer for individual service types. This basically rules out
_Meyers Singleton_ -- and so the adequate implementation technique for our usage pattern is _Double Checked Locking._
In the past, there was much debate about DCL being
link:http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html[broken] -- which indeed was true when
_assuming full portability and arbitrary target platform._ Since our focus is primarily on PC-with-Linux systems,
this argument seems to lean more to the theoretical side though, since the x86/64 platform is known to employ rather
strong memory and cache coherency constraints. With the recent advent of ARM systems, the situation has changed however.
Anyway, since C++11 there
link:http://preshing.com/20130930/double-checked-locking-is-fixed-in-cpp11[is now a portable solution available]
for writing a correct DCL implementation, based on `std::atomic`.
The idea underlying Double Checked Locking is to optimise for the access path, which is achieved by moving the
expensive locking entirely out of that path. However, any kind of concurrent consistency assertion requires us
to establish a »happens before« relation between two events of information exchange. Both traditional locking
and lock-free concurrency implement this relation by establishing a *synchronises-with* relation between two actions
on a common *guard* entity -- for traditional locking, this would be a Lock, Mutex, Monitor or Semaphore, while
lock-free concurrency uses the notion of a _fence_ connected with some well defined action on a userspace guard variable.
In modern C++, typically we use _Atomic variables_ as guard. In addition to well defined semantics regarding concurrent
visibility of changes, these link:http://en.cppreference.com/w/cpp/atomic["atomics"] offer indivisible access and
exchange operations. A correct concurrent interaction must involve some kind of well defined handshake to establish
the aforementioned _synchronises-with_ relation -- otherwise we just can not assume anything. Herein lies the problem
with Double Checked Locking: when we move all concurrency precautions away from the optimised access path, we get
performance close to a direct local memory access, but we can not give any correctness assertions in this setup.
If we are lucky (and the underlying hardware does much to yield predictable behaviour), everything works as expected,
but we can never be sure about that. A correct solution thus inevitably needs to take away some of the performance
from the optimised access path. Fortunately, with properly used atomics this price tag is known to be low.
At the end of the day, correctness is more important than some superficially performance boost.
To gain insight into the rough proportions of performance impact, in 2018 we conducted some micro benchmarks
(using a 8 core AMD FX-8350 64bit CPU running Debian/Jessie and GCC 4.9 compiler)
The following table lists averaged results _in relative numbers,_
in relation to a single threaded optimised direct non virtual member function invocation (≈ 0.3ns)
[width="80%",cols="4e,4*>",frame="topbot",options="header"]
|==========================
| Access Technique 2+^| development 2+^| optimised
||[small]#singlethreaded#
|[small]#multithreaded#
|[small]#singlethreaded#
|[small]#multithreaded#
|direct invoke on shared local object | 15.13| 16.30| *1.00*| 1.59
|invoke existing object through unique_ptr | 60.76| 63.20| 1.20| 1.64
|lazy init unprotected (not threadsafe) | 27.29| 26.57| 2.37| 3.58
|lazy init always mutex protected | 179.62| 10917.18| 86.40| 6661.23
|Double Checked Locking with mutex | 27.37| 26.27| 2.04| 3.26
|DCL with std::atomic and mutex for init | 44.06| 52.27| 2.79| 4.04
|==========================
These benchmarks used a dummy service class holding a `volatile int`, initialised to a random value.
The complete code was visible to the compiler and thus eligible for inlining. Repeatedly the benchmarked code
accessed this dummy object through the means listed in the table, then retrieved the (actually constant) value
from the private volatile variable within the service and compared it to zero.
This setup ensures the optimiser can not remove the code altogether, while the access to the service dominates
the measured time. The concurrent measurement used 8 threads (number of cores), each performing the same timing loop
on a local instance. The number of invocations within each thread was high enough (several millions) to amortise
the actual costs of object allocation.
Some observations:
- The numbers obtained pretty much confirm
link:http://www.modernescpp.com/index.php/thread-safe-initialization-of-a-singleton[other people's measurments].
- Synchronisation is indeed necessary;
the unprotected lazy init crashed several times randomly during multithreaded tests.
- Contention on concurrent access is very tangible;
even for unguarded access the cache and memory hardware has to perform additional work
- However, the concurrency situation in this example is rather extreme and deliberately provokes collisions;
in practice we'd be closer to the single threaded case
- Double Checked Locking is a very effective implementation strategy and results in timings
within the same order of magnitude as direct unprotected access
- Unprotected lazy initialisation performs spurious duplicate initialisations, which can be avoided by DCL
- Naïve Mutex locking is slow even with non-recursive Mutex without contention
- Optimisation achieves access times around ≈ 1ns
Architecture
------------
Dependency management does not define the architecture, nor can it solve architecture problems.
Rather, its purpose is to _enact_ the architecture. A dependency is something we need in order to perform
the task at hand, yet essence of a dependency lies outside the scope and relates to concerns beyond and theme
of this actual task. A naïve functional approach -- pass everything you need as argument -- would be as harmful
as thoughtlessly manipulating some off-site data to fit current needs. The local function would be splendid,
strict and referentially transparent -- yet anyone using it would be infected with issues of tangling and
tight coupling. As remedy, a _global context_ can be introduced, which works well as long as this global
context does not exhibit any other state than ``being available''. The root of those problems however
lies in the drive to conceive matters simpler as they are.
- collaboration typically leads to indirect mutual dependency.
We can only define precisely _what is required locally,_ and then _pull our requirements_ on demand.
- a given local action can be part of a process, or a conversation or interaction chain, which in turn
might originate from various, quite distinct contexts. At _that level,_ we might find a simpler structure
to hinge questions of lifecycle on.
In Lumiera we encounter both these kinds of circumstances. On a global level, we have a simple and well defined
order of dependencies, cast into link:{ldoc}/design/architecture/Subsystems.html[Subsystem relations].
We know e.g. that mutating changes to the session can originate from scripts or from UI interactions.
It suffices thus, when the _leading subsystem_ (the UI or the script runner) refrains from emitting any further
external activities, _prior_ to reaching that point in the lifecycle where everything is ``basically set''.
Yet however self evident this insight might be, it yields some unsettling and challenging consequences:
The UI _must not assume_ the presence of specific data structures within the lower layers, nor is it allowed to
``pull'' session contents as a dependency while starting up. Rather the UI-Layer is bound to bootstrap itself into
completely usable and operative state, without the ability to attach anything onto existing tangible content structures.
This runs completely counter common practice of UI programming, where it is customary to wire most of the
application internals somehow directly below the UI ``shell''. Rather, in Lumiera the UI must be conceived
as a _collection of services_ -- and when running, a _population request_ can be issued to fill the prepared
UI framework with content. This is Inversion-of-Control at work.

View file

@ -401,11 +401,39 @@ Common Services
Subsystem runner
~~~~~~~~~~~~~~~~
_tbw_
Conceptually, the application is structured into layers. However, as actual runtime
entities we rely on a small number of link:{ldoc}/design/architecture/Subsystems.html[Subsystems],
which can be started and stopped and have well defined dependencies:
- Engine
- Session and Builder
- Play and Render Subsystem
- Lumiera GUI
- Script runner [yellow-background small]#planned#
- Renderfarm node [yellow-background small]#planned#
Lumiera's `main()` function defines an _Application Object_, which in turn operates
the _Subsystem Runner_. Each Subsystem implements a descriptor with the necessary
hooks callbacks to be started and stopped when necessary.
Lifecycle events
~~~~~~~~~~~~~~~~
_tbw_
Beyond parsing the commandline and triggering start of the subsystems, the Application
Object also issues _Lifecycle Events_. Individual components can register a callback
to be invoked when this happens. Currently (2018) the following events are known
(see 'include/lifecycle.h'):
- ON_BASIC_INIT
- ON_GLOBAL_INIT
- ON_GLOBAL_SHUTDOWN
- ON_EMERGENCY_EXIT
-> see here for
link:{ldoc}/design/application/SubsystemLifecycle.html#_initialisation_and_lifecycle[explanation in detail]
[small]#(somewhat preliminary and messy, yet still valid as of [yellow-background]##2018##)# +
-> more
link:{ldoc}/design/architecture/Subsystems.html#_lifecycle[documentation]
[small]#(likewise incomplete, but we're getting there, eventually [yellow-background]##2018##)#
Interface system
~~~~~~~~~~~~~~~~
@ -448,9 +476,6 @@ Config loader
~~~~~~~~~~~~~
_tbw_
Lua Scripting
~~~~~~~~~~~~~
_tbw_
@ -656,18 +681,22 @@ mayfail()
Singletons
~~~~~~~~~~
Singletons and Dependencies
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Deliberately, Lumiera stays below the level of utilising a dependency injection framework.
Consequently, we access most services _by type name_, pulling up a single implementation
instance on demand. Rather than placing this singleton lifecycle logic into the individual
implementation classes, we use a _singleton factory_, managing the lifecycle through static
variables and placing the singleton object into a static memory block. Singleton initialisation
is protected by a monitor per singleton type, while shutdown is triggered by the clean-up of
a static variable. This results in the general policy that within Lumiera, performing any
``business'' code in the application shutdown phase (after exiting +main()+) is _strictly
prohibited._ Generally speaking, destructors _must not perform any significant work_ and
are are expected to be failsafe.
instance on demand. Yet some aspects of the *Singleton Pattern* are known to be problematic.
Rather than placing this singleton lifecycle logic into the individual implementation classes,
we use a _singleton factory_, to manage the instance creation and lifecycle of the singleton
or service instance separate from the singleton class itself. Singleton initialisation happens
lazily with this approach, and thus needs to be protected in a multithreaded environment; we
use Double Checked Locking with std::atomic for this purpose.
Singletons need to be written in a way to cope with that somewhat nondeterministic environment.
They will be created on-demand, and be destroyed _eventually,_ in the application shutdown phase.
This results in the general policy that within Lumiera, performing any ``business'' code in the
application shutdown phase (after exiting +main()+) is _strictly prohibited._ Generally speaking,
destructors _must not perform any significant work_ and are are expected to be failsafe.
.accessing the singleton instance
By convention, when clients are expected actively to access the singleton instance,
@ -675,16 +704,21 @@ the interface class holds the singleton factory as a public static member with t
name +instance+. This allows clients to write `SomeService::instance()` to get a
reference to the implementation.
.subclasses and mock testing support
There is a mechanism to ``push aside'' the existing singleton instance and shadow
it temporarily with a mock implementation. Besides, there is a variation of the
general singleton factory, allowing to fabricate a specific subclass of the
exposed interface. Both of these facilities are rather technically involved
and require some specific set-up -- fortunately it turned out that they are
used only occasionally and rarely required. (Probably this is a result of
Lumiera being developed _test-driven_ -- things are being written mostly
in a unit test friendly fashion).
.subclasses and services
Actually, what appears as a mere ``singleton'' here is a form of _Dependency Injection,_
and can easily be extended to cover other forms of dependencies and other types of lifecycle.
Our experience indicates that this is rarely necessary, but when it happens, it's essential.
Lumiera's Dependency-Factory thus offers a configuration mechanism, which allows to switch
those modes of operation for individual types; the client consuming the ``singleton'' remains
unaware of such special setup.
.mock testing support
Moreover, the Dependency-Factory also offers a mechanism to ``push aside'' the existing singleton
or serviec instance and shadow it temporarily with a mock implementation. Again, it turned out that
such mocking mechanisms are used only occasionally and rarely required. Likely this is a result of
Lumiera being developed _test-driven_ -- things are being written mostly in a unit test friendly way.
-> more about link:{ldoc}/technical/library/Dependencies.html[dependency handling]
Extensible Factory
~~~~~~~~~~~~~~~~~~
@ -749,6 +783,16 @@ only _unique values,_ to _take a snapshot on-the-fly_ etc. There are convenience
builder functions for those operations, figuring out the actual source and destination
types by template metaprogramming.
Pipeline Builder
^^^^^^^^^^^^^^^^
As an extension to the iterator framework, the pipeline builder is able
to wrap arbitrary source iterators, decorate them with filters and transformations
provided as Lambda, and finally package all together into a self contained new
iterator (value object). This is a rather new feature [small]#(as of 2017)# and
we still need to determine its viability, yet it proved helpful when implementing
certain kinds of search algorithms on top of an abstracted data source exposed
as iterator. -> 'lib/iter-tree-explorer.hpp'
Front-end for boost::format
~~~~~~~~~~~~~~~~~~~~~~~~~~~

View file

@ -10,7 +10,7 @@
// 4/08 - comparison operators on shared_ptr<Asset>
// 4/08 - conversions on the value_type used for boost::any
// 5/08 - how to guard a downcasting access, so it is compiled in only if the involved types are convertible
// 7/08 - combining partial specialisation and subclasses
// 7/08 - combining partial specialisation and subclasses
// 10/8 - abusing the STL containers to hold noncopyable values
// 6/09 - investigating how to build a mixin template providing an operator bool()
// 12/9 - tracking down a strange "warning: type qualifiers ignored on function return type"
@ -32,194 +32,86 @@
// 1/16 - generic to-string conversion for ostream
// 1/16 - build tuple from runtime-typed variant container
// 3/17 - generic function signature traits, including support for Lambdas
// 9/17 - manipulate variadic templates to treat varargs in several chunks
// 11/17 - metaprogramming to detect the presence of extension points
// 11/17 - detect generic lambda
// 12/17 - investigate SFINAE failure. Reason was indirect use while in template instantiation
// 03/18 - Dependency Injection / Singleton initialisation / double checked locking
/** @file try.cpp
** Metaprogramming: unified treatment of functors, function references and lambdas.
** Rework our existing function signature trait to also support lambdas, which forces us
** to investigate and in the end to change the handling of function member pointers.
**
** This investigation is a partial step towards #994 and became necessary to support
** Command definition by Lambda
**
** Rework of the template lib::Depend for singleton and service access.
** The (now for the third time rewritten) dependency factory can be configured to yield
** a subclass singleton, or to bind to an external service. Lazy initialisation relies on
** Double Checked Locking, which we need switch to C++11 Atomics in order to be correct
** (in theory). The impact of this initialisation guard should be investigated by benchmark.
*/
typedef unsigned int uint;
#include "lib/format-cout.hpp"
#include "lib/format-util.hpp"
#include "lib/meta/function.hpp"
#include <functional>
#include <string>
using lib::meta::_Fun;
using std::function;
using std::placeholders::_1;
using std::bind;
using std::string;
using std::tuple;
using std::move;
int
funny (uint i)
{
return -i+1;
}
struct Funky
{
int ii = 2;
int
fun (uint i2)
{
return ii + funny(i2);
}
int
operator() (uint i2)
{
return 2*ii - fun(i2);
}
static int
notfunny (uint i)
{
return 2*funny (i);
}
};
#include "lib/depend.hpp"
#include "lib/depend-inject.hpp"
//#include "lib/meta/util.hpp"
#include "lib/test/test-helper.hpp"
#include "lib/util.hpp"
#define SHOW_TYPE(_TY_) \
cout << "typeof( " << STRINGIFY(_TY_) << " )= " << lib::meta::typeStr<_TY_>() <<endl;
#define EVAL_PREDICATE(_PRED_) \
cout << STRINGIFY(_PRED_) << "\t : " << _PRED_ <<endl;
#define SHOW_EXPR(_XX_) \
cout << "Probe " << STRINGIFY(_XX_) << " ? = " << _XX_ <<endl;
template<typename F>
void
showType (F)
namespace error = lumiera::error;
using lib::Depend;
using lib::DependInject;
//////////////////////////////////////////////////////////////////////////Microbenchmark
#include "lib/test/microbenchmark.hpp"
using lib::test::microbenchmark;
//////////////////////////////////////////////////////////////////////////(End)Microbenchmark
#include "include/lifecycle.h"
#include "lib/test/testoption.hpp"
#include "lib/test/suite.hpp"
using lumiera::LifecycleHook;
using lumiera::ON_GLOBAL_INIT;
using lumiera::ON_GLOBAL_SHUTDOWN;
///////////////////////////////////////////////////////Usage
class BlackHoleService
: util::NonCopyable
{
using Sig = typename _Fun<F>::Sig;
volatile int theHole_ = rand() % 1000;
SHOW_TYPE (F);
SHOW_TYPE (Sig);
}
public:
int readMe() { return theHole_; }
};
template<typename F>
void
showRef (F&)
{
using Sig = typename _Fun<F>::Sig;
SHOW_TYPE (F);
SHOW_TYPE (Sig);
}
template<typename F>
void
showCRef (F&)
{
using Sig = typename _Fun<F>::Sig;
SHOW_TYPE (F);
SHOW_TYPE (Sig);
}
template<typename F>
void
showRRef (F&&)
{
using Sig = typename _Fun<F>::Sig;
SHOW_TYPE (F);
SHOW_TYPE (Sig);
}
using Fun = function<int(uint)>;
using Fuk = function<int(Funky&, uint)>;
int
main (int, char**)
{
Fun f1{funny};
Fun f2{&funny};
std::srand(std::time(nullptr));
LifecycleHook::trigger (ON_GLOBAL_INIT);
Fun f3{Funky::notfunny};
Fun f4{&Funky::notfunny};
Depend<BlackHoleService> mystery;
auto memfunP = &Funky::fun;
thread_local int64_t cnt = 0;
Fuk f5{memfunP};
Funky funk;
Fun f6{bind (f5, funk, _1)};
auto lambda = [&](uint ii) { return funk.fun(ii); };
Fun f7{lambda};
showType (funny);
showType (&funny);
showType (Funky::notfunny);
showType (memfunP);
showType (lambda);
showType (f7);
cout << "\n\n-------\n";
showRef (funny);
showRef (lambda);
showRef (f7);
showCRef (funny);
showCRef (lambda);
showCRef (f7);
showRRef (move(lambda));
showRRef (move(f7));
showType (move(&funny));
showType (move(lambda));
showType (move(f7));
Fun& funRef = f1;
Funky& funkyRef = funk;
Fun const& funCRef = f1;
Funky const& funkyCRef = funk;
showType (funRef);
showType (funkyRef);
showType (funCRef);
showType (funkyCRef);
cout << "\n\n-------\n";
SHOW_TYPE (decltype(&Funky::operator()));
SHOW_TYPE (decltype(lambda));
SHOW_TYPE (_Fun<int(uint)>::Sig);
SHOW_TYPE (_Fun<Fun&>::Sig);
SHOW_TYPE (_Fun<Fun&&>::Sig);
SHOW_TYPE (_Fun<Fun const&>::Sig);
SHOW_TYPE (_Fun<Funky&>::Sig);
SHOW_TYPE (_Fun<Funky&&>::Sig);
SHOW_TYPE (_Fun<Funky const&>::Sig);
using Siggy = _Fun<Fun>::Sig;
SHOW_TYPE (_Fun<Siggy&>::Sig);
SHOW_TYPE (_Fun<Siggy&&>::Sig);
SHOW_TYPE (_Fun<Siggy const&>::Sig);
cout << microbenchmark<8> ([&]()
{
cnt += mystery().readMe();
}
,50000000)
<< endl;
LifecycleHook::trigger (ON_GLOBAL_SHUTDOWN);
cout << "\n.gulp.\n";
return 0;

View file

@ -69,7 +69,7 @@ namespace engine {
EngineConfig();
~EngineConfig();
friend class lib::DependencyFactory;
friend class lib::DependencyFactory<EngineConfig>;
public:
/** access point to the Engine Interface.

View file

@ -213,7 +213,7 @@ namespace engine {
*/
class JobClosure
: public lumiera_jobClosure
, boost::noncopyable // ....has distinct identity and stable address
, util::NonCopyable // ....has distinct identity and stable address
{
public:
virtual ~JobClosure(); ///< this is an interface

View file

@ -38,13 +38,11 @@
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
#include "lib/hash-value.h"
#include "lib/time/timevalue.hpp"
#include "backend/engine/scheduler-frontend.hpp"
#include <boost/noncopyable.hpp>
//#include <string>
namespace backend{
namespace engine {
@ -68,7 +66,7 @@ namespace engine {
* of SchedulerDiagnostics may be used.
*/
class SchedulerDiagnostics
: boost::noncopyable
: util::NonCopyable
{
SchedulerFrontend& scheduler_;

View file

@ -49,7 +49,6 @@
//#include "lib/iter-source.hpp"
//#include "lib/sync.hpp"
//#include <boost/noncopyable.hpp>
//#include <string>
//#include <vector>
//#include <memory>

View file

@ -41,7 +41,9 @@
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
#include "include/logging.h"
#include "lib/meta/function.hpp"
#include "lib/result.hpp"
extern "C" {
@ -49,18 +51,16 @@ extern "C" {
}
#include "backend/threadpool-init.hpp"
#include <functional>
#include <boost/noncopyable.hpp>
#include <type_traits>
#include <utility>
namespace backend {
using std::bind;
using std::function;
using lib::Literal;
namespace error = lumiera::error;
using error::LUMIERA_ERROR_STATE;
using error::LUMIERA_ERROR_EXTERNAL;
using error::LERR_(STATE);
using error::LERR_(EXTERNAL);
typedef struct nobug_flag* NoBugFlag;
@ -108,94 +108,95 @@ namespace backend {
* If this doesn't happen, you'll block forever.
*/
class Thread
: boost::noncopyable
: util::MoveOnly
{
/** @internal perfect forwarding through a C-style `void*` */
template<class FUN>
static FUN&&
forwardInitialiser (void* rawPtr) noexcept
{
REQUIRE (rawPtr);
FUN& initialiser = *reinterpret_cast<FUN*> (rawPtr);
return static_cast<FUN&&> (initialiser);
}
template<class FUN>
static void
threadMain (void* arg)
{
using Fun= typename lib::meta::_Fun<FUN>::Functor;
Fun _doIt_{forwardInitialiser<FUN> (arg)};
lumiera_thread_sync (); // sync point: arguments handed over
try {
_doIt_(); // execute the actual operation in the new thread
}
catch (std::exception& failure)
{
if (!lumiera_error_peek())
LUMIERA_ERROR_SET (sync, STATE
,failure.what());
}
catch (...)
{
LUMIERA_ERROR_SET_ALERT (sync, EXTERNAL
, "Thread terminated abnormally");
}
}
protected:
typedef function<void(void)> Operation;
LumieraThread threadHandle_;
/** @internal derived classes may create an inactive thread */
Thread() : threadHandle_(0) { }
struct ThreadStartContext
: boost::noncopyable
/** @internal use the Lumiera thread manager to start a new thread and hand over the operation */
template<class FUN>
void
launchThread (Literal purpose, FUN&& operation, NoBugFlag logging_flag, uint additionalFlags =0)
{
REQUIRE (!lumiera_error(), "Error pending at thread start");
using Functor = typename std::remove_reference<FUN>::type;
threadHandle_ =
lumiera_thread_run ( LUMIERA_THREADCLASS_INTERACTIVE | additionalFlags
, &threadMain<Functor>
, reinterpret_cast<void*> (&operation)
, purpose.c()
, logging_flag
);
if (!threadHandle_)
throw error::State ("Failed to start a new Thread for \"+purpose+\""
, lumiera_error());
Operation const& operation_;
static void
run (void* arg)
{
REQUIRE (arg);
ThreadStartContext* ctx = reinterpret_cast<ThreadStartContext*>(arg);
Operation _doIt_(ctx->operation_);
lumiera_thread_sync (); // sync point: arguments handed over
try {
_doIt_(); // execute the actual operation in the new thread
}
catch (std::exception& failure)
{
if (!lumiera_error_peek())
LUMIERA_ERROR_SET (sync, STATE
,failure.what());
}
catch (...)
{
LUMIERA_ERROR_SET_ALERT (sync, EXTERNAL
, "Thread terminated abnormally");
}
}
public:
ThreadStartContext (LumieraThread& handle
,Operation const& operation_to_execute
,Literal& purpose
,NoBugFlag logging_flag
,uint additionalFlags =0
)
: operation_(operation_to_execute)
{
REQUIRE (!lumiera_error(), "Error pending at thread start");
handle =
lumiera_thread_run ( LUMIERA_THREADCLASS_INTERACTIVE | additionalFlags
, &run // invoking the run helper and..
, this // passing this start context as parameter
, purpose.c()
, logging_flag
);
if (!handle)
throw error::State ("Failed to start a new Thread for \"+purpose+\""
, lumiera_error());
// make sure the new thread had the opportunity to take the Operation
// prior to leaving and thereby possibly destroying this local context
lumiera_thread_sync_other (handle);
}
};
// make sure the new thread had the opportunity to take the Operation
// prior to leaving and thereby possibly destroying this local context
lumiera_thread_sync_other (threadHandle_);
}
LumieraThread thread_;
Thread() : thread_(0) { }
public:
/** Create a new thread to execute the given operation.
* The new thread starts up synchronously, it can't
* be cancelled and it can't be joined.
* The new thread starts up synchronously, can't be cancelled and it can't be joined.
* @param purpose fixed char string used to denote the thread for diagnostics
* @param logging_flag NoBug flag to receive diagnostics regarding the new thread
* @param operation defining what to execute within the new thread. Any functor
* which can be bound to function<void(void)>. Note this functor will be
* copied onto the stack of the new thread, thus it can be transient.
* @param operation a functor holding the code to execute within the new thread.
* Any function-like entity with signature `void(void)` is acceptable.
* @warning The operation functor will be forwarded to create a copy residing
* on the stack of the new thread; thus it can be transient, however
* anything referred through a lambda closure here must stay alive
* until the new thread terminates.
*/
Thread (Literal purpose, Operation const& operation, NoBugFlag logging_flag = &NOBUG_FLAG(thread))
: thread_(0)
template<class FUN>
Thread (Literal purpose, FUN&& operation, NoBugFlag logging_flag = &NOBUG_FLAG(thread))
: threadHandle_{nullptr}
{
ThreadStartContext (thread_, operation, purpose, logging_flag);
launchThread (purpose, std::forward<FUN> (operation), logging_flag);
}
@ -207,7 +208,7 @@ namespace backend {
bool
isValid() const
{
return thread_;
return threadHandle_;
}
@ -219,7 +220,7 @@ namespace backend {
sync()
{
REQUIRE (isValid(), "Thread not running");
if (!lumiera_thread_sync_other (thread_))
if (!lumiera_thread_sync_other (threadHandle_))
lumiera::throwOnError();
}
@ -242,7 +243,7 @@ namespace backend {
REQUIRE (isValid(), "Thread not running");
LumieraThread current = lumiera_thread_self ();
return current
and current == this->thread_;
and current == this->threadHandle_;
}
};
@ -259,11 +260,13 @@ namespace backend {
: public Thread
{
public:
ThreadJoinable (Literal purpose, Operation const& operation,
template<class FUN>
ThreadJoinable (Literal purpose, FUN&& operation,
NoBugFlag logging_flag = &NOBUG_FLAG(thread))
: Thread{}
{
ThreadStartContext (thread_, operation, purpose, logging_flag,
LUMIERA_THREAD_JOINABLE);
launchThread<FUN> (purpose, std::forward<FUN> (operation), logging_flag,
LUMIERA_THREAD_JOINABLE);
}
@ -279,8 +282,8 @@ namespace backend {
throw error::Logic ("joining on an already terminated thread");
lumiera_err errorInOtherThread =
lumiera_thread_join (thread_);
thread_ = 0;
lumiera_thread_join (threadHandle_);
threadHandle_ = 0;
if (errorInOtherThread)
return error::State ("Thread terminated with error", errorInOtherThread);

View file

@ -95,14 +95,13 @@
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
#include "lib/null-value.hpp"
#include "lib/symbol.hpp"
#include "lib/util.hpp"
#include "common/advice/binding.hpp"
#include <boost/noncopyable.hpp>
namespace lumiera{
namespace advice {
@ -315,7 +314,7 @@ namespace advice {
template<class AD>
class ActiveProvision
: public PointOfAdvice
, boost::noncopyable
, util::NonCopyable
{
AD theAdvice_;

View file

@ -90,6 +90,7 @@
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
#include "lib/del-stash.hpp"
#include "lib/depend.hpp"
#include "lib/symbol.hpp"
@ -99,7 +100,6 @@
#include "common/advice.hpp"
#include "common/advice/index.hpp"
#include <boost/noncopyable.hpp>
using lib::Literal;
using lib::DelStash;
@ -121,7 +121,7 @@ namespace advice {
*/
class AdviceSystem
: public lib::Sync<>
, boost::noncopyable
, util::NonCopyable
{
DelStash adviceDataRegistry_;

View file

@ -509,7 +509,7 @@ namespace advice {
{
SelfCheckFailure (Literal failure)
: error::Fatal (string("Failed test: ")+failure
,LUMIERA_ERROR_INDEX_CORRUPTED)
,LERR_(INDEX_CORRUPTED))
{ }
};
}

View file

@ -33,7 +33,6 @@
#include "lib/error.hpp"
#include "include/lifecycle.h"
#include "common/appstate.hpp"
#include "common/subsystem-runner.hpp"
@ -65,20 +64,12 @@ namespace lumiera {
if (lumiera_err errorstate = lumiera_error ())
ALERT (common, "*** Unexpected error: %s\n Triggering emergency exit.", errorstate);
}
void
createAppStateInstance(){
AppState::instance();
}
LifecycleHook schedule_ (ON_BASIC_INIT, &createAppStateInstance);
}
/** perform initialisation triggered on first access.
/** perform initialisation triggered on first access.
* Will execute BasicSetup sequence to determine the location
* of the executable and read in \c setup.ini --
* Since above a LifecycleHook is installed ON_BASIC_INIT,
@ -94,17 +85,8 @@ namespace lumiera {
, core_up_{false}
{ }
AppState&
AppState::instance() // Meyer's singleton
{
static unique_ptr<AppState> theApp_;
if (!theApp_) theApp_.reset (new AppState ());
return *theApp_;
}
/** storage for the Appstate Singleton instance */
lib::Depend<AppState> AppState::instance;
string
@ -120,7 +102,7 @@ namespace lumiera {
// ===== Implementation startup and shutdown sequence for main() ========
#define _THROW_IF \
#define _MAYBE_THROW_ \
maybeThrow<error::Fatal> ("internal failure while initialising the "\
"Lumiera application framework");
@ -132,17 +114,17 @@ namespace lumiera {
TRACE (common, "initialising application core...");
lumiera_interfaceregistry_init ();
_THROW_IF
_MAYBE_THROW_
lumiera_plugin_discover (lumiera_plugin_load, lumiera_plugin_register);
_THROW_IF
_MAYBE_THROW_
lumiera_config_interface_init ();
_THROW_IF
_MAYBE_THROW_
core_up_= true;
LifecycleHook::trigger (ON_GLOBAL_INIT);
_THROW_IF
_MAYBE_THROW_
subsystems_.reset (new SubsystemRunner (options));
@ -174,7 +156,7 @@ namespace lumiera {
* the termination of all other subsystems is initiated; when detecting this case,
* the emergency exit sequence is called. Any error which can't be handled within
* this scheme, should be thrown as exception, in which case the abort handler
* is activated.
* is activated.
*/
ExitCode
AppState::maybeWait()
@ -269,6 +251,4 @@ namespace lumiera {
} // namespace lumiera

View file

@ -41,11 +41,12 @@
#define LUMIERA_APPSTATE_H
#include "lib/symbol.hpp"
#include "lib/nocopy.hpp"
#include "lib/depend.hpp"
#include "common/option.hpp"
#include "common/subsys.hpp"
#include "common/basic-setup.hpp"
#include <boost/noncopyable.hpp>
#include <memory>
#include <string>
#include <map>
@ -55,7 +56,6 @@
namespace lumiera {
using std::string;
using boost::noncopyable;
class SubsystemRunner;
@ -63,24 +63,24 @@ namespace lumiera {
/**
* The Lumiera Application state and basic initialisation.
* Singleton to hold global flags directing the overall application behaviour,
* for triggering lifecycle events and performing early initialisation tasks.
* responsible for triggering lifecycle events and performing early initialisation tasks.
* AppState services are available already from static initialisation code.
* @warning don't use AppState in destructors.
*/
class AppState
: private noncopyable
: util::NonCopyable
{
private:
AppState ();
~AppState ();
friend class std::default_delete<AppState>;
friend class lib::DependencyFactory<AppState>;
public:
/** get the (single) AppState instance.
/** get the (single) AppState instance.
* @warning don't use it after the end of main()! */
static AppState& instance();
static lib::Depend<AppState> instance;
/** evaluate the result of option parsing and maybe additional configuration
@ -112,7 +112,7 @@ namespace lumiera {
* On termination of one of those components, tear down the remaining
* components and initiate a normal or emergency shutdown of the
* application, depending on the triggering component's
* mode of termination (exit or exception).
* mode of termination (exit or exception).
* @return global application exit code
*/
ExitCode maybeWait();
@ -128,7 +128,7 @@ namespace lumiera {
ExitCode abort () noexcept;
private:
using PSub = std::unique_ptr<SubsystemRunner>;

View file

@ -75,9 +75,9 @@ namespace lumiera {
/**
* Creating the BasicSetup object performs the
* initial self-configuration of the Lumiera Application.
* For this, the \c setup.ini file is located relative to the
* For this, the `setup.ini` file is located relative to the
* current application executable, read in and parsed into a
* map of setup variables.
* map of setup variables.
*/
BasicSetup::BasicSetup (string bootstrapIni)
: syntax("Lumiera installation and platform configuration")
@ -118,7 +118,7 @@ namespace lumiera {
opt::parsed_options parsed = opt::parse_config_file (configIn, syntax);
opt::store (parsed, settings);
opt::notify(settings);
opt::notify(settings);
}

View file

@ -55,10 +55,10 @@
#include "lib/error.hpp"
#include "lib/symbol.hpp"
#include "lib/nocopy.hpp"
#include "lib/util.hpp"
#include <boost/program_options.hpp>
#include <boost/noncopyable.hpp>
#include <string>
@ -95,7 +95,7 @@ namespace lumiera {
* @see AppState
*/
class BasicSetup
: boost::noncopyable
: util::NonCopyable
{
opt::options_description syntax;
opt::variables_map settings;

View file

@ -46,7 +46,7 @@
#include "lib/searchpath.hpp"
#include "lib/util.hpp"
extern "C" {
extern "C" {
#include "common/config.h"
}
@ -61,7 +61,7 @@ extern "C" {
/** Similarly, this key is used to fetch the configured default
* plugin/module search path from the basic setup.ini
* This patch is used by the plugin-loader to discover
* lumiera plugins and extensions.
* lumiera plugins and extensions.
*/
#define KEY_PLUGIN_PATH "Lumiera.modulepath"
@ -81,8 +81,8 @@ namespace lumiera {
namespace {
void
pull_up_ConfigSystem ()
void
pull_up_ConfigSystem ()
{
TRACE (common, "booting up config system");
Config::instance();
@ -139,7 +139,7 @@ extern "C" { /* ==== implementation C interface for accessing setup.ini =======
const char*
const char*
lumiera_get_plugin_path_default ()
{
static string pathSpec;
@ -147,7 +147,7 @@ extern "C" { /* ==== implementation C interface for accessing setup.ini =======
{
pathSpec += "plugin.path="; // syntax expected by lumiera_config_setdefault
// fetch plugin search path from setup.ini and expand any $ORIGIN token
// fetch plugin search path from setup.ini and expand any $ORIGIN token
SearchPathSplitter pathElement(Config::get (KEY_PLUGIN_PATH));
while (pathElement)
pathSpec += pathElement.next() +":";

View file

@ -22,35 +22,29 @@
/** @file display-interface-proxy.hpp
** This is an implementation fragment, intended to be included into common/interfaceproxy.cpp
**
/** @file display-interface-proxy.cpp
** Interface-Proxy for the lumiera::Display facade (experimental/deprecated).
** The purpose is to define a proxy implementation of lumiera::Display, in order to
** redirect any calls through the associated C Language Interface "lumieraorg_Display"
**
** @see display-facade.hpp
** @see display-service.hpp actual implementation within the GUI
** @deprecated obsolete early design draft from 2009;
** as of 2018 only kept in source to compile some likewise obsolete UI widgets.
*/
/* ==================== gui::Display ====================================== */
#include "include/display-facade.h"
#include "include/interfaceproxy.hpp"
namespace lumiera {
/** storage for the facade proxy factory
* used by client code to invoke through the interface */
lumiera::facade::Accessor<Display> Display::facade;
/// emit the vtable here into this translation unit within liblumieracommon.so...
Display::~Display() { }
/** static storage for the facade access front-end */
lib::Depend<Display> Display::facade;
} // namespace lumiera
@ -58,14 +52,19 @@ namespace lumiera {
namespace lumiera {
namespace facade {
typedef InstanceHandle< LUMIERA_INTERFACE_INAME(lumieraorg_Display, 0)
, lumiera::Display
> IHandle_Display;
/* ==================== gui::Display ====================================== */
using Interface = LUMIERA_INTERFACE_INAME(lumieraorg_Display, 0);
using Facade = lumiera::Display;
using IHandle = InstanceHandle<Interface, Facade>;
template<>
class Proxy<IHandle_Display>
: public Holder<IHandle_Display>
class Proxy<IHandle>
: public Binding<IHandle>
{
//----Proxy-Implementation-of-lumiera::Display--------
@ -83,11 +82,12 @@ namespace facade {
public:
Proxy (IHandle const& iha) : THolder(iha) {}
using Binding<IHandle>::Binding;
};
template void openProxy<IHandle_Display> (IHandle_Display const&);
template void closeProxy<IHandle_Display> (void);
/** emit proxy code here... */
template
class Link<Interface,Facade>;
}} // namespace lumiera::facade
}} //namespace facade::lumiera

View file

@ -57,7 +57,7 @@ namespace gui {
/** load and start the GUI as a plugin */
struct GuiRunner
: boost::noncopyable
: util::NonCopyable
{
typedef InstanceHandle<LUMIERA_INTERFACE_INAME(lumieraorg_Gui, 1)> GuiHandle;

View file

@ -1,5 +1,5 @@
/*
INSTANCEHANDLE.hpp - automatically handling interface lifecycle
INSTANCEHANDLE.hpp - automatically handling interface lifecycle
Copyright (C) Lumiera.org
2008, Hermann Vosseler <Ichthyostega@web.de>
@ -32,7 +32,7 @@
** @see gui::GuiFacade usage example
** @see interface.h
** @see interfaceproxy.hpp (more explanations)
** @see interfaceproxy.cpp (Implementation of the proxies)
** @see session-command-interface-proxy.cpp (Proxy implementation example)
**
*/
@ -43,14 +43,14 @@
#include "include/logging.h"
#include "lib/error.hpp"
#include "include/interfaceproxy.hpp"
#include "lib/nocopy.hpp"
#include "lib/depend-inject.hpp"
extern "C" {
#include "common/interface.h"
#include "common/interfaceregistry.h"
}
#include <boost/noncopyable.hpp>
#include <string>
@ -66,17 +66,17 @@ namespace lumiera {
namespace { // implementation details
void
throwIfError()
throwIfError()
{
if (lumiera_error_peek())
throw lumiera::error::Config("failed to open interface or plugin.",lumiera_error());
}
/** takes a (single) instance definitions, as typically created
* when defining interfaces for external use, and registers it
* with the InterfaceSystem. Then uses the data found in the
* \em given instance descriptor to open an instance handle.
* @throws error::Config when the registration process fails
* @throws error::Config when the registration process fails
*/
LumieraInterface
register_and_open (LumieraInterface descriptor)
@ -97,15 +97,32 @@ namespace lumiera {
verify_validity (LumieraInterface ifa)
{
REQUIRE (ifa);
return (ifa == lumiera_interfaceregistry_interface_find (ifa->interface,
ifa->version,
return (ifa == lumiera_interfaceregistry_interface_find (ifa->interface,
ifa->version,
ifa->name));
}
} // (End) impl details
} // (End) impl details
namespace facade {
/**
* to be specialised and implemented for each
* individual interface and facade interface.
* The actual proxy implements the facade interface
* and reroutes each call to the corresponding function
* on the CL-Interface for the Lumiera interface system.
*/
template<class IHA>
class Proxy;
/** The ServiceHandle automatically creates and manages the Proxy instance */
template<class I, class FA>
using ServiceHandle = typename lib::DependInject<FA>::template ServiceInstance<Proxy<InstanceHandle<I,FA>>>;
/**
* @internal Helper/Adapter for establishing a link
@ -118,39 +135,37 @@ namespace lumiera {
*/
template<class I, class FA>
struct Link
: boost::noncopyable
: ServiceHandle<I,FA>
{
typedef InstanceHandle<I,FA> IH;
using IH = InstanceHandle<I,FA>;
Link (IH const& iha) { facade::openProxy(iha); }
~Link() { facade::closeProxy<IH>(); }
Link (IH const& iha);
~Link ();
FA&
operator() (IH const&) const
{
return facade::Accessor<FA>()();
}
FA* operator->() const;
};
/**
* @internal when the InstanceHandle isn't associated with a
* facade interface, then this specialisation switches
* facade interface, then this specialisation switches
* the facade::Link into "NOP" mode.
*/
template<class I>
struct Link<I,I>
: boost::noncopyable
: util::NonCopyable
{
typedef InstanceHandle<I,I> IH;
using IH = InstanceHandle<I,I>;
IH& ih_;
Link (IH const&) { /* NOP */ }
~Link() { /* NOP */ }
I&
operator() (IH const& handle) const
Link (IH& ih)
: ih_{ih}
{ }
I*
operator->() const
{
return handle.get();
return & ih_.get();
}
};
@ -174,8 +189,8 @@ namespace lumiera {
, class FA = I ///< facade interface type to be used by clients
>
class InstanceHandle
: private boost::noncopyable
{
: util::NonCopyable
{
LumieraInterface desc_;
I* instance_;
facade::Link<I,FA> facadeLink_;
@ -190,14 +205,14 @@ namespace lumiera {
*/
InstanceHandle (string const& iName, uint version, size_t minminor, string const& impName)
: desc_(0)
, instance_(reinterpret_cast<I*>
, instance_(reinterpret_cast<I*>
(lumiera_interface_open (iName.c_str(), version, minminor, impName.c_str())))
, facadeLink_(*this)
{
{
throwIfError();
}
/** Set up an InstanceHandle managing the
/** Set up an InstanceHandle managing the
* registration and deregistration of interface(s).
* Should be placed at the service providing side.
* @param a (single) interface descriptor, which can be created with
@ -207,7 +222,7 @@ namespace lumiera {
: desc_(descriptor)
, instance_(reinterpret_cast<I*> (register_and_open (desc_)))
, facadeLink_(*this)
{
{
throwIfError();
}
@ -220,24 +235,41 @@ namespace lumiera {
/** act as smart pointer providing access through the facade.
* @note we don't provide operator* */
FA * operator-> () const { return &(facadeLink_(*this)); }
/** act as smart pointer to allow access through the facade.
* @note we don't provide `operator*`
*/
FA*
operator-> () const
{
return facadeLink_.operator ->();
}
/** directly access the instance via the CL interface */
I& get () const { ENSURE(instance_); return *instance_; }
I&
get () const
{
ENSURE(instance_);
return *instance_;
}
explicit operator bool() const { return isValid(); }
bool operator! () const { return not isValid();}
explicit
operator bool() const
{
return isValid();
}
bool
operator!() const
{
return not isValid();
}
private:
bool
bool
isValid() const
{
return instance_
{
return instance_
&& verify_validity (&instance_->interface_header_);
}
};

View file

@ -1,124 +0,0 @@
/*
INTERFACE-FACADE-LINK - a switchable link from interface to service implementation
Copyright (C) Lumiera.org
2011, 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 interface-facade-link.hpp
** Opening, accessing and closing the service access through a facade interface.
** Client code is assumed to access an application level service through an facade
** interface, while the actual implementation object remains an opaque internal detail.
** Moreover, services may come up and shut down, so the implementation might change
** during the Lifecycle. The facility defined here in this header provides a basic
** implementation for this access mechanism, but without any adaptation, binding
** or plugin access layer. It works only under the assumption that both the
** interface and the actual service implementation coexist in the same
** executable and are written in C++, so any invocation of an
** interface method boils down to a language-level call.
**
** Usually, client code doesn't need to include this header. Clients are assumed
** to use the facade interface of the service in question. This facade interface
** contains a static member of type \c lumiera::facade::Accessor<I> (where I is
** the type of the facade interface). The Accessor baseclass is defined in
** interfaceproxy.hpp and typically included through the facade header.
**
** @note there is a way more elaborate implementation of the same mechanism
** for use with the Lumiera Interface/Plugin system.
**
** @see interfaceproxy.hpp description of the more general use case
** @see PlayService example for the simple use case
*/
#ifndef LUMIERA_FACADE_INTERFACE_FACADE_LINK_H
#define LUMIERA_FACADE_INTERFACE_FACADE_LINK_H
#include "lib/error.hpp"
#include "lib/meta/util.hpp"
#include "include/interfaceproxy.hpp"
#include "lib/symbol.hpp"
#include <boost/noncopyable.hpp>
namespace lumiera {
namespace facade {
using lib::Literal;
/********************************************************************//**
* simple access-frontend to the implementation of a service (C++ only).
* Usually, an instance of Accessor is placed as static member right into
* the facade interface used to access the service. This implementation
* of the access mechanism handles the simple case that both the facade
* and the service implementation are written in C++ and calls happen
* within the main executable as direct language calls, without an
* binding layer and without involving the Interface/Plugin system.
*
* Typically, the InterfaceFacadeLink becomes a member of the service
* implementation class and is directly tied into the constructor of
* the latter. Being a subclass of lumiera::facade::Accessor, it is
* allowed to "open" the facade access just by setting the static
* protected pointer Accessor::implProxy_
*/
template<class FA>
class InterfaceFacadeLink
: protected Accessor<FA>
, boost::noncopyable
{
string displayName_;
void
__checkLifecycle ()
{
if (Accessor<FA>::implProxy_)
throw error::State("Attempt to open an already opened Facade interface."
, error::LUMIERA_ERROR_LIFECYCLE);
}
public:
InterfaceFacadeLink(FA& serviceImpl, Literal interfaceName_for_Log=0)
: displayName_(interfaceName_for_Log? string(interfaceName_for_Log)
: util::typeStr<FA>())
{
__checkLifecycle();
Accessor<FA>::implProxy_ = &serviceImpl;
INFO (interface, "interface %s opened", displayName_.c_str());
}
~InterfaceFacadeLink()
{
INFO (interface, "closing interface %s...", displayName_.c_str());
Accessor<FA>::implProxy_ = 0;
}
};
/** storage for the static access pointer */
template<class FA>
FA* Accessor<FA>::implProxy_;
}} // namespace lumiera::facade
#endif

View file

@ -1,162 +0,0 @@
/*
InterfaceProxy - definition of forwarding proxies for the facade interfaces
Copyright (C) Lumiera.org
2008, 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 interfaceproxy.cpp
** Implementation of C++ binding proxies on top of the (plain-C based)
** interface system. This is an implementation facility within the application core,
** which allows to embody just an ["interface instance handle"](\ref instancehandle.hpp),
** in order to get RAII-style registration of interfaces and loading of plug-ins.
**
** A *crucial requirement* for this approach to work is, that any relevant interface
** to be bound and exposed as C++ object needs to set up a concrete specialisation of
** lumiera::facade::Proxy to drive instantiation of the actual binding proxy.
** The relevant specialisations _need to be included explicitly_ into this
** compilation unit!
**
** The result of this setup is that clients can just invoke `SomeInterface::facade()`
** and thus call through proper C++ bindings with type safety and automatic
** lifecycle management.
**
*/
#include "include/interfaceproxy.hpp"
#include "common/instancehandle.hpp"
#include "lib/error.hpp"
#include "lib/util.hpp"
using util::cStr;
namespace lumiera{
namespace facade {
LUMIERA_ERROR_DEFINE (FACADE_LIFECYCLE, "facade is closed; service currently not accessible");
/**
* Implementation Base
* for building Facade Proxy implementations.
* Typically the purpose of such a proxy is to route
* any calls through the C-Bindings of the Lumiera Interface system.
* The actual storage for the concrete proxy object is embedded,
* inline within the #open() function. For access by the clients,
* a frontend-object of type \c Accessor<FA> may be placed into
* the facade interface; this accessor-frontend is basically
* a concealed static pointer to the proxy, and will be set,
* when the interface is opened. This opening and closing
* of the interface itself is controlled by the
* InstanceHandle, which in turn is typically
* created and managed within the context
* of the service implementation.
*/
template<class IHA>
class Holder;
template<class FA, class I>
class Holder<InstanceHandle<I,FA>>
: Accessor<FA>
, protected FA
{
protected:
typedef InstanceHandle<I,FA> IHandle;
typedef Holder<IHandle> THolder;
typedef Proxy<IHandle> TProxy;
typedef Accessor<FA> Access;
I& _i_;
Holder (IHandle const& iha)
: _i_(iha.get())
{ }
public:
static TProxy& open(IHandle const& iha)
{
static char buff[sizeof(TProxy)];
TProxy* p = new(buff) TProxy(iha);
Access::implProxy_ = p;
return *p;
}
static void close()
{
if (!Access::implProxy_) return;
TProxy* p = static_cast<TProxy*> (Access::implProxy_);
Access::implProxy_ = 0;
p->~TProxy();
}
};
template<class FA>
FA* Accessor<FA>::implProxy_;
template<class IHA>
void
openProxy (IHA const& iha)
{
Proxy<IHA>::open(iha);
}
template<class IHA>
void
closeProxy ()
{
Proxy<IHA>::close();
}
}} // namespace lumiera::facade
/* ===================================================================== */
/* including explicit Proxy-Definitions for all facade interfaces */
/* ===================================================================== */
/* ==================== SessionCommand ================================= */
#include "proc/control/session-command-interface-proxy.hpp"
/* ==================== GuiNotification ================================ */
#include "gui/notification-interface-proxy.hpp"
/* ==================== gui::Display =================================== */
#include "gui/display-interface-proxy.hpp"
/* ==================== DummyPlayer ==================================== */
#include "proc/play/dummy-player-interface-proxy.hpp"

View file

@ -22,9 +22,8 @@
/** @file notification-interface-proxy.hpp
** This is an implementation fragment, intended to be included into common/interfaceproxy.cpp
**
/** @file notification-interface-proxy.cpp
** Interface-Proxy for the gui::GuiNotification facade.
** The purpose is to define a proxy implementation of gui::GuiNotification, in order to
** redirect any calls through the associated C Language Interface "lumieraorg_GuiNotification"
**
@ -33,37 +32,31 @@
*/
/* ==================== GuiNotification =================================== */
#include "include/gui-notification-facade.h"
#include "include/interfaceproxy.hpp"
namespace gui {
/** storage for the facade proxy factory
* used by client code to invoke through the interface */
lumiera::facade::Accessor<GuiNotification> GuiNotification::facade;
} // namespace gui
/** static storage for the facade access front-end */
lib::Depend<GuiNotification> GuiNotification::facade;
}
namespace lumiera {
namespace facade {
using gui::ID;
using lib::diff::MutationMessage;
typedef InstanceHandle< LUMIERA_INTERFACE_INAME(lumieraorg_GuiNotification, 0)
, gui::GuiNotification
> IHandle_GuiNotification;
/* ==================== SessionCommand =================================== */
using Interface = LUMIERA_INTERFACE_INAME(lumieraorg_GuiNotification, 0);
using Facade = gui::GuiNotification;
using IHandle = InstanceHandle<Interface, Facade>;
template<>
class Proxy<IHandle_GuiNotification>
: public Holder<IHandle_GuiNotification>
class Proxy<IHandle>
: public Binding<IHandle>
{
using Level = gui::NotifyLevel;
@ -77,12 +70,12 @@ namespace facade {
public:
Proxy (IHandle const& iha) : THolder(iha) {}
using Binding<IHandle>::Binding;
};
template void openProxy<IHandle_GuiNotification> (IHandle_GuiNotification const&);
template void closeProxy<IHandle_GuiNotification> (void);
/** emit proxy code here... */
template
class Link<Interface,Facade>;
}} // namespace lumiera::facade
}} //namespace facade::lumiera

View file

@ -74,7 +74,7 @@ namespace lumiera {
* unrecognised parts.
*/
class Option
: boost::noncopyable
: util::NonCopyable
{
public:
Option (lib::Cmdline& cmdline);

View file

@ -123,7 +123,7 @@ namespace lumiera {
* of the type of query.
*/
class Goal
: util::no_copy_by_client
: util::Cloneable
{
public:
virtual ~Goal(); ///< this is a marker baseclass

View file

@ -53,7 +53,7 @@ using util::_Fmt;
using proc::ConfigResolver;
using lumiera::query::QueryHandler; ///////TODO preliminary interface defined in config-rules.hpp
using lumiera::query::LUMIERA_ERROR_CAPABILITY_QUERY;
using lumiera::query::LERR_(CAPABILITY_QUERY);
namespace lumiera{
@ -66,7 +66,8 @@ namespace query {
DefsManager::DefsManager () noexcept
: defsRegistry_(new DefsRegistry)
{
TODO ("setup basic technical defaults of the session?");
INFO (session, "Configure technical defaults of the session.");
}

View file

@ -85,6 +85,14 @@
** - for sake of completeness, we can also require a specific element to
** be purged from knowledge
**
** ## Fake implementation
** As of 2018, the Lumiera project still has to reach the goal of a complete
** running engine; we are proceeding with partial integrations for the time being.
** And we postpone advanced topics, like integration of an actual rules solver
** to future milestones. Meanwhile, we use a [fake implementation](\ref MockConfigRules)
** with preconfigured, hard-wired "answers" to some frequently encountered standard queries.
** This Fake implementation is configured and instantiated by the [Defaults Manager](\ref DefsManager)
**
** @see DefsManager_test
** @see DefsManagerImpl_test
**
@ -96,9 +104,9 @@
#include "lib/p.hpp"
#include "lib/nocopy.hpp"
#include "common/query.hpp"
#include <boost/noncopyable.hpp>
#include <memory>
@ -127,7 +135,8 @@ namespace query {
* roughly final, as of 12/09 most of the actual object
* handling is placeholder code.
*/
class DefsManager : private boost::noncopyable
class DefsManager
: util::NonCopyable
{
unique_ptr<impl::DefsRegistry> defsRegistry_;

View file

@ -55,11 +55,11 @@
#include "lib/format-string.hpp"
#include "lib/query-util.hpp"
#include "common/query.hpp"
#include "lib/nocopy.hpp"
#include <set>
#include <vector>
#include <memory>
#include <boost/noncopyable.hpp>
namespace lumiera{
@ -193,7 +193,7 @@ namespace query {
* exact behaviour has to be defined.
*/
class DefsRegistry
: boost::noncopyable
: util::NonCopyable
{
Table table_;

View file

@ -37,8 +37,8 @@
#include "lib/iter-adapter.hpp"
#include "common/query.hpp"
#include "lib/nocopy.hpp"
#include <boost/noncopyable.hpp>
#include <functional>
#include <memory>
#include <string>
@ -47,7 +47,6 @@ using std::function;
namespace lumiera {
using boost::noncopyable;
using std::unique_ptr;
using std::string;
@ -65,7 +64,7 @@ namespace lumiera {
* of an individual query resolution
*/
class Resolution
: boost::noncopyable
: util::NonCopyable
{
public:
typedef Goal::Result Result;
@ -106,7 +105,7 @@ namespace lumiera {
* Thus the implementation might downcast query and resultset.
*/
class QueryResolver
: noncopyable
: util::NonCopyable
{
unique_ptr<QueryDispatcher> dispatcher_;

View file

@ -44,8 +44,8 @@
#define LUMIERA_SUBSYS_H
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
#include <boost/noncopyable.hpp>
#include <functional>
#include <string>
#include <vector>
@ -55,7 +55,6 @@
namespace lumiera {
using std::string;
using boost::noncopyable;
using std::function;
class Option;
@ -69,7 +68,7 @@ namespace lumiera {
* @note synchronisation is up to the implementor.
*/
class Subsys
: private noncopyable
: util::NonCopyable
{
public:
typedef function<void(string*)> SigTerm;

View file

@ -94,7 +94,7 @@ namespace lumiera {
* correspond to a single component, interface or plugin. It may well be a
* complete layer of the application (e.g. the GUI).
*
* \par Protocol of operation
* ## Protocol of operation
* The SubsystemRunner is to be configured with a lumiera::Option object first.
* Then, primary subsystems are \link #maybeRun provided \endlink for eventual
* startup, which may depend on conditions defined by the subsystem. When
@ -112,7 +112,7 @@ namespace lumiera {
* will be triggered. The #wait() function returns after shutdown of all subsystems,
* signalling an emergency exit (caused by an exception) with its return value.
*
* @todo maybe use my refArray (see builder) to use Subsys& instead of Subsys* ??
* @todo 2018 this component works well but could be (re)written in a cleaner way
*
* @see lumiera::AppState
* @see lumiera::Subsys

View file

@ -25,7 +25,8 @@
/** @file config-keys.hpp
** Hard wired key constants to access the global application configuration.
**
** \par configuration and resource search
** # configuration and resource search
**
** The UI layer retrieves the necessary configuration values from lumiera::Config,
** the config facade in the application core. Currently as of 2/2011 these values are
** loaded from setup.ini, because the full-blown config system is not yet implemented.

View file

@ -43,12 +43,12 @@
#include "gui/gtk-base.hpp"
#include "gui/ctrl/global-ctx.hpp"
#include "gui/ctrl/window-list.hpp"
#include "gui/ctrl/window-locator.hpp"
#include "gui/workspace/workspace-window.hpp"
#include "gui/workspace/panel-manager.hpp"
#include "lib/format-string.hpp"
#include "lib/nocopy.hpp"
#include <boost/noncopyable.hpp>
#include <string>
@ -74,7 +74,7 @@ namespace ctrl {
* user action events.
*/
class Actions
: boost::noncopyable
: util::NonCopyable
{
GlobalCtx& globalCtx_;
@ -133,8 +133,8 @@ namespace ctrl {
menu("WindowMenu", _("_Window"));
entry ([&]() { globalCtx_.windowList_.newWindow(); } , "WindowNewWindow", StockID("new_window"));
entry ([&]() { globalCtx_.windowList_.closeWindow();}, "WindowCloseWindow", _("Close Window"));
entry ([&]() { globalCtx_.windowLoc_.newWindow(); } , "WindowNewWindow", StockID("new_window"));
entry ([&]() { globalCtx_.windowLoc_.closeWindow();} , "WindowCloseWindow", _("Close Window"));
actionGroup->add(Action::create("WindowShowPanel", _("_Show Panel")));
@ -143,6 +143,10 @@ namespace ctrl {
assetsPanelAction->signal_toggled().connect ( [&]() { onMenu_view_assets(); });
actionGroup->add(assetsPanelAction);
infoboxPanelAction = ToggleAction::create("ViewInfoBox", StockID("panel_infobox"));
infoboxPanelAction->signal_toggled().connect ( [&]() { onMenu_view_infobox(); });
actionGroup->add(infoboxPanelAction);
timelinePanelAction = ToggleAction::create("ViewTimeline", StockID("panel_timeline"));
timelinePanelAction->signal_toggled().connect( [&]() { onMenu_view_timeline(); });
actionGroup->add(timelinePanelAction);
@ -181,6 +185,7 @@ namespace ctrl {
</menu>
<menu action='ViewMenu'>
<menuitem action='ViewAssets'/>
<menuitem action='ViewInfoBox'/>
<menuitem action='ViewTimeline'/>
<menuitem action='ViewViewer'/>
</menu>
@ -282,9 +287,9 @@ namespace ctrl {
cuString panelName = ustring::compose("Panel%1", i);
actionGroup->add(Action::create(panelName, StockID(stock_id)),
[i,this]() {
globalCtx_.windowList_.findActiveWindow()
.getPanelManager()
.showPanel (i);
globalCtx_.windowLoc_.findActiveWindow()
.getPanelManager()
.showPanel (i);
});
}
@ -314,6 +319,16 @@ namespace ctrl {
unimplemented ("view assets");
}
void
onMenu_view_infobox()
{
/////////////////////////////////////////////////////////////////////////////////////TODO defunct since GTK-3 transition
//if(!is_updating_action_state)
// workspaceWindow.infoboxPanel->show(
// infoboxPanelAction->get_active()); //////global -> InteractionDirector
unimplemented ("view infobox");
}
void
onMenu_view_timeline()
{
@ -347,6 +362,7 @@ namespace ctrl {
Glib::RefPtr<Gtk::ActionGroup> actionGroup;
Glib::RefPtr<Gtk::ToggleAction> assetsPanelAction;
Glib::RefPtr<Gtk::ToggleAction> infoboxPanelAction;
Glib::RefPtr<Gtk::ToggleAction> timelinePanelAction;
Glib::RefPtr<Gtk::ToggleAction> viewerPanelAction;

View file

@ -60,10 +60,10 @@
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
#include "lib/idi/entry-id.hpp"
#include "lib/diff/gen-node.hpp"
#include <boost/noncopyable.hpp>
#include <utility>
#include <string>
@ -103,6 +103,7 @@ namespace ctrl{
* some element.
*/
class BusTerm
: util::MoveOnly
{
protected:
using EntryID = lib::idi::BareEntryID;
@ -135,9 +136,6 @@ namespace ctrl{
/** may be moved, but not copied,
* due to the embedded identity */
BusTerm(BusTerm&&) = default;
BusTerm(BusTerm const&) = delete;
BusTerm& operator= (BusTerm const&) = delete;
protected:
/**

View file

@ -81,6 +81,7 @@
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
#include "lib/idi/entry-id.hpp"
#include "lib/diff/gen-node.hpp"
#include "include/session-command-facade.h"
@ -88,8 +89,6 @@
#include "gui/ctrl/bus-term.hpp"
#include "gui/ctrl/nexus.hpp"
#include <boost/noncopyable.hpp>
namespace gui {
namespace ctrl{
@ -107,7 +106,7 @@ namespace ctrl{
*/
class CoreService
: public BusTerm
, boost::noncopyable
, util::NonCopyable
{
Nexus uiBusBackbone_;

View file

@ -43,8 +43,10 @@
#define GUI_CTRL_FACADE_H
#include "gui/notification-service.hpp"
#include "gui/display-service.hpp"
#include "lib/depend-inject.hpp"
#include "lib/nocopy.hpp"
#include <boost/noncopyable.hpp>
namespace gui {
@ -59,9 +61,13 @@ namespace ctrl {
* @remark the UiManager is responsible to activate and deactivate those interfaces
*/
class Facade
: boost::noncopyable
: util::NonCopyable
{
NotificationService notificationService_;
using Instance_Notification = lib::DependInject<NotificationService>::ServiceInstance<>;
using Instance_DisplayService = lib::DependInject<DisplayService>::ServiceInstance<>;
Instance_Notification notificationService_;
Instance_DisplayService displayService_; ///////////////////////////////////////////////////////TICKET #82 obsolete and will go away once we have a real OutputSlot offered by the UI
public:
@ -69,7 +75,8 @@ namespace ctrl {
* Activate all external UI facade interfaces.
*/
Facade (UiBus& bus, UiManager& manager)
: notificationService_(bus.getAccessPoint(), manager) // opens the GuiNotificationService instance
: notificationService_{bus.getAccessPoint(), manager} // opens the GuiNotificationService instance
, displayService_{} // opens the DisplayService instance ////////TICKET #82 obsolete
{
INFO (gui, "UI-Facade Interfaces activated.");
}

View file

@ -40,7 +40,7 @@
** - connection to the [UI-Bus](\ref ui-bus.hpp)
** - the UiManager
** - the InteractionDirector
** - the WindowList
** - the WindowLocator
** - the Wizard
**
** @see gtk-lumiera.hpp
@ -56,11 +56,11 @@
#include "gui/gtk-base.hpp"
#include "gui/ui-bus.hpp"
#include "gui/ctrl/ui-manager.hpp"
#include "gui/ctrl/window-list.hpp"
#include "gui/ctrl/window-locator.hpp"
#include "gui/interact/wizard.hpp"
#include "gui/interact/interaction-director.hpp"
#include "lib/nocopy.hpp"
#include <boost/noncopyable.hpp>
//#include <string>
//#include <memory>
@ -80,14 +80,14 @@ namespace ctrl {
* @remark the UiManager is responsible to install this top-level context
*/
class GlobalCtx
: boost::noncopyable
: util::NonCopyable
{
public:
UiBus& uiBus_;
UiManager& uiManager_;
WindowList windowList_;
WindowLocator windowLoc_;
InteractionDirector director_;
interact::Wizard wizard_;
@ -99,7 +99,7 @@ namespace ctrl {
GlobalCtx (UiBus& bus, UiManager& manager)
: uiBus_{bus}
, uiManager_{manager}
, windowList_{*this}
, windowLoc_{*this}
, director_{*this}
, wizard_{*this}
{ }

View file

@ -41,6 +41,7 @@
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
#include "lib/idi/genfunc.hpp"
#include "lib/diff/mutation-message.hpp"
#include "lib/diff/tree-diff-application.hpp"
@ -48,7 +49,6 @@
#include "gui/model/tangible.hpp"
#include "lib/idi/entry-id.hpp"
#include <boost/noncopyable.hpp>
#include <unordered_map>
@ -75,7 +75,7 @@ namespace ctrl{
*/
class Nexus
: public BusTerm
, boost::noncopyable
, util::NonCopyable
{
typedef std::unordered_map<EntryID, Tangible*, EntryID::UseEmbeddedHash> RoutingTable;

View file

@ -0,0 +1,82 @@
/*
PANEL-LOCATOR.hpp - manage all top level windows
Copyright (C) Lumiera.org
2017, 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 panel-locator.hpp
** Access and query front-end to locate, access and place docking panels.
** This service is an implementation detail of the global WindowLocator and
** allows to abstract from the concrete top-level window when dealing with
** docking panels on a conceptual level
**
** @see view-locator.hpp
** @see interaction-director.hpp
*/
#ifndef GUI_CTRL_PANEL_LOCATOR_H
#define GUI_CTRL_PANEL_LOCATOR_H
#include "gui/gtk-base.hpp"
#include "lib/nocopy.hpp"
#include <memory>
#include <list>
namespace gui {
namespace workspace { class WorkspaceWindow; }
namespace ctrl {
using std::list;
/**
* Service to access, locate or place panels within top-level windows
* Access- and query front-end to the individual panel managers, which are
* located within the top-level windows.
*/
class PanelLocator
: util::NonCopyable
{
using PWindow = std::shared_ptr<workspace::WorkspaceWindow>;
using WindowList = list<PWindow>;
WindowList& windowList_;
public:
PanelLocator (WindowList&);
private:
};
/** */
}}// namespace gui::ctrl
#endif /*GUI_CTRL_PANEL_LOCATOR_H*/

View file

@ -62,7 +62,7 @@ namespace ctrl {
{
if (not instance)
throw error::Logic ("GTK UI is not in running state"
, error::LUMIERA_ERROR_LIFECYCLE);
, error::LERR_(LIFECYCLE));
return *instance;
}

View file

@ -42,8 +42,8 @@
#include "gui/gtk-base.hpp"
#include "include/dummy-player-facade.h"
#include "include/display-facade.h"
#include "lib/nocopy.hpp"
#include <boost/noncopyable.hpp>
namespace gui {
@ -53,7 +53,7 @@ namespace ctrl {
/** @deprecated we need a durable design for the playback process */
class PlaybackController
: boost::noncopyable
: util::NonCopyable
{
volatile bool playing_;

View file

@ -55,8 +55,8 @@
#include "lib/idi/entry-id.hpp"
#include "lib/diff/gen-node.hpp"
#include "lib/nocopy.hpp"
#include <boost/noncopyable.hpp>
#include <string>
@ -77,7 +77,7 @@ namespace ctrl {
* element.
*/
class StateManager
: boost::noncopyable
: util::NonCopyable
{
protected:
virtual ~StateManager(); ///< this is an interface

View file

@ -38,11 +38,11 @@
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
#include "lib/idi/entry-id.hpp"
#include "lib/diff/gen-node.hpp"
#include "lib/util.hpp"
#include <boost/noncopyable.hpp>
#include <unordered_map>
#include <algorithm>
#include <set>
@ -72,7 +72,7 @@ namespace ctrl {
* @see StateMapGroupingStorage_test
*/
class StateMapGroupingStorage
: boost::noncopyable
: util::NonCopyable
{
using StateData = std::set<GenNode, GenNode::IDComparator>;
using Storage = std::unordered_map<BareEntryID, StateData, BareEntryID::UseEmbeddedHash>;

View file

@ -77,8 +77,9 @@
#include "gui/gtk-base.hpp"
#include "lib/call-queue.hpp"
#include "lib/format-string.hpp"
#include "lib/nocopy.hpp"
#include <boost/noncopyable.hpp>
#include <utility>
@ -86,7 +87,19 @@ namespace gui {
namespace ctrl {
using std::move;
using ::util::_Fmt;
namespace {
/** @note reads and clears the lumiera error flag */
inline string
generateErrorResponse (const char* problem = "unexpected problem")
{
static _Fmt messageTemplate{"asynchronous UI response failed: %s (error flag was: %s)"};
string response{messageTemplate % problem % lumiera_error()};
WARN (gui, "%s", response.c_str());
return response;
}
}
/**
@ -96,7 +109,7 @@ namespace ctrl {
* it outlives the GTK event loop
*/
class UiDispatcher
: boost::noncopyable
: util::NonCopyable
{
lib::CallQueue queue_;
Glib::Dispatcher dispatcher_;
@ -104,13 +117,23 @@ namespace ctrl {
using Operation = lib::CallQueue::Operation;
public:
UiDispatcher()
template<class FUN>
UiDispatcher(FUN notification)
: queue_{}
, dispatcher_{}
{
dispatcher_.connect(
[this]() {
queue_.invoke(); /////////////////////TICKET #1098 : ensure no exception escapes from here!!
[=]() {try {
queue_.invoke();
}
catch (std::exception& problem)
{
notification (generateErrorResponse(problem.what()));
}
catch (...)
{
notification (generateErrorResponse());
}
});
}

View file

@ -116,8 +116,8 @@ namespace ctrl {
void
UiManager::createApplicationWindow()
{
if (globals_->windowList_.empty())
globals_->windowList_.newWindow();
if (globals_->windowLoc_.empty())
globals_->windowLoc_.newWindow();
}
@ -158,7 +158,7 @@ namespace ctrl {
UiManager::updateWindowFocusRelatedActions()
{
///////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1076 find out how to handle this properly
actions_->updateActionState (globals_->windowList_.findActiveWindow());
actions_->updateActionState (globals_->windowLoc_.findActiveWindow());
}

View file

@ -37,7 +37,7 @@
** - the global Actions available though the menu
** - the InteractionDirector (top-level controller)
** - the StyleManager
** - the WindowList
** - the WindowLocator
**
** @see gtk-lumiera.hpp
** @see ui-bus.hpp
@ -48,8 +48,8 @@
#define GUI_CTRL_UI_MANAGER_H
#include "gui/gtk-base.hpp"
#include "lib/nocopy.hpp"
#include <boost/noncopyable.hpp>
#include <string>
#include <memory>
@ -72,7 +72,7 @@ namespace ctrl {
/** Framework initialisation base */
class ApplicationBase
: boost::noncopyable
: util::NonCopyable
{
protected:
ApplicationBase();

View file

@ -55,8 +55,8 @@
#define GUI__H
#include "gui/gtk-base.hpp"
#include "lib/nocopy.hpp"
#include <boost/noncopyable.hpp>
//#include <string>
//#include <memory>
@ -82,7 +82,7 @@ namespace ctrl {
* @todo initial draft as of 2/2017 -- actual implementation has to be filled in
*/
class UiState
: boost::noncopyable
: util::NonCopyable
{
StateManager& stateManager_;
interact::FocusTracker& tracker_;

View file

@ -1,5 +1,5 @@
/*
WindowList - manage all top level windows
WindowLocator - manage all top level windows
Copyright (C) Lumiera.org
2008, Joel Holdsworth <joel@airwebreathe.org.uk>
@ -21,14 +21,16 @@
* *****************************************************/
/** @file window-list.cpp
** Implementation parts of the list to manage all top level windows.
/** @file window-locator.cpp
** Implementation details of management and access to all top level windows and docking panels.
** @see ui-manager.hpp
*/
#include "gui/ctrl/global-ctx.hpp"
#include "gui/ctrl/window-list.hpp"
#include "gui/ctrl/panel-locator.hpp"
#include "gui/ctrl/window-locator.hpp"
#include "gui/interact/elem-access-dir.hpp"
#include "gui/workspace/workspace-window.hpp"
#include "lib/util.hpp"
@ -47,20 +49,31 @@ namespace ctrl {
WindowList::WindowList (GlobalCtx& globals)
: globalCtx_{globals}
, windowList_{}
PanelLocator::PanelLocator (WindowList& all_top_level_wndows)
: windowList_{all_top_level_wndows}
{ }
WindowLocator::WindowLocator (GlobalCtx& globals)
: globalCtx_{globals}
, windowList_{}
, panelLoc_{windowList_}
, elementAccess_{*this, panelLoc_}
{ }
WindowLocator::~WindowLocator () { }
void
WindowList::newWindow ()
WindowLocator::newWindow ()
{
PWindow window (new WorkspaceWindow{globalCtx_.uiManager_});
REQUIRE(window);
window->signal_delete_event().connect(sigc::mem_fun(
this, &WindowList::on_window_closed));
this, &WindowLocator::on_window_closed));
windowList_.push_back(window);
@ -76,7 +89,7 @@ namespace ctrl {
* the fist one in list will be killed
*/
void
WindowList::closeWindow()
WindowLocator::closeWindow()
{
findActiveWindow().hide();
}
@ -90,7 +103,7 @@ namespace ctrl {
* called when there is at least one Lumiera window.
*/
WorkspaceWindow&
WindowList::findActiveWindow()
WindowLocator::findActiveWindow()
{
REQUIRE (not isnil (windowList_));
@ -108,7 +121,7 @@ namespace ctrl {
* has keyboard focus. This may very well be the case.
*/
WorkspaceWindow&
WindowList::findFocusWindow()
WindowLocator::findFocusWindow()
{
REQUIRE (not isnil (windowList_));
@ -122,7 +135,7 @@ namespace ctrl {
bool
WindowList::on_window_closed (GdkEventAny* event)
WindowLocator::on_window_closed (GdkEventAny* event)
{
REQUIRE(event);
REQUIRE(event->window);
@ -157,7 +170,7 @@ namespace ctrl {
void
WindowList::updateCloseWindowInMenus()
WindowLocator::updateCloseWindowInMenus()
{
globalCtx_.uiManager_.allowCloseWindow ( 1 < windowList_.size());
}

View file

@ -1,5 +1,5 @@
/*
WINDOW-LIST.hpp - manage all top level windows
WINDOW-LOCATOR.hpp - manage all top level windows
Copyright (C) Lumiera.org
2008, Joel Holdsworth <joel@airwebreathe.org.uk>
@ -21,10 +21,10 @@
*/
/** @file window-list.hpp
/** @file window-locator.hpp
** Manager for all top level application windows.
** The central WindowList instance is owned by the GtkLumiera object and
** initialised in GTK-main. The WindowList allows to create new windows
** The central WindowLocator is part of the [UI global context](\ref GlobalCtx) and thus
** initialised on startup of the UI. The WindowLocator allows to create new windows
** integrated with the application framework.
**
** @see gtk-lumiera.hpp
@ -32,12 +32,14 @@
*/
#ifndef GUI_CTRL_WINDOW_LIST_H
#define GUI_CTRL_WINDOW_LIST_H
#ifndef GUI_CTRL_WINDOW_LOCATOR_H
#define GUI_CTRL_WINDOW_LOCATOR_H
#include "gui/gtk-base.hpp"
#include "gui/ctrl/panel-locator.hpp"
#include "lib/depend-inject.hpp"
#include "lib/nocopy.hpp"
#include <boost/noncopyable.hpp>
#include <memory>
#include <list>
@ -48,10 +50,13 @@ namespace gui {
namespace controller { class Controller; } ////////////////////////////////////////////////////TICKET #1048 : rectify UI lifecycle
namespace workspace { class WorkspaceWindow; }
namespace model {
class ElementAccess;
}
namespace ctrl {
class GlobalCtx;
class ElemAccessDir;
using std::list;
@ -59,17 +64,22 @@ namespace ctrl {
/**
* A centralised manager of all top level application windows.
*/
class WindowList
: boost::noncopyable
class WindowLocator
: ::util::NonCopyable
{
using PWindow = std::shared_ptr<workspace::WorkspaceWindow>;
using Service_ElementAccess = lib::DependInject<model::ElementAccess>::ServiceInstance<ElemAccessDir>;
GlobalCtx& globalCtx_;
list<PWindow> windowList_;
PanelLocator panelLoc_;
Service_ElementAccess elementAccess_;
public:
WindowList (GlobalCtx&);
WindowLocator (GlobalCtx&);
~WindowLocator ();
bool empty() const;
@ -79,6 +89,8 @@ namespace ctrl {
workspace::WorkspaceWindow& findActiveWindow();
workspace::WorkspaceWindow& findFocusWindow();
PanelLocator& locatePanel() { return panelLoc_; }
private:
@ -104,7 +116,7 @@ namespace ctrl {
inline bool
WindowList::empty() const
WindowLocator::empty() const
{
return windowList_.empty();
}
@ -112,4 +124,4 @@ namespace ctrl {
}}// namespace gui::ctrl
#endif /*GUI_CTRL_WINDOW_LIST_H*/
#endif /*GUI_CTRL_WINDOW_LOCATOR_H*/

View file

@ -30,7 +30,7 @@
** - and sends a "mark" message towards some UI demo widget
** - which in turn displays the mark text
**
** @todo WIP as of 8/20176 ////////////////////////////////////////////////////////////////////////TICKET #1099
** @todo WIP as of 8/2017 ////////////////////////////////////////////////////////////////////////TICKET #1099
**
** @see CallQueue_test
** @see notification-service.hpp
@ -43,8 +43,8 @@
#include "lib/depend.hpp"
#include "gui/ui-bus.hpp"
#include "gui/ctrl/ui-manager.hpp"
#include "lib/nocopy.hpp"
#include <boost/noncopyable.hpp>
#include <string>
@ -77,14 +77,14 @@ namespace gui {
* @see CallQueue_test
*/
class DemoGuiRoundtrip
: boost::noncopyable
: util::NonCopyable
{
string nothing_;
DemoGuiRoundtrip();
~DemoGuiRoundtrip();
friend class lib::DependencyFactory;
friend class lib::DependencyFactory<DemoGuiRoundtrip>;
public:
/** access point to set up the scaffolding.

View file

@ -33,6 +33,7 @@
#include "gui/display-service.hpp"
#include "lib/depend.hpp"
extern "C" {
#include "common/interface-descriptor.h"
@ -119,10 +120,9 @@ namespace gui {
using lumiera::facade::LUMIERA_ERROR_FACADE_LIFECYCLE;
typedef lib::SingletonRef<DisplayService>::Accessor InstanceRef;
using lumiera::error::LERR_(LIFECYCLE);
InstanceRef _instance; ///< a backdoor for the C Language impl to access the actual DisplayService implementation...
lib::Depend<DisplayService> _instance; ///< a backdoor for the C Language impl to access the actual SessionCommand implementation...
@ -136,14 +136,14 @@ namespace gui {
{
if (!_instance)
{
lumiera_error_set(LUMIERA_ERROR_FACADE_LIFECYCLE, 0);
lumiera_error_set (LUMIERA_ERROR_LIFECYCLE, 0);
return;
}
REQUIRE (slotHandle);
try
{
_instance->allocate (slotHandle,true);
_instance().allocate (slotHandle,true);
}
catch (lumiera::Error&){ /* error state remains set */ }
}
@ -153,12 +153,12 @@ namespace gui {
{
if (!_instance)
{
lumiera_error_set(LUMIERA_ERROR_FACADE_LIFECYCLE, 0);
lumiera_error_set (LUMIERA_ERROR_LIFECYCLE, 0);
return;
}
REQUIRE (slotHandle);
_instance->allocate (slotHandle,false);
_instance().allocate (slotHandle,false);
}
)
, LUMIERA_INTERFACE_INLINE (put,
@ -168,7 +168,7 @@ namespace gui {
REQUIRE (_instance && !lumiera_error_peek());
REQUIRE (slotHandle);
DisplayerSlot& slot = _instance->resolve (slotHandle);
DisplayerSlot& slot = _instance().resolve (slotHandle);
slot.put (frame);
}
)
@ -183,19 +183,18 @@ namespace gui {
DisplayService::DisplayService()
: error_("")
, implInstance_(this,_instance)
: error_{}
, serviceInstance_( LUMIERA_INTERFACE_REF (lumieraorg_Display, 0, lumieraorg_DisplayService))
{
INFO (progress, "Display Facade opened.");
}
{
INFO (progress, "Display Facade opened.");
}
LumieraDisplaySlot
DisplayService::setUp (FrameDestination const& outputDestination)
{
DisplayerTab& slots (_instance->slots_);
DisplayerTab& slots (_instance().slots_);
return &slots.manage (new DisplayerSlot (outputDestination));
}

View file

@ -52,13 +52,12 @@
#include "include/display-facade.h"
#include "common/instancehandle.hpp"
#include "lib/singleton-ref.hpp"
#include "lib/scoped-ptrvect.hpp"
#include "include/logging.h"
#include "lib/nocopy.hpp"
#include <glibmm.h>
#include <sigc++/sigc++.h>
#include <boost/noncopyable.hpp>
#include <string>
#include <vector>
@ -84,7 +83,7 @@ namespace gui {
*/
class DisplayerSlot
: public lumiera_displaySlot,
boost::noncopyable
util::NonCopyable
{
Dispatcher dispatcher_;
FrameSignal hasFrame_;
@ -128,7 +127,7 @@ namespace gui {
* course of the play process for outputting frames.
*/
class DisplayService
: boost::noncopyable
: util::NonCopyable
{
string error_;
@ -140,17 +139,12 @@ namespace gui {
typedef lumiera::InstanceHandle< LUMIERA_INTERFACE_INAME(lumieraorg_Display, 0)
, lumiera::Display
> ServiceInstanceHandle;
lib::SingletonRef<DisplayService> implInstance_;
ServiceInstanceHandle serviceInstance_;
public:
DisplayService();
~DisplayService() {
INFO (proc_dbg, "Display service dying...");
}
~DisplayService() { INFO (proc_dbg, "Display service dying..."); }
/** open a new display, sending frames to the given output destination

View file

@ -26,10 +26,10 @@
*/
#include "gui/util/cairo-util.hpp"
#include "gui/draw/cairo-util.hpp"
namespace gui {
namespace util {
namespace draw {
RefPtr<SolidPattern>
CairoUtil::pattern_set_red (const RefPtr<SolidPattern> color, double red)
@ -83,4 +83,4 @@ namespace util {
return Cairo::SolidPattern::create_rgba (red, blue, green, alpha);
}
}}// namespace gui::util
}}// namespace gui::draw

View file

@ -28,15 +28,15 @@
#include <cairomm/cairomm.h>
#ifndef GUI_UTIL_CAIRO_H
#define GUI_UTIL_CAIRO_H
#ifndef GUI_DRAW_CAIRO_H
#define GUI_DRAW_CAIRO_H
using Cairo::RefPtr;
using Cairo::SolidPattern;
namespace gui {
namespace util {
namespace draw {
/**
* @todo this is not a real entity; consider to turn this into a set of free functions!
@ -78,5 +78,5 @@ namespace util {
};
}}// namespace gui::util
#endif /*GUI_UTIL_CAIRO_H*/
}}// namespace gui::draw
#endif /*GUI_DRAW_CAIRO_H*/

View file

@ -25,7 +25,7 @@
** Implementation of functions to work with rectangular screen ranges
*/
#include "gui/util/rectangle.hpp"
#include "gui/draw/rectangle.hpp"
#include <algorithm>
@ -33,7 +33,7 @@ using std::max;
using std::min;
namespace gui {
namespace util{
namespace draw{
bool
pt_in_rect(const Gdk::Point &point, const Gdk::Rectangle &rect)
@ -57,4 +57,4 @@ namespace util{
}
}}// namespace gui::util
}}// namespace gui::draw

View file

@ -26,13 +26,13 @@
*/
#ifndef GUI_UTIL_RECTANGLE_H
#define GUI_UTIL_RECTANGLE_H
#ifndef GUI_DRAW_RECTANGLE_H
#define GUI_DRAW_RECTANGLE_H
#include "gui/gtk-base.hpp"
namespace gui {
namespace util {
namespace draw {
/** determine whether point is within the bounding box of a rectangle */
bool pt_in_rect(Gdk::Point const& point, Gdk::Rectangle const& rect);
@ -42,5 +42,5 @@ namespace util {
bool rects_overlap(Gdk::Rectangle const& a, Gdk::Rectangle const& b);
}}// namespace gui::util
#endif // RECTANGLE_HPP
}}// namespace gui::draw
#endif /*GUI_DRAW_RECTANGLE_H*/

View file

@ -149,8 +149,13 @@ namespace gui {
/** Lumiera custom widgets. */
namespace widget {}
/** GUI helpers, utility functions and classes. */
namespace util {}
/** Display and drawing helpers and utilities. */
namespace draw {}
/** generic definitions for element ID and access.
* @see id-scheme.hpp
*/
namespace idi {}
}// namespace gui

View file

@ -52,16 +52,15 @@
#include "gui/ui-bus.hpp"
#include "gui/guifacade.hpp"
#include "gui/ctrl/ui-manager.hpp"
#include "gui/display-service.hpp"
#include "backend/thread-wrapper.hpp"
#include "common/subsys.hpp"
#include "lib/nocopy.hpp"
extern "C" {
#include "common/interface.h"
#include "common/interface-descriptor.h"
}
#include <boost/noncopyable.hpp>
#include <string>
@ -84,22 +83,19 @@ namespace gui {
/**************************************************************************//**
* Implement the necessary steps for actually making the Lumiera Gui available.
* Establish the UI backbone services and start up the GTK GUI main event loop.
* @todo to ensure invocation of the termination signal, any members
* should be failsafe on initialisation (that means, we must not
* open other interfaces here...) ///////////////////////////TICKET #82
* @warning to ensure reliable invocation of the termination signal,
* any members should be failsafe on initialisation
*/
class GtkLumiera
: boost::noncopyable
: util::NonCopyable
{
UiBus uiBus_;
UiManager uiManager_;
DisplayService activateDisplayService_; ///////////////////////////TICKET #82 will go away once we have a real OutputSlot offered by the UI
public:
GtkLumiera ()
: uiBus_{}
, uiManager_{uiBus_}
, activateDisplayService_() ///////////////////////////TICKET #82 obsolete (and incurs a race)
{ }

View file

@ -39,12 +39,12 @@
#include "common/subsys.hpp"
#include "lib/nocopy.hpp"
extern "C" {
#include "common/interface.h"
}
#include <boost/noncopyable.hpp>
namespace gui {
@ -77,7 +77,7 @@ namespace gui {
*
*/
class GuiFacade
: boost::noncopyable
: util::NonCopyable
{
public:

175
src/gui/id-scheme.hpp Normal file
View file

@ -0,0 +1,175 @@
/*
ID-SCHEME.hpp - naming and ID scheme definitions for the GUI
Copyright (C) Lumiera.org
2017, 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 id-scheme.hpp
** Hard wired definitions for the object identification system used within the UI.
**
** # Identities and element addressing
**
** Within the UI-Layer, all components of global relevance are connected to the [UI-Bus](\ref ui-bus.hpp)
** and can thus be reached just by knowing their ID. This allows to address such UI components as
** [generic UI-Element](\ref model::Tangible), relying on a generic element manipulation protocol.
** Beyond this rather unspecific manipulation scheme, the UI relies on direct ownership relations.
** Typically, some element is created as result of another action and managed as child of some
** maintaining entity; generally speaking, UI elements live _free floating_ and are mostly
** interconnected by signals to react on user interaction. There is a rather limited
** [global UI-Context](\ref GlobalCtx) of directly interconnected backbone services,
** which allow to achieve activities cross-cutting the ownership relationship.
**
** Element IDs are always formed as EntryID, typed to the corresponding type in the Session model.
** Thus, starting from a given model element, it is always possible to "cast" some message towards
** the corresponding UI view element, without actually knowing much about that element's implementation.
**
** @note This header provides the basic definitions for easily accessing relevant UI elements.
**
** @see interaction-director.hpp
** @see ui-bus.hpp
** @see tangible.hpp
** @see bus-term.hpp
** @see entry-id.hpp
**
*/
#ifndef GUI_ID_SCHEME_H
#define GUI_ID_SCHEME_H
#include "lib/symbol.hpp"
#include "lib/idi/entry-id.hpp"
#include "gui/interact/view-spec-dsl.hpp"
/* === forward declarations === */
namespace proc {
namespace asset {
class Timeline;
}
}
namespace gui {
namespace timeline {
class TimelineController;
}
namespace widget {
class ErrorLogWidget;
}
namespace idi {
using lib::Literal;
using lib::idi::EntryID;
using interact::UICoord;
using ID = lib::idi::BareEntryID const&;
/* === primary component view types === */
using TimelineView = timeline::TimelineController;
using ErrorLogView = widget::ErrorLogWidget;
/** Generic Component View descriptors */
template<class V>
struct Descriptor
{
static_assert (not sizeof(V), "unknown generic view type");
};
/**
* Descriptor for the Timeline UI
*/
template<>
struct Descriptor<TimelineView>
{
using Model = proc::asset::Timeline;
};
/**
* Descriptor for error log display within the UI
*/
template<>
struct Descriptor<ErrorLogView>
{
///////////////////////////////////////////////////////////////////////////////////////////TICKET #1105 : need a model placeholder to represent UI specific global entities
///////////////////////////////////////////////////////////////////////////////////////////TICKET #1105 : consider use of a DSL to configure component view access
//
// alloc = onePerWindow
// locate = within(InfoBoxPanel)
};
/*
///////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1105 : DSL design draft...
// Timeline
// add to group of timelines within the timelinePanel
alloc = unlimited
locate = perspective(edit).panel(timeline)
or panel(timeline)
or currentWindow().panel(timeline).create()
// Viewer
// here multiple alternatives are conceivable
// - allow only a single view instance in the whole application
alloc = onlyOne
locate = external(beamer)
or view(viewer)
or perspective(mediaView).panel(viewer)
or panel(viewer)
or firstWindow().panel(viewer).view(viewer).create()
// - allow two viewer panels (the standard layout of editing applications)
alloc = limitPerWindow(2)
locate = perspective(edit).panel(viewer)
or currentWindow().panel(viewer)
or panel(viewer)
or currentWindow().panel(viewer).create()
// (Asset)Bin
// within the dedicated asset panel, add to the appropriate group for the kind of asset
alloc = unlimited
locate = currentWindow().perspective(edit).tab(assetType())
or perspective(asset).view(asset)
or tab(assetType())
or view(asset).tab(assetType()).create()
or firstWindow().panel(asset).view(asset).create()
// Error-Log
// use the current `InfoBoxPanel` if such exists, fall back to using a single view on the primary window
alloc = limitPerWindow(1)
locate = currentWindow().panel(infobox)
or view(error)
or panel(infobox)
or firstWindow().panel(infobox).view(error).create()
*/
}//namespace gui::idi
} // namespace gui
#endif /*GUI_ID_SCHEME_H*/

View file

@ -34,7 +34,6 @@
//#include "include/logging.h"
#include "gui/interact/cmd-context.hpp"
//#include <boost/noncopyable.hpp>
//#include <string>
//#include <map>

View file

@ -42,6 +42,7 @@
** context-bound command becomes executable by picking up suitable parameter values from this context.
**
** @todo as of 3/2017 this is a early design draft and WIP-WIP-WIP
** @todo after initial design draft, postponed as of 10/2017
**
** @see CmdContext_test
** @see ////TODO_test usage example
@ -54,13 +55,13 @@
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
//#include "gui/ctrl/bus-term.hpp"
//#include "lib/idi/entry-id.hpp"
#include "lib/hash-indexed.hpp"
#include "lib/symbol.hpp"
//#include "lib/util.hpp"
#include <boost/noncopyable.hpp>
#include <string>
@ -80,7 +81,7 @@ namespace interact {
* @todo write type comment...
*/
class CmdContext
: boost::noncopyable
: util::NonCopyable
{
public:

View file

@ -0,0 +1,114 @@
/*
ELEM-ACCESS-DIR.hpp - service to access generic elements in the UI
Copyright (C) Lumiera.org
2018, 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 elem-access-directory.hpp
** A service to discover and access raw UI elements in a cross cutting way.
** This is the actual low-level implementation of the model::ElementAccess interface
** Tightly coupled to the internals of Lumiera's GTK UI, this implementation embodies
** all the inside knowledge necessary to navigate over the various levels (Windows,
** panels, views, specific components) to get at those elements abstracted as
** UI-Coordinates.
**
** @note as of 4/2018 this is a first draft and will remain unimplemented for the time being
** @todo WIP-WIP-WIP need to learn more about the concrete UI implementation ///////////////////////TICKET #1134
**
** @see view-locator.hpp
** @see navigator.hpp
**
*/
#ifndef GUI_CTRL_ELEM_ACCESS_DIR_H
#define GUI_CTRL_ELEM_ACCESS_DIR_H
#include "lib/error.hpp"
#include "gui/model/element-access.hpp"
#include "gui/ctrl/window-locator.hpp"
#include "gui/ctrl/panel-locator.hpp"
//#include "lib/symbol.hpp"
//#include "lib/util.hpp"
//#include <string>
namespace gui {
namespace ctrl{
// using util::isnil;
// using std::string;
using interact::UICoord;
/**
* Low-level service to navigate the internals of the Lumiera GTK UI.
* @todo mostly not yet implemented as of 4/2018 -- need to learn more about aforementioned internals.
*/
class ElemAccessDir
: public model::ElementAccess
{
WindowLocator& windowLoc_;
PanelLocator& panelLoc_;
public:
explicit
ElemAccessDir (WindowLocator& windowLocatorService, PanelLocator& panelLocatorService)
: windowLoc_{windowLocatorService}
, panelLoc_{panelLocatorService}
{ }
/* == ElementAccess interface == */
RawResult
performAccessTo (UICoord const&, size_t limitCreation) override
{
UNIMPLEMENTED ("implementation of access function based on real UI widgets");
}
#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #1134
if (isDeaf())
this->transmogrify (solution);
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #1134
protected:
};
/** @internal in case
*/
}} // namespace gui::ctrl
#endif /*GUI_CTRL_ELEM_ACCESS_DIR_H*/

View file

@ -55,8 +55,8 @@
#define GUI_INTERACT_FOCUS_TRACKER_H
#include "gui/gtk-base.hpp"
#include "lib/nocopy.hpp"
#include <boost/noncopyable.hpp>
//#include <string>
//#include <memory>
@ -81,7 +81,7 @@ namespace interact {
* @todo initial draft as of 2/2017 -- actual implementation has to be filled in
*/
class FocusTracker
: boost::noncopyable
: util::NonCopyable
{
Navigator& navigator_;

View file

@ -31,9 +31,11 @@
#include "gui/ctrl/bus-term.hpp"
#include "gui/ctrl/global-ctx.hpp"
#include "gui/interact/interaction-director.hpp"
#include "gui/interact/view-locator.hpp"
#include "gui/interact/spot-locator.hpp"
#include "gui/interact/navigator.hpp"
#include "gui/interact/focus-tracker.hpp"
#include "gui/interact/ui-coord-resolver.hpp"
#include "gui/dialog/preferences-dialog.hpp"
#include "gui/dialog/render.hpp"
#include "gui/workspace/workspace-window.hpp"
@ -82,8 +84,9 @@ namespace interact {
InteractionDirector::InteractionDirector (GlobalCtx& globals)
: model::Controller(session::Root::getID(), globals.uiBus_.getAccessPoint())
, globalCtx_(globals)
, viewLocator_{new ViewLocator}
, spotLocator_{new SpotLocator}
, navigator_{new Navigator{*spotLocator_}}
, navigator_{*spotLocator_, *viewLocator_} // Service exposed as Depend<LocationQuery>
, tracker_{new FocusTracker{*navigator_}}
, uiState_{new UiState{globals.uiBus_.getStateManager(), *tracker_}}
, assets_{new AssetController{session::Root::getAssetID(), this->uiBus_}}
@ -261,7 +264,7 @@ namespace interact {
workspace::WorkspaceWindow&
InteractionDirector::getWorkspaceWindow()
{
return globalCtx_.windowList_.findActiveWindow();
return globalCtx_.windowLoc_.findActiveWindow();
}

View file

@ -55,8 +55,8 @@
//#include "gui/gtk-base.hpp"
#include "gui/model/controller.hpp"
#include "lib/depend-inject.hpp"
//#include <boost/noncopyable.hpp>
//#include <cairomm/cairomm.h>
//#include <string>
#include <vector>
@ -86,9 +86,12 @@ namespace interact {
using std::unique_ptr;
//class Actions;
class SpotLocator;
class LocationQuery;
class Navigator;
class SpotLocator;
class FocusTracker;
class ViewLocator;
@ -101,9 +104,14 @@ namespace interact {
{
ctrl::GlobalCtx& globalCtx_;
// == exposed to Depend on ==
using Service_LocationQuery = lib::DependInject<LocationQuery>::ServiceInstance<Navigator>;
// == global Services ==
unique_ptr<ViewLocator> viewLocator_;
unique_ptr<SpotLocator> spotLocator_;
unique_ptr<Navigator> navigator_;
Service_LocationQuery navigator_;
unique_ptr<FocusTracker> tracker_;
// == Model globals ==

View file

@ -34,7 +34,6 @@
//#include "include/logging.h"
#include "gui/interact/interaction-state.hpp"
//#include <boost/noncopyable.hpp>
//#include <string>
//#include <map>

View file

@ -39,12 +39,12 @@
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
#include "gui/ctrl/bus-term.hpp"
#include "lib/idi/entry-id.hpp"
//#include "lib/symbol.hpp"
//#include "lib/util.hpp"
#include <boost/noncopyable.hpp>
#include <string>
@ -59,9 +59,10 @@ namespace interact {
/**
* Abstract foundation of UI state tracking components.
* @todo write type comment...
* ///////////////////////////////////TODO do we need a translation unit interaction-state.cpp (otherwise delete it!)
*/
class InteractionState
: boost::noncopyable
: util::NonCopyable
{
protected:

View file

@ -23,8 +23,14 @@
/** @file navigator.cpp
** Implementation of global interface navigation mechanisms.
** Especially we implement the LocationQuery interface, which exposes the structures
** of the UI as an abstracted, tree-shaped topology. This task adds up to levelling all the
** specifics of accessing individual components and to assemble them into a virtual component tree.
** The actual details of component access are thereby delegated to the ViewLocator, which is a sibling
** service maintained by the InteractionDirector.
**
** @todo WIP 2/2017 early draft / foundations of "interaction control"
** @todo WIP 1/2018 integrating the concept of UI-Coordinate navigation and resolution. Still WIP-WIP-WIP...
*/
@ -47,11 +53,36 @@ namespace interact {
{ }
Navigator::Navigator (SpotLocator& spotLocator)
: spotLocator_{spotLocator}
Navigator::Navigator (SpotLocator& spotLoc, ViewLocator& viewLoc)
: spotLocator_{spotLoc}
, viewLocator_{viewLoc}
{ }
/* ==== implementing the LocationQuery API ==== */
Literal
Navigator::determineAnchor (UICoord const& path)
{
UNIMPLEMENTED ("LocationQuery in real UI: resolve anchor point of given UI-Coordinates");
}
size_t
Navigator::determineCoverage (UICoord const& path)
{
UNIMPLEMENTED ("LocationQuery in real UI: determine explicit coverage of given UI-Coordinates");
}
LocationQuery::ChildIter
Navigator::getChildren (UICoord const& path, size_t pos)
{
UNIMPLEMENTED ("LocationQuery in real UI: build child iterator rooted at given point in the UI tree");
}
/** */

View file

@ -39,8 +39,9 @@
#define GUI_INTERACT_NAVIGATOR_H
#include "gui/gtk-base.hpp"
#include "gui/interact/ui-coord-resolver.hpp"
#include "lib/nocopy.hpp"
#include <boost/noncopyable.hpp>
//#include <string>
//#include <memory>
@ -53,23 +54,32 @@ namespace interact {
// class GlobalCtx;
class SpotLocator;
class ViewLocator;
/**
* Global cross-cutting navigation in interface space
* Global cross-cutting navigation within user interface space
*
* @todo initial draft as of 2/2017 -- actual implementation has to be filled in
* @see UiCoordResolver
*/
class Navigator
: boost::noncopyable
: public LocationQuery
, util::NonCopyable
{
SpotLocator& spotLocator_;
ViewLocator& viewLocator_;
public:
Navigator (SpotLocator&);
Navigator (SpotLocator&, ViewLocator&);
~Navigator ();
/* === LocationQuery API === */
Literal determineAnchor (UICoord const& path) override final;
size_t determineCoverage (UICoord const& path) override final;
ChildIter getChildren (UICoord const& path, size_t pos) override final;
private:
};

View file

@ -40,8 +40,8 @@
#define GUI_INTERACT_SPOT_LOCATOR_H
#include "gui/gtk-base.hpp"
#include "lib/nocopy.hpp"
#include <boost/noncopyable.hpp>
//#include <string>
//#include <memory>
@ -62,7 +62,7 @@ namespace interact {
* @todo initial draft as of 2/2017 -- actual implementation has to be filled in
*/
class SpotLocator
: boost::noncopyable
: util::NonCopyable
{
public:

View file

@ -0,0 +1,208 @@
/*
UICoordResolver - resolve UI coordinate spec against actual window topology
Copyright (C) Lumiera.org
2017, 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 ui-coord-resolver.cpp
** Implementation details of resolving a UICoord path against the actual UI topology.
** Algorithm to match a UI-Coord spec, possibly with wildcards, against the current actual tree of UI widgets.
**
*/
#include "gui/interact/ui-coord-resolver.hpp"
#include "lib/util.hpp"
using util::isnil;
using lib::Symbol;
using lib::treeExplore;
namespace gui {
namespace interact {
// emit typeinfo and VTables here....
TreeStructureNavigator::~TreeStructureNavigator() { }
LocationQuery::~LocationQuery() { }
/** storage for the global LocationQuery service access point */
lib::Depend<LocationQuery> LocationQuery::service;
namespace { // Helpers for patch matching algorithm
/**
* Special UI-Coordinate builder
* to define the solution path step by step
* while we recurse down into the reference tree
* @note when backtracking, an existing solution will
* be partially rewritten starting from lower depth
*/
class PathManipulator
: public UICoord
{
size_t currDepth_;
public:
PathManipulator ()
: UICoord{}
, currDepth_{0}
{ }
// inherited copy operations
UICoord const&
retrieveResult()
{
PathArray::truncateTo(currDepth_);
return *this;
}
void
setAt (size_t depth, Literal newSpec)
{
Literal* storage = PathArray::expandPosition (depth);
PathArray::setContent (storage, newSpec);
currDepth_ = depth+1;
}
};
size_t
find_wildcardFree_suffix (UICoord const& uic)
{
size_t pos = uic.size();
for ( ; 0 < pos; --pos)
{
Literal const& elm = uic[pos-1];
if (elm == Symbol::ANY or elm == Symbol::EMPTY)
break;
}
return pos;
}
}//(End) implementation details
/**
* Since UICoord path specifications may contain gaps and wildcards, we may attempt
* to fill in these missing parts by matching against the topological structure of an actual UI.
* In the general case, finding a solution requires a depth-first exponential brute-force search
* over the whole structure tree, since we have to try every possible branch until we can disprove
* the possibility of a match. Implemented as depth-first search with backtracking, this scanning
* pass produces a list of possible matches, from which we pick the first one with maximum
* coverage, to yield a single solution.
* @remark the search and matching is based on an iterator pipeline builder, with the special ability
* to expand and recurse into the children of the current element on demand: when `expandChildren()`
* was invoked, the next iteration will continue with the first child element; there is a stack of
* such "child expansions" -- meaning that the search will backtrack and explore further possibilities
* later on. Each position where the pattern matches the actual tree is marked as possible solution.
* As a sideeffect, a new coordinate spec to reflect the actual coverage is built and re-written,
* while the algorithm proceeds. Thus, at any point marked as solution, the current (partial)
* solution can be retrieved and copied from this PathManipulator buffer.
* An additional filter layer discriminates the first maximal solutions seen thus far.
*/
bool
UICoordResolver::pathResolution()
{
// Helper to detect a wildcard match
auto wildMatch = [&](Literal patt, Literal curr, size_t depth)
{
return patt == Symbol::ANY
or patt == Symbol::EMPTY
or patt == UIC_ELIDED // "existentially quantified"
or (isAnchored() and curr == res_.anchor and depth == UIC_WINDOW);
}; // transitive argument: assuming res_.anchor was computed for
// the same coordinate pattern used here for patch resolution
// algorithm state
size_t maxDepth = 0;
PathManipulator coverage;
const size_t coordDepth = this->uic_.size();
const size_t minSolutionDepth = find_wildcardFree_suffix (uic_);
auto searchAlgo = query_.getChildren (uic_, 0)
.expandOnIteration()
.filter ([&](auto& iter)
{
size_t depth = iter.depth(); // we are at that depth in target tree
if (depth >= coordDepth) // search pattern exhausted without match...
return false;
Literal patt = uic_[depth]; // pick search pattern component at that depth
Literal curr = *iter; // iterator points at current tree position (ID)
if (patt == curr or // if either direct match
wildMatch(patt,curr,depth)) // or wildcard match
{
coverage.setAt (depth,curr); // record match rsp. interpolate wildcard into output
iter.expandChildren(); // next iteration will match one level down into the tree
}
return patt == curr // direct match counts as (partial) solution
or patt == UIC_ELIDED; // existentially quantified elements also accepted
})
.filter ([&](auto& iter)
{
if (iter.depth() < minSolutionDepth)
return false; // filter solutions which did not bind all wildcards
if (iter.depth()+1 <= maxDepth) // filter for maximum solution length
return false;
maxDepth = 1 + iter.depth();
return true;
})
.transform ([&](auto&) -> UICoord const&
{
return coverage.retrieveResult();
});
// is (partial) coverage possible?
// search computes definitive answer!
res_.isResolved = true;
// perform the matching
if (isnil (searchAlgo))
return false; // no solution found
while (searchAlgo) // pull first maximal solution
{
if (not res_.covfefe)
res_.covfefe.reset (new UICoord {*searchAlgo});
else
*res_.covfefe = *searchAlgo;
++searchAlgo;
}
ENSURE (res_.covfefe and res_.covfefe->size() >= 1);
res_.anchor = res_.covfefe->getWindow();
// but depth reflects only that part coverable without wildcards
if (res_.depth == 0)
res_.depth = query_.determineCoverage(uic_);
if (res_.depth == 0 and res_.anchor)
res_.depth = 1;
// signal success only when total coverage is possible
return res_.covfefe->size() == uic_.size();
}
}}// namespace gui::interact

View file

@ -0,0 +1,601 @@
/*
UI-COORD-RESOLVER.hpp - resolve UI coordinate spec against actual window topology
Copyright (C) Lumiera.org
2017, 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 ui-coord-resolver.hpp
** Evaluation of UI coordinates against a concrete window topology.
** [UI-Coordinates](\ref UICoord) allow to describe and locate an interface component within the
** Lumiera GUI through a topological access path. As such these coordinate specifications are abstract,
** and need to be related, attached or resolved against the actual configuration of widgets in the UI.
** Through this relation it becomes possible to pose meaningful queries over these coordinates, or to
** build, rebuild and remould a coordinate specification.
**
** We need to avoid tainting with the intrinsics of the actual UI toolkit though -- which indicates
** the UICoordResolver should be designed as an abstract intermediary, built on top of a command and
** query interface, provided by the \ref Navigator and backed by the actual UI configuration.
**
** # Abstraction
**
** The abstraction used to found this interface is twofold. For one, we rely on the notion of logical,
** topological [Coordinates in User Interface space](\ref UICoord). And secondly, we rely on a very
** limited form of navigation: we navigate a tree-shaped (abstracted) structure just by
** - iteration over siblings, which are children of our previous starting point
** - the ability, _on this iterator,_ to expand the "current child" and inject
** the next level of child iteration at its place, similar to the `flatMap`
** operation known from functional programming.
** Together, these two capabilities allow us to build exploring and backtracking evaluations,
** which is enough to build a secondary helper component on top, the gui::interact::UICoordResolver
**
**
** # UI coordinate path evaluation
**
** Such a _resolver_ can be used to relate and match a given, incomplete UI coordinate specification
** (a "pattern") against the actual UI topology. Evaluation is accomplished by first constituting an anchoring,
** followed by traversal of the coordinate spec and matching against a navigation path within the actual UI window
** configuration. This process might involve interpretation of some meta-symbols and interpolation of wildcards.
**
** As indicated above, the coordinate resolver internally relies on a [context query interface](\ref LocationQuery),
** to find out about existing windows, panels, views and tabs and to navigate the real UI structure. The actual
** implementation of this context query interface is backed by the \ref Navigator component exposed through the
** \ref InteractionDirector.
**
** ## Query operations
** In addition to the _locally decidable properties_ of a [coordinate spec](\ref UICoord), which are the explicitness
** and the presence of some component, several contextual predications may be queried:
**
** - *anchorage*
** ** the way the given coordinate spec is or can be anchored
** *** it is already _explicitly anchored_ by referring either to a specific window or by generic specification
** *** it _can be a anchored_ by interpolation of some wildcards
** *** it is _incomplete_ and need to be extended to allow anchoring
** *** it is _impossible to anchor_ in the current UI configuration
**
** - *coverage*
** ** the extent to which a given coordinate spec is backed by the actual UI configuration
** ** _please note_: to determine the coverage, the spec needs to be anchored, either explicitly,
** or by interpolation, or by extension of an incomplete spec
** *** it is _completely covered_
** *** it is _partially covered_ with an remaining, uncovered extension part
** *** it is _possible to cover completely_
** *** it is _impossible to cover_ related to the current UI topology
**
** \par Some fine points to note
** Anchorage and coverage are not the same thing, but coverage implies anchorage. Only when a path is complete
** (i.e. it starts with the window spec) and explicit (has no wildcards), then anchorage implies also partial coverage
** (namely at least to depth 1). To determine the possibility of coverage means to perform a resolution with backtracking
** to pick the maximal solution. Moreover, since "covered" means that the path specification _is at least partially supported
** by the real UI,_ we establish an additional constraint to ensure this resolution did not just match some arbitrary wildcards.
** Rather we demand that behind rsp. below the last wildcard there is at least one further explicit component in the path spec,
** which is supported by the real UI. As a consequence, the coverage resolution may fail altogether, while still providing at
** least a possible anchor point.
**
** ## Mutations
** In addition to querying the interpretation of a given coordinate spec with respect to the current UI environment,
** it is also possible to rewrite or extend the spec based on this environment
**
** - *anchoring*
** ** in correspondence to the possible states of anchorage, we may derive an explicitly anchored spec
** *** by interpolating the given spec
** *** by interpretation and extension of the given spec
**
** - *covering*
** ** we may construct the covered part of a given spec, which includes automatic anchoring
**
** - *extending*
** ** a given UI coordinate pattern is covered...
** ** and _truncated_ to the covered part
** ** the given _extension suffix_ is then attached behind
**
** @see UICoordResolver_test
** @see UICoord_test
** @see navigator.hpp
** @see view-locator.hpp
*/
#ifndef GUI_INTERACT_UI_COORD_RESOLVER_H
#define GUI_INTERACT_UI_COORD_RESOLVER_H
#include "lib/error.hpp"
#include "lib/symbol.hpp"
#include "lib/format-string.hpp"
#include "gui/interact/ui-coord.hpp"
#include "lib/iter-tree-explorer.hpp"
#include "lib/iter-source.hpp"
#include "lib/depend.hpp"
#include "lib/util.hpp"
#include <utility>
#include <memory>
namespace gui {
namespace interact {
namespace error = lumiera::error;
using std::unique_ptr;
using util::unConst;
using lib::Literal;
using lib::Symbol;
/**
* Interface to locate and move within a tree shaped structure.
* The actual nature of this structure is to be kept abstracted through this interface.
* The purpose of this construct is to build evaluations and matching operations on top.
*/
class TreeStructureNavigator
: public lib::IterSource<Literal>
{
public:
virtual ~TreeStructureNavigator(); ///< this is an interface
/** expand into exploration of child elements at "current position".
* At any point, a TreeStructureNavicator instance indicates and represents a position
* within a tree-like structure. At the same time, it is part of a sequence of siblings,
* which is accessible through iteration. This operation now allows to extend visitation
* of siblings by consuming the current element and replacing it with the sequence of its
* immediate child elements, exposing the first one as the _"current position"_.
* @return pointer to a new heap allocated TreeStructureNavigator implementation, which
* represents the sequence of children. The object `this` will not be affected.
* @note it is the caller's responsibility to own and manage the generated navigator.
* The typical (and recommended) way to achieve this is to rely on the embedded
* type #iterator, which exposes an appropriately wired iterator::expandChildren()
*/
virtual TreeStructureNavigator* expandChildren() const =0;
/** build a Lumiera Forward Iterator as front-end and managing Handle for a TreeStructureNavigator
* or subclass. The provided pointer is assumed to point to heap allocated storage.
* @return copyable iterator front-end handle, which allows to retrieve once all values
* yielded by this IterSource. The front-end _takes ownership_ of the given object.
* @note the generated iterator is preconfigured to allow for _"child expansion"_, thereby
* calling through the virtual API function expandChildren()
*/
static auto
buildIterator (TreeStructureNavigator* source)
{
return lib::treeExplore (source)
.expand([](TreeStructureNavigator& parent){ return parent.expandChildren(); });
}
};
/**
* Interface to discover a backing structure for the purpose of path navigation and resolution.
* UICoord are meant to designate a position within the logical structure of an UI -- yet in fact
* they may be resolved against any tree-like topological structure, which can be queried through
* this interface.
* @see Navigator the implementation used in the Lumiera UI, as backed by actual GUI components
* @see InteractionDirector manages the Navigator and exposes it via lib::Depend<LocationQuery>
* @see GenNodeLocationQuery a dummy/test implementation, where the "UI topology" is hard wired
* as a tree of GenNode elements. This serves the purpose of unit testing, without having
* to rely on an actual UI.
*/
class LocationQuery
{
public:
virtual ~LocationQuery(); ///< this is an interface
/** access point to global LocationQuery service implementation */
static lib::Depend<LocationQuery> service;
using ChildIter = decltype (TreeStructureNavigator::buildIterator(0));
/** make the real anchor point explicit.
* @param path an explicit UICoord spec to be anchored in the actual UI
* @return _explicit_ literal window name, where the path can be anchored
* Symbol::BOTTOM in case the given path can not be anchored currently.
* @remark here "to anchor" means to match and thus "attach" the starting point
* of the UIcoord path, i.e. the window spec, with an actual top-level
* window existing in the current UI configuration and state. This operation
* either confirms existence of a window given by explicit ID, or it supplies
* the current meaning of the meta specs `currentWindow` and `firstWindow`,
* again in the form of an explicit window name
*/
virtual Literal determineAnchor (UICoord const& path) =0;
/** evaluate to what extent a UIcoord spec matches the actual UI
* @return the depth to which the given spec is _"covered"_ by the actual UI.
* Can be zero, in which case the given coordinates can not be resolved
* and addressed within the currently existing windows, panes and views.
* @remark a depth > 0 also implies that the path can be _anchored._
* @note this operation does not perform any _resolution_ or interpolation of wildcards,
* it just matches explicit UI component names.
* @see UICoordResolver for a facility to perform such a resolution and to navigate paths.
*/
virtual size_t determineCoverage (UICoord const& path) =0;
/** get the sequence of child components at a designated position in the actual UI
* @param path an explicit UIcoord spec, expected to be anchored and at least partially
* covered within the current configuration and state of the UI
* @param pos depth where the given path shall be evaluated, starting with 0 at window level
* @return an iterator to enumerate all child components actually existing in the current
* UI below the location designated by path and pos.
* @remark the path is only evaluated up to (not including) the given depth. Especially
* when `pos == 0`, then the path is not evaluated and matched at all, rather
* just the current list of top-level windows is returned.
* @throw error::State when navigating the given path touches a non-existing element
*/
virtual ChildIter getChildren (UICoord const& path, size_t pos) =0;
};
/**
* Query and mutate UICoord specifications in relation to actual UI topology.
* This specialised builder uses a _location query facility_ to retrieve information about
* the current actual UI topology. The UI coordinate spec given as initialisation acts as a
* _pattern_, to be *queried and resolved* against that actual UI topology in various ways
* - determine the _anchor point_, where this UI coordinate pattern will be rooted in the UI
* - determine to which extent this UI coordinate pattern can be _"covered"_ (= supported)
* by the exiting UI. To resolve this query, it is necessary to perform a matching search
* with backtracking, in order to find the best possible coverage
* ** the coverage can be _complete_, which means that all components mentioned explicitly
* within the pattern in fact exist in the actual UI
* ** a _partial coverage_ means that there is a prefix actually supported, while some
* extraneous tailing components do not (yet) exist in the UI.
* Moreover, the pattern can be *mutated to conform* with the existing UI topology:
* - the anchor point can be made explicit, which means to replace the _meta specifications_
* ** `firstWindow`
* ** `currentWindow`
* - the calculated _coverage solution_ can be interpolated into the pattern, thereby binding
* and replacing any placeholders ("wildcards", i.e. components designated as `"*"`)
* - we may _extend_ the pattern by attaching further elements, _behind_ the covered part.
* @note this is a _coordinate builder,_ which means that it works on a copy of the UI coordinate
* pattern provided at initialisation. The result, a possibly reworked UI coordinate spec
* can be _moved out_ into a new target UI coordinate (which is then immutable). The
* various query and binding operations work by side-effect on the internal state
* encapsulated within this builder.
* @warning computing a coverage solution for a sparsely defined pattern against a large
* UI topology can be expensive, since in worst case we have to perform a depth-first
* scan of the whole tree structure
*/
class UICoordResolver
: public UICoord::Builder
{
struct Resolution
{
const char* anchor = nullptr;
size_t depth = 0;
unique_ptr<UICoord> covfefe{};
bool isResolved = false;
};
LocationQuery& query_;
Resolution res_;
public:
UICoordResolver (UICoord const& uic, LocationQuery& queryAPI)
: Builder{uic}
, query_{queryAPI}
, res_{}
{
attempt_trivialResolution();
}
UICoordResolver (UICoord && uic, LocationQuery& queryAPI)
: Builder{std::move(uic)}
, query_{queryAPI}
, res_{}
{
attempt_trivialResolution();
}
/* === query functions === */
/** is this path explicitly anchored at an existing window?
* @remarks this also implies the path is complete and explicit (no wildcards).
*/
bool
isAnchored() const
{
return res_.anchor
and res_.anchor != Symbol::BOTTOM;
}
/** determine if a mutation is possible to anchor the path explicitly
* @remarks basically this either means the path isAnchored(), or we're able
* to calculate a path resolution, interpolating any wildcards.
* And while the path resolution as such might fail, it was at least
* successful to determine an anchor point. The existence of such an
* anchor point implies the path is not totally in contradiction to the
* existing UI */
bool
canAnchor() const
{
return isAnchored()
or (res_.isResolved and res_.covfefe)
or unConst(this)->pathResolution()
or isAnchored(); // resolution failed, but computed at least an anchor
}
/** is this path at least _partially_ covered?
* A covered path describes an access path through widgets actually existing in the UI.
* @remark this also implies the path is anchored, complete and explicit.
* @note this predicate tests for _partial_ coverage, which means, there might
* be some extraneous suffix in this path descending beyond existing UI
*/
bool
isCoveredPartially() const
{
return res_.isResolved
and res_.depth > 0;
}
/** this path is completely covered by the currently existing UI structure;
* @remark there is no extraneous uncovered suffix in this path spec;
* moreover, the path is anchored, complete and explicit
*/
bool
isCovered() const
{
return res_.isResolved
and res_.depth == this->uic_.size();
}
/** synonymous to #isCovered() */
bool
isCoveredTotally() const
{
return isCovered();
}
/** determine if a mutation is possible to get the path (partially) covered.
* @remarks in order to be successful, a path resolution must interpolate any gaps in the
* path spec _and_ reach a point behind / below the gap (wildcards), where an existing
* explicitly stated component in the path can be confirmed (covered) by the existing UI.
* The idea behind this definition is that we do not want just some interpolated wildcards,
* rather we really want to _confirm_ the essence of the path specification. Yet we accept
* an extraneous suffix _in the explicitly given part_ of the path spec to extend beyond or
* below what exists currently within the UI.
*/
bool
canCover() const
{
return isCovered() // either explicit coverage known
or (res_.isResolved and res_.covfefe) // or previous matching run found (partial) solution
or unConst(this)->pathResolution() // perform matching run now to find total coverage
or (res_.covfefe); // or at least partial coverage was found
}
/* === mutation functions === */
/** mutate the path to get it totally covered
* - make the anchorage explicit
* - possibly match and expand any wildcards
* - then truncate the UI-Coordinate spec to that part
* actually covered by the UI
* @note if the coordinate spec can not be covered at all,
* it will be truncated to zero size
*/
UICoordResolver&&
cover()
{
if (isCoveredPartially() and not res_.covfefe)
{
ASSERT (res_.anchor); // depth > 0 implies anchorage
window (res_.anchor); // thus make this anchor explicit
truncateTo (res_.depth);
}
else if (canCover())
{
ASSERT (res_.isResolved);
REQUIRE (res_.covfefe);
res_.depth = res_.covfefe->size();
this->uic_ = std::move (*res_.covfefe);
res_.covfefe.reset();
}
else
{
ASSERT (res_.isResolved);
REQUIRE (res_.depth == 0);
REQUIRE (not res_.covfefe);
truncateTo (0);
}
ENSURE (isCovered());
return std::move (*this);
}
/** mutate the path by resolving all wildcards to achieve partial coverage
* - anchorage and all wildcards will be resolved against current UI
* - but an extraneous, uncovered, explicit suffix is retained
* @note if the coordinate spec can not be (partially) covered at all,
* it will be truncated to zero size
*/
UICoordResolver&&
coverPartially()
{
if (isCoveredPartially() and not res_.covfefe)
{
ASSERT (res_.anchor);
window (res_.anchor); // just ensure the anchor info is explicit,
} // the rest is already in place and explicit
else if (canCover())
{
ASSERT (res_.isResolved);
REQUIRE (res_.covfefe);
REQUIRE (uic_.size() >= res_.covfefe->size());
res_.depth = res_.covfefe->size();
// possibly overwrite placeholders by explicitly resolved info...
for (size_t pos = 0; pos < res_.depth; ++pos )
overwrite (pos, (*res_.covfefe)[pos]);
res_.covfefe.reset();
}
else
{
ASSERT (res_.isResolved);
REQUIRE (res_.depth == 0);
REQUIRE (not res_.covfefe);
truncateTo (0);
}
ENSURE (empty() or (isCoveredPartially() and uic_.isExplicit()));
return std::move (*this); // no wildcards remain
}
/** mutate the window part of the path such as
* to make the anchorage explicit, if possible
* @remark if the path starts with meta specs like
* `firstWindow` or `currentWindow`, they will be
* replaced by their current meaning. If the path
* is incomplete, but can somehow be resolved, we
* use the anchorage as indicated by that resolution,
* without interpolating the rest of the path.
*/
UICoordResolver&&
anchor()
{
if (canAnchor())
{
window (res_.anchor);
normalise();
}
return std::move (*this);
}
/** mutate the path to extend it while keeping it partially covered
* @param pathExtension a literal specification, which is extended immediately
* behind the actually covered part of the path, irrespective of the depth
* @note the extension may contain '/', which are treated as component separators
*/
UICoordResolver&&
extend (Literal pathExtension)
{
if (not isCovered())
cover();
ENSURE (isCovered());
append (pathExtension);
res_.depth = query_.determineCoverage (this->uic_); // coverage may grow
return std::move (*this);
}
/** mutate the path and extend it with components at fixed positions
* @param partialExtensionSpec UI coordinates used as extension
* @throw if the extension would overwrite the covered part.
* @remark this is the typical use case, where we want to place some component
* explicitly at a given depth (e.g. a new view or tab)
*/
UICoordResolver&&
extend (UICoord const& partialExtensionSpec)
{
if (not canCover())
uic_ = partialExtensionSpec;
else
{
REQUIRE (res_.isResolved);
size_t coverable = res_.covfefe? res_.covfefe->size() : res_.depth;
auto newContent = partialExtensionSpec.begin();
size_t extensionPos = newContent? partialExtensionSpec.indexOf(*newContent) : coverable;
if (coverable > extensionPos)
throw error::Invalid (util::_Fmt{"Attempt to extend covered path %s with %s "
"would overwrite positions %d to %d (incl)"}
% (res_.covfefe? *res_.covfefe : UICoord{uic_.rebuild().truncateTo(res_.depth)})
% partialExtensionSpec
% extensionPos
% (coverable-1));
cover();
for ( ; newContent; ++newContent, ++extensionPos )
overwrite (extensionPos, *newContent);
normalise();
}
res_ = Resolution{}; // start over with pristine resolution state
attempt_trivialResolution();
canCover();
return std::move (*this);
}
/** mutate to turn a wildcard into _existentially quantified._
* This means to assume (or require) that an element actually exists at the given position,
* without knowing or caring about its actual name. This becomes relevant when matching for
* _partially covered_ path; normal wildcards are only accepted to build a solution, when
* the matching can be confirmed by an explicit element after (below) the wildcard part.
* While this restriction serves to prevent ambiguous and surprising arbitrary matches,
* it can sometimes defeat any solution, e.g. when creating a new panel on top of an
* existing but irrelevant perspective.
*/
UICoordResolver&&
existentiallyQuantify (size_t pos)
{
if (pos < uic_.size() and not uic_.isPresent(pos))
overwrite (pos, UIC_ELIDED);
return std::move (*this);
}
/** diagnostics */
operator string() const { return string(this->uic_); }
size_t coverDepth() const { return res_.depth; }
private:
/** establish a trivial anchorage and coverage, if possible.
* @note when the UICoord contains wildcards or is incomplete,
* a full resolution with backtracking is necessary to
* determine anchorage and coverage. The same is true
* when the pattern uses an elided element, since such
* an existentially quantified item must be interpolated.
*/
void
attempt_trivialResolution()
{
res_.anchor = query_.determineAnchor (this->uic_);
if (not uic_.isExplicit()) return;
res_.depth = query_.determineCoverage(this->uic_);
if (util::contains (this->uic_, UIC_ELIDED)) return; // existentially quantified
res_.isResolved = true;
}
/** @internal algorithm to resolve this UICoord path against the actual UI topology.
* @return true if total coverage is possible (by interpolating wildcards)
* @remark after invoking this function, res_.isResolved and possible coverage are set.
*/
bool pathResolution();
};
}}// namespace gui::interact
#endif /*GUI_INTERACT_UI_COORD_RESOLVER_H*/

View file

@ -0,0 +1,763 @@
/*
UI-COORD.hpp - generic topological location addressing scheme within the UI
Copyright (C) Lumiera.org
2017, 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 ui-coord.hpp
** A topological addressing scheme to designate structural locations within the UI.
** Contrary to screen pixel coordinates, we aim at a topological description of the UI structure.
** This foundation allows us
** - to refer to some "place" or "space" within the interface
** - to remember and return to such a location
** - to move a work focus structurally within the UI
** - to describe and configure the pattern view access and arrangement
**
** As starting point, we'll pick the notion of an access path within a hierarchical structure
** - the top-level window
** - the perspective used within that window
** - the panel within this window
** - a view group within the panel
** - plus a locally defined access path further down to the actual UI element
**
** @todo WIP 9/2017 first draft ////////////////////////////////////////////////////////////////////////////TICKET #1106 generic UI coordinate system
**
** @note UICoord is designed with immutability in mind; possibly we may decide to disallow assignment.
**
** # Coordinate Builder
**
** Since UI-Coordinates are conceived as immutable values, we provide a distinct _Builder notation_
** for the purpose of defining and assembling coordinate specifications. This notation also helps
** to keep such specifications more readable, since each component is "tagged" with its intended
** position within the tree, and thus the order of definition is irrelevant. Relying on that
** notation, it is also possible to write _sparse_ or _incomplete_ coordinate specifications,
** since each component _not explicitly given_ is assumed to be a _wildcard_ (`*`). E.g.
**
** UICoord::currentWindow()
** .path("a/b/c")
** .tab(3)
** .view("Timeline")
**
** is translated into the following UI-Coordinate specification
**
** UI::currentWindow[*]-*.Timeline.#3/a/b/c
**
** which indicates the currently active main window, disregarding the perspective, just some
** panel, holding the view "Timeline", selecting the third tab/group within this view and there
** to descend the path of local sub-components "c" nested within "b" nested within "a"
**
** @warning beware, all the builder functions offer a *move* reference!
** These builder expressions are intended for typical inline constant expressions
** according to the *builder pattern*; any other use, like assignment of the builder
** itself to a variable might be dangerous, since, after using the builder result
** _just once_, the result has been moved away. Even worse, this fact may remain
** concealed, since short Coordinate specs use inline storage, which the compiler
** may choose to copy instead of actually moving; only the heap allocated extension
** will typically be moved.
**
** @see UICoord_test
** @see id-scheme.hpp
** @see view-spec-dsl.hpp
** @see view-locator.hpp
*/
#ifndef GUI_INTERACT_UI_COORD_H
#define GUI_INTERACT_UI_COORD_H
#include "lib/error.hpp"
#include "lib/symbol.hpp"
#include "lib/path-array.hpp"
#include "lib/nocopy.hpp"
#include "lib/util.hpp"
#include <cstring>
#include <string>
#include <vector>
#include <utility>
namespace gui {
namespace interact {
namespace error = lumiera::error;
using std::string;
using lib::Literal;
using lib::Symbol;
using util::unConst;
using util::isnil;
using util::min;
enum {
UIC_INLINE_SIZE = 8
};
/* === predefined DSL symbols === */
enum UIPathElm
{
UIC_WINDOW,
UIC_PERSP,
UIC_PANEL,
UIC_VIEW,
UIC_TAB,
UIC_PATH
};
extern const Symbol UIC_CURRENT_WINDOW; ///< window spec to refer to the _current window_ @see view-locator.cpp
extern const Symbol UIC_FIRST_WINDOW; ///< window spec to refer to the _first window_ of the application
extern const Symbol UIC_ELIDED; ///< indicate that a component is elided or irrelevant here
/**
* Describe a location within the UI through structural/topological coordinates.
* A UICoord specification is a sequence of Literal tokens, elaborating a path descending
* through the hierarchy of UI elements down to the specific UI element to refer.
* @see UICoord_test
*/
class UICoord
: public lib::PathArray<UIC_INLINE_SIZE>
{
public:
/**
* UI-Coordinates can be created explicitly by specifying a sequence of Literal tokens,
* which will be used to initialise and then normalise the underlying PathArray.
* @warning Literal means _"literal"_ with guaranteed storage during the whole execution.
* @remarks - in case you need to construct some part, then use \ref Symbol to _intern_
* the resulting string into the global static SymbolTable.
* - usually the Builder API leads to more readable definitions,
* explicitly indicating the meaning of the coordinate's parts.
*/
template<typename...ARGS>
explicit
UICoord (ARGS&& ...args) : PathArray(std::forward<ARGS> (args)...) { }
UICoord (UICoord&&) = default;
UICoord (UICoord const&) = default;
UICoord (UICoord& o) : UICoord((UICoord const&)o) { }
UICoord& operator= (UICoord const&) = default;
UICoord& operator= (UICoord &&) = default;
/* === Builder API === */
class Builder;
/** shortcut to allow init from builder expression */
UICoord (Builder&& builder);
/** Builder: start definition of UI-Coordinates rooted in the `firstWindow` */
static Builder firstWindow();
/** Builder: start definition of UI-Coordinates rooted in the `currentWindow` */
static Builder currentWindow();
/** Builder: start definition of UI-Coordinates rooted in given window */
static Builder window (Literal windowID);
//----- convenience shortcuts to start a copy-builder....
Builder persp (Literal perspectiveID) const;
Builder panel (Literal panelID)const;
Builder view (Literal viewID) const;
Builder tab (Literal tabID) const;
Builder tab (uint tabIdx) const;
Builder noTab () const;
//----- convenience shortcuts to start mutation on a copy...
Builder path (Literal pathDefinition) const;
Builder append (Literal elmID) const;
Builder prepend (Literal elmID) const;
Builder rebuild() const;
/* === named component access === */
Literal getWindow() const { return accesComponent (UIC_WINDOW);}
Literal getPersp() const { return accesComponent (UIC_PERSP); }
Literal getPanel() const { return accesComponent (UIC_PANEL); }
Literal getView() const { return accesComponent (UIC_VIEW); }
Literal getTab() const { return accesComponent (UIC_TAB); }
/* === query functions === */
/**
* @remark _incomplete_ UI-Coordinates have some fragment of the path
* defined, but lacks the definition of an anchor point,
* i.e. it has no window ID
*/
bool
isIncomplete() const
{
return not empty()
and isnil (getWindow());
}
bool
isComplete() const
{
return not empty()
and not isnil (getWindow());
}
/**
* @remark an _explicit_ coordinate spec does not use wildcards
* and is anchored in a window spec
*/
bool
isExplicit() const
{
return isComplete()
and not util::contains (*this, Symbol::ANY);
}
bool
isPresent (size_t idx) const
{
Literal* elm = unConst(this)->getPosition(idx);
return not isnil(elm)
and *elm != Symbol::ANY;
}
bool
isWildcard (size_t idx) const
{
Literal* elm = unConst(this)->getPosition(idx);
return elm
and *elm == Symbol::ANY;
}
/**
* Check if this coordinate spec can be seen as an extension
* of the given parent coordinates and thus reaches further down
* towards specific UI elements in comparison to the parent path
* This constitutes a _partial order_, since some paths might just
* be totally unrelated to each other and thus not comparable.
* @note we tolerate (but not demand) expansion/interpolation
* of the given parent, i.e. parent may be incomplete
* or contain `'*'` placeholders.
* @todo 10/2017 have to verify suitability of this definition
*/
bool
isExtendedBelow (UICoord const& parent) const
{
size_t subSiz = this->size(),
parSiz = parent.size(),
idx = 0;
if (parSiz >= subSiz)
return false;
while (idx < parSiz
and ( (*this)[idx]== parent[idx]
or Symbol::ANY == parent[idx]
or isnil (parent[idx])))
++idx;
ENSURE (idx < subSiz);
return idx == parSiz;
} // meaning: this goes further down
/* === String representation === */
explicit
operator string() const
{
if (isnil (*this))
return "UI:?";
string component = getComp();
string path = getPath();
if (isnil (component))
return "UI:?/" + path;
if (isnil (path))
return "UI:" + component;
else
return "UI:" + component + "/" + path;
}
string
getComp() const
{
if (empty()) return "";
size_t end = min (size(), UIC_PATH);
size_t pos = indexOf (*begin());
if (pos >= end)
return ""; // empty or path information only
string buff;
buff.reserve(80);
if (0 < pos) // incomplete UI-Coordinates (not anchored)
buff += "?";
for ( ; pos<end; ++pos )
switch (pos) {
case UIC_WINDOW:
buff += getWindow();
break;
case UIC_PERSP:
buff += "["+getPersp()+"]";
break;
case UIC_PANEL:
buff += "-"+getPanel();
break;
case UIC_VIEW:
buff += "."+getView();
break;
case UIC_TAB:
if (UIC_ELIDED != getTab())
buff += "."+getTab();
break;
default:
NOTREACHED ("component index numbering broken");
}
return buff;
}
string
getPath() const
{
size_t siz = size();
if (siz <= UIC_PATH)
return ""; // no path information
string buff; // heuristic pre-allocation
buff.reserve (10 * (siz - UIC_PATH));
iterator elm = pathSeq();
if (isnil (*elm))
{ // irregular case : only a path fragment
elm = this->begin();
buff += "?/";
}
for ( ; elm; ++elm )
buff += *elm + "/";
// chop off last delimiter
size_t len = buff.length();
ASSERT (len >= 1);
buff.resize(len-1);
return buff;
}
/** iterative access to the path sequence section */
iterator
pathSeq() const
{
return size()<= UIC_PATH? end()
: iterator{this, unConst(this)->getPosition(UIC_PATH)};
}
private:
/** @note Builder allowed to manipulate stored data */
friend class Builder;
size_t
findStartIdx() const
{
REQUIRE (not empty());
return indexOf (*begin());
}
Literal
accesComponent (UIPathElm idx) const
{
Literal* elm = unConst(this)->getPosition(idx);
return elm? *elm : Symbol::EMPTY;
}
void
setComponent (size_t idx, Literal newContent)
{
Literal* storage = expandPosition (idx);
setContent (storage, newContent);
}
/** replace / overwrite existing content starting at given index.
* @param idx where to start adding content; storage will be expanded to accommodate
* @param newContent either a single element, or several elements delimited by `'/'`
* @note - a path sequence will be split at `'/'` and the components _interned_
* - any excess elements will be cleared
* @warning need to invoke PathArray::normalise() afterwards
*/
void
setTailSequence (size_t idx, Literal newContent)
{
std::vector<Literal> elms;
if (not isnil (newContent))
{
if (not std::strchr (newContent, '/'))
{
// single element: just place it as-is
// and remove any further content behind
elms.emplace_back (newContent);
}
else
{ // it is actually a sequence of elements,
// which need to be split first, and then
// interned into the global symbol table
string sequence{newContent};
size_t pos = 0;
size_t last = 0;
while (string::npos != (last = sequence.find ('/', pos)))
{
elms.emplace_back (Symbol{sequence.substr(pos, last - pos)});
pos = last + 1; // delimiter stripped
}
sequence = sequence.substr(pos);
if (not isnil (sequence))
elms.emplace_back (Symbol{sequence});
} }
setTailSequence (idx, elms);
}
/** replace the existing path information with the given elements
* @note - storage will possibly be expanded to accommodate
* - the individual path elements are required to persist
* - any excess elements will be cleared
* - the pathElms can be _empty_ in which case just
* any content starting from `idx` will be cleared
* @warning need to invoke PathArray::normalise() afterwards
*/
void
setTailSequence (size_t idx, std::vector<Literal>& pathElms)
{
size_t cnt = pathElms.size();
expandPosition (idx + cnt); // preallocate
for (size_t i=0 ; i < cnt; ++i)
setContent (expandPosition(idx + i), pathElms[i]);
size_t end = size();
for (size_t i = idx+cnt; i<end; ++i)
setContent (expandPosition(i), nullptr);
}
public: /* ===== relational operators : equality and partial order ===== */
friend bool
operator== (UICoord const& l, UICoord const& r)
{
return static_cast<PathArray const&> (l) == static_cast<PathArray const&> (r);
}
friend bool
operator< (UICoord const& l, UICoord const& r)
{
return l.isExtendedBelow (r);
}
friend bool operator> (UICoord const& l, UICoord const& r) { return (r < l); }
friend bool operator<= (UICoord const& l, UICoord const& r) { return (l < r) or (l == r); }
friend bool operator>= (UICoord const& l, UICoord const& r) { return (r < l) or (l == r); }
friend bool operator!= (UICoord const& l, UICoord const& r) { return not (l == r); }
};
/* === Builder API === */
class LocationClause;
class UICoord::Builder
: util::MoveOnly
{
protected:
UICoord uic_;
template<typename...ARGS>
explicit
Builder (ARGS&& ...args) : uic_{std::forward<ARGS> (args)...} { }
Builder (UICoord && anonRef) : uic_{std::move(anonRef)} { }
Builder (UICoord const& base) : uic_{base} { }
/** builder instances created by UICoord */
friend class UICoord;
public:
/** @remark moving a builder instance is acceptable */
Builder (Builder &&) = default;
size_t size() const { return uic_.size(); }
bool empty() const { return uic_.empty();}
/* == Builder functions == */
/** change UI coordinate spec to define it to be rooted within the given window
* @note this function allows to _undefine_ the window, thus creating an incomplete spec */
Builder&&
window (Literal windowID)
{
uic_.setComponent (UIC_WINDOW, windowID);
return std::move (*this);
}
/** augment UI coordinates to mandate a specific perspective to be active within the window */
Builder&&
persp (Literal perspectiveID)
{
uic_.setComponent (UIC_PERSP, perspectiveID);
return std::move (*this);
}
/** augment UI coordinates to indicate a specific view to be used */
Builder&&
panel (Literal panelID)
{
uic_.setComponent (UIC_PANEL, panelID);
return std::move (*this);
}
/** augment UI coordinates to indicate a specific view to be used */
Builder&&
view (Literal viewID)
{
uic_.setComponent (UIC_VIEW, viewID);
return std::move (*this);
}
/** augment UI coordinates to indicate a specific tab within the view" */
Builder&&
tab (Literal tabID)
{
uic_.setComponent (UIC_TAB, tabID);
return std::move (*this);
}
/** augment UI coordinates to indicate a tab specified by index number */
Builder&&
tab (uint tabIdx)
{
uic_.setComponent (UIC_TAB, Symbol{"#"+util::toString (tabIdx)});
return std::move (*this);
}
/** augment UI coordinates to indicate that no tab specification is necessary
* @remarks typically this happens when a panel just holds a simple view */
Builder&&
noTab()
{
uic_.setComponent (UIC_TAB, UIC_ELIDED);
return std::move (*this);
}
/** augment UI coordinates by appending a further component at the end.
* @note the element might define a sequence of components separated by `'/'`,
* in which case several elements will be appended.
*/
Builder&&
append (Literal elm)
{
if (not isnil(elm))
uic_.setTailSequence (uic_.size(), elm);
return std::move (*this);
}
/** augment partially defined UI coordinates by extending them towards the root */
Builder&&
prepend (Literal elmID)
{
if (not uic_.isIncomplete())
throw error::Logic ("Attempt to prepend "+elmID
+" to the complete rooted path "+string(uic_));
uic_.setComponent (uic_.findStartIdx() - 1, elmID);
return std::move (*this);
}
/**
* augment UI coordinates to define a complete local path
* @param pathDef a path, possibly with multiple components separated by `'/'`
* @note any existing path definition is completely replaced by the new path
*/
Builder&&
path (Literal pathDef)
{
uic_.setTailSequence (UIC_PATH, pathDef);
return std::move (*this);
}
/** possibly shorten this path specification to a limited depth */
Builder&&
truncateTo (size_t depth)
{
uic_.truncateTo (depth);
return std::move (*this);
}
/** support for use in the [ViewSpec-DSL](\ref ui-location-solver.hpp) */
operator LocationClause();
LocationClause create();
protected:
Builder&&
normalise()
{
uic_.normalise();
return std::move (*this);
}
Builder&&
overwrite (size_t depth, Literal newSpec)
{
Literal* storage = uic_.expandPosition (depth);
uic_.setContent (storage, newSpec);
return std::move (*this);
}
};
/**
* @note this ctor is used to "fix" and normalise
* the contents established in the Builder thus far.
*/
inline
UICoord::UICoord (Builder&& builder)
: UICoord{std::move (builder.uic_)}
{
PathArray::normalise();
}
/** @return a minimally defined Builder, allowing to define further parts;
* to finish the definition, cast / store it into UICoord, which itself is immutable.
*/
inline UICoord::Builder
UICoord::currentWindow()
{
return window (UIC_CURRENT_WINDOW);
}
inline UICoord::Builder
UICoord::firstWindow()
{
return window (UIC_FIRST_WINDOW);
}
/** @return a Builder with just the windowID defined */
inline UICoord::Builder
UICoord::window (Literal windowID)
{
return Builder{windowID};
}
/** @return a Builder holding a clone copy of the original UICoord,
* with the perspective information set to a new value.
* @remarks This Builder can then be used do set further parts
* independently of the original. When done, store it
* as new UICoord object. To achieve real mutation,
* assign it to the original variable. */
inline UICoord::Builder
UICoord::persp (Literal perspectiveID) const
{
return Builder(*this).persp (perspectiveID);
}
inline UICoord::Builder
UICoord::panel (Literal panelID) const
{
return Builder(*this).panel (panelID);
}
inline UICoord::Builder
UICoord::view (Literal viewID) const
{
return Builder(*this).view (viewID);
}
inline UICoord::Builder
UICoord::tab (Literal tabID) const
{
return Builder(*this).tab (tabID);
}
inline UICoord::Builder
UICoord::tab (uint tabIdx) const
{
return Builder(*this).tab (tabIdx);
}
inline UICoord::Builder
UICoord::noTab () const
{
return Builder(*this).noTab();
}
/**
* convenience builder function so set a full path definition
* @note the given path string will be split at `'/'` and the
* resulting components will be stored/retrieved as Symbol
*/
inline UICoord::Builder
UICoord::path (Literal pathDefinition) const
{
return Builder(*this).path (pathDefinition);
}
inline UICoord::Builder
UICoord::append (Literal elmID) const
{
return Builder(*this).append (elmID);
}
inline UICoord::Builder
UICoord::prepend (Literal elmID) const
{
return Builder(*this).prepend (elmID);
}
inline UICoord::Builder
UICoord::rebuild () const
{
return Builder(*this);
}
}}// namespace gui::interact
#endif /*GUI_INTERACT_UI_COORD_H*/

View file

@ -0,0 +1,327 @@
/*
UI-LOCATION-SOLVER.hpp - decide upon a possible location for some UI component
Copyright (C) Lumiera.org
2018, 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 ui-location-solver.hpp
** A solver to match incomplete coordinate specifications against the actual UI topology.
** Within the Lumiera UI, a _component view_ is typically _created or retrieved_ to live at some position
** within the tree-like topology of the interface. Such happens as a consequence of interaction or other
** events, and the logic as to where and how to place a new UI element shall not be intermingled with the
** actual event handling code. Rather, the ViewLocator, as a service related to the InteractionDirector,
** can be invoked to draw on some default configuration plus the actual UI topology present at this time.
**
** ## Pattern matching against the actual UI-tree
**
** This Location solver is based on the pattern matching algorithm [embedded](\ref ui-coord-resolver.cpp)
** within the UICoordResolver component. This mechanism allows to treat (typically) incomplete coordinate
** specifications as rules for locating an element. Several such coordinate _clauses_ can be combined into
** a _disjunctive_ LocationRule, which is evaluated by matching the clauses one by one, in given order,
** against the currently existing UI tree (topology). Each clause is evaluated individually from scratch
** (there is no common variable binding); the first clause to produce a successful match is used as
** solution -- with any placeholders replaced by the actually matching UI elements.
**
** ## Default View location configuration DSL
**
** Whenever a new UI element of a given kind is to be created, we query a standard location configuration
** to determine it's actual location within the interface. This standard configuration is known as
** ["ViewSpec DSL"](\ref id-scheme.hpp) and hard-wired into the UI code. Based on the aforementioned
** pattern matching rules, it allows to express placement rules dependent on the already existing UI.
** There are two kinds of location clauses
** - the standard rules describe an element required to exist. Typically this is the _parent element_
** of the UI widget in question. But it is also possible to write clauses directly mentioning this
** element, in which case such an element must already exist in the UI and will be retrieved as result.
** - the more relaxed _create clauses_ describe a new location / path within the UI-tree, meaning that
** any (parent) elements not yet present are to be created. A _create clause_ is defined within the
** DSL by ending a UI coordinate specification with the term `.create()`. It may still be incomplete
** (i.e. contain wildcards), which means, that the first explicitly given element after (below) the
** wildcards must exist in the tree, to allow for an unambiguous pattern match. Otherwise, for
** creating a new path completely from scratch, all elements have to be given explicitly.
** As a minimum requirement, each LocationRule should be concluded with such a "catch-all" explicit
** create clause, which describes the standard location of the element in question.
**
** @see UILocationSolver_test
** @see ViewSpecDSL_test
** @see UICoordResolver
** @see view-locator.hpp
*/
#ifndef GUI_INTERACT_UI_LOCATION_SOLVER_H
#define GUI_INTERACT_UI_LOCATION_SOLVER_H
#include "lib/error.hpp"
#include "lib/symbol.hpp"
#include "lib/format-util.hpp"
#include "gui/interact/ui-coord.hpp"
#include "gui/interact/ui-coord-resolver.hpp"
#include "lib/nocopy.hpp"
#include <utility>
#include <vector>
#include <string>
namespace gui {
namespace interact {
using lib::Literal;
using std::string;
using std::move;
class LocationQuery;
using LocationQueryAccess = std::function<LocationQuery&()>;
/**
* A single location specification to be matched and fulfilled.
* It is created from a -- typically incomplete -- UICoord spec,
* which in turn can be built through a DSL notation.
* @todo maybe add a flag to require the current query goal to exist in tree //////////////////////////////TICKET #1130
*/
struct LocationClause
: util::NonCopyable
{
UICoord pattern;
bool createParents;
LocationClause (UICoord && locationPattern, bool allowCreate =false)
: pattern{move (locationPattern)}
, createParents{allowCreate}
{ }
LocationClause (LocationClause && rr)
: pattern{move (rr.pattern)}
, createParents{rr.createParents}
{ }
operator string() const;
};
/**
* A rule to determine some location by matching against the UI-tree.
* It is comprised of one or several disjunctive [clauses](\ref LocationClause),
* each of which is a coordinate pattern to be matched. The clauses are tried
* in order and the first successfully matched clause wins.
*/
class LocationRule
: util::NonCopyable
{
using Clauses = std::vector<LocationClause>;
Clauses clauses_;
public:
LocationRule (LocationClause && firstRule)
: clauses_{}
{
this->append (move (firstRule));
}
LocationRule (LocationRule && rr)
: clauses_{move (rr.clauses_)}
{ }
LocationRule&&
append (LocationClause && furtherRule)
{
clauses_.emplace_back (move (furtherRule));
return move (*this);
}
using iterator = lib::RangeIter<Clauses::const_iterator>;
iterator begin() const { return iterator{clauses_.begin(), clauses_.end()}; }
iterator end() const { return iterator(); }
operator string() const;
};
/* ==== Support of UI-Coordinate notation within the ViewSpec-DSL ==== */
/** interprets the current (inline) contents of an UICoord builder expression
* as a standard LocationClause, which has the meaning of "when an element
* exists at the location XYZ in the real UI"
* @warning like all the UICoord::Builder functions, the contents are moved.
* Thus, after using this conversion path _once_, the Builder _is defunct_
*/
inline
UICoord::Builder::operator LocationClause()
{
return LocationClause{move(*this), false};
}
/** interprets the current (inline) builder contents as _create clause_,
* which has the meaning "create a new element XYZ when possible" */
inline LocationClause
UICoord::Builder::create()
{
return LocationClause{move(*this), true};
}
/** DSL operator to assemble a sequence of clauses.
* Introduced solely for the purpose of writing location specifications within the
* [ViewSpec-DSL](\ref id-scheme.hpp), this operator acts on several UI-Coordinate specs
* to create a sequence of clauses, to be checked against the currently existing UI topology,
* in the given order, ranging from more specific to more general patterns.
*/
inline LocationRule
operator or (UICoord::Builder && firstRule, UICoord secondRule)
{
return LocationRule{move (firstRule)}
.append (move (secondRule));
}
inline LocationRule&&
operator or (LocationRule && ruleSet, UICoord furtherRule)
{
ruleSet.append (move (furtherRule));
return move(ruleSet);
}
/* === diagnostics === */
inline
LocationClause::operator string() const
{
return string{pattern}
+ (createParents? " create!":"");
}
inline
LocationRule::operator string() const
{
return "=~\t.. "
+ util::join(clauses_, "\n\tOR ");
}
/**
* Service to determine the location of an UI component view.
* @see LocationRule
* @see UILocationSolver_test::simple_usage_example()
*/
class UILocationSolver
: util::NonCopyable
{
LocationQueryAccess getLocationQuery;
public:
explicit
UILocationSolver (LocationQueryAccess accessor)
: getLocationQuery{accessor}
{ }
explicit
UILocationSolver (LocationQuery& locationQueryService)
: getLocationQuery{[&]() -> LocationQuery& { return locationQueryService; }}
{ }
/**
* Solve for a location according to the given location rule.
* @param depth desired kind of UI element (and thus the depth in the UI topology tree)
* @param elementType designator of the specific element to be created at that level
* @return an explicit location, resolved against the current UI topology. May be empty
* @remarks the returned path is either empty (no solution exists), or it is "partially covered"
* by the existing UI; here, the "covered" part are the already existing UI elements,
* while the remaining, uncovered extension describes additional elements to be created.
* When the resolution process found an already existing UI element, the returned path
* is completely covered. The degree of coverage of a path can be found out with the
* help of a UICoordResolver, which also needs a LocationQuery (service) to discover
* the currently existing UI topology.
*/
UICoord
solve (LocationRule const& rule, size_t depth, Literal elementTypeID)
{
for (auto& clause : rule)
{
// Clauses which do not at least describe an element at parent level
// will never lead to a solution and can thus be skipped
if (depth+1 < clause.pattern.size()
or depth > clause.pattern.size())
continue;
// try to solve the current Clause by matching against real UI topology
UICoordResolver resolver{clause.pattern, getLocationQuery()};
preprocess (resolver, clause);
resolver.coverPartially(); // now either holds a solution or is empty
if (not isnil(resolver) // Solution found!
and (clause.createParents // The "create" case requires only some part to exist,
or resolver.isCoveredTotally())) // while in the default case we demand complete coverage
{
if (depth == clause.pattern.size())
// append ID of the new element to be created
// unless it's already there (and thus exists)
resolver.append (elementTypeID);
return move (resolver);
// use the first suitable solution and exit
}
else
if (clause.createParents and clause.pattern.isExplicit())
// allow creation of a totally new path from scratch
// as long as it is complete and explicitly given
{
if (depth == clause.pattern.size())
return clause.pattern.append (elementTypeID);
else
return clause.pattern;
}
}
//all clauses tried without success...
return UICoord();
}
private:
/** perform adjustments on the current pattern to support some very specific situations
* - when we want to create a new panel (or re-use an existing of the same name), on top
* of an existing (but irrelevant) perspective, we mark this perspective as "just there".
* This allows to get a new path as solution, which is just covered up to and including
* that perspective; without special treatment, such a match would otherwise be rejected.
*/
void
preprocess (UICoordResolver& builder, LocationClause const& clause)
{
if (clause.createParents
and clause.pattern.isComplete())
{
builder.existentiallyQuantify (UIC_PERSP);
}
}
};
}}// namespace gui::interact
#endif /*GUI_INTERACT_UI_LOCATION_SOLVER_H*/

View file

@ -0,0 +1,111 @@
/*
ViewLocator - access and allocation of UI component views
Copyright (C) Lumiera.org
2017, 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 view-locator.cpp
** Implementation details of a machinery to allocate UI global component views.
** Essentially this compilation unit hosts the implementation backing the [ViewSpec-DSL](view-spec-dsl.hpp)
** and the [Location Solver](ui-location-solver.hpp) to drive evaluation of the standard location defaults,
** which are [hard-wired into the UI](\ref id-scheme.hpp). For this integration of various facilities and
** frameworks we rely on actual definitions for the DSL-tokens, which themselves are functors and tied by
** lambda-binding into the implementation side of ViewLocator; which both acts as a front-end to access
** the service to "get or create" a component and also holds the UILocationSolver component to perform
** the actual location solving mechanism. The DSL thus sits between both poles, since _resolving a view_
** draws upon the default locating configuration encoded as DSL tokens, which in turn call back into
** the implementation services within ViewLocator to resolve those partial location specifications
** against the currently existing UI topology and then to access or create new UI components as
** laid out through those definitions.
**
** @todo WIP 9/2017 early draft ////////////////////////////////////////////////////////////TICKET #1104
*/
#include "gui/gtk-base.hpp"
#include "gui/interact/view-locator.hpp"
#include "gui/interact/view-spec-dsl.hpp"
#include "gui/interact/ui-location-solver.hpp"
#include "gui/ctrl/panel-locator.hpp"
#include "gui/model/element-access.hpp"
#include "gui/interact/ui-coord-resolver.hpp"
#include "lib/depend.hpp"
#include "lib/symbol.hpp"
//#include "lib/util.hpp"
//using util::cStr;
//using util::isnil;
using lib::Symbol;
//using gui::ctrl::PanelLocator;
//using gui::ctrl::WindowLocator;
using gui::model::ElementAccess;
namespace gui {
namespace error = lumiera::error;
/* ==== definitions and concrete bindings for the View-Spec-DSL ==== */
namespace idi { // Namespace for the actual ViewSpec DSL tokens
AllocSpec<uint> limitAllocation =[&](UICoord target, uint limit)
{
UNIMPLEMENTED ("Actual DSL token to cause a (limited) view allocation");
return target; /////////////////////////////////////////////////////////TICKET #1129 : need at least a working draft implementation here
};
}
namespace interact {
const Symbol UIC_CURRENT_WINDOW{"currentWindow"};
const Symbol UIC_FIRST_WINDOW {"firstWindow"};
const Symbol UIC_ELIDED {"."};
ViewLocator::ViewLocator ()
: locResolver_{LocationQuery::service}
{ }
// dtors via smart-ptr invoked from here...
ViewLocator::~ViewLocator() { }
/* === Service accessors within global context === */
// PanelLocator&
// ViewLocator::panelLocator()
// {
// return windowLoc_.locatePanel();
// }
/** */
}}// namespace gui::interact

View file

@ -0,0 +1,134 @@
/*
VIEW-LOCATOR.hpp - access and allocation of UI component views
Copyright (C) Lumiera.org
2017, 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 view-locator.hpp
** Access and allocation of UI component views.
** Within the Lumiera UI, a _component view_ is a building block to deal with some component
** of relevance to _»the model«. As such, all component views exhibit some distinctive traits:
** - they conform to a built-in fixed list of view types, each of which is unique and dedicated
** to a very specific purpose: *Timeline*, *Viewer*, (Asset)*Bin*, *Infobox*, *Playcontrol*,...
** - each component view has a distinguishable identity and is connected to and addressable
** through the [UI-Bus](\ref ui-bus.hpp).
** - it can be hosted only at a dedicated location within one or several specific [docking panels](\ref panel.hpp).
** - multiplicity (one, one per window, many) depends on the type of view and needs to be managed.
** - such a view is not just _created_ -- rather it needs to be _allocated_
**
** ## LocationQuery and the View-Spec DSL
**
** Implementation wise, there is a tight connection between the ViewLocator service, the Navigator service
** and the configuration how and where to create standard view elements (the "View-Spec DSL"). Several interactions
** require the UI to access or create some specific view widget by side-effect. And the logic how and where to create
** views can be quite intricate and is closely related to global, overarching topics of interaction design. Lumiera
** thus relies on a framework for default configuration, and a matching mechanism to determine the location and
** creation modes of such views. This matching mechanism in turn requires an abstracted view on the UI seen as
** a topological tree structure of relevant entities (windows, panels, views,...) -- which is basically the
** service provided by the Navigator; yet this Navigator service can be abstracted into the gui::interact::LocationQuery
** API, and this abstraction allows to keep all the intricacies of navigating concrete UI widgets confined within the
** implementation of the Navigator service.
**
** @todo WIP 9/2017 early draft ////////////////////////////////////////////////////////////TICKET #1104
**
** @see interaction-director.hpp
** @see panel-locator.hpp
*/
#ifndef GUI_INTERACT_VIEW_LOCATOR_H
#define GUI_INTERACT_VIEW_LOCATOR_H
#include "lib/depend-inject.hpp"
#include "gui/id-scheme.hpp"
#include "gui/interact/view-spec-dsl.hpp"
#include "gui/model/element-access.hpp"
#include "lib/idi/genfunc.hpp"
#include "lib/symbol.hpp"
#include "lib/nocopy.hpp"
#include <functional>
#include <string>
#include <memory>
namespace gui {
namespace ctrl{
class PanelLocator;
}
namespace interact {
using std::unique_ptr;
using std::string;
using lib::Symbol;
class UILocationSolver;
/**
* Access or allocate a UI component view
*
* @todo initial draft as of 9/2017 -- actual implementation need to be filled in
*/
class ViewLocator
: ::util::NonCopyable
{
using Service_LocationSolver = lib::DependInject<UILocationSolver>::ServiceInstance<>;
lib::Depend<model::ElementAccess> elementAccess;
Service_LocationSolver locResolver_;
public:
ViewLocator ();
~ViewLocator();
/**
* Access and possibly create _just some_ component view of the desired type
*/
template<class V>
V& get();
private:
/* === accessors to sibling global services === */
};
template<class V>
inline V&
ViewLocator::get()
{
using ViewSpec = idi::Descriptor<V>;
ViewSpec viewSpec; /////////////////////////////////////////////////////////TICKET #1129 : can't create instances all the time. Need some kind of Factory
Symbol viewID{lib::idi::typeSymbol<V>()};
UICoord targetLocation = viewSpec.locate(viewID);
UICoord realView = viewSpec.alloc (targetLocation);
return elementAccess().access<V> (realView);
}
}}// namespace gui::interact
#endif /*GUI_INTERACT_VIEW_LOCATOR_H*/

View file

@ -0,0 +1,265 @@
/*
VIEW-SPEC-DSL.hpp - configuration of view allocation patterns
Copyright (C) Lumiera.org
2017, 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 view-spec-dsl.hpp
** A framework for configuration of view access and allocation patterns.
** Component views are building blocks of the Lumiera UI, and may, depending on their
** type, be instantiated or allocated according to specific rules and patterns. And these
** might vary in accordance to the desired working style. To give a typical example, at times
** it might be mandatory to use a single, external output for all kind of media playback, while
** other users prefer the classical editing application layout with two media viewers side by
** side. And yet another working style would be to use a stack of media viewers allocated on
** demand in MRU-fashion.
**
** To specify those standard behaviour patterns, we provide a small internal DSL to spell out
** the default configuration in a (hopefully) self explanatory way.
** @todo as of 9/2017, we confirm the intention to open this configuration DSL for some kind
** of user provided flexible configuration of screen layouts eventually; yet right now,
** only the foundation for this flexible configuration is provided while the defaults
** are to be compiled into the UI as hard wired constants.
**
**
** # Allocation of UI component views
**
** Within this context, _Allocation_ means...
** - to constitute the desired element's identity
** - to consider multiplicity and possibly retrieve an existing instance
** - to determine the hosting location
** - possibly to instantiate and register a new instance
** - and finally to configure that instance for the desired role
**
** ## View access
**
** The global access point to component views is the ViewLocator within InteractionDirector,
** which exposes a generic access- and management API to
** - get (possibly create) some view of given type
** - get (possibly create) a view with specific identity
** - destroy a specific view
** Together these operations expose a strictly typed direct reference to the respective view component
** and thus allow to manage them like child objects. Operating on top of these primitive operations,
** the _configuration of view access patterns_ creates a flexible binding layer, which isolates the
** users of component views (typically other parts of the UI) from the actual mechanics of locating.
** While the client just retrieves a view instance, a dedicated _allocation logic_ ensures this view
** instance is actually placed at the desired place within the UI, and manages active view instances.
**
** ## Configuration DSL
**
** Access to component views typically happens through the ViewLocator provided by the InteractionDirector.
** The implementation of view access in turn redirects access through the configured patterns.
** - at [definition site](\ref id-scheme.hpp), explicit specialisations are given for the relevant types
** of component view
** - each of those general view configurations
** * defines the multiplicity allowed for this kind of view
** * defines how to locate this view
** ... and that location definition is given as a list of alternatives in order of precedence.
** That is, the system tries each pattern of location and uses the first one applicable.
**
** Actually, the definitions and tokens used within the configuration DSL are implemented as _functors._
** At implementation level, the basic specification tokens are bound to the corresponding operations
** within ViewLocator and PanelLocator. Thus, the "value" retrieved from the configuration is actually
** a functor, which provides the desired behaviour pattern
** - the *locate* spec is evaluated to yield [user interface coordinates](\ref UICoord) representing the
** desired location of the view
** - the *allocate* spec is then evaluated as a second step, while providing the coordinates from first step.
** This results in calling a dedicated _view allocator_ to create a new view or re-use an existing one
** as appropriate.
**
** @note the actual binding for the DSL tokens can be found in the [ViewLocator implementation](view-locator.cpp)
**
** @see ViewSpecDSL_test
** @see id-scheme.hpp
** @see view-locator.hpp
** @see view-locator.cpp
** @see interaction-director.hpp
*/
#ifndef GUI_INTERACT_VIEW_SPEC_DSL_H
#define GUI_INTERACT_VIEW_SPEC_DSL_H
#include "lib/error.hpp"
#include "lib/symbol.hpp"
#include "lib/depend.hpp"
#include "lib/meta/function.hpp"
#include "lib/meta/tuple-helper.hpp"
#include "lib/meta/function-closure.hpp"
#include "gui/interact/ui-location-solver.hpp"
#include "gui/interact/ui-coord.hpp"
#include <functional>
#include <utility>
#include <string>
namespace gui {
namespace interact {
using std::forward;
using std::string;
/**
* Locator is a functor to resolve to a topological location in the UI-tree.
* It takes a _viewID_ as argument, which actually is more of a typeID to designate
* the kind of view or UI widget, which shall be attached at or retrieved from the
* location resolved through the LocationRule. The latter is essentially what is
* embedded within the Locator functor.
*/
using Locator = std::function<UICoord(Literal)>;
/**
* A specification to describe the desired location of a component view within the Lumiera UI.
* LocatorSpec is basically a set of [UI coordinates](\ref UICoord), with the additional possibility
* of specifying several alternatives, with the intention to pick the first applicable one.
* @tparam depth the level in the tree addressed by this locator
* @remarks Locator is built from a DSL expression, which is basically a UICoord::Builder.
* This coordinate spec describes a sequence of several places where to locate
* the UI-Element in question. The template parameter clarifies if we're talking
* about windows here, or panels or views. The latter is the [default case](\ref ViewSpec).
*/
template<size_t depth>
class LocatorSpec
: public Locator
{
LocationRule rules_;
public:
LocatorSpec(LocationRule && ruleToDetermineLocation)
: Locator([&](Literal componentID) -> UICoord
{
lib::Depend<UILocationSolver> locationSolver;
return locationSolver().solve (rules_, depth, componentID);
})
, rules_{move (ruleToDetermineLocation)}
{ }
/** shortcut to allow initialisation from UI-Coordinate builder expression */
LocatorSpec(UICoord::Builder&& simpleLocationSpec)
: LocatorSpec{
LocationRule{
LocationClause{
UICoord{std::move (simpleLocationSpec)}}}}
{ }
operator string() const
{
return string{rules_};
}
};
/**
* A specification to describe the desired location of a component view within the Lumiera UI.
* ViewSpec is basically a set of [UI coordinates](\ref UICoord), with the additional possibility
* of specifying several alternatives, with the intention to pick the first applicable one.
*/
using ViewSpec = LocatorSpec<UIC_VIEW>;
/**
* Allocator is a functor to resolve a given, desired location of a view within the UI, resulting
* in creation or allocation of the view. The result of this invocation are the UI coordinates
* of an existing or newly created view.
*/
using Allocator = std::function<UICoord(UICoord)>;
/**
* A specification to describe the strategy for allocating (placing, retrieving) a component view.
* On a DSL-technical level, AllocSpec is a _function generator_: it produces Allocator entities,
* which in turn are functions to perform the actual allocation.
* @note AllocSpec relies on a specific *convention* how to specify the actual allocation operation:
* - the operation takes one main argument and additional parameters
* - its first argument is the _work triggering argument_, namely the concrete UI coordinates
* passed to the Allocator, requesting to create or retrieve or claim the view at that location
* - the additional argument(s) serves for parametrisation or specialisation of the strategy;
* they will be "baked" into the generated allocator.
* @remark to give a typical example: an allocation operation might want to limit the number of
* generated views per window and take this instance limit as additional parameter. But for the DSL
* we want a allocation spec of the form `limitPerWindow(cnt)` -- which is a function accepting the
* concrete limit and producing the actual allocator function `UICoord -> UICoord`, which has this
* actual limit "baked in".
*/
template<typename... ARGS>
class AllocSpec
: public std::function<Allocator(ARGS&&...)>
{
/** @internal analyse and dissect the given allocation operation
* and fabricate a builder to accept the additional arguments
* and produce a partially closed allocation functor
* @note the generated lambda captures by value */
template<class FUN>
static auto
buildPartialApplicator (FUN&& fun)
{
using lib::meta::_Fun;
using lib::meta::Split;
using lib::meta::Tuple;
using lib::meta::Types;
using lib::meta::func::_Sig;
using lib::meta::func::PApply;
typedef typename _Fun<FUN>::Ret Ret;
typedef typename _Fun<FUN>::Args Args;
typedef typename Split<Args>::Head Arg1;
typedef typename Split<Args>::Tail FurtherArgs;
static_assert (std::is_convertible<UICoord, Arg1>::value,
"Allocator function must accept UICoordinates (where to create/locate) as first argument");
static_assert (std::is_convertible<Ret, UICoord>::value,
"Allocator function must produce UICoordinates (of the actually allocated UI element)");
static_assert (std::is_convertible<FurtherArgs, Types<ARGS...>>::value,
"Additional parameters of the allocator function must match the AllocSpec<ARGS> template parameters");
using ArgTuple = Tuple<FurtherArgs>;
return [=](auto&&... args) -> Allocator
{
ArgTuple params {forward<decltype(args)> (args)...};
return PApply<FUN,FurtherArgs>::bindBack (fun, params);
};
}
public:
template<class FUN>
AllocSpec(FUN&& fun)
: std::function<Allocator(ARGS&&...)> {buildPartialApplicator (forward<FUN> (fun))}
{ }
};
} // namespace interact
namespace idi {
//bring definitions into scope for DSL use...
using interact::ViewSpec;
using interact::Allocator;
using interact::AllocSpec;
extern AllocSpec<uint> limitAllocation;
}}// namespace gui::idi
#endif /*GUI_INTERACT_VIEW_SPEC_DSL_H*/

View file

@ -96,7 +96,7 @@ namespace interact {
dialog.set_website(Config::get (KEY_WEBSITE));
dialog.set_authors(authorsList);
WorkspaceWindow& currentWindow = globalCtx_.windowList_.findActiveWindow();
WorkspaceWindow& currentWindow = globalCtx_.windowLoc_.findActiveWindow();
dialog.set_transient_for (currentWindow);
// Show the about dialog

View file

@ -43,8 +43,8 @@
#define GUI_INTERACT_WIZARD_H
#include "gui/gtk-base.hpp"
#include "lib/nocopy.hpp"
#include <boost/noncopyable.hpp>
//#include <string>
//#include <memory>
@ -69,7 +69,7 @@ namespace interact {
* @todo initial draft as of 2/2017 -- actual implementation has to be filled in
*/
class Wizard
: boost::noncopyable
: util::NonCopyable
{
ctrl::GlobalCtx& globalCtx_;

View file

@ -41,8 +41,8 @@
#define GUI_INTERACT_WORK_SITE_TRAIL_H
#include "gui/gtk-base.hpp"
#include "lib/nocopy.hpp"
#include <boost/noncopyable.hpp>
//#include <string>
//#include <memory>
@ -63,7 +63,7 @@ namespace interact {
* @todo initial draft as of 2/2017 -- actual implementation has to be filled in
*/
class WorkSiteTrail
: boost::noncopyable
: util::NonCopyable
{
public:

View file

@ -46,8 +46,8 @@
#define GUI_INTERACT_WORK_SITE_H
#include "gui/gtk-base.hpp"
#include "lib/nocopy.hpp"
#include <boost/noncopyable.hpp>
//#include <string>
//#include <memory>
@ -70,7 +70,7 @@ namespace interact {
* @todo initial draft as of 2/2017 -- actual implementation has to be filled in
*/
class WorkSite
: boost::noncopyable
: util::NonCopyable
{
public:

View file

@ -39,10 +39,10 @@
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
//#include "lib/symbol.hpp"
//#include "lib/util.hpp"
#include <boost/noncopyable.hpp>
//#include <string>
@ -64,7 +64,7 @@ namespace model {
* @see NA_test
*/
class Diagnostics
: boost::noncopyable
: util::NonCopyable
{
public:

View file

@ -0,0 +1,205 @@
/*
ELEMENT-ACCESS.hpp - access to generic elements in the UI
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 element-access.hpp
** Interface to discover and access raw UI elements in a cross cutting way.
** We have several orthogonal identification and access schemes within the UI.
** A naively written UI application just attaches the core logic below some widgets and
** controllers -- not only does this lead to a hard to maintain codebase, this approach
** is even outright impossible for Lumiera, since the core is able to run standalone and
** the UI is loaded as plug-in, which places us into the situation to connect a self
** contained core with a self contained UI. This is a binding, which, as a sideline, also
** generates a control structure of its own. An another kind of generic access happens
** when we _navigate_ the topological UI structure for focus management.
**
** This interface defines an abstract service to translate a generic element designation
** into a (language level) access to internal structures of the UI toolkit (GTK in our case).
** This access to low-level structure proceeds in two stages:
** - navigate down the UI topology. Optionally, this may involve a mutation (create element)
** - evaluate the result (found, not found, element created) and access the target,
** possibly with conversion (which might fail)
**
** @note as of 4/2018 this is a first draft and WIP-WIP-WIP
** @todo WIP ///////////////////////TICKET #1134
**
** @see ElementAccess_test
** @see elem-access-dir.hpp implementation
**
*/
#ifndef GUI_MODEL_ELEMENT_ACCESS_H
#define GUI_MODEL_ELEMENT_ACCESS_H
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
#include "lib/result.hpp"
#include "include/limits.h"
#include "lib/variant.hpp"
#include "lib/meta/typelist-manip.hpp"
#include "lib/access-casted.hpp"
#include "gui/interact/ui-coord.hpp"
//#include "lib/format-string.hpp"
//#include "lib/symbol.hpp"
//#include "lib/util.hpp"
#include "lib/test/test-helper.hpp" ////////TOD-o
#include <string>
namespace Gtk {
class Widget;
}
namespace gui {
namespace model {
namespace error = lumiera::error;
using interact::UICoord;
using lib::meta::Types;
// using util::isnil;
using std::string;
class Tangible;
/**
* Interface: access UI elements by navigating the UI topology.
*
* @see gui::interact::Navigator
* @see ElementAccess_test
*/
class ElementAccess
: util::NonCopyable
{
public:
virtual ~ElementAccess () { } ///< this is an interface
template<class TAR>
using Result = lib::Result<TAR>;
/* == Access by Location == */
template<class TAR>
Result<TAR&> access (UICoord const& destination);
template<class TAR>
Result<TAR&> access_or_create (UICoord const& destination, size_t limitCreation = LUMIERA_MAX_ORDINAL_NUMBER);
protected:
using RawResult = lib::Variant<Types<model::Tangible*, Gtk::Widget*>>;
/** @internal drill down according to coordinates, maybe create element */
virtual RawResult performAccessTo (UICoord const&, size_t limitCreation) =0;
private:
template<class TAR>
struct TypeConverter;
};
/** @internal helper to perform conversion to the desired result type.
* This is a bit of tricky metaprogramming to deal with the problem that we can not
* assume a single base interface for all the UI elements or widgets accessible through
* UI-Coordinates. Rather we have to deal with a small set of possible base interfaces,
* and thus the actual [access function](\ref #performAccessTo) returns a _variant record_
* holding a pointer, and internally tagged with the base interface type to apply. Now the
* public API functions are templated to the _result type as expected by the invoking clinent_
* and thus we get a matrix of possible cases; when the expected result type is _reachable by
* dynamic downcast_ from the actual base interface type returned by the internal access function,
* we can perform this dynamic_cast. Otherwise the returned result proxy object is marked as empty.
* @remark technically this solution relies on the lib::Variant::Visitor to provide a NOP default
* implementation. The TypeConverter template is instantiated with the desired target type
* and thus only overwrites _the first case where an conversion is possible._ In all other
* cases the default implementation does nothing and thus retains the embedded result proxy
* in empty state.
*/
template<class TAR>
struct ElementAccess::TypeConverter
: RawResult::Visitor
{
Result<TAR&> result{"not convertible"};
template<typename X> // note the "backward" use. We pick that base interface
using canUpcast = std::is_convertible<TAR*, X>; // into which our desired result type can be upcast, because
// we know then the following dynamic_cast (downcast) can succeed
using Base = typename RawResult::FirstMatching<canUpcast>::Type;
virtual void
handle (Base& pb) override
{
if (pb)
result = *dynamic_cast<TAR*> (pb);
else
result = Result<TAR&>{"access returns empty answer"};
}
};
/** Navigate the UI topology to access the designated component.
* @tparam TAR type of result element expected at the designated location
* @return suitably converted direct (language) reference to the desired element
* wrapped as _result proxy_
* @note when access was not possible because either the element does not exist,
* or is not convertible to the desired target type, the result proxy is empty
* and convertible to `bool(false)`
*/
template<class TAR>
inline ElementAccess::Result<TAR&>
ElementAccess::access (UICoord const& destination)
{
return access_or_create<TAR> (destination, 0);
}
/** Navigate to the designated component, possibly create the element and parents
* @return suitably converted direct (language) reference to the desired element
* wrapped as _result proxy_
* @note when access was not possible because the element could not been created,
* or is not convertible to the desired target type, the result proxy is empty,
* and convertible to `bool(false)` (or raises an exception on attempted access)
*/
template<class TAR>
inline ElementAccess::Result<TAR&>
ElementAccess::access_or_create (UICoord const& destination, size_t limitCreation)
{
TypeConverter<TAR> converter;
RawResult targetElm = performAccessTo (destination, limitCreation);
targetElm.accept (converter);
return converter.result;
}
}} // namespace gui::model
#endif /*GUI_MODEL_ELEMENT_ACCESS_H*/

View file

@ -1,127 +0,0 @@
/*
ELEMENT.hpp - a generic element in the UI-Model
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 element.hpp
** Generic building block in the Lumiera GUI model.
** A model::Element has a unique identifier, which is tied to the
** identification scheme used in the "real" model in Proc-Layer.
** Model elements can be addressed receive mutations caused by changes
** and rebuilding of elements within the Session; moreover, a generic
** representation of attributes is provided.
**
** @note as of 1/2015 this is a first draft and WIP-WIP-WIP
** @todo WIP ///////////////////////TICKET #959
**
** @see ////TODO_test usage example
** @see element.cpp implementation
**
*/
#ifndef GUI_MODEL_ELEMENT_H
#define GUI_MODEL_ELEMENT_H
#include "lib/error.hpp"
#include "lib/hash-value.h"
//#include "lib/symbol.hpp"
#include "lib/util.hpp"
#include <boost/noncopyable.hpp>
#include <string>
namespace gui {
namespace model {
using lib::HashVal;
using util::isnil;
using std::string;
/**
* Basic (abstracted) view of...
*
* @see SomeSystem
* @see NA_test
*/
template<class X>
class Element
{
string nothing_;
public:
explicit
Element (string const& b)
: nothing_(b)
{ }
// using default copy/assignment
/* == Adapter interface for == */
void
setSolution (string const& solution ="")
{
UNIMPLEMENTED ("tbw");
#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #888
if (isDeaf())
this->transmogrify (solution);
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #888
}
protected:
void maybe () const;
friend HashVal
hash_value (Element const& entry)
{
return hash_value (entry.nothing_);
}
};
/** @internal in case
*/
template<class X>
inline void
Element<X>::maybe () const
{
UNIMPLEMENTED ("tbw");
}
}} // namespace gui::model
#endif /*GUI_MODEL_ELEMENT_H*/

View file

@ -41,7 +41,6 @@
#include "gui/model/session-facade.hpp"
#include "lib/depend.hpp"
//#include <boost/noncopyable.hpp>
//#include <string>
//#include <map>

View file

@ -39,10 +39,10 @@
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
//#include "lib/symbol.hpp"
//#include "lib/util.hpp"
#include <boost/noncopyable.hpp>
#include <string>
@ -61,7 +61,7 @@ namespace model {
* @see NA_test
*/
class SessionFacade
: boost::noncopyable
: util::NonCopyable
{
string nothing_;

View file

@ -131,12 +131,12 @@
#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 <boost/noncopyable.hpp>
#include <sigc++/trackable.h>
#include <utility>
#include <string>
@ -156,7 +156,7 @@ namespace model {
* 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
* @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.
@ -164,7 +164,7 @@ namespace model {
class Tangible
: public sigc::trackable
, public lib::diff::DiffMutable
, boost::noncopyable
, util::NonCopyable
{
public:
using ID = ctrl::BusTerm::ID;

View file

@ -51,6 +51,7 @@
#include "lib/diff/mutation-message.hpp"
#include "lib/diff/gen-node.hpp"
#include "include/logging.h"
#include "lib/depend.hpp"
#include "lib/util.hpp"
extern "C" {
@ -91,6 +92,7 @@ namespace gui {
{
INFO (gui, "@GUI: display '%s' as notification message.", cStr(text)); ///////////////////////////////////TICKET #1102 : build a message display box in the UI
////////////////////////TODO actually push the information to the GUI ///////////////////////////////////TICKET #1098 : use a suitable Dispatcher
////////////////////////////////////////////////TICKET #1047 : as a temporary solution, use the InfoBox panel...
}
@ -124,7 +126,7 @@ namespace gui {
{
NOTICE (gui, "@GUI: shutdown triggered with explanation '%s'....", cStr(cause));
displayInfo (NOTE_ERROR, cause);
dispatch_->event ([=]()
dispatch_->event ([this]()
{
uiManager_.terminateUI();
});
@ -207,10 +209,9 @@ namespace gui {
using lumiera::facade::LUMIERA_ERROR_FACADE_LIFECYCLE;
typedef lib::SingletonRef<GuiNotification>::Accessor InstanceRef;
using lumiera::error::LERR_(LIFECYCLE);
InstanceRef _instance; ///< a backdoor for the C Language impl to access the actual GuiNotification implementation...
lib::Depend<NotificationService> _instance; ///< a backdoor for the C Language impl to access the actual SessionCommand implementation...
@ -222,41 +223,41 @@ namespace gui {
, LUMIERA_INTERFACE_INLINE (displayInfo,
void, (uint severity, const char* text),
{
if (!_instance) lumiera_error_set(LUMIERA_ERROR_FACADE_LIFECYCLE, text);
if (!_instance) lumiera_error_set (LUMIERA_ERROR_LIFECYCLE, text);
else
_instance->displayInfo (NotifyLevel(severity), text);
_instance().displayInfo (NotifyLevel(severity), text);
}
)
, LUMIERA_INTERFACE_INLINE (markError,
void, (LumieraUid element, const char* text),
{
if (!_instance) lumiera_error_set(LUMIERA_ERROR_FACADE_LIFECYCLE, text);
if (!_instance) lumiera_error_set (LUMIERA_ERROR_LIFECYCLE, text);
else
_instance->markError (reinterpret_cast<ID> (*element), text);
_instance().markError (reinterpret_cast<ID> (*element), text);
}
)
, LUMIERA_INTERFACE_INLINE (markNote,
void, (LumieraUid element, const char* text),
{
if (!_instance) lumiera_error_set(LUMIERA_ERROR_FACADE_LIFECYCLE, text);
if (!_instance) lumiera_error_set (LUMIERA_ERROR_LIFECYCLE, text);
else
_instance->markNote (reinterpret_cast<ID> (*element), text);
_instance().markNote (reinterpret_cast<ID> (*element), text);
}
)
, LUMIERA_INTERFACE_INLINE (mutate,
void, (LumieraUid element, void* diff),
{
if (!_instance) lumiera_error_set(LUMIERA_ERROR_FACADE_LIFECYCLE, "passing diff message");
if (!_instance) lumiera_error_set (LUMIERA_ERROR_LIFECYCLE, "passing diff message");
else
_instance->mutate (reinterpret_cast<ID> (*element), move(*reinterpret_cast<MutationMessage*> (diff)));
_instance().mutate (reinterpret_cast<ID> (*element), move(*reinterpret_cast<MutationMessage*> (diff)));
}
)
, LUMIERA_INTERFACE_INLINE (triggerGuiShutdown,
void, (const char* cause),
{
if (!_instance) lumiera_error_set(LUMIERA_ERROR_FACADE_LIFECYCLE, cause);
if (!_instance) lumiera_error_set (LUMIERA_ERROR_LIFECYCLE, cause);
else
_instance->triggerGuiShutdown (cause);
_instance().triggerGuiShutdown (cause);
}
)
);
@ -269,21 +270,21 @@ namespace gui {
/**
* When started, NotificationService connects to the [UI-Bus](ui-bus.hpp) via
* the provided connection. This is a simple, unidirectional up-link connection,
* without actively adding NotificationService into the routing tables in [Nexus].
* Yet this simple connection is sufficient to implement this service by talking
* to other facilities within the UI layer.
* When started, NotificationService connects to the [UI-Bus](ui-bus.hpp) via the provided connection.
* This is a simple, unidirectional up-link connection, without actively adding NotificationService
* into the routing tables in [Nexus]. Yet this simple connection is sufficient to implement this
* service by talking to other facilities within the UI layer.
* @remark internally this service relies on a UiDispatcher queue to hand over any invocations
* into the GTK event loop thread.
*/
NotificationService::NotificationService (ctrl::BusTerm& upLink, ctrl::UiManager& uiManager)
: BusTerm{lib::idi::EntryID<NotificationService>{}, upLink}
, dispatch_{new UiDispatcher{}}
, dispatch_{new UiDispatcher{[this](string msg){ displayInfo (NOTE_ERROR, msg); }}}
, uiManager_{uiManager}
, implInstance_(this,_instance)
, serviceInstance_( LUMIERA_INTERFACE_REF (lumieraorg_GuiNotification, 0,lumieraorg_GuiNotificationService))
{
INFO (gui, "GuiNotification Facade opened.");
}
{
INFO (gui, "GuiNotification Facade opened.");
}
NotificationService::~NotificationService() { } // emit dtors of embedded objects here...

View file

@ -42,7 +42,6 @@
#include "include/gui-notification-facade.h"
#include "common/instancehandle.hpp"
#include "gui/ctrl/bus-term.hpp"
#include "lib/singleton-ref.hpp"
#include <memory>
@ -78,19 +77,10 @@ namespace gui {
, public ctrl::BusTerm
{
/* === Implementation of the Facade Interface === */
void displayInfo (NotifyLevel,string const& text) override;
void markError (ID uiElement, string const& text) override;
void markNote (ID uiElement, string const& text) override;
void mutate (ID uiElement, MutationMessage&&) override;
void triggerGuiShutdown (string const& cause) override;
private:
std::unique_ptr<ctrl::UiDispatcher> dispatch_;
ctrl::UiManager& uiManager_;
void dispatchMsg(ID, lib::diff::GenNode&&);
void dispatchMsg (ID, lib::diff::GenNode&&);
/* === Interface Lifecycle === */
@ -98,15 +88,20 @@ namespace gui {
using ServiceInstanceHandle = lumiera::InstanceHandle< LUMIERA_INTERFACE_INAME(lumieraorg_GuiNotification, 0)
, GuiNotification
> ;
lib::SingletonRef<GuiNotification> implInstance_;
ServiceInstanceHandle serviceInstance_;
public:
~NotificationService();
NotificationService (ctrl::BusTerm& upLink, ctrl::UiManager& uiManager);
/* === Implementation of the Facade Interface === */
void displayInfo (NotifyLevel,string const& text) override;
void markError (ID uiElement, string const& text) override;
void markNote (ID uiElement, string const& text) override;
void mutate (ID uiElement, MutationMessage&&) override;
void triggerGuiShutdown (string const& cause) override;
};

View file

@ -0,0 +1,93 @@
/*
InfoBoxPanel - A dockable panel to expose information and parameters
Copyright (C) Lumiera.org
2017, 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 infobox-panel.cpp
** Implementation of a (dockable) panel to display and manipulate parameters.
*/
#include "gui/gtk-base.hpp"
#include "gui/panel/infobox-panel.hpp"
#include "lib/format-string.hpp"
using util::_Fmt;
using Glib::ustring;
namespace gui {
namespace panel{
InfoBoxPanel::InfoBoxPanel (workspace::PanelManager& panelManager, Gdl::DockItem& dockItem)
: Panel(panelManager, dockItem, getTitle(), getStockID())
, twoParts_{Gtk::ORIENTATION_VERTICAL}
, buttons_{}
, frame_{"UI Integration Experiments"}
, errorLog_{}
{
twoParts_.pack_start(frame_);
twoParts_.pack_start(buttons_, Gtk::PACK_SHRINK);
buttons_.set_layout (Gtk::BUTTONBOX_START);
// buttons to trigger experiments
button_1_.set_label ("_bang");
button_1_.set_use_underline();
button_1_.set_tooltip_markup ("<b>Experiment 1</b>:\ntrigger Proc-GUI roundtrip");
button_1_.signal_clicked().connect(
mem_fun(*this, &InfoBoxPanel::experiment_1));
buttons_.add (button_1_);
//(End)buttons...
frame_.set_border_width (5);
frame_.add (errorLog_);
// show everything....
this->add (twoParts_);
this->show_all();
}
const char*
InfoBoxPanel::getTitle()
{
return _("InfoBox");
}
const gchar*
InfoBoxPanel::getStockID()
{
return "panel_infobox";
}
void
InfoBoxPanel::experiment_1()
{
frame_.set_label("Experiment 1... BANG");
static uint bangNo{0};
static _Fmt msgTemplate{"Bang #%d\n"};
errorLog_.showMsg(NOTE_WARN, msgTemplate % ++bangNo);
}
}}// namespace gui::panel

View file

@ -0,0 +1,72 @@
/*
infobox-panel.hpp - A dockable panel to expose information and parameters
Copyright (C) Lumiera.org
2017, 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 infobox-panel.hpp
** A (dockable) panel to display and manage information and parameters.
** Such an »Info Box« typically exposes detail settings from some other component
** currently selected, and allows to access those in a non modal fashion.
**
** @todo as of 8/2017 this is (ab)used as space for UI / Proc-Layer integration experiments
*/
#ifndef GUI_PANEL_INFOBOX_PANEL_H
#define GUI_PANEL_INFOBOX_PANEL_H
#include "gui/panel/panel.hpp"
#include "gui/widget/error-log-widget.hpp"
namespace gui {
namespace panel{
class InfoBoxPanel
: public Panel
{
public:
/** Build a new InfoBox-Panel
* @param PanelManager The owner panel manager widget.
* @param DockItem The GdlDockItem that will host this panel.
*
* @todo as of 8/2017 this is placeholder code for UI experiments...
*/
InfoBoxPanel (workspace::PanelManager&, Gdl::DockItem&);
static const char* getTitle(); ///< @deprecated need better design of the PanelManager /////TICKET #1026
static const gchar* getStockID();
private:
Gtk::Box twoParts_;
Gtk::ButtonBox buttons_;
Gtk::Button button_1_;
Gtk::Frame frame_;
widget::ErrorLogWidget errorLog_;
void experiment_1();
};
}}// namespace gui::panel
#endif /*GUI_PANEL_INFOBOX_PANEL_H*/

Some files were not shown because too many files have changed in this diff Show more