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:
commit
f06038828c
414 changed files with 34590 additions and 7553 deletions
|
|
@ -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'):
|
||||
|
|
|
|||
BIN
data/icons/prerendered/16x16/panel-infobox.png
Normal file
BIN
data/icons/prerendered/16x16/panel-infobox.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
258
doc/design/architecture/Subsystems.txt
Normal file
258
doc/design/architecture/Subsystems.txt
Normal 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.
|
||||
|
||||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
137
doc/design/gui/InteractionControl.txt
Normal file
137
doc/design/gui/InteractionControl.txt
Normal 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`...
|
||||
|
||||
|
||||
|
|
@ -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*)
|
||||
|
|
|
|||
|
|
@ -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''...
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
226
doc/technical/library/Dependencies.txt
Normal file
226
doc/technical/library/Dependencies.txt
Normal 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.
|
||||
|
||||
|
|
@ -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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
|||
218
research/try.cpp
218
research/try.cpp
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ namespace engine {
|
|||
EngineConfig();
|
||||
~EngineConfig();
|
||||
|
||||
friend class lib::DependencyFactory;
|
||||
friend class lib::DependencyFactory<EngineConfig>;
|
||||
|
||||
public:
|
||||
/** access point to the Engine Interface.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@
|
|||
//#include "lib/iter-source.hpp"
|
||||
//#include "lib/sync.hpp"
|
||||
|
||||
//#include <boost/noncopyable.hpp>
|
||||
//#include <string>
|
||||
//#include <vector>
|
||||
//#include <memory>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
|
|
|
|||
|
|
@ -509,7 +509,7 @@ namespace advice {
|
|||
{
|
||||
SelfCheckFailure (Literal failure)
|
||||
: error::Fatal (string("Failed test: ")+failure
|
||||
,LUMIERA_ERROR_INDEX_CORRUPTED)
|
||||
,LERR_(INDEX_CORRUPTED))
|
||||
{ }
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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() +":";
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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_);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -74,7 +74,7 @@ namespace lumiera {
|
|||
* unrecognised parts.
|
||||
*/
|
||||
class Option
|
||||
: boost::noncopyable
|
||||
: util::NonCopyable
|
||||
{
|
||||
public:
|
||||
Option (lib::Cmdline& cmdline);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.");
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
|
|
|
|||
|
|
@ -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.");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
{ }
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
82
src/gui/ctrl/panel-locator.hpp
Normal file
82
src/gui/ctrl/panel-locator.hpp
Normal 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*/
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
@ -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*/
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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*/
|
||||
|
|
@ -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
|
||||
|
|
@ -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*/
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{ }
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
175
src/gui/id-scheme.hpp
Normal 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*/
|
||||
|
|
@ -34,7 +34,6 @@
|
|||
//#include "include/logging.h"
|
||||
#include "gui/interact/cmd-context.hpp"
|
||||
|
||||
//#include <boost/noncopyable.hpp>
|
||||
//#include <string>
|
||||
//#include <map>
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
114
src/gui/interact/elem-access-dir.hpp
Normal file
114
src/gui/interact/elem-access-dir.hpp
Normal 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*/
|
||||
|
|
@ -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_;
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ==
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@
|
|||
//#include "include/logging.h"
|
||||
#include "gui/interact/interaction-state.hpp"
|
||||
|
||||
//#include <boost/noncopyable.hpp>
|
||||
//#include <string>
|
||||
//#include <map>
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** */
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
208
src/gui/interact/ui-coord-resolver.cpp
Normal file
208
src/gui/interact/ui-coord-resolver.cpp
Normal 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
|
||||
601
src/gui/interact/ui-coord-resolver.hpp
Normal file
601
src/gui/interact/ui-coord-resolver.hpp
Normal 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*/
|
||||
763
src/gui/interact/ui-coord.hpp
Normal file
763
src/gui/interact/ui-coord.hpp
Normal 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*/
|
||||
327
src/gui/interact/ui-location-solver.hpp
Normal file
327
src/gui/interact/ui-location-solver.hpp
Normal 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*/
|
||||
111
src/gui/interact/view-locator.cpp
Normal file
111
src/gui/interact/view-locator.cpp
Normal 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
|
||||
134
src/gui/interact/view-locator.hpp
Normal file
134
src/gui/interact/view-locator.hpp
Normal 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*/
|
||||
265
src/gui/interact/view-spec-dsl.hpp
Normal file
265
src/gui/interact/view-spec-dsl.hpp
Normal 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*/
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
205
src/gui/model/element-access.hpp
Normal file
205
src/gui/model/element-access.hpp
Normal 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*/
|
||||
|
|
@ -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*/
|
||||
|
|
@ -41,7 +41,6 @@
|
|||
#include "gui/model/session-facade.hpp"
|
||||
#include "lib/depend.hpp"
|
||||
|
||||
//#include <boost/noncopyable.hpp>
|
||||
//#include <string>
|
||||
//#include <map>
|
||||
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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...
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
93
src/gui/panel/infobox-panel.cpp
Normal file
93
src/gui/panel/infobox-panel.cpp
Normal 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
|
||||
72
src/gui/panel/infobox-panel.hpp
Normal file
72
src/gui/panel/infobox-panel.hpp
Normal 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
Loading…
Reference in a new issue