MERGE: recent doc and website improvements
This commit is contained in:
commit
03d5600f57
203 changed files with 18679 additions and 4014 deletions
|
|
@ -18,6 +18,7 @@ authors = Joel Holdsworth|Christian Thäter|Hermann Voßeler|Stefan Kangas|
|
|||
copyright = 2007 - 2015
|
||||
|
||||
[Gui]
|
||||
stylesheet = gtk-lumiera.css
|
||||
#stylesheet = lumiera.css
|
||||
stylesheet = lumiera-light-theme-complement.css
|
||||
iconpath = $ORIGIN/../../share/lumiera/icons:$ORIGIN/gui/icons:~/.lumiera/icons
|
||||
resourcepath = $ORIGIN/../../share/lumiera/gui:$ORIGIN/gui
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ test and better to reason about. If it really can not be avoided to use such uni
|
|||
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
|
||||
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.
|
||||
|
||||
|
|
@ -223,7 +223,7 @@ the event `ON_GLOBAL_INIT` is emitted.
|
|||
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.
|
||||
This event indicates to cease 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.
|
||||
|
|
|
|||
|
|
@ -84,6 +84,10 @@ General Code Arrangement and Layout
|
|||
The primary author(s) and the year of the initial copyright claim should be mentioned.
|
||||
- Each header should be focused on a specific purpose. Preferably it starts with a file-level
|
||||
doxygen comment explaining the intention and anything not obvious from reading the code.
|
||||
At lest a `@file` tag with one line of classification in a doxygen comment at the top of every
|
||||
file is mandatory.footnote:[this rule stands simply because, without such a file-level doxygen
|
||||
comment, doxygen will _ignore all contents_ of this file (really, might be surprising, yet it is
|
||||
the way it is...)]
|
||||
- when arranging headers and compilation units, please take care of the compilation times and the
|
||||
code size. Avoid unnecessary includes. Use forward declarations where applicable.
|
||||
Yet still, _all immediately required direct dependencies should be mentioned_, even if already
|
||||
|
|
@ -91,11 +95,20 @@ General Code Arrangement and Layout
|
|||
link:{ldoc}/technical/code/linkingStructure.html#_imports_and_import_order[issues of code organisation]
|
||||
- The include block starts with our own dependencies, followed by a second block with the library
|
||||
dependencies. After that, optionally some symbols may be brought into scope (through +using+ clauses).
|
||||
Avoid cluttering top-level namespaces. Never import full namespaces (no +using namespace boost;+ please!)
|
||||
Avoid cluttering top-level namespaces. Never import full namespaces.footnote:[no `using namespace gtk;`
|
||||
or `using namespace boost` please! Experience shows, in the end you'll be using 5 names or so, but
|
||||
pull in all the others just for sake of laziness. Just type the f**g `using` clause for every
|
||||
import individually, and we'll all be better off...]
|
||||
- the includes for our own dependencies shall be given relative to source-root (or test root). Don't use
|
||||
relative includes for headers located in the same directory, or -- worse still -- in the parent directory.
|
||||
- sometimes, the actual translation units will combine several facilities for technical reasons, e.g.
|
||||
when sharing an implementation-level class or even singleton instance.
|
||||
- sometimes, the actual translation units will combine several facilities for technical reasons.footnote:
|
||||
[this means, there can be ``headers'', which are actually only be intended for inclusion on one or two
|
||||
distinct places. This should be mentioned in the file-level comment, but generally is an acceptable
|
||||
practice, and better then lumping everything into a 1000 lines header. As a guideline, if you expect
|
||||
a rather technical concern not to be of much interest for most readers of a header, then better
|
||||
extract it into a separate self-contained header and include it. E.g., you might be sharing an
|
||||
an implementation-level class or even singleton instance and some constant definitions. Just
|
||||
be sure not to include definitions several times.]
|
||||
Anonymous namespaces should be used liberally to avoid unnecessary exports.
|
||||
- template code mostly needs to reside in headers. (same for metaprogramming code).
|
||||
We employ the simple inclusion model (``Borland model'') for template instantiation.
|
||||
|
|
|
|||
94
doc/technical/gui/CodePolicy.txt
Normal file
94
doc/technical/gui/CodePolicy.txt
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
Coding Policies within UI-Layer
|
||||
===============================
|
||||
:Toc:
|
||||
|
||||
NOTE: for the time being, this is a loose collection of
|
||||
Conventions and Policies deemed adequate for work
|
||||
on the Lumiera UI-Layer
|
||||
|
||||
|
||||
Architecture
|
||||
------------
|
||||
|
||||
The UI is loaded as Plug-In, which instantiates a `GtkLumiera` object.
|
||||
All of the UI activity happens within the blocking function `GtkLumiera::run()`.
|
||||
This UI activity boils down to run the GTK event loop; we use a dedicated thread,
|
||||
and all of the GUI thus performs single-threaded and need not be thread-safe.
|
||||
All UI activities thus need to be short running and with deterministic outcome.
|
||||
It is strictly prohibited to spawn other threads from within the event loop.
|
||||
|
||||
We hold up a strict distinction between the _UI mechanics_ and the _core editing
|
||||
concerns_. The latter _must not be implemented within the UI-Layer._ Rather, you need
|
||||
to send an `act(CommandID)` message over the UI-Bus, which causes the corresponding
|
||||
Proc-Layer command script to be dispatched within the Session thread. It is good
|
||||
practice to give an immediate visual clue regarding the fact of sending such
|
||||
a command (e.g. pressing a button). But be prepared that the actual feedback
|
||||
of invoking a command happens asynchronously.
|
||||
|
||||
UI-Model
|
||||
~~~~~~~~
|
||||
In short: _there is no dedicated UI-Model._ Stateful working data goes directly
|
||||
into the widgets. However, some widgets or controllers are special, insofar they
|
||||
correspond to and reflect some entities within the _Session model._ These special
|
||||
entities must be implemented as subclasses of `gui::model::Tangible`. These are
|
||||
always connected to the UI-Bus, and they are populated and mutated by receiving
|
||||
diff messages pushed up from the Proc-Layer.
|
||||
|
||||
There is an _inner circle_ of UI backbone services. These are all mutually dependent
|
||||
on each other and their start-up sequence is intricate. Implement global and cross-cutting
|
||||
concerns here. In case some widget or controller needs access to such a service, then you
|
||||
should prefer using `lib::Depend<ServiceInterface>`, and install the corresponding service
|
||||
via `lib::DependInject<ServiceInterface>::ServiceInstance<ServiceImpl>`.
|
||||
|
||||
|
||||
Behaviour patterns
|
||||
------------------
|
||||
|
||||
Parent Containers
|
||||
~~~~~~~~~~~~~~~~~
|
||||
A _Parent Container_ is an object managing a collection of children, with respect to
|
||||
the structure of _tangible UI elements._ Irrespective if this container is also a
|
||||
`Gtk::Container` widget, or ``just'' a controller. The key point to turn something
|
||||
into a _Parent Container_ is the fact that this entity creates the children
|
||||
in response to a (population) diff message.
|
||||
|
||||
.Responsibilities
|
||||
- a _Parent Container_ has to wire each child properly, so to enable the adequate
|
||||
use of the UI element protocol. This includes
|
||||
|
||||
** to ensure the child is attached to the UI-Bus (usually enforced by ctor call)
|
||||
** to install a suitable `Expander` functor if the child supports expand/collapse
|
||||
** to install a suitable `Revealer` when it is relevant for the child to be brought
|
||||
into sight in response to some message (e.g. to indicate error state).
|
||||
|
||||
- whenever a child is detached from the container, you need to _ensure it is destroyed_
|
||||
right away, before there is any chance of processing some other UI event.footnote:[
|
||||
Invoking the destructor triggers a lot of magic here, especially it causes the child
|
||||
to be properly detached from signals. We want to ensure this happens as soon as the
|
||||
child is taken out of service. Do not use an ``can collect garbage later'' approach.]
|
||||
|
||||
Error handling
|
||||
~~~~~~~~~~~~~~
|
||||
Be aware that GTK is written in C. And while GTKmm has some safeguards in place,
|
||||
better be sure no exception can emanate from event handling code.
|
||||
|
||||
WARNING: [red]#TODO#: probably we'll need a common wrapper to do so...
|
||||
|
||||
|
||||
Code organisation
|
||||
-----------------
|
||||
GTK is massive and compilation times tend to be problematic in the UI-Layer.
|
||||
Thus you should be diligent with the organisation of includes. Try to confine the
|
||||
more heavyweight includes within the implementation translation units. Use forward
|
||||
declarations and PImpl if possible. However, it is fine to write a mere implementation
|
||||
widget in header-only fashion, in case this improves the locality and readability of code.
|
||||
|
||||
NOTE: `using namespace Gtk` and similar wildcard includes are prohibited.
|
||||
|
||||
We are forced to observe a tricky include sequence, due to NoBug's `ERROR` macro,
|
||||
and also in order to get I18N right.
|
||||
Thus any actual translation unit should ensure that effectively the first include
|
||||
is that of 'gui/gtk-base.hpp'.footnote:[you need not include it literally. It is
|
||||
perfectly fine if you can be sure the first included header somehow drags in
|
||||
'gtk-base.hpp' before any other header.]
|
||||
|
||||
80
doc/technical/gui/GuiConnect.txt
Normal file
80
doc/technical/gui/GuiConnect.txt
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
GUI Connection
|
||||
==============
|
||||
|
||||
The Lumiera application is built from several segregated software layers, and the user interface, the GUI-Layer is
|
||||
optional and loaded as a plug-in. This deliberate design decision has far reaching consequences in all parts of the
|
||||
application: Anything of relevance for editing or rendering need to be _represented explicitly_ within a model, distinct
|
||||
and abstracted from the presentation for the user. For example, many media handling applications let you ``snap'' some
|
||||
entity to a ``grid'', which happens conveniently during a given user interaction, just resulting in a new position for
|
||||
the element at hand. In Lumiera, we can not follow this obvious and pragmatic approach -- rather, both the time grid
|
||||
and the fact an object as been attached and aligned to such a grid need to be represented in the session model proper,
|
||||
resulting in a `Placement`. More specifically, we can not derive behaviour of such an object from circumstantial
|
||||
state data present in the UI. Rather, we need to compute the new position based on rules within the session.
|
||||
And then we need to _communicate_ the result back into the UI -- if and when an UI is present to reflect that change.
|
||||
|
||||
So instead of having a _»Controller«_ mediate the interplay of a _»Model«_ and the _»View«_ presentation, we end up
|
||||
with a complete _communication cycle,_ where messages are sent in both directions, and _sent asynchronously._
|
||||
|
||||
Principles of UI / Core interaction
|
||||
-----------------------------------
|
||||
By all means, we avoid a common shared data structure as foundation for interaction. Such a _data centric_ approach
|
||||
may lean towards two extremes, both of which are problematic. A _naively written application_ starts with building
|
||||
the user interface and hinges any further internal structures directly below. For a tiny application, which is
|
||||
basically ``just some interface controls'', such an approach might be adequate -- yet maintaining the ``code behind''
|
||||
the control surface quickly becomes a nightmare. We may avoid those immediate problems by settling down on a strictly
|
||||
codified central _core data model._ And while this shift towards the other extreme of a data centric architecture
|
||||
indeed resolves the immediately pressing maintenance problems, this settlement comes with an substantial price tag.
|
||||
Since interface and core functionality are now torn apart, the code conducting even the simplest interaction is
|
||||
scattered over various parts of the system, and thus everything need to be very well defined and documented.
|
||||
Such a system becomes rigid as soon as it is built. And additionally it tends to suffer from structure duplication
|
||||
and multiplication, since cross interactions involving several parts of the core and the surface tend to be had
|
||||
to express: Interaction state need to be materialised into the core data, to allow other, distinct parts of the
|
||||
interface to collaborate.
|
||||
|
||||
We may escape from that dichotomy when we cease to _rely on data as the fundamental substrate of our system._
|
||||
To collaborate, both parts still need to share a structure, yet it suffices for this structure to be a _common
|
||||
understanding:_ both the UI-Layer and the Proc-Layer need to share a common understanding how the parts of a editing
|
||||
session can be arranged. Yet they do not need to share a _common data representation_ of the session.
|
||||
|
||||
The consequence is that both sides, ``the core'' and ``the UI'' remain autonomous within their realm. For some concerns, namely _the core concerns,_ that is editing, arranging, processing, the core is in charge and has absolute authority.
|
||||
On the other hand, when it comes to user interaction, especially the _mechanics and materiality of interaction,_ the UI
|
||||
is the authority; it is free to decide about what is exposed and in which way. Yet the common structural understanding
|
||||
is never fully, totally cast in concrete data structures.
|
||||
|
||||
Rather, the core sends *diff messages* up to the UI, indicating how it sees this virtual structure to be changing.
|
||||
The UI reflects these changes into _its own understanding and representation,_ which is here a structure of display
|
||||
widgets. When the user interacts with these structures of the presentation layer, *action command messages* are generated,
|
||||
using the element-IDs to designate the arguments of the intended operation. Acting on these messages again causes reaction and change in the core, which is reflected back in the form of further diff messages.
|
||||
|
||||
Anatomy of the Proc/GUI interface
|
||||
---------------------------------
|
||||
- the `GuiFacade` is used as a general lifecycle facade to start up the GUI and to set up the »Layer Separation
|
||||
Interfaces«. The `GuiFacade` is implemented by a class _in Proc-Layer_ and loads the Lumiera GTK-UI as a plug-in.
|
||||
- once the UI is running, it exposes the `GuiNotificationFacade`, to allow pushing state and structure updates up into
|
||||
the user interface.
|
||||
- in the opposite direction, for initiating actions from the UI, the Session Subsystem opens the `SessionCommandFacade`,
|
||||
which can be considered __ ``the'' public session interface.__
|
||||
|
||||
Both these primary interfaces operate *message based.* As immediate consequence, these interfaces are comparatively
|
||||
narrow, and interactions can easily be serialised, enqueued and dispatched. On the Proc-Layer side, the `ProcDispatcher`
|
||||
coordinates command execution and the _Builder run_ to derive the consequences of those session command invocations.
|
||||
And within the UI, the GTK-Event loop runs likewise single threaded, and the Notification facade automatically
|
||||
dispatches any invocation into the UI thread.
|
||||
|
||||
Within the UI-Layer, we do not rely on a central ``shadow model'', rather, the backbone of the UI is a communication
|
||||
structure, the *UI-Bus*. Data within the GUI is kept very local, close to the presentation widgets. In fact, we
|
||||
identify a selection of _really important_ UI elements, which we call ``the tangible elements'' (`gui::model::Tangible`).
|
||||
Each of those primary elements within the UI _is connected to the UI-Bus,_ and can be addressed by its ID, and it
|
||||
happens to correspond and reflect an entity within the session model, there denoted by the same ID. The core can
|
||||
thus send a message towards the corresponding tangible UI element, without any knowledge as to how and where this
|
||||
element is actually attached within the widget tree of the user interface. Even diff messages are dispatched over
|
||||
the UI-Bus; it is up to the receiving tangible element to decide about the meaning of such a diff message, and
|
||||
how to reshape its internal widget state to reflect that change.
|
||||
|
||||
When it comes to the more elaborate parts of the model and the UI -- especially for the timeline -- those tangible
|
||||
elements need not be widgets by themselves. Rather, they can be local presentation controllers, often also referred
|
||||
to as *Presenter*. These manage and control a selection of slave widgets to form the visual presentation of the
|
||||
corresponding session entity. For example, a `ClipPresenter` injects one or a conglomerate of several widgets
|
||||
into the track canvas, depending on the current zoom level and the display state of the clip (collapsed, expanded,
|
||||
abridged display).
|
||||
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
Lumiera GUI style and theming
|
||||
=============================
|
||||
|
||||
//Menu: label Theme/Style
|
||||
|
||||
_this page collects some pieces of information regarding the visual appearance of the Lumiera GTK UI_
|
||||
|
||||
'this page collects some pieces of information regarding the visual appearance of the Lumiera GTK GUI'
|
||||
|
||||
GTK-3 styles
|
||||
------------
|
||||
|
|
@ -15,7 +17,9 @@ introduce additional engine specific styles and settings.
|
|||
|
||||
GTK-3 supports the powerful 'cascading' and 'contextual selectors' from CSS. Thus the nesting of elements
|
||||
in the GUI forms the base for creating styling rules. Hereby, widget class names translate into ``tag'' names
|
||||
in the CSS selectors. Widgets may also expose CSS classes for styling -- the standard widgets define a generic set
|
||||
in the CSS selectors.footnote:[The ``tag'' name is the widget's class name without the `Gtk::` prefix, and transformed
|
||||
into all lower caps, single word, no underscores. E.g. `Gtk::TextView` -> `textview`]
|
||||
Widgets may also expose CSS classes for styling -- the standard widgets define a generic set
|
||||
of https://developer.gnome.org/gtk3/3.4/GtkStyleContext.html#gtkstylecontext-classes[predefined CSS style classes],
|
||||
which can be used to establish the foundation for theming. Obviously it is preferable to keep styling rules as
|
||||
concise, generic and systematic as possible; yet we may still refer to individual gui elements by name (`#ID`) though.
|
||||
|
|
@ -27,6 +31,8 @@ Recommended reading
|
|||
and
|
||||
https://developer.gnome.org/gtk3/3.4/GtkStyleContext.html#gtkstylecontext-classes[predefined style classes]
|
||||
in the GTK-3 reference manual.
|
||||
* there is an https://developer.gnome.org/gtk3/stable/chap-css-overview.html[overview page in the developer manual],
|
||||
and a https://developer.gnome.org/gtk3/stable/chap-css-properties.html[reference of supported properties].
|
||||
* to start, look at this http://thegnomejournal.wordpress.com/2011/03/15/styling-gtk-with-css/[introductory text],
|
||||
or the more http://worldofgnome.org/making-gtk3-themes-part-1-basics/[hands-on series of articles from world of gnome]
|
||||
* this http://forums.fedoraforum.org/showthread.php?t=281568[post from fedora forum] features a conciese description
|
||||
|
|
@ -49,6 +55,15 @@ A good recommendation is really to ``probe'' settings by changing them temporari
|
|||
(e.g. `background-color: red`). It is just too easy to learn wrong techniques based on false conclusions.
|
||||
|
||||
|
||||
the GTK+ inspector
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
An essential tool when working with styles and Gtk widgets in general is the
|
||||
https://wiki.gnome.org/Projects/GTK%2B/Inspector[GTK+ inspector], which is part of the standard GTK distribution.
|
||||
It allows to inspect all GTK objects with their properties, and to see the actual tree of CSS nodes and the
|
||||
corresponding selectors. You can even add a style class or state flag (like ``hover'') dynamically, and you
|
||||
may add style rules and verify the effect on the application immediately. To use this ispector, launch the
|
||||
application like `GTK_DEBUG=interactive target/lumiera`
|
||||
|
||||
|
||||
binary themes
|
||||
~~~~~~~~~~~~~
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
## Things defined here usuall won't be installed
|
||||
##
|
||||
|
||||
Import('env core support_lib')
|
||||
Import('env envGtk core support_lib')
|
||||
|
||||
|
||||
|
||||
|
|
@ -12,9 +12,14 @@ envR = env.Clone()
|
|||
envR.Append(CPPPATH='research')
|
||||
# envR.Append(CCFLAGS=' -O3 ')
|
||||
|
||||
envRGtk = envGtk.Clone()
|
||||
envRGtk.Append(CPPPATH='research')
|
||||
|
||||
# build additional test and administrative tools....
|
||||
experiments = [ envR.Program('try', ['try.cpp'] + core) #### to try out some feature...
|
||||
, envR.Program('clang-static-init', ['clang-static-init-1.cpp', 'clang-static-init-2.cpp'])
|
||||
, envR.Program('clang-static-init', ['clang-static-init-1.cpp', 'clang-static-init-2.cpp'])
|
||||
|
||||
, envRGtk.Program('gtk-canvas-experiment', ['gtk-canvas-experiment.cpp', 'gtk-canvas-main.cpp'] + core)
|
||||
]
|
||||
|
||||
#
|
||||
|
|
|
|||
368
research/gtk-canvas-experiment.cpp
Normal file
368
research/gtk-canvas-experiment.cpp
Normal file
|
|
@ -0,0 +1,368 @@
|
|||
/*
|
||||
GtkCanvasExperiment - Explore capabilities of the GTK canvas widget
|
||||
|
||||
Copyright (C) Lumiera.org
|
||||
2016, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of
|
||||
the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
|
||||
* *****************************************************/
|
||||
|
||||
|
||||
/** @file gtk-canvas-experiment.cpp
|
||||
** Implementation of gtk canvas experiments.
|
||||
**
|
||||
** @remark as of 10/2018 we start to build a new timeline widget,
|
||||
** based on this technology demo.
|
||||
** @see timeline-widget.hpp
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#include "gtk-canvas-experiment.hpp"
|
||||
|
||||
#include "lib/format-string.hpp"
|
||||
#include "lib/format-cout.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
|
||||
|
||||
|
||||
using util::_Fmt;
|
||||
using util::isnil;
|
||||
using Gtk::Widget;
|
||||
using sigc::mem_fun;
|
||||
using sigc::ptr_fun;
|
||||
using std::string;
|
||||
using std::rand;
|
||||
using std::max;
|
||||
|
||||
|
||||
namespace demo {
|
||||
|
||||
|
||||
CanvasDemoPanel::CanvasDemoPanel ()
|
||||
: Box{}
|
||||
, twoParts_(Gtk::ORIENTATION_VERTICAL)
|
||||
, buttons_()
|
||||
, frame_("Gtk::Layout Experiments")
|
||||
, scroller_()
|
||||
, canvas_()
|
||||
{
|
||||
twoParts_.pack_start(buttons_, Gtk::PACK_SHRINK);
|
||||
twoParts_.pack_start(frame_);
|
||||
|
||||
buttons_.set_layout(Gtk::BUTTONBOX_START);
|
||||
|
||||
// buttons to trigger experiments
|
||||
button_1_.set_label("_place");
|
||||
button_1_.set_use_underline();
|
||||
button_1_.set_tooltip_markup("<b>Experiment 1</b>:\nplace new child widget\nat random position on the canvas");
|
||||
button_1_.signal_clicked().connect(
|
||||
mem_fun(*this, &CanvasDemoPanel::experiment_1));
|
||||
buttons_.add(button_1_);
|
||||
|
||||
button_2_.set_label("_move");
|
||||
button_2_.set_use_underline();
|
||||
button_2_.set_tooltip_markup("<b>Experiment 2</b>:\nmove all child widgets randomly");
|
||||
button_2_.signal_clicked().connect(
|
||||
mem_fun(*this, &CanvasDemoPanel::experiment_2));
|
||||
buttons_.add(button_2_);
|
||||
|
||||
button_3_.set_label("a_lign");
|
||||
button_3_.set_use_underline();
|
||||
button_3_.set_tooltip_markup("<b>Experiment 3</b>:\nalign all child widgets in a row\nwith silight random vertical offset");
|
||||
button_3_.signal_clicked().connect(
|
||||
mem_fun(*this, &CanvasDemoPanel::experiment_3));
|
||||
buttons_.add(button_3_);
|
||||
|
||||
button_4_.set_label("_grow");
|
||||
button_4_.set_use_underline();
|
||||
button_4_.set_tooltip_markup("<b>Experiment 4</b>:\nextend arbitrary child widget's text");
|
||||
button_4_.signal_clicked().connect(
|
||||
mem_fun(*this, &CanvasDemoPanel::experiment_4));
|
||||
buttons_.add(button_4_);
|
||||
|
||||
button_5_.set_label("_kill");
|
||||
button_5_.set_use_underline();
|
||||
button_5_.set_tooltip_markup("<b>Experiment 5</b>:\nkill arbitrary child widget");
|
||||
button_5_.signal_clicked().connect(
|
||||
mem_fun(*this, &CanvasDemoPanel::experiment_5));
|
||||
buttons_.add(button_5_);
|
||||
|
||||
toggleDraw_.set_label("draw");
|
||||
toggleDraw_.signal_clicked().connect(
|
||||
[this]() {
|
||||
canvas_.enableDraw (this->toggleDraw_.get_active());
|
||||
});
|
||||
buttons_.add(toggleDraw_);
|
||||
//(End)buttons...
|
||||
|
||||
frame_.add(scroller_);
|
||||
frame_.set_border_width(5);
|
||||
|
||||
scroller_.set_shadow_type(Gtk::SHADOW_IN);
|
||||
scroller_.property_expand() = true; // dynamically grab any available additional space
|
||||
scroller_.set_border_width(10);
|
||||
scroller_.add(canvas_);
|
||||
|
||||
canvas_.adjustSize();
|
||||
|
||||
// show everything....
|
||||
this->add(twoParts_);
|
||||
this->show_all();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
CanvasDemoPanel::experiment_1()
|
||||
{
|
||||
frame_.set_label("Experiment 1... PLACE");
|
||||
|
||||
ChildEx* chld = makeChld();
|
||||
childz_.push_back(chld);
|
||||
uint x = rand() % 1000;
|
||||
uint y = rand() % 500;
|
||||
canvas_.put(*chld, x, y);
|
||||
chld->show();
|
||||
canvas_.adjustSize();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
CanvasDemoPanel::experiment_2()
|
||||
{
|
||||
frame_.set_label("Experiment 2... MOVE");
|
||||
for (Widget* chld : childz_)
|
||||
{
|
||||
uint x = canvas_.child_property_x(*chld);
|
||||
uint y = canvas_.child_property_y(*chld);
|
||||
int deltaX = -20 + rand() % 41;
|
||||
int deltaY = -15 + rand() % 31;
|
||||
x = uint(max (0, int(x) + deltaX));
|
||||
y = uint(max (0, int(y) + deltaY));
|
||||
|
||||
canvas_.move (*chld, x,y);
|
||||
}
|
||||
canvas_.adjustSize();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
CanvasDemoPanel::experiment_3()
|
||||
{
|
||||
frame_.set_label("Experiment 3... ALIGN");
|
||||
uint pos=0;
|
||||
for (Widget* chld : childz_)
|
||||
{
|
||||
uint y = rand() % 30;
|
||||
canvas_.move (*chld, pos, y);
|
||||
|
||||
int width = chld->get_allocated_width();
|
||||
pos += 0.6 * width;
|
||||
}
|
||||
canvas_.adjustSize();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
CanvasDemoPanel::experiment_4()
|
||||
{
|
||||
frame_.set_label("Experiment 4... GROW");
|
||||
if (isnil (childz_))
|
||||
{
|
||||
ERROR (test, "need to fabricate more childz before you can grow 'em...");
|
||||
return;
|
||||
}
|
||||
uint selector = rand() % childz_.size();
|
||||
ChildEx& toGrow = *childz_[selector];
|
||||
toGrow.set_label ("***"+toGrow.get_label()+"***");
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
CanvasDemoPanel::experiment_5()
|
||||
{
|
||||
frame_.set_label("Experiment 5... KILL");
|
||||
if (isnil (childz_))
|
||||
{
|
||||
WARN (test, "no children to kill. so sad.");
|
||||
return;
|
||||
}
|
||||
uint killPos = rand() % childz_.size();
|
||||
ChildV::iterator killThat(&childz_[killPos]);
|
||||
ChildEx* victim = *killThat;
|
||||
childz_.erase (killThat);
|
||||
canvas_.remove (*victim);
|
||||
delete victim;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Canvas::enableDraw (bool yes)
|
||||
{
|
||||
shallDraw_ = yes;
|
||||
|
||||
// force redrawing of the visible area...
|
||||
auto win = get_window();
|
||||
if (win)
|
||||
{
|
||||
int w = get_allocation().get_width();
|
||||
int h = get_allocation().get_height();
|
||||
Gdk::Rectangle rect{0, 0, w, h};
|
||||
win->invalidate_rect(rect, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void
|
||||
Canvas::adjustSize()
|
||||
{
|
||||
recalcExtension_ = true;
|
||||
}
|
||||
|
||||
void
|
||||
Canvas::determineExtension()
|
||||
{
|
||||
if (not recalcExtension_) return;
|
||||
|
||||
uint extH=20, extV=20;
|
||||
Gtk::Container::ForeachSlot callback
|
||||
= [&](Gtk::Widget& chld)
|
||||
{
|
||||
auto alloc = chld.get_allocation();
|
||||
uint x = alloc.get_x();
|
||||
uint y = alloc.get_y();
|
||||
x += alloc.get_width();
|
||||
y += alloc.get_height();
|
||||
extH = max (extH, x);
|
||||
extV = max (extV, y);
|
||||
};
|
||||
foreach(callback);
|
||||
recalcExtension_ = false;
|
||||
set_size (extH, extV);
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Canvas::on_draw(Cairo::RefPtr<Cairo::Context> const& cox)
|
||||
{
|
||||
if (shallDraw_)
|
||||
{
|
||||
uint extH, extV;
|
||||
determineExtension();
|
||||
get_size (extH, extV);
|
||||
|
||||
auto adjH = get_hadjustment();
|
||||
auto adjV = get_vadjustment();
|
||||
double offH = adjH->get_value();
|
||||
double offV = adjV->get_value();
|
||||
|
||||
cox->save();
|
||||
cox->translate(-offH, -offV);
|
||||
|
||||
// draw red diagonal line
|
||||
cox->set_source_rgb(0.8, 0.0, 0.0);
|
||||
cox->set_line_width (10.0);
|
||||
cox->move_to(0, 0);
|
||||
cox->line_to(extH, extV);
|
||||
cox->stroke();
|
||||
cox->restore();
|
||||
|
||||
// cause child widgets to be redrawn
|
||||
bool event_is_handled = Gtk::Layout::on_draw(cox);
|
||||
|
||||
// any drawing which follows happens on top of child widgets...
|
||||
cox->save();
|
||||
cox->translate(-offH, -offV);
|
||||
|
||||
cox->set_source_rgb(0.2, 0.4, 0.9);
|
||||
cox->set_line_width (2.0);
|
||||
cox->rectangle(0,0, extH, extV);
|
||||
cox->stroke();
|
||||
cox->restore();
|
||||
|
||||
return event_is_handled;
|
||||
}
|
||||
else
|
||||
return Gtk::Layout::on_draw(cox);
|
||||
}
|
||||
|
||||
|
||||
/* === Support for Investigation === */
|
||||
|
||||
namespace {
|
||||
_Fmt childID("Chld-%02d");
|
||||
|
||||
int instanceCnt = 0;
|
||||
}
|
||||
|
||||
|
||||
uint ChildEx::childNo = 0;
|
||||
|
||||
|
||||
ChildEx::ChildEx()
|
||||
: Gtk::Button(string (childID % childNo++))
|
||||
{
|
||||
++instanceCnt;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ChildEx::on_clicked()
|
||||
{
|
||||
cout << "|=="<<get_label()<<endl;
|
||||
}
|
||||
|
||||
|
||||
ChildEx*
|
||||
CanvasDemoPanel::makeChld()
|
||||
{
|
||||
return Gtk::manage(new ChildEx);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////TICKET #1020 : verification code for instance management
|
||||
ChildEx::~ChildEx()
|
||||
{
|
||||
--instanceCnt;
|
||||
if (instanceCnt > 0)
|
||||
cout << " ↯↯ still "<<instanceCnt<<" children to kill..."<<endl;
|
||||
else
|
||||
if (instanceCnt == 0)
|
||||
cout << "+++ Success: all children are dead..."<<endl;
|
||||
else
|
||||
cout << "### ALARM ###"<<endl
|
||||
<< "instanceCnt == "<<instanceCnt <<endl;
|
||||
}
|
||||
|
||||
void
|
||||
__verifyDeadChildren()
|
||||
{
|
||||
if (instanceCnt == 0)
|
||||
cout << "+++ Success: all children are dead..."<<endl;
|
||||
else
|
||||
cout << "### ALARM ###"<<endl
|
||||
<< "instanceCnt == "<<instanceCnt <<endl;
|
||||
}
|
||||
////////////////////////////////////////////////////////////////////TICKET #1020 : verification code for instance management
|
||||
|
||||
|
||||
|
||||
} // namespace demo
|
||||
143
research/gtk-canvas-experiment.hpp
Normal file
143
research/gtk-canvas-experiment.hpp
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
GTK-CANVAS-EXPERIMENT.hpp - Explore capabilities of the GTK canvas widget
|
||||
|
||||
Copyright (C) Lumiera.org
|
||||
2016, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of
|
||||
the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/** @file gtk-canvas-experiment.hpp
|
||||
** A custom widget using a canvas to draw and to place widgets.
|
||||
** @remark as of 10/2018 we start to build a new timeline widget,
|
||||
** based on this technology demo.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef RESEARCH_GTK_CANVAS_EXPERIMENT_H
|
||||
#define RESEARCH_GTK_CANVAS_EXPERIMENT_H
|
||||
|
||||
#include "gui/gtk-base.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace demo {
|
||||
|
||||
|
||||
/**
|
||||
* "experimental" child widget for investigation of Gtk::Layout
|
||||
*/
|
||||
class ChildEx
|
||||
: public Gtk::Button
|
||||
{
|
||||
static uint childNo;
|
||||
|
||||
public:
|
||||
ChildEx();
|
||||
~ChildEx();
|
||||
|
||||
private:
|
||||
void on_clicked() override;
|
||||
};
|
||||
|
||||
void __verifyDeadChildren();
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* "experimental" custom canvas, based on Gtk::Layout.
|
||||
* In addition this customised widget supports direct drawing
|
||||
*/
|
||||
class Canvas
|
||||
: public Gtk::Layout
|
||||
{
|
||||
bool shallDraw_;
|
||||
bool recalcExtension_ = false;
|
||||
|
||||
public:
|
||||
void enableDraw (bool);
|
||||
void adjustSize();
|
||||
|
||||
private:
|
||||
virtual bool on_draw (Cairo::RefPtr<Cairo::Context> const&) override;
|
||||
|
||||
void determineExtension();
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Dockable panel to hold timeline widget(s).
|
||||
* @todo build the actual implementation, after finishing the investigation
|
||||
*
|
||||
* ## Investigation of gtk::GtkLayout
|
||||
* As of 10/2016, we start this task with an exploration of GTK behaviour
|
||||
*
|
||||
* \par Plan of investigation
|
||||
* 1. place some simple widgets (Buttons) ✔
|
||||
* 2. learn how to draw ✔
|
||||
* 3. place a huge number of widgets, to scrutinise scrolling and performance
|
||||
* 4. place widgets overlapping and irregularly, beyond the scrollable area ✔
|
||||
* 5. bind signals to those widgets, to verify event dispatching ✔
|
||||
* 6. bind some further signal(s) to the ~GtkLayout container
|
||||
* 7. hide and re-show a partially and a totally overlapped widget
|
||||
* 8. find a way to move a widget ✔ and delete arbitrary widgets ✔
|
||||
* 9. expand an existing widget (text change) ✔
|
||||
* 10. build a custom "clip" widget ✘
|
||||
* 11. retrofit all preceding tests to use this "clip" widget ✘
|
||||
*/
|
||||
class CanvasDemoPanel
|
||||
: public Gtk::Box
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @param panel_manager The owner panel manager widget.
|
||||
* @param dock_item The GdlDockItem that will host this panel.
|
||||
*/
|
||||
CanvasDemoPanel();
|
||||
|
||||
|
||||
|
||||
private:
|
||||
Gtk::Box twoParts_;
|
||||
Gtk::ButtonBox buttons_;
|
||||
Gtk::Button button_1_;
|
||||
Gtk::Button button_2_;
|
||||
Gtk::Button button_3_;
|
||||
Gtk::Button button_4_;
|
||||
Gtk::Button button_5_;
|
||||
Gtk::CheckButton toggleDraw_;
|
||||
Gtk::Frame frame_;
|
||||
Gtk::ScrolledWindow scroller_;
|
||||
Canvas canvas_;
|
||||
|
||||
ChildEx* makeChld();
|
||||
|
||||
using ChildV = std::vector<ChildEx*>;
|
||||
ChildV childz_;
|
||||
|
||||
void experiment_1();
|
||||
void experiment_2();
|
||||
void experiment_3();
|
||||
void experiment_4();
|
||||
void experiment_5();
|
||||
};
|
||||
|
||||
|
||||
}// namespace demo
|
||||
#endif /*RESEARCH_GTK_CANVAS_EXPERIMENT_H*/
|
||||
64
research/gtk-canvas-main.cpp
Normal file
64
research/gtk-canvas-main.cpp
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
gtk-canvas-main.cpp - demo application to explore the abilities of Gtk::Canvas
|
||||
|
||||
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 gtk-canvas-main.cpp
|
||||
** Simple GTK Application frame to explore canvas handling.
|
||||
** This demo application was extracted in 10/2018 to preserve
|
||||
** [exploration experiments](\ref demo::CanvasDemoPanel), carried
|
||||
** out in 2016 to decide upon the technology to support Lumiera's
|
||||
** gui::timeline::TimelineWidget.
|
||||
*/
|
||||
|
||||
|
||||
#include "gui/gtk-base.hpp"
|
||||
#include "gtk-canvas-experiment.hpp"
|
||||
#include "lib/error.hpp"
|
||||
|
||||
|
||||
namespace demo {
|
||||
|
||||
class Window
|
||||
: public Gtk::Window
|
||||
{
|
||||
CanvasDemoPanel demoPanel_;
|
||||
|
||||
public:
|
||||
Window()
|
||||
: demoPanel_{}
|
||||
{
|
||||
this->add (demoPanel_);
|
||||
}
|
||||
};
|
||||
}//namespace
|
||||
|
||||
|
||||
|
||||
int
|
||||
main (int argc, char* argv[])
|
||||
{
|
||||
NOTICE (main, "*** GTK-Canvas Experiment ***");
|
||||
|
||||
auto guiApp = Gtk::Application::create (argc, argv);
|
||||
|
||||
demo::Window window;
|
||||
return guiApp->run(window);
|
||||
}
|
||||
|
|
@ -39,11 +39,14 @@
|
|||
// 03/18 - Dependency Injection / Singleton initialisation / double checked locking
|
||||
// 04/18 - investigate construction of static template members
|
||||
// 08/18 - Segfault when compiling some regular expressions for EventLog search
|
||||
// 10/18 - investigate insidious reinterpret cast
|
||||
|
||||
|
||||
/** @file try.cpp
|
||||
* Heisenbug hunt: random crashes in BusTerm_test, seemingly emerging from regular expression compilation.
|
||||
* Not able to reproduce the crash, unfortunately. //////////////////////////////////////////////////////////TICKET #1158
|
||||
* Document an insidious wild cast, caused by the syntax `Type(arg)`.
|
||||
* I was under the wrong assumption this would be handled equivalent to a constructor invocation.
|
||||
* Seemingly it is rather handled as a C-style cast, i.e. equivalent to `(Type)arg`.
|
||||
* @see [Question on Stackoverflow](https://stackoverflow.com/q/52782967/444796)
|
||||
*/
|
||||
|
||||
typedef unsigned int uint;
|
||||
|
|
@ -52,12 +55,10 @@ typedef unsigned int uint;
|
|||
#include "lib/test/test-helper.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using std::string;
|
||||
using VecS = std::vector<string>;
|
||||
using util::isSameObject;
|
||||
|
||||
|
||||
|
||||
|
|
@ -69,25 +70,32 @@ using VecS = std::vector<string>;
|
|||
|
||||
|
||||
|
||||
class Wau
|
||||
{
|
||||
int i = -1;
|
||||
};
|
||||
|
||||
class Miau
|
||||
{
|
||||
public:
|
||||
uint u = 1;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int
|
||||
main (int, char**)
|
||||
{
|
||||
VecS rexs{{"after.+_ATTRIBS_.+ins.+1 of 62 ≺293.gen029≻.+mut.+1 of 62 ≺293.gen029≻.+ins.+borgID.+293.+emu.+1 of 62 ≺293.gen029≻"
|
||||
,"after.+_ATTRIBS_.+ins.+1 of 64 ≺251.gen019≻.+mut.+1 of 64 ≺251.gen019≻.+ins.+borgID.+251.+emu.+1 of 64 ≺251.gen019≻"
|
||||
,"after.+_ATTRIBS_.+ins.+1 of 8 ≺203.gen036≻.+mut.+1 of 8 ≺203.gen036≻.+ins.+borgID.+203.+emu.+1 of 8 ≺203.gen036≻"
|
||||
,"after.+?_ATTRIBS_.+?ins.+?53 of 57 ≺358.gen010≻.+?mut.+?53 of 57 ≺358.gen010≻.+?ins.+?borgID.+?358.+?emu.+?53 of 57 ≺358.gen010≻"
|
||||
,"after.+?_ATTRIBS_.+?ins.+?53 of 63 ≺178.gen028≻.+?mut.+?53 of 63 ≺178.gen028≻.+?ins.+?borgID.+?178.+?emu.+?53 of 63 ≺178.gen028≻"
|
||||
,"after.+?_ATTRIBS_.+?ins.+?53 of 59 ≺498.gen038≻.+?mut.+?53 of 59 ≺498.gen038≻.+?ins.+?borgID.+?498.+?emu.+?53 of 59 ≺498.gen038≻"
|
||||
,"after.+?_ATTRIBS_.+?ins.+?53 of 60 ≺223.gen003≻.+?mut.+?53 of 60 ≺223.gen003≻.+?ins.+?borgID.+?223.+?emu.+?53 of 60 ≺223.gen003≻"
|
||||
,"after.+?_ATTRIBS_.+?ins.+?53 of 78 ≺121.gen015≻.+?mut.+?53 of 78 ≺121.gen015≻.+?ins.+?borgID.+?121.+?emu.+?53 of 78 ≺121.gen015≻"
|
||||
}};
|
||||
for (auto const& str : rexs)
|
||||
{
|
||||
auto rex = std::regex{str};
|
||||
SHOW_EXPR (std::regex_search(str, rex));
|
||||
}
|
||||
Wau wau;
|
||||
using ID = Miau &;
|
||||
ID wuff = ID(wau);
|
||||
|
||||
cout << "Miau=" << wuff.u
|
||||
<< " ref to same object: " << isSameObject (wau, wuff)
|
||||
<< endl;
|
||||
|
||||
cout << "\n.gulp.\n";
|
||||
|
||||
|
|
|
|||
|
|
@ -38,13 +38,12 @@ plugins = [] # currently none
|
|||
# the Lumiera GTK GUI
|
||||
envGtk = env.Clone()
|
||||
envGtk.mergeConf(['gtkmm-3.0','sigc++-2.0','gthread-2.0','cairomm-1.0','gdl','xv','x11','xext','sm'])
|
||||
envGtk.Append(LIBS=core)
|
||||
|
||||
guimodule = envGtk.LumieraPlugin('gtk_gui', srcSubtree('gui'), install=True)
|
||||
guimodule = envGtk.LumieraPlugin('gtk_gui', srcSubtree('gui') + core, install=True)
|
||||
gui = ( guimodule
|
||||
+ icons
|
||||
+ [env.GuiResource(f) for f in env.Glob('gui/*.css')]
|
||||
)
|
||||
|
||||
|
||||
Export('lumiera core core_lib app_lib backend_lib support_lib plugins guimodule gui')
|
||||
Export('lumiera core core_lib app_lib backend_lib support_lib plugins envGtk guimodule gui')
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
** @todo as of 2016, this seems not to be used much, if at all.
|
||||
** The GTK-UI, which in itself is very preliminary, retrieves
|
||||
** some values from configuration, most notably the name of
|
||||
** the GTK stylesheet (`gtk-lumiera.css` is the default)
|
||||
** the GTK stylesheet (`lumiera.css` is the default)
|
||||
*/
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
/** @file guifacade.cpp
|
||||
** Implementation of the GUI loader. Code generated from this
|
||||
** translation unit is linked into the core application, where it implements
|
||||
** the [Subsystem descriptor](subsys.hpp) for the _UI Subsystem._ When main()
|
||||
** the [Subsystem descriptor](\ref subsys.hpp) for the _UI Subsystem._ When main()
|
||||
** activates and starts this subsystem, an instance of gui::GuiRunner will be
|
||||
** created, which causes the Lumiera UI plug-in to be loaded and a new thread
|
||||
** to be spawned, which launches the UI and performs the event loop.
|
||||
|
|
@ -150,7 +150,7 @@ namespace gui {
|
|||
ERROR_LOG_AND_IGNORE (guifacade, "trigger shutdown of the GUI");
|
||||
}
|
||||
|
||||
bool
|
||||
bool
|
||||
checkRunningState () noexcept override
|
||||
{
|
||||
return bool(facade);
|
||||
|
|
@ -210,15 +210,6 @@ namespace gui {
|
|||
{
|
||||
return bool(facade);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace gui
|
||||
|
||||
/*
|
||||
// Local Variables:
|
||||
// mode: C++
|
||||
// c-file-style: "gnu"
|
||||
// indent-tabs-mode: nil
|
||||
// End:
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ namespace gui {
|
|||
namespace lumiera {
|
||||
namespace facade {
|
||||
using gui::ID;
|
||||
using lib::diff::GenNode;
|
||||
using lib::diff::MutationMessage;
|
||||
|
||||
|
||||
|
|
@ -62,11 +63,12 @@ namespace facade {
|
|||
|
||||
//----Proxy-Implementation-of-GuiNotification--------
|
||||
|
||||
void displayInfo (Level level, string const& text) override { _i_.displayInfo (level, cStr(text)); }
|
||||
void markError (ID uiElement, string const& text) override { _i_.markError(uiElement.getHash().get(), cStr(text)); }
|
||||
void markNote (ID uiElement, string const& text) override { _i_.markNote (uiElement.getHash().get(), cStr(text)); }
|
||||
void mutate (ID uiElement, MutationMessage&& diff) override { _i_.mutate (uiElement.getHash().get(), &diff); }
|
||||
void triggerGuiShutdown (string const& cause) override { _i_.triggerGuiShutdown (cStr(cause)); }
|
||||
void displayInfo (Level level, string const& text) override { _i_.displayInfo (level, cStr(text)); }
|
||||
void markError (ID uiElement, string const& text) override { _i_.markError(&uiElement, cStr(text)); }
|
||||
void markNote (ID uiElement, string const& text) override { _i_.markNote (&uiElement, cStr(text)); }
|
||||
void mark (ID uiElement, GenNode&& stateMark) override { _i_.mark (&uiElement, &stateMark); }
|
||||
void mutate (ID uiElement, MutationMessage&& diff) override { _i_.mutate (&uiElement, &diff); }
|
||||
void triggerGuiShutdown (string const& cause) override { _i_.triggerGuiShutdown (cStr(cause)); }
|
||||
|
||||
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ namespace lumiera {
|
|||
* @todo describe the actual options
|
||||
*/
|
||||
Option::Option (lib::Cmdline& cmdline)
|
||||
: syntax("Lumiera, the non linear video editor. Supported parameters"),
|
||||
: syntax("Lumiera, the non linear video editor.\nSupported parameters"),
|
||||
parameters()
|
||||
{
|
||||
syntax.add_options()
|
||||
|
|
@ -64,7 +64,7 @@ namespace lumiera {
|
|||
("session,f", op::value<string>(),
|
||||
"session file to load (UNIMPLEMENTED)")
|
||||
("script,s", op::value<VectS>(),
|
||||
"execute the given LUA script (UNIMPLEMENTED)")
|
||||
"execute the given script (UNIMPLEMENTED)")
|
||||
("headless", op::bool_switch(),
|
||||
"start without GUI")
|
||||
("port,p", op::value<int>(),
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ namespace lumiera {
|
|||
/**
|
||||
* Interface: a facility for resolving (some kind of) queries
|
||||
* A concrete subclass has the ability to create Resolution instances
|
||||
* in response to specific queries of some kind, [if applicable](QueryResolver::canHandle).
|
||||
* in response to specific queries of some kind, [if applicable](\ref QueryResolver::canHandle).
|
||||
* Every resolution mechanism is expected to enrol by calling #installResolutionCase.
|
||||
* Such a registration is considered permanent; a factory function gets stored,
|
||||
* assuming that the entity to implement this function remains available
|
||||
|
|
|
|||
|
|
@ -22,12 +22,13 @@
|
|||
|
||||
|
||||
/** @file subsystem-runner.hpp
|
||||
** Manage execution of the independend [Subsystems](\ref subsys.hpp)
|
||||
** Manage execution of the independent [Subsystems](\ref subsys.hpp)
|
||||
** of the Lumiera application. The SubsystemRunner is used by
|
||||
** lumiera::AppState::init() for actually "performing" the various subsystems,
|
||||
** as defined by their [Subsystem descriptors](\ref lumiera::Subsys). Together
|
||||
** these parts define the lifecycle protocol.
|
||||
**
|
||||
** @todo as of 2018, a design rework seems indicated eventually /////////////////////////TICKET #1177
|
||||
** @remark While this facility is still pretty much the first implementation draft
|
||||
** from 2008, it fulfilled our needs to express simple dependencies and to
|
||||
** conduct a controlled shutdown in case of fatal problems. The operations
|
||||
|
|
|
|||
63
src/common/ui-protocol.cpp
Normal file
63
src/common/ui-protocol.cpp
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
UiProtocol - magic keys used for communication with the Lumiera 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 ui-protocol.cpp
|
||||
** Storage for key constants and basic definitions used for interfacing with the GUI.
|
||||
*/
|
||||
|
||||
//#include "gui/gtk-base.hpp" /////////////////////TODO necessary?
|
||||
#include "include/ui-protocol.hpp"
|
||||
|
||||
|
||||
namespace gui {
|
||||
|
||||
const Symbol META_kind{"kind"};
|
||||
|
||||
const Symbol TYPE_Fork{"Fork"};
|
||||
const Symbol TYPE_Clip{"Clip"};
|
||||
const Symbol TYPE_Marker{"Marker"};
|
||||
const Symbol TYPE_Channel{"Channel"};
|
||||
const Symbol TYPE_Effect{"Effect"};
|
||||
|
||||
const Symbol ATTR_name{"name"};
|
||||
const Symbol ATTR_fork{"fork"};
|
||||
const Symbol ATTR_assets{"assets"};
|
||||
|
||||
|
||||
/* ======== UI-Element protocol ======== */
|
||||
|
||||
const Symbol MARK_reset{"reset"};
|
||||
const Symbol MARK_clearErr{"clearErr"};
|
||||
const Symbol MARK_clearMsg{"clearMsg"};
|
||||
const Symbol MARK_expand{"expand"};
|
||||
const Symbol MARK_reveal{"reveal"};
|
||||
const Symbol MARK_Flash{"Flash"};
|
||||
const Symbol MARK_Error{"Error"};
|
||||
const Symbol MARK_Warning{"Warning"};
|
||||
const Symbol MARK_Message{"Message"};
|
||||
|
||||
/** */
|
||||
|
||||
|
||||
|
||||
}// namespace gui
|
||||
|
|
@ -70,8 +70,8 @@ namespace ctrl {
|
|||
|
||||
|
||||
/**
|
||||
* A helper class which registers and handles
|
||||
* user action events.
|
||||
* A helper class which registers global user actions
|
||||
* and populates the main menu and toolbar.
|
||||
*/
|
||||
class Actions
|
||||
: util::NonCopyable
|
||||
|
|
@ -130,6 +130,7 @@ namespace ctrl {
|
|||
|
||||
menu("HelpMenu", _("_Help"));
|
||||
entry ([&]() { globalCtx_.wizard_.show_HelpAbout(); } , "HelpAbout", Stock::ABOUT);
|
||||
entry ([&]() { globalCtx_.wizard_.launchTestCtrl(); } , "HelpTest", _("Self _Tests..."));
|
||||
|
||||
|
||||
menu("WindowMenu", _("_Window"));
|
||||
|
|
@ -202,6 +203,7 @@ namespace ctrl {
|
|||
</menu>
|
||||
<menu action='HelpMenu'>
|
||||
<menuitem action='HelpAbout'/>
|
||||
<menuitem action='HelpTest'/>
|
||||
</menu>
|
||||
</menubar>
|
||||
<toolbar name='ToolBar'>
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
** which it will be wired. Moreover, each BusTerm bears a distinct
|
||||
** [identity](\ref BusTerm::endpointID_), which is used as _implicit subject_
|
||||
** for emanating messages, or as explicit destination for routing.
|
||||
** The whole [UI-Bus](ui-bus.hpp) is built to perform within the
|
||||
** The whole [UI-Bus](\ref ui-bus.hpp) is built to perform within the
|
||||
** UI event thread and thus is _not threadsafe_. For that reason,
|
||||
** the automatic detachment built into each BusTerm's dtor is
|
||||
** sufficient to ensure sane connectivity.
|
||||
|
|
|
|||
|
|
@ -39,6 +39,11 @@
|
|||
** since both CoreService and Nexus are mutually interdependent from an
|
||||
** operational perspective, since they exchange messages in both directions.
|
||||
**
|
||||
** In fact, the CoreService even _holds and thus manages_ the Nexus as a
|
||||
** private member, while the latter controls and connects all nodes attached
|
||||
** to the bus at runtime, including CoreService. This crisscross arrangement
|
||||
** ensures sane start-up and shutdown of the whole UI-Bus compound.
|
||||
**
|
||||
** ## Bus connection and topology
|
||||
** The CoreService plays a central role within the UI, since it represents
|
||||
** _»the application core«_ from the UI layer's viewpoint. But it is not
|
||||
|
|
@ -47,8 +52,8 @@
|
|||
** routing table in Nexus, can be addressed as a _first class citizen,_
|
||||
** that is, we're able to direct messages towards such an element, knowing
|
||||
** only it's ID. But there is a twist: all connections to the Bus are made
|
||||
** from [bus terminals](ctrl::BusTerm), and each _node_, i.e. each
|
||||
** [tangible model element](model::Tangible) has a BusTerm member and
|
||||
** from [bus terminals](\ref ctrl::BusTerm), and each _node_, i.e. each
|
||||
** [tangible model element](\ref model::Tangible) has a BusTerm member and
|
||||
** thus inherits the ability to talk to the bus. But only when _actively_
|
||||
** connected to the bus, a full link and entry in the routing table is
|
||||
** established. The constructor of model::Tangible indeed makes such
|
||||
|
|
@ -65,8 +70,8 @@
|
|||
** for the Nexus, and thus gains the ability to respond to those few special
|
||||
** messages, which can not be handled in a generic way on the Nexus:
|
||||
** - *act* handles command invocation within the Session core, and
|
||||
** is treated by [forwarding](command-handler.hpp) it over the
|
||||
** SessionCommand facade to the [Proc-Dispatcher](proc-dispatcher.hpp)
|
||||
** is treated by [forwarding](\ref command-handler.hpp) it over the
|
||||
** SessionCommand facade to the [Proc-Dispatcher](\ref proc-dispatcher.hpp)
|
||||
** - *note* observes and captures presentation state note messages, which
|
||||
** are to be handled by a central presentation state manager (TODO 1/17).
|
||||
**
|
||||
|
|
@ -103,6 +108,8 @@ namespace ctrl{
|
|||
* handles those messages to be processed by centralised services:
|
||||
* - commands need to be sent down to Proc-Layer
|
||||
* - presentation state messages need to be recorded and acted upon.
|
||||
* As an object, CoreService encases the heart of the UI-Bus, the
|
||||
* \ref Nexus, and acts as "PImpl" for the gui::UiBus front-end.
|
||||
*/
|
||||
class CoreService
|
||||
: public BusTerm
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@
|
|||
**
|
||||
** @note GTK operates single threaded by design.
|
||||
** For this reason, any call from other parts of the application need to be explicitly
|
||||
** dispatched into the UI event loop. The external facade interfaces are defined in a way
|
||||
** to ensure this constraint is met.
|
||||
** dispatched into the UI event loop. The external façade interfaces are constructed
|
||||
** appropriately to ensure this constraint is regarded.
|
||||
**
|
||||
** @see notification-service.hpp
|
||||
** @see ui-manager.hpp
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@
|
|||
|
||||
/** @file nexus.hpp
|
||||
** Core hub and routing table of the UI-Bus.
|
||||
** Any relevant element within the Lumiera GTK UI is connected to the [UI-Bus](ui-bus.hpp)
|
||||
** through some [bus terminal](bus-term.hpp). Actually, there is one special BustTerm
|
||||
** Any relevant element within the Lumiera GTK UI is connected to the [UI-Bus](\ref ui-bus.hpp)
|
||||
** through some [bus terminal](\ref bus-term.hpp). Actually, there is one special BustTerm
|
||||
** implementation, which acts as router and messaging hub.
|
||||
**
|
||||
** @note messages to unknown target elements are silently dropped.
|
||||
|
|
@ -69,7 +69,7 @@ namespace ctrl{
|
|||
*
|
||||
* Thus, the "uplink", which is in fact the CoreService, is left to handle
|
||||
* - the *`act`* messages (command invocation)
|
||||
* - the *`mark`* messages (upstream state change notification)
|
||||
* - the *`note`* messages (upstream state change notification)
|
||||
*
|
||||
* @todo write type comment
|
||||
*/
|
||||
|
|
|
|||
208
src/gui/ctrl/notification-hub.hpp
Normal file
208
src/gui/ctrl/notification-hub.hpp
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
/*
|
||||
NOTIFICATION-HUB.hpp - receive and reroute notification messages
|
||||
|
||||
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 notification-hub.hpp
|
||||
** Controller to receive and handle notification messages for the user.
|
||||
** This component is addressed in order to show error, warning and notification messages
|
||||
** in the UI. It is responsible for maintaining a local log of these messages, and to
|
||||
** allocate an appropriate display widgets, to show such notifications asynchronously.
|
||||
** On first use, an InfoboxPanel is allocated to hold an ErrorLogDisplay widget for
|
||||
** presentation of those messages
|
||||
**
|
||||
** # Notification Controller Behaviour
|
||||
**
|
||||
** This controller serves the purpose to present information and error messages to the user.
|
||||
** For the actual display, it allocates an appropriate view, placed into a docking pane
|
||||
** reserved for information display, in accordance to the configured screen layout
|
||||
** @todo 8/2018 for now we just use and possibly create a hard wired display within the
|
||||
** first (primary) application window. Moreover, we do so directly, instead of
|
||||
** consulting the (not yet fully implemented) ViewSpec mechanism. (via ViewLocator).
|
||||
**
|
||||
** The actual widget for information display is prepared, but it is actually allocated when
|
||||
** the need for information display arises. Which means, the user may close this display
|
||||
** widget, thereby discarding its current information content -- but when the next notification
|
||||
** needs to be shown, the controller will ensure to allocate an appropriate widget again.
|
||||
** Moreover, the widget can be _expanded_ or _collapsed_, without affecting its content.
|
||||
** - information messages are just added to the buffer without much ado. No attempt is made
|
||||
** to reveal or expand the widget (but if necessary, a new widget is allocated)
|
||||
** - error messages also set an error marker state, and cause expansion of the display widget.
|
||||
** - the error state can be _cleared_, which also demotes all error messages to mere information.
|
||||
** - information content can also be _cleared_, which removes all mere information messages,
|
||||
** while retaining the error entries.
|
||||
** - the doReset() operation completely clears the log contents, collapses the widget and clears state.
|
||||
** - expanding of the display widget is state marked, irrespective if it happened by user interaction
|
||||
** or as result of some display. However, the actual message content is _not_ state marked; it needs
|
||||
** to be persisted elsewhere (in the session) and replayed from there if desired.
|
||||
**
|
||||
** @see error-log-display.hpp
|
||||
** @see notification-service.hpp
|
||||
*/
|
||||
|
||||
|
||||
#ifndef GUI_CTRL_NOTIFICATION_HUB_H
|
||||
#define GUI_CTRL_NOTIFICATION_HUB_H
|
||||
|
||||
#include "gui/widget/error-log-display.hpp"
|
||||
#include "lib/diff/tree-mutator.hpp"
|
||||
#include "gui/model/controller.hpp"
|
||||
#include "gui/model/w-link.hpp"
|
||||
|
||||
#include <functional>
|
||||
|
||||
|
||||
namespace gui {
|
||||
namespace ctrl {
|
||||
|
||||
using lib::diff::TreeMutator;
|
||||
using model::WLink;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Service to receive and display error, warning and notification messages.
|
||||
* These are sent over the UI-Bus through the NotificationService; after receiving
|
||||
* such a message, this controller ensures to display the message and alert the
|
||||
* user, while not blocking the overall UI.
|
||||
*/
|
||||
class NotificationHub
|
||||
: public model::Controller
|
||||
{
|
||||
|
||||
|
||||
/** population and manipulation of persistent content via UI-Bus */
|
||||
void
|
||||
buildMutator (lib::diff::TreeMutator::Handle buffer) override
|
||||
{
|
||||
buffer.create (
|
||||
TreeMutator::build()
|
||||
);
|
||||
UNIMPLEMENTED ("define and implement what need to be reflected from asset::ErrorLog");
|
||||
}
|
||||
|
||||
|
||||
/* ==== Tangible interface ==== */
|
||||
|
||||
virtual bool
|
||||
doReset() override
|
||||
{
|
||||
if (not widget_) return false;
|
||||
|
||||
widget_->clearAll();
|
||||
widget_->expand (false);
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool
|
||||
doMsg (string text) override
|
||||
{
|
||||
getWidget().addInfo (text);
|
||||
return false; // logging is no persistent state
|
||||
}
|
||||
|
||||
virtual bool
|
||||
doClearMsg () override
|
||||
{
|
||||
if (widget_)
|
||||
widget_->clearInfoMsg();
|
||||
return false; // not persistent (sticky)
|
||||
}
|
||||
|
||||
virtual bool
|
||||
doErr (string text) override
|
||||
{
|
||||
getWidget().addError (text);
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool
|
||||
doClearErr () override
|
||||
{
|
||||
if (widget_)
|
||||
widget_->turnError_into_InfoMsg();
|
||||
return false; // not persistent (sticky)
|
||||
}
|
||||
|
||||
/** adds special treatment for a state mark tagged as `"Warning"` */
|
||||
virtual void
|
||||
doMark (GenNode const& stateMark) override
|
||||
{
|
||||
if (stateMark.idi.getSym() == MARK_Warning)
|
||||
getWidget().addWarn (stateMark.data.get<string>());
|
||||
else
|
||||
// forward to default handler
|
||||
Controller::doMark (stateMark);
|
||||
}
|
||||
|
||||
virtual void
|
||||
doFlash() override
|
||||
{
|
||||
if (not widget_) return;
|
||||
widget_->reveal();
|
||||
widget_->triggerFlash();
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
using WidgetAllocator = std::function<widget::ErrorLogDisplay&(void)>;
|
||||
|
||||
|
||||
NotificationHub (ID identity, ctrl::BusTerm& nexus, WidgetAllocator wa)
|
||||
: model::Controller{identity, nexus}
|
||||
, allocateWidget_{wa}
|
||||
, widget_{}
|
||||
{
|
||||
installExpander([&](){ return widget_ and widget_->expand; }
|
||||
,[&](bool yes)
|
||||
{
|
||||
if (widget_ or yes)
|
||||
getWidget().expand (yes);
|
||||
}
|
||||
);
|
||||
installRevealer([&](){ getWidget().reveal(); }); // implementation implies also expand
|
||||
}
|
||||
|
||||
~NotificationHub() { };
|
||||
|
||||
|
||||
private:
|
||||
/** external operation to find or allocate an log display widget */
|
||||
WidgetAllocator allocateWidget_;
|
||||
|
||||
/** collaboration with a log display allocated elsewhere */
|
||||
WLink<widget::ErrorLogDisplay> widget_;
|
||||
|
||||
|
||||
widget::ErrorLogDisplay&
|
||||
getWidget()
|
||||
{
|
||||
if (not widget_)
|
||||
widget_.connect (allocateWidget_());
|
||||
return *widget_;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
}}// namespace gui::ctrl
|
||||
#endif /*GUI_CTRL_NOTIFICATION_HUB_H*/
|
||||
|
|
@ -25,7 +25,21 @@
|
|||
** 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
|
||||
** docking panels on a conceptual level.
|
||||
**
|
||||
** @todo 8/2018 refactoring of docking panel handling is underway ///////////////////////////////////////TICKET # 1144
|
||||
** The PanelLocator is planned to become the one central coordinating
|
||||
** manager, while the existing panel managers in each window will be
|
||||
** demoted to a mere DockArea, corresponding to a `gdl::Dock`
|
||||
**
|
||||
** @todo 8/2018 there is some overlap with the (not yet fully functional)
|
||||
** ViewLocator in the InteractionDirector. Right now, PanelLocator
|
||||
** fills some gaps here and offers preliminary solutions for some
|
||||
** lookup and allocation tasks ultimately to be performed on a more
|
||||
** abstract level through the ViewSpec-DSL. This corresponds to the
|
||||
** observation, that PanelLocator and WindowLocator are somewhat
|
||||
** tangled currently. The goal is to turn them into access services,
|
||||
** and shift the allocation logic to the aforementioned higher level.
|
||||
**
|
||||
** @see view-locator.hpp
|
||||
** @see interaction-director.hpp
|
||||
|
|
@ -36,17 +50,29 @@
|
|||
#define GUI_CTRL_PANEL_LOCATOR_H
|
||||
|
||||
#include "gui/gtk-base.hpp"
|
||||
#include "gui/workspace/panel-manager.hpp"
|
||||
#include "lib/format-string.hpp"
|
||||
#include "lib/meta/util.hpp"
|
||||
#include "lib/nocopy.hpp"
|
||||
|
||||
#include <type_traits>
|
||||
#include <memory>
|
||||
#include <list>
|
||||
|
||||
|
||||
namespace gui {
|
||||
namespace workspace { class WorkspaceWindow; }
|
||||
namespace workspace {
|
||||
class WorkspaceWindow;
|
||||
}
|
||||
namespace panel {
|
||||
class Panel;
|
||||
}
|
||||
namespace ctrl {
|
||||
|
||||
namespace error = lumiera::error;
|
||||
|
||||
using panel::Panel;
|
||||
using util::_Fmt;
|
||||
using std::list;
|
||||
|
||||
|
||||
|
|
@ -68,13 +94,42 @@ namespace ctrl {
|
|||
public:
|
||||
PanelLocator (WindowList&);
|
||||
|
||||
private:
|
||||
/** lookup or allocate "the" instance of the indicated Panel(subtype).
|
||||
* While there can be multiple instances of some panel type in various
|
||||
* windows, this function will look through a canonical search order
|
||||
* to either retrieve the first instance found, or create a new one
|
||||
* in the _primary window_.
|
||||
* @return reference to the indicated Panel subclass (down-casted)
|
||||
* @throw error::Invalid when requesting a target class not recognised
|
||||
* as known docking panel entity.
|
||||
* @deprecated 8/2018 the (planned) ViewLocator will perform essentially
|
||||
* the same task, albeit on a more flexible and configurable level.
|
||||
*/
|
||||
template<class PAN>
|
||||
PAN& find_or_create();
|
||||
|
||||
|
||||
private:
|
||||
panel::Panel& preliminary_impl_PanelLookup (int typeID);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/** */
|
||||
/** @remark delegating front-end for type safety and error handling. */
|
||||
template<class PAN>
|
||||
inline PAN&
|
||||
PanelLocator::find_or_create()
|
||||
{
|
||||
static_assert (std::is_base_of<panel::Panel, PAN>(), "Lumiera Panel subclass expected");
|
||||
|
||||
int typeID = workspace::PanelManager::findPanelID<PAN>();
|
||||
if (-1 == typeID)
|
||||
throw error::Invalid (_Fmt{"Requested type «%s» not usable as docking panel."}
|
||||
% util::typeStr<PAN>()
|
||||
,error::LERR_(WRONG_TYPE));
|
||||
|
||||
return dynamic_cast<PAN&> (preliminary_impl_PanelLookup (typeID));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@
|
|||
#include "gui/ctrl/bus-term.hpp"
|
||||
#include "gui/ctrl/state-manager.hpp"
|
||||
#include "gui/ctrl/state-map-grouping-storage.hpp"
|
||||
#include "include/ui-protocol.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
|
|
@ -174,19 +175,27 @@ namespace ctrl {
|
|||
void
|
||||
recordState (ID uiElm, StateMark stateMark)
|
||||
{
|
||||
if ("reset" == stateMark.idi.getSym())
|
||||
if (MARK_reset == stateMark.idi.getSym())
|
||||
storage_.clearState (uiElm);
|
||||
else
|
||||
if ("clearErr" == stateMark.idi.getSym())
|
||||
storage_.clearProperty (uiElm, "Error");
|
||||
if (MARK_clearErr == stateMark.idi.getSym())
|
||||
storage_.clearProperty (uiElm, string{MARK_Error});
|
||||
else
|
||||
if ("clearMsg" == stateMark.idi.getSym())
|
||||
storage_.clearProperty (uiElm, "Message");
|
||||
if (MARK_clearMsg == stateMark.idi.getSym())
|
||||
storage_.clearProperty (uiElm, string{MARK_Message});
|
||||
else
|
||||
storage_.record (uiElm, stateMark);
|
||||
}
|
||||
|
||||
private:
|
||||
/** @remark depending on the response of the notified element,
|
||||
* this might lead to a reentrant record() call, because the
|
||||
* element might chose to send a _note_ message immediately,
|
||||
* to record a state change, since it can not know this _is_
|
||||
* already the replay of a stored state note. However, this
|
||||
* reentrant call is harmless, it just updates the existing
|
||||
* entry with identical content.
|
||||
*/
|
||||
void
|
||||
replayPropertiesOf (Record entry)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ namespace ctrl {
|
|||
}
|
||||
|
||||
|
||||
/** similar to #findFocusWindow(), for the 'has_focus' property
|
||||
/** similar to #findActiveWindow(), for the 'has_focus' property
|
||||
* @note likewise we return the first window in list, in case no window
|
||||
* has keyboard focus. This may very well be the case.
|
||||
*/
|
||||
|
|
@ -176,6 +176,32 @@ namespace ctrl {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* @internal preliminary implementation of docking panel lookup and allocation.
|
||||
* @todo 8/2018 the actual implementation shall eventually be provided by the ViewLocator
|
||||
* on a higher abstraction level, and configurable through the ViewSpec-DSL.
|
||||
* For now we use a braindead iterate-through-all-windows approach.
|
||||
*/
|
||||
panel::Panel&
|
||||
PanelLocator::preliminary_impl_PanelLookup (int typeID)
|
||||
{
|
||||
REQUIRE (not isnil(windowList_));
|
||||
|
||||
for (auto window : windowList_)
|
||||
if (window->getPanelManager().hasPanel (typeID))
|
||||
return window->getPanelManager().showPanel (typeID);
|
||||
|
||||
// no instance of the desired panel type exists yet...
|
||||
for (auto window : windowList_)
|
||||
if (window->is_active())
|
||||
return window->getPanelManager().showPanel (typeID);
|
||||
|
||||
// use the first window in list when none is active
|
||||
return windowList_.front()->getPanelManager().showPanel (typeID);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}}// namespace gui::ctrl
|
||||
|
|
|
|||
|
|
@ -1,112 +0,0 @@
|
|||
/*
|
||||
DemoGuiRoundtrip - placeholder to further progress of the GUI-Session connection
|
||||
|
||||
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 demo-gui-roundtrip.cpp
|
||||
** Dummy and scaffolding to help development of the UI - Session connection.
|
||||
** The Goal is to establish a full messaging round-trip between UI and Proc-Layer
|
||||
** - a menu entry
|
||||
** - which issues a Proc-Command
|
||||
** - which in turn accesses the Notification-facade
|
||||
** - and sends a "mark" message towards some UI demo widget
|
||||
** - which in turn displays the mark text
|
||||
**
|
||||
** @todo WIP as of 8/2017 ////////////////////////////////////////////////////////////////////////TICKET #1099
|
||||
**
|
||||
** @see CallQueue_test
|
||||
** @see notification-service.hpp
|
||||
**
|
||||
*/
|
||||
|
||||
#include "gui/gtk-base.hpp"
|
||||
|
||||
#include "lib/error.hpp"
|
||||
#include "lib/depend.hpp"
|
||||
#include "gui/ui-bus.hpp"
|
||||
#include "gui/ctrl/ui-manager.hpp"
|
||||
#include "lib/nocopy.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
|
||||
|
||||
//using lumiera::error::LUMIERA_ERROR_STATE;
|
||||
|
||||
using std::string;
|
||||
|
||||
|
||||
namespace gui {
|
||||
|
||||
namespace { // implementation details
|
||||
|
||||
//using util::isnil;
|
||||
using std::string;
|
||||
|
||||
} // (End) impl details
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Quick-n-dirty hack to establish a prototypic connection between Application Core and UI...
|
||||
*
|
||||
* @todo be commendable
|
||||
* @todo don't misbehave
|
||||
*
|
||||
* @see SomeSystem
|
||||
* @see CallQueue_test
|
||||
*/
|
||||
class DemoGuiRoundtrip
|
||||
: util::NonCopyable
|
||||
{
|
||||
string nothing_;
|
||||
|
||||
DemoGuiRoundtrip();
|
||||
~DemoGuiRoundtrip();
|
||||
|
||||
friend class lib::DependencyFactory<DemoGuiRoundtrip>;
|
||||
|
||||
public:
|
||||
/** access point to set up the scaffolding.
|
||||
* @internal this is a development helper and will be remoulded all the time.
|
||||
*/
|
||||
static lib::Depend<DemoGuiRoundtrip> instance;
|
||||
|
||||
|
||||
/* == X interface for == */
|
||||
|
||||
/* == Adapter interface for == */
|
||||
|
||||
#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #1099
|
||||
void setSolution (string const& solution = "")
|
||||
{
|
||||
UNIMPLEMENTED ("tbw");
|
||||
if (isDeaf())
|
||||
this->transmogrify (solution);
|
||||
}
|
||||
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #1099
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace gui
|
||||
362
src/gui/dialog/test-control.hpp
Normal file
362
src/gui/dialog/test-control.hpp
Normal file
|
|
@ -0,0 +1,362 @@
|
|||
/*
|
||||
TEST-CONTROL.hpp - child window to trigger self tests and diagnostics
|
||||
|
||||
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 test-control.hpp
|
||||
** Non-modal dialog window to trigger diagnostics and self test routines.
|
||||
** This window can be launched from the help menu as child window of the current
|
||||
** top-level workspace window and stays outside of the regular window and perspective
|
||||
** management of the Lumiera UI. This window allows for quick-n-dirty development
|
||||
** helper routines to be hooked up easily within the UI. Actions launched from here
|
||||
** perform within the same environment as regular user interactions.
|
||||
**
|
||||
** @todo as of 9/2018, this is a first rough draft, relevant for #1099 ////////////////////////////TICKET #1140 gradually augment the self-diagnostics controls in the UI
|
||||
** @todo this header also features a design draft how to simplify building notebook widgets.
|
||||
** Which could be polished and moved into a separate utility header eventually.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef GUI_DIALOG_TEST_CONTROL_H
|
||||
#define GUI_DIALOG_TEST_CONTROL_H
|
||||
|
||||
|
||||
#include "gui/gtk-base.hpp"
|
||||
#include "gui/dialog/dialog.hpp"
|
||||
#include "gui/ctrl/bus-term.hpp"
|
||||
#include "gui/model/tangible.hpp"
|
||||
#include "include/ui-protocol.hpp"
|
||||
|
||||
#include "proc/cmd.hpp"
|
||||
#include "include/gui-notification-facade.h"
|
||||
#include "proc/mobject/session/dummy-session-connection.hpp"
|
||||
|
||||
#include "lib/test/test-helper.hpp"
|
||||
#include "lib/diff/gen-node.hpp"
|
||||
#include "lib/scoped-ptrvect.hpp"
|
||||
#include "lib/format-string.hpp"
|
||||
#include "lib/format-cout.hpp"
|
||||
#include "lib/nocopy.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <string>
|
||||
|
||||
namespace gui {
|
||||
namespace dialog {
|
||||
|
||||
using std::string;
|
||||
using std::forward;
|
||||
using lib::diff::GenNode;
|
||||
using util::sanitise;
|
||||
using util::_Fmt;
|
||||
|
||||
|
||||
/**
|
||||
* Building block for a segment within a dialog page.
|
||||
* This helper widget provides the typical sub section of a dialog
|
||||
* with several child widgets stacked vertically and enclosed within
|
||||
* a frame with a label. The frame serves as the parent widget, as far
|
||||
* as the widget hierarchy is concerned. Both parts are publicly accessible
|
||||
* as members, while providing a shortcut for publishing the box.
|
||||
*/
|
||||
struct FrameBox
|
||||
{
|
||||
Gtk::Frame frame;
|
||||
Gtk::Box box;
|
||||
|
||||
FrameBox (cuString label,
|
||||
Gtk::Orientation orientation =Gtk::ORIENTATION_VERTICAL)
|
||||
: frame{label}
|
||||
, box{orientation}
|
||||
{
|
||||
frame.add (box);
|
||||
}
|
||||
|
||||
operator Gtk::Widget&() { return frame; }
|
||||
|
||||
void
|
||||
pack_start(Gtk::Widget& child, Gtk::PackOptions options =Gtk::PACK_EXPAND_WIDGET, guint padding =0)
|
||||
{
|
||||
box.pack_start (child, options, padding);
|
||||
}
|
||||
};
|
||||
|
||||
/** explicitly named shortcut for the typical dialog page content holder */
|
||||
class Page
|
||||
: public Gtk::Box
|
||||
, util::NonCopyable
|
||||
{
|
||||
public:
|
||||
Page()
|
||||
: Gtk::Box{Gtk::ORIENTATION_VERTICAL}
|
||||
{ }
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper widget to simplify construction and wiring of a [Notebook] widget.
|
||||
* Gtk::Notebook is quite powerful container foundation to build complex dialog widgets with
|
||||
* multiple pages on tabs. However, the construction, wiring and setup is notoriously tedious,
|
||||
* due to the repetitiveness and the sheer amount of child widgets spread over various pages.
|
||||
*
|
||||
* This design draft is an attempt to mitigate the required boilerplate, without overly much
|
||||
* obscuring the structure. The basic idea is to package each page into a locally defined child
|
||||
* struct, which is actually heap allocated and managed automatically. This way, each child page
|
||||
* gets its own namespace, and wiring to other components is made explicit by passing named ctor
|
||||
* arguments -- while the overall structure of building and wiring of widgets stays close to the
|
||||
* habits of [programming with GTKmm](https://developer.gnome.org/gtkmm-tutorial/stable/).
|
||||
* - define the pages as custom widgets, typically just as locally known struct types
|
||||
* - invoke #buildPage passing the type and tab label for each page
|
||||
* - define the wiring of the components within a page in the page's ctor
|
||||
* - possibly pass external dependencies for wiring into that ctor
|
||||
* @note the page widgets are actually heap allocated and managed automatically
|
||||
* @see gui::dialog::TestControl as a usage example
|
||||
*
|
||||
* [Notebook]: https://developer.gnome.org/gtkmm-tutorial/stable/sec-multi-item-containers.html.en#sec-notebook
|
||||
*/
|
||||
class Notebook
|
||||
: public Gtk::Notebook
|
||||
, lib::ScopedPtrVect<Gtk::Widget>
|
||||
{
|
||||
public:
|
||||
template<class PAG, typename...ARGS>
|
||||
Notebook&
|
||||
buildPage (cuString pageLabel, ARGS&& ...ctorArgs)
|
||||
{
|
||||
Gtk::Notebook::append_page (this->manage (new PAG(forward<ARGS> (ctorArgs)...))
|
||||
,pageLabel);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A complex, tabbed-notebook-style non-modal dialog window,
|
||||
* dedicated to development, diagnostics and experimentation.
|
||||
* The TestControl can be launched from Lumiera's "Help" menu,
|
||||
* offers an (passive, up-link) [UI-Bus connection](\ref ui-bus.hpp)
|
||||
* and simplifies adding pages for occasional experiments and diagnostics.
|
||||
*/
|
||||
class TestControl
|
||||
: public Gtk::Dialog
|
||||
{
|
||||
using Bus = ctrl::BusTerm&;
|
||||
|
||||
Bus uiBus_;
|
||||
Notebook notebook_;
|
||||
|
||||
|
||||
/**
|
||||
* Ticket #1099 : perform a dummy round-trip to verify Proc-GUI integration.
|
||||
* This routine invokes the command `test_meta_displayInfo` and friends down in Proc-Layer,
|
||||
* passing the settings from the radio buttons to select the flavour of feedback, and the text
|
||||
* for feedback content. The expected behaviour is for the invoked command to send a feedback
|
||||
* via UI-Bus towards the ErrorLogDisplay within the InfoboxPanel.
|
||||
*/
|
||||
struct Page1 : Page
|
||||
{
|
||||
Gtk::Entry content_;
|
||||
FrameBox seg_1_{_("log notification"), Gtk::ORIENTATION_HORIZONTAL},
|
||||
seg_2_{_("mark via UI-Bus")};
|
||||
Gtk::Button trig_1_, trig_2_, trig_3_, trig_4_;
|
||||
Gtk::RadioButton level_info_{"Info"},
|
||||
level_warn_{"Warn"},
|
||||
level_erro_{"Error"};
|
||||
Gtk::Box markParam_;
|
||||
Gtk::ComboBoxText actionID_{true}; // has free-text entry field
|
||||
|
||||
int
|
||||
getLogLevel()
|
||||
{
|
||||
return level_info_.get_active()? NOTE_INFO :
|
||||
level_warn_.get_active()? NOTE_WARN : NOTE_ERROR;
|
||||
}
|
||||
|
||||
string
|
||||
getContent()
|
||||
{
|
||||
return string{content_.get_text()};
|
||||
}
|
||||
|
||||
string
|
||||
getActionID()
|
||||
{
|
||||
return string{actionID_.get_entry_text()};
|
||||
}
|
||||
|
||||
|
||||
Page1 (Bus bus)
|
||||
{
|
||||
content_.set_tooltip_markup (_("<b>Ticket #1099</b>:\n"
|
||||
"text message content\n"
|
||||
"<i>when invoking a suitable action,\n"
|
||||
"it will be passed down and sent back</i>"));
|
||||
trig_1_.set_use_underline();
|
||||
trig_1_.set_label ("_display text");
|
||||
trig_1_.property_xalign() = 0;
|
||||
trig_1_.set_tooltip_markup (_("Trigger Proc-GUI <b>roundtrip</b>\n"
|
||||
"Proc invokes GuiNotification::displayInfo"));
|
||||
|
||||
level_warn_.join_group(level_info_);
|
||||
level_erro_.join_group(level_info_);
|
||||
level_warn_.set_active();
|
||||
|
||||
trig_2_.set_use_underline();
|
||||
trig_2_.property_xalign() = 0;
|
||||
trig_2_.set_label ("mark _error");
|
||||
trig_2_.set_tooltip_markup (_("trigger Proc-command, which in turn\n"
|
||||
"sends an error state mark via UI-Bus"));
|
||||
|
||||
trig_3_.set_use_underline();
|
||||
trig_3_.property_xalign() = 0;
|
||||
trig_3_.set_label ("mark _info");
|
||||
trig_3_.set_tooltip_markup (_("trigger Proc-command, which in turn\n"
|
||||
"sends an info state mark via UI-Bus"));
|
||||
|
||||
trig_4_.set_use_underline();
|
||||
trig_4_.set_label ("_mark");
|
||||
trig_4_.property_xalign() = 0;
|
||||
trig_4_.set_tooltip_markup (_("trigger Proc-command, which in turn\n"
|
||||
"sends an <b>state mark</b> message, using\n"
|
||||
"the message action-ID from the combobox"));
|
||||
actionID_.append (cuString{MARK_Flash});
|
||||
actionID_.append (cuString{MARK_reveal});
|
||||
actionID_.append (cuString{MARK_clearErr});
|
||||
actionID_.append (cuString{MARK_clearMsg});
|
||||
actionID_.append (cuString{MARK_expand});
|
||||
actionID_.append (cuString{MARK_reset});
|
||||
actionID_.set_active(1);
|
||||
actionID_.set_tooltip_markup("select the specific action-ID\n"
|
||||
"when sending a <b>mark</b> message.\n"
|
||||
"<u>note</u>: can enter arbitrary ID");
|
||||
|
||||
markParam_.pack_start(trig_4_);
|
||||
markParam_.pack_start(actionID_, Gtk::PACK_SHRINK);
|
||||
|
||||
seg_1_.pack_start (trig_1_, Gtk::PACK_EXPAND_WIDGET);
|
||||
seg_1_.pack_start (level_info_, Gtk::PACK_SHRINK);
|
||||
seg_1_.pack_start (level_warn_, Gtk::PACK_SHRINK);
|
||||
seg_1_.pack_start (level_erro_, Gtk::PACK_SHRINK);
|
||||
|
||||
seg_2_.pack_start (trig_2_);
|
||||
seg_2_.pack_start (trig_3_);
|
||||
seg_2_.pack_start (markParam_);
|
||||
|
||||
pack_start (content_, Gtk::PACK_SHRINK);
|
||||
pack_start (seg_1_, Gtk::PACK_SHRINK);
|
||||
pack_start (seg_2_, Gtk::PACK_SHRINK);
|
||||
|
||||
// define the action triggers...
|
||||
trig_1_.signal_clicked().connect(
|
||||
[&]{ bus.act (model::commandMessage (proc::cmd::test_meta_displayInfo, getLogLevel(), getContent())); });
|
||||
trig_2_.signal_clicked().connect(
|
||||
[&]{ bus.act (model::commandMessage (proc::cmd::test_meta_markError, getContent())); });
|
||||
trig_3_.signal_clicked().connect(
|
||||
[&]{ bus.act (model::commandMessage (proc::cmd::test_meta_markNote, getContent())); });
|
||||
trig_4_.signal_clicked().connect(
|
||||
[&]{ bus.act (model::commandMessage (proc::cmd::test_meta_markAction, getActionID(), getContent())); });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Ticket #1014 : populate the Timeline in the GUI.
|
||||
* This page allows to send various diff messages up into the UI, to provide timeline content.
|
||||
*/
|
||||
struct Page2 : Page
|
||||
{
|
||||
Gtk::Entry dummy_;
|
||||
FrameBox part_1_{_("populate"), Gtk::ORIENTATION_HORIZONTAL},
|
||||
part_2_{_("modify content")};
|
||||
Gtk::Button seq_1_, seq_2_;
|
||||
Gtk::Button mut_1_;
|
||||
|
||||
string
|
||||
pickDummyID()
|
||||
{
|
||||
string dummyID = sanitise (dummy_.get_text());
|
||||
dummy_.set_text (string{_Fmt{"d%s%02d"} % lib::test::randStr(2) % (1 + rand() % 99)});
|
||||
return dummyID;
|
||||
}
|
||||
|
||||
|
||||
Page2 (Bus bus)
|
||||
{
|
||||
seq_1_.set_label ("Sequence 1");
|
||||
seq_1_.set_tooltip_markup (_("Push <b>population diff</b> up into the UI\n"
|
||||
"provides the typical simple default timeline structure"));
|
||||
|
||||
seq_2_.set_label ("Sequence 2");
|
||||
seq_2_.set_tooltip_markup (_("Push <b>population diff</b> up into the UI\n"
|
||||
"provides a complex nested timeline structure"));
|
||||
|
||||
dummy_.set_tooltip_markup (_("<i>dummy ID value</i>\n"
|
||||
"used to build names in the generated content"));
|
||||
dummy_.set_max_width_chars(12);
|
||||
pickDummyID();
|
||||
|
||||
part_1_.pack_start (seq_1_, Gtk::PACK_SHRINK);
|
||||
part_1_.pack_start (seq_2_, Gtk::PACK_SHRINK);
|
||||
part_1_.pack_start (dummy_, Gtk::PACK_EXPAND_WIDGET);
|
||||
|
||||
|
||||
mut_1_.set_label ("move elements");
|
||||
mut_1_.set_tooltip_markup (_("randomly manipulate temporal position of dummy elements"));
|
||||
|
||||
part_2_.pack_start (mut_1_);
|
||||
|
||||
pack_start (part_1_, Gtk::PACK_SHRINK);
|
||||
pack_start (part_2_, Gtk::PACK_SHRINK);
|
||||
|
||||
// define the action triggers...
|
||||
seq_1_.signal_clicked().connect(
|
||||
[&]{ bus.act (model::commandMessage (proc::cmd::test_fake_injectSequence_1, pickDummyID())); });
|
||||
seq_2_.signal_clicked().connect(
|
||||
[&]{ bus.act (model::commandMessage (proc::cmd::test_fake_injectSequence_2, pickDummyID())); });
|
||||
|
||||
mut_1_.signal_clicked().connect(
|
||||
[&]{ cerr << "gelldaschaugst..." <<endl; });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
public:
|
||||
TestControl (ctrl::BusTerm& upLink, Gtk::Window& parent)
|
||||
: Dialog(_("Test and Diagnostics"), parent, Gtk::DIALOG_DESTROY_WITH_PARENT)
|
||||
, uiBus_{upLink}
|
||||
{
|
||||
// Setup the overall dialog layout
|
||||
set_border_width (BorderPadding);
|
||||
get_content_area()->pack_start (notebook_);
|
||||
|
||||
// construct and wire the pages...
|
||||
notebook_.buildPage<Page1> (_("#1099"), uiBus_);
|
||||
notebook_.buildPage<Page2> (_("Populate"), uiBus_);
|
||||
|
||||
show_all();
|
||||
notebook_.set_current_page(-1);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}} // namespace gui::dialog
|
||||
#endif /*GUI_DIALOG_TEST_CONTROL_H*/
|
||||
|
|
@ -99,8 +99,8 @@ namespace gui {
|
|||
{ }
|
||||
|
||||
|
||||
void
|
||||
run (Subsys::SigTerm reportOnTermination)
|
||||
string
|
||||
run()
|
||||
{
|
||||
string errorMsgBuff;
|
||||
try
|
||||
|
|
@ -122,18 +122,21 @@ namespace gui {
|
|||
if (lumiera_error_peek())
|
||||
errorMsgBuff = string{lumiera_error()};
|
||||
|
||||
reportOnTermination(&errorMsgBuff); // inform main thread that the GUI has been shut down.
|
||||
return errorMsgBuff;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void
|
||||
runGUI (Subsys::SigTerm& reportOnTermination)
|
||||
runGUI (Subsys::SigTerm reportOnTermination)
|
||||
{
|
||||
GtkLumiera{}.run (reportOnTermination);
|
||||
}
|
||||
string shutdownLog = GtkLumiera{}.run();
|
||||
|
||||
// inform main thread that the GUI has been shut down...
|
||||
reportOnTermination (&shutdownLog);
|
||||
}//(End) GUI-Thread.
|
||||
|
||||
} // (End) impl details
|
||||
}//(End) impl details
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -63,6 +63,10 @@
|
|||
namespace proc {
|
||||
namespace asset {
|
||||
class Timeline;
|
||||
|
||||
namespace meta {
|
||||
class ErrorLog;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -71,7 +75,7 @@ namespace gui {
|
|||
class TimelineController;
|
||||
}
|
||||
namespace widget {
|
||||
class ErrorLogWidget;
|
||||
class ErrorLogDisplay;
|
||||
}
|
||||
namespace idi {
|
||||
|
||||
|
|
@ -84,7 +88,7 @@ namespace gui {
|
|||
|
||||
/* === primary component view types === */
|
||||
using TimelineView = timeline::TimelineController;
|
||||
using ErrorLogView = widget::ErrorLogWidget;
|
||||
using ErrorLogView = widget::ErrorLogDisplay;
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -103,7 +107,7 @@ namespace gui {
|
|||
template<>
|
||||
struct Descriptor<ErrorLogView>
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////////TICKET #1105 : need a model placeholder to represent UI specific global entities
|
||||
using Model = proc::asset::meta::ErrorLog;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////TICKET #1105 : consider use of a DSL to configure component view access
|
||||
//
|
||||
|
|
|
|||
|
|
@ -41,13 +41,17 @@
|
|||
#include "gui/workspace/workspace-window.hpp"
|
||||
#include "gui/ctrl/ui-state.hpp"
|
||||
#include "gui/setting/asset-controller.hpp"
|
||||
#include "gui/timeline/timeline-controller.hpp"
|
||||
#include "gui/panel/timeline-panel.hpp"
|
||||
//#include "gui/timeline/timeline-widget.hpp"
|
||||
//#include "gui/timeline/timeline-controller.hpp" /////////////////TODO still required?
|
||||
#include "include/ui-protocol.hpp"
|
||||
#include "proc/mobject/session/root.hpp"
|
||||
#include "proc/asset/sequence.hpp" ///////////////////////////////////////////////////////////TICKET #1096 : avoid direct inclusion to reduce compile times
|
||||
#include "proc/mobject/session/fork.hpp" ///////////////////////////////////////////////////////////TICKET #1096 : avoid direct inclusion to reduce compile times
|
||||
#include "proc/cmd.hpp"
|
||||
#include "backend/real-clock.hpp"
|
||||
#include "lib/diff/tree-mutator.hpp"
|
||||
#include "lib/format-string.hpp"
|
||||
#include "lib/format-obj.hpp"
|
||||
//#include "gui/ui-bus.hpp"
|
||||
//#include "lib/util.hpp"
|
||||
|
|
@ -56,11 +60,16 @@
|
|||
//#include <list>
|
||||
|
||||
//using util::isnil;
|
||||
using util::_Fmt;
|
||||
//using std::list;
|
||||
//using std::shared_ptr;
|
||||
using lib::idi::EntryID;
|
||||
using lib::hash::LuidH;
|
||||
using lib::diff::Rec;
|
||||
using lib::diff::TreeMutator;
|
||||
using lib::diff::collection;
|
||||
using lib::diff::LUMIERA_ERROR_DIFF_STRUCTURE;
|
||||
using std::make_unique;
|
||||
using util::toString;
|
||||
|
||||
|
||||
|
|
@ -126,13 +135,32 @@ namespace interact {
|
|||
void
|
||||
InteractionDirector::buildMutator (TreeMutator::Handle buffer)
|
||||
{
|
||||
// using Attrib = std::pair<const string,string>;
|
||||
// using lib::diff::collection;
|
||||
|
||||
buffer.create (
|
||||
TreeMutator::build()
|
||||
);
|
||||
unimplemented ("create a sensible binding between root-controller and root-model element");
|
||||
.attach (collection(timelines_)
|
||||
.isApplicableIf ([&](GenNode const& spec) -> bool
|
||||
{ // »Selector« : require object-like sub scope
|
||||
return spec.data.isNested();
|
||||
})
|
||||
.matchElement ([&](GenNode const& spec, TimelineGui const& elm) -> bool
|
||||
{ // »Matcher« : how to know we're dealing with the right timeline object
|
||||
return spec.idi == ID{elm};
|
||||
})
|
||||
.constructFrom ([&](GenNode const& spec) -> TimelineGui
|
||||
{ // »Constructor« : what to do when the diff mentions a new entity
|
||||
return injectTimeline (spec);
|
||||
})
|
||||
.buildChildMutator ([&](TimelineGui& targetTimeline, GenNode::ID const& subID, TreeMutator::Handle buff) -> bool
|
||||
{ // »Mutator« : how to apply the diff recursively to a nested scope
|
||||
if (ID{targetTimeline} != subID) return false;
|
||||
targetTimeline.buildMutator (buff); // - delegate to child(Timeline) to build nested TreeMutator
|
||||
return true;
|
||||
}))
|
||||
.mutateAttrib(ATTR_fork, [&](TreeMutator::Handle buff)
|
||||
{ // »Attribute Mutator« : how enter an object field as nested scope
|
||||
REQUIRE (assets_);
|
||||
assets_->buildMutator(buff);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -283,6 +311,62 @@ namespace interact {
|
|||
{
|
||||
return globalCtx_.windowLoc_.findActiveWindow();
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
/**
|
||||
* The timeline is actually a front-end to a binding to a root track.
|
||||
* For that reason, we always create the root-track representation alongside
|
||||
* the timeline, and thus we need a very special INS message to create a timeline:
|
||||
* - it must be a record (an "object")
|
||||
* - a nested attribute with key ATTR_fork is mandatory
|
||||
* - this nested attribute likewise needs to be a record
|
||||
* - and must be tagged with TYPE_Fork
|
||||
* More specifically, the usual way to deliver such a structure is not allowed here,
|
||||
* which would be first to send an empty record and then to open and populate it.
|
||||
* All the elements mentioned above need to be present right within the payload
|
||||
* value of the INS message creating the timeline. This non-standard format is
|
||||
* perfectly legal in our tree diff language (just requires a heap allocation
|
||||
* behind the scenes to hold the nested data, but who cares?)
|
||||
*/
|
||||
ID
|
||||
verifyDiffStructure_and_extract_RootTrack (GenNode const& spec)
|
||||
{
|
||||
if (not (spec.data.isNested()
|
||||
and spec.data.get<Rec>().hasAttribute(string{ATTR_fork})
|
||||
and TYPE_Fork == spec.data.get<Rec>().get(string{ATTR_fork}).data.recordType()
|
||||
) )
|
||||
throw error::State (_Fmt{"When populating a new Timeline, a root track must be given immediately"
|
||||
"nested into INS message. We got the following initialisation payload: %s"}
|
||||
% spec
|
||||
, LERR_(DIFF_STRUCTURE));
|
||||
|
||||
return spec.data.get<Rec>().get(string{ATTR_fork}).idi;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal allocate a new TimelineWidget and attach it as child.
|
||||
* @remarks assuming the structure of the diff is adequate, we'll first create a proxy
|
||||
* to manage this timeline. Then we use some service to find a suitable location to house
|
||||
* a TimelineWidget. And finally we trigger creation of the widget, which in turn creates
|
||||
* a TimelineController attached to the UI-Bus
|
||||
*/
|
||||
TimelineGui
|
||||
InteractionDirector::injectTimeline (GenNode const& spec)
|
||||
{
|
||||
ID rootTrack = verifyDiffStructure_and_extract_RootTrack (spec);
|
||||
TimelineGui anchorProxy{spec.idi, rootTrack};
|
||||
|
||||
globalCtx_.windowLoc_
|
||||
.locatePanel()
|
||||
.find_or_create<panel::TimelinePanel>()
|
||||
.addTimeline (
|
||||
anchorProxy.buildTimelineWidget (this->uiBus_));
|
||||
|
||||
return anchorProxy;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@
|
|||
#define GUI_INTERACT_INTERACTION_DIRECTOR_H
|
||||
|
||||
#include "gui/model/controller.hpp"
|
||||
#include "gui/timeline/timeline-gui.hpp"
|
||||
#include "lib/depend-inject.hpp"
|
||||
|
||||
//#include <string>
|
||||
|
|
@ -80,6 +81,7 @@ namespace workspace {
|
|||
}
|
||||
namespace interact {
|
||||
|
||||
using lib::diff::GenNode;
|
||||
//using std::string;
|
||||
using std::unique_ptr;
|
||||
|
||||
|
|
@ -91,6 +93,8 @@ namespace interact {
|
|||
class FocusTracker;
|
||||
class ViewLocator;
|
||||
|
||||
using timeline::TimelineGui;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -113,7 +117,7 @@ namespace interact {
|
|||
unique_ptr<FocusTracker> tracker_;
|
||||
|
||||
// == Model globals ==
|
||||
using Timelines = std::vector<unique_ptr<timeline::TimelineController>>;
|
||||
using Timelines = std::vector<TimelineGui>;
|
||||
using Assets = unique_ptr<setting::AssetController>;
|
||||
using State = unique_ptr<ctrl::UiState>;
|
||||
|
||||
|
|
@ -150,6 +154,7 @@ namespace interact {
|
|||
|
||||
private:
|
||||
workspace::WorkspaceWindow& getWorkspaceWindow();
|
||||
TimelineGui injectTimeline (GenNode const&);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@
|
|||
** 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),
|
||||
** As indicated above, the coordinate resolver internally relies on a [context query interface](\ref gui::interact::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.
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@
|
|||
|
||||
/** @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,
|
||||
** Essentially this compilation unit hosts the implementation backing the [ViewSpec-DSL](\ref view-spec-dsl.hpp)
|
||||
** and the [Location Solver](\ref 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
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@
|
|||
** 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)
|
||||
** @note the actual binding for the DSL tokens can be found in the [ViewLocator implementation](\ref view-locator.cpp)
|
||||
**
|
||||
** @see ViewSpecDSL_test
|
||||
** @see id-scheme.hpp
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@
|
|||
#include "gui/interact/wizard.hpp"
|
||||
#include "gui/interact/spot-locator.hpp"
|
||||
#include "gui/workspace/workspace-window.hpp"
|
||||
#include "gui/panel/infobox-panel.hpp"
|
||||
#include "gui/dialog/test-control.hpp"
|
||||
#include "gui/ctrl/notification-hub.hpp"
|
||||
#include "gui/ctrl/global-ctx.hpp"
|
||||
#include "lib/format-string.hpp"
|
||||
//#include "lib/util.hpp"
|
||||
|
|
@ -55,6 +58,7 @@ namespace interact {
|
|||
using boost::algorithm::is_any_of;
|
||||
using boost::algorithm::split;
|
||||
using workspace::WorkspaceWindow;
|
||||
using ctrl::NotificationHub;
|
||||
using ctrl::GlobalCtx;
|
||||
using Gtk::AboutDialog;
|
||||
|
||||
|
|
@ -66,6 +70,14 @@ namespace interact {
|
|||
|
||||
Wizard::Wizard (GlobalCtx& globals)
|
||||
: globalCtx_{globals}
|
||||
, notificationHub_{new NotificationHub{getErrorLogID()
|
||||
,globals.uiBus_.getAccessPoint()
|
||||
,[&]() -> widget::ErrorLogDisplay&
|
||||
{
|
||||
return globalCtx_.windowLoc_.locatePanel()
|
||||
.find_or_create<panel::InfoBoxPanel>()
|
||||
.getLog();
|
||||
}} }
|
||||
{ }
|
||||
|
||||
|
||||
|
|
@ -104,4 +116,22 @@ namespace interact {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Launch a non modal child window to trigger self-test actions.
|
||||
* This is a tool for diagnostics and development. The operations exposed here
|
||||
* allow to launch some hard wired actions and test routines, performing within
|
||||
* the regular UI environment and on equal footing with user operated controls.
|
||||
*/
|
||||
void
|
||||
Wizard::launchTestCtrl()
|
||||
{
|
||||
if (testControlWindow_)
|
||||
testControlWindow_->present(); // just (re)show the existing window
|
||||
else
|
||||
testControlWindow_ = std::make_unique<dialog::TestControl> (globalCtx_.uiBus_.getAccessPoint(),
|
||||
globalCtx_.windowLoc_.findActiveWindow());
|
||||
}
|
||||
|
||||
|
||||
|
||||
}}// namespace gui::interact
|
||||
|
|
|
|||
|
|
@ -42,23 +42,36 @@
|
|||
#ifndef GUI_INTERACT_WIZARD_H
|
||||
#define GUI_INTERACT_WIZARD_H
|
||||
|
||||
#include "gui/gtk-base.hpp"
|
||||
#include "lib/nocopy.hpp"
|
||||
#include "lib/idi/entry-id.hpp"
|
||||
|
||||
//#include <string>
|
||||
//#include <memory>
|
||||
#include <memory>
|
||||
|
||||
|
||||
namespace proc {
|
||||
namespace asset {
|
||||
namespace meta {
|
||||
class ErrorLog;
|
||||
|
||||
extern lib::idi::EntryID<ErrorLog> theErrorLog_ID;
|
||||
} } }
|
||||
|
||||
namespace gui {
|
||||
using ID = lib::idi::BareEntryID const&;
|
||||
|
||||
namespace ctrl {
|
||||
class GlobalCtx;
|
||||
class NotificationHub;
|
||||
}
|
||||
namespace dialog {
|
||||
class TestControl;
|
||||
}
|
||||
namespace interact {
|
||||
|
||||
// using std::unique_ptr;
|
||||
using std::unique_ptr;
|
||||
// using std::string;
|
||||
|
||||
// class GlobalCtx;
|
||||
// class SpotLocator;
|
||||
|
||||
|
||||
|
|
@ -73,14 +86,19 @@ namespace interact {
|
|||
: util::NonCopyable
|
||||
{
|
||||
ctrl::GlobalCtx& globalCtx_;
|
||||
unique_ptr<ctrl::NotificationHub> notificationHub_;
|
||||
unique_ptr<dialog::TestControl> testControlWindow_;
|
||||
|
||||
public:
|
||||
Wizard (ctrl::GlobalCtx&);
|
||||
~Wizard ();
|
||||
|
||||
void show_HelpAbout();
|
||||
private:
|
||||
void launchTestCtrl();
|
||||
|
||||
static ID getErrorLogID() { return proc::asset::meta::theErrorLog_ID; }
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
33
src/gui/lumiera-light-theme-complement.css
Normal file
33
src/gui/lumiera-light-theme-complement.css
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
lumiera-light-theme-complement.css - minimal supplement for GTK-3 light style sheet
|
||||
|
||||
Copyright (C) Lumiera.org
|
||||
2018, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
Styles and Graphics of the Lumiera GUI can be used and redistributed
|
||||
under the the terms of the GNU General Public License version 2 or
|
||||
above, or (at your option) under Creative Commons CC-By-SA.
|
||||
|
||||
* ********************************************************/
|
||||
|
||||
|
||||
/* CSS stylesheet to provide the additional definitions
|
||||
* required to run the Lumiera GTK UI with a typical
|
||||
* "light" standard system theme
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
/* ---------- Styles for Lumiera Widgets ---------- */
|
||||
|
||||
|
||||
/* ---------- Styles for special markup ---------- */
|
||||
|
||||
.indication_flash,
|
||||
.indication_flash * {
|
||||
background-color: LightYellow;
|
||||
}
|
||||
.indication_flash text {
|
||||
border: 4px solid red;
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
gtk-lumiera.css - GTK-3 style sheet for the Lumiera GUI
|
||||
lumiera.css - GTK-3 style sheet for the Lumiera GUI
|
||||
|
||||
Copyright (C) Lumiera.org
|
||||
2014, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
|
@ -35,13 +35,12 @@
|
|||
|
||||
|
||||
* {
|
||||
engine: adwaita;
|
||||
|
||||
border-width: 1px;
|
||||
border-style: outset;
|
||||
border-color: shade(@bg_color, 0.80);
|
||||
|
||||
color: inherit; /* by default inherit content colour from parent widget */
|
||||
color: inherit; /* by default inherit content colour from parent widget */
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
GtkWindow {
|
||||
|
|
@ -276,3 +275,14 @@ class "gtkmm__CustomObject_TimelineRuler" style:highest "timeline_ruler"
|
|||
class "gtkmm__CustomObject_TimelineHeaderWidget" style:highest "timeline_header_widget"
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/* ---------- Styles for special markup ---------- */
|
||||
|
||||
.indication_flash,
|
||||
.indication_flash * {
|
||||
background-color: shade(@hover_bg_color, 1.6);
|
||||
}
|
||||
.indication_flash text {
|
||||
border: 4px solid red;
|
||||
}
|
||||
|
|
@ -25,6 +25,8 @@
|
|||
** Common Abstraction of all sub-controller, coordinated by the UI-Bus.
|
||||
**
|
||||
** @todo as of 1/2015 this is complete WIP-WIP-WIP
|
||||
** @todo as of 8/2018 we should consider to distill a common default implementation usable for most controllers.
|
||||
** especially, there might be a default (NOP) implementation of the diff mutator builder
|
||||
**
|
||||
** @see model::Tangible
|
||||
** @see gui::UIBus
|
||||
|
|
@ -68,24 +70,6 @@ namespace model {
|
|||
UNIMPLEMENTED ("Controller reset");
|
||||
}
|
||||
|
||||
virtual bool
|
||||
doExpand (bool yes) override
|
||||
{
|
||||
UNIMPLEMENTED ("Controller doExpand");
|
||||
}
|
||||
|
||||
virtual void
|
||||
doReveal (ID child) override
|
||||
{
|
||||
UNIMPLEMENTED ("Controller doReveal");
|
||||
}
|
||||
|
||||
virtual void
|
||||
doRevealYourself() override
|
||||
{
|
||||
UNIMPLEMENTED ("Controller doRevealYourself");
|
||||
}
|
||||
|
||||
virtual bool
|
||||
doMsg (string text) override
|
||||
{
|
||||
|
|
@ -116,10 +100,16 @@ namespace model {
|
|||
UNIMPLEMENTED ("Controller doFlash");
|
||||
}
|
||||
|
||||
protected:
|
||||
/** default handler for all generic mark messages. Forwards to Tangible::doMark
|
||||
* @todo is there any default implementation for special messages,
|
||||
* which might be eligible as a base class implementation??
|
||||
*/
|
||||
virtual void
|
||||
doMark (GenNode const& mark) override
|
||||
{
|
||||
UNIMPLEMENTED ("Controller doMark");
|
||||
// forward to default handler
|
||||
Tangible::doMark (mark);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
189
src/gui/model/expander-revealer.hpp
Normal file
189
src/gui/model/expander-revealer.hpp
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
EXPANDER-REVEALER.hpp - functor components for standard UI element actions
|
||||
|
||||
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 expander-revealer.hpp
|
||||
** Helper components to implement some standard UI-element actions by installing a functor.
|
||||
** The protocol for operating UI elements connected to the [UI-Bus](\ref ui-bus.hpp) includes
|
||||
** some generic operations, which need to be concretised for each actual usage situation.
|
||||
** Moreover, there are many situations where implementing those actions does not make
|
||||
** much sense, and thus they need to be treated as optional.
|
||||
** - some widgets or elements can be expanded or collapsed, to show more information
|
||||
** or to save screen real estate. Such widgets also expose an _expansion_ state.
|
||||
** - sometimes it is necessary to bring a given widget into sight, be it to show
|
||||
** the effect of some processing, or be it to indicate an error situation.
|
||||
**
|
||||
** ## Usage in the default implementation
|
||||
**
|
||||
** The base class of all [tangible UI elements](\ref Tangible) provides a default implementation
|
||||
** for these generic interaction mechanisms: It offers slots to connect UI signals against, and
|
||||
** it understands the \em `mark` messages `"expand"` and `"reveal"`. These are implemented
|
||||
** by delegating to the \ref Expander and \ref Revealer functors respectively. Moreover, this
|
||||
** default implementation automatically detects a resulting state change and emits an appropriate
|
||||
** \em `note` message on the UI-Bus, so to make those state changes persistent. However, in order
|
||||
** to supply an concrete implementation, depending on the circumstances, either the widget itself
|
||||
** or a parent container holding the element needs to install lambdas into those functor holders,
|
||||
** to detail what actually needs to be done. E.g. it is quite common to implement the "expand"
|
||||
** functionality by wrapping the widget into a `Gtk::Expander`. Which effectively means that
|
||||
** the widget itself is not able to expand itself, since this `Gtk::Expander` container widget
|
||||
** lives within the parent widget to hold the element. So this parent needs to install a
|
||||
** lambda when establishing the child element, and bind it to the `Gtk::Expander::set_expanded`
|
||||
** property, and the corresponding `Gtk::Expander::get_expanded` to retrieve the current
|
||||
** _expansion state_. However, to the contrary, some widgets do implement their _expansion state_
|
||||
** all by themselves, e.g. by switching the presentation layout. Those widgets will have to
|
||||
** install an appropriate Expander functor within their constructor.
|
||||
**
|
||||
** @see [UI-Element protocol](\ref tangible.hpp)
|
||||
** @see [simple test case](\ref AbstractTangible_test::revealer())
|
||||
** @see [usage example](\ref error-log-display.hpp)
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#ifndef GUI_MODEL_EXPANDER_REVEALER_H
|
||||
#define GUI_MODEL_EXPANDER_REVEALER_H
|
||||
|
||||
|
||||
#include "lib/error.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
|
||||
namespace gui {
|
||||
namespace model {
|
||||
|
||||
using std::move;
|
||||
|
||||
|
||||
/**
|
||||
* Functor component to support the default implementation of expanding/collapsing.
|
||||
* It is built based on closures (lambdas) to explicate how to detect the expansion
|
||||
* state of the widget in question and how actively to expand or collapse it.
|
||||
* A default constructed Expander disables this feature -- which can be detected
|
||||
* through the #canExpand() function.
|
||||
*/
|
||||
class Expander
|
||||
{
|
||||
public:
|
||||
using ProbeFun = std::function<bool(void)>;
|
||||
using ChangeFun = std::function<void(bool)>;
|
||||
|
||||
private:
|
||||
ProbeFun probeState_;
|
||||
ChangeFun changeState_;
|
||||
|
||||
public:
|
||||
Expander() { }
|
||||
Expander(ProbeFun detectCurrExpansionState, ChangeFun expand_collapse)
|
||||
: probeState_{move (detectCurrExpansionState)}
|
||||
, changeState_{move (expand_collapse)}
|
||||
{
|
||||
ENSURE (canExpand());
|
||||
}
|
||||
|
||||
bool
|
||||
canExpand() const
|
||||
{
|
||||
return bool{probeState_}
|
||||
and bool{changeState_};
|
||||
}
|
||||
|
||||
explicit
|
||||
operator bool() const
|
||||
{
|
||||
REQUIRE (canExpand());
|
||||
return probeState_();
|
||||
}
|
||||
|
||||
bool
|
||||
operator() (bool shallExpand)
|
||||
{
|
||||
REQUIRE (canExpand());
|
||||
bool currState = probeState_();
|
||||
if (currState != shallExpand)
|
||||
changeState_(shallExpand);
|
||||
return currState;
|
||||
}
|
||||
|
||||
/* === alternate "expressive" API === */
|
||||
bool
|
||||
expand (bool yes =true)
|
||||
{
|
||||
return this->operator() (yes);
|
||||
}
|
||||
|
||||
bool
|
||||
collapse()
|
||||
{
|
||||
return expand (false);
|
||||
}
|
||||
|
||||
bool
|
||||
isExpanded() const
|
||||
{
|
||||
return bool{*this};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Functor component to support the default implementation of revealing an UI-Element.
|
||||
* It is built based on a closure (lambda) detailing what actually to do in order
|
||||
* to bring a given UI element into sight. A default constructed Revealer disables
|
||||
* this feature, which can be detected through the #canReveal() function.
|
||||
*/
|
||||
class Revealer
|
||||
{
|
||||
public:
|
||||
using RevealeItFun = std::function<void()>;
|
||||
|
||||
private:
|
||||
RevealeItFun revealIt_;
|
||||
|
||||
public:
|
||||
Revealer() { }
|
||||
Revealer(RevealeItFun how_to_uncover_the_element)
|
||||
: revealIt_{move (how_to_uncover_the_element)}
|
||||
{
|
||||
ENSURE (canReveal());
|
||||
}
|
||||
|
||||
bool
|
||||
canReveal() const
|
||||
{
|
||||
return bool{revealIt_};
|
||||
}
|
||||
|
||||
void
|
||||
operator()()
|
||||
{
|
||||
REQUIRE (canReveal());
|
||||
revealIt_();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
}} // namespace gui::model
|
||||
#endif /*GUI_MODEL_EXPANDER_REVEALER_H*/
|
||||
90
src/gui/model/flash-deco.hpp
Normal file
90
src/gui/model/flash-deco.hpp
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
FLASH-DECO.hpp - widget decorator to add a visual flash action
|
||||
|
||||
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 flash-deco.hpp
|
||||
** Decorator to add the ability to display a visual flash action to a GTK widget.
|
||||
** The protocol for operating UI elements connected to the [UI-Bus](\ref ui-bus.hpp) includes
|
||||
** a message to make the receiving widget flash for a short time, to mandate the user's attention.
|
||||
** The visual change itself is effected by adding a CSS class, while adding a timeout callback
|
||||
** to revert to normal display after a short timespan. In itself, this modification sequence
|
||||
** is entirely generic, and can thus be added by decorating the widget to affect; moreover,
|
||||
** adding such a callback need to be done properly, to avoid a crash in case the widget
|
||||
** is destroyed during the timeout period.
|
||||
**
|
||||
** @see [UI-Element protocol](\ref tangible.hpp)
|
||||
** @see [usage example](\ref error-log-display.hpp)
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#ifndef GUI_MODEL_FLASH_DECO_H
|
||||
#define GUI_MODEL_FLASH_DECO_H
|
||||
|
||||
|
||||
#include "gui/gtk-base.hpp"
|
||||
#include "gui/style-scheme.hpp"
|
||||
|
||||
|
||||
namespace gui {
|
||||
namespace model {
|
||||
|
||||
|
||||
/**
|
||||
* Decorator for a Gtk::Widget to add a visual flash action.
|
||||
* @tparam WIT type of the target widget to decorate. All ctors are passed through.
|
||||
* @remark invoke the #flash function to trigger
|
||||
* @see \ref gui::CSS_CLASS_FLASH
|
||||
*/
|
||||
template<class WIT>
|
||||
class FlashDeco
|
||||
: public WIT
|
||||
{
|
||||
static_assert (std::is_base_of<Gtk::Widget, WIT>()
|
||||
,"wrapped target type required to be a Gtk::Widget");
|
||||
public:
|
||||
using WIT::WIT;
|
||||
|
||||
void
|
||||
flash()
|
||||
{
|
||||
auto styleContext = this->get_style_context();
|
||||
styleContext->add_class (CSS_CLASS_FLASH);
|
||||
|
||||
Glib::signal_timeout()
|
||||
.connect_once (sigc::mem_fun(*this, &FlashDeco::flashback)
|
||||
,STYLE_FLASH_DURATION_in_ms
|
||||
,Glib::PRIORITY_LOW); // after all pending drawing tasks
|
||||
}
|
||||
|
||||
private:
|
||||
void
|
||||
flashback()
|
||||
{
|
||||
auto styleContext = this->get_style_context();
|
||||
styleContext->remove_class (CSS_CLASS_FLASH);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}} // namespace gui::model
|
||||
#endif /*GUI_MODEL_FLASH_DECO_H*/
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
** Common base implementation of all tangible and connected interface elements.
|
||||
**
|
||||
** @see abstract-tangible-test.cpp
|
||||
** @see [explanation of the fundamental interactions](tangible.hpp)
|
||||
** @see [explanation of the fundamental interactions](\ref tangible.hpp)
|
||||
**
|
||||
*/
|
||||
|
||||
|
|
@ -33,6 +33,7 @@
|
|||
#include "gui/model/tangible.hpp"
|
||||
#include "gui/model/widget.hpp"
|
||||
#include "gui/model/controller.hpp"
|
||||
#include "include/ui-protocol.hpp"
|
||||
#include "lib/diff/gen-node.hpp"
|
||||
|
||||
|
||||
|
|
@ -60,7 +61,7 @@ namespace model {
|
|||
Tangible::reset()
|
||||
{
|
||||
if (this->doReset())
|
||||
uiBus_.note (GenNode{"reset", true});
|
||||
uiBus_.note (GenNode{string{MARK_reset}, true});
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -82,7 +83,7 @@ namespace model {
|
|||
Tangible::clearErr()
|
||||
{
|
||||
if (this->doClearErr())
|
||||
uiBus_.note (GenNode{"clearErr", true});
|
||||
uiBus_.note (GenNode{string{MARK_clearErr}, true});
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -93,7 +94,7 @@ namespace model {
|
|||
Tangible::clearMsg()
|
||||
{
|
||||
if (this->doClearMsg())
|
||||
uiBus_.note (GenNode{"clearMsg", true});
|
||||
uiBus_.note (GenNode{string{MARK_clearMsg}, true});
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -125,7 +126,7 @@ namespace model {
|
|||
Tangible::markMsg (string message)
|
||||
{
|
||||
if (this->doMsg (message))
|
||||
uiBus_.note (GenNode{"Message", message});
|
||||
uiBus_.note (GenNode{string{MARK_Message}, message});
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -136,7 +137,7 @@ namespace model {
|
|||
Tangible::markErr (string error)
|
||||
{
|
||||
if (this->doErr (error))
|
||||
uiBus_.note (GenNode("Error", error));
|
||||
uiBus_.note (GenNode{string{MARK_Error}, error});
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -148,12 +149,15 @@ namespace model {
|
|||
* behaviour. If this virtual method returns `true`, the
|
||||
* state change is deemed relevant and persistent, and
|
||||
* thus a "state mark" is sent on the UI-Bus.
|
||||
* @remark a default implementation of ::doExpand() is provided,
|
||||
* based on installing an \ref Expander functor through
|
||||
* the [configuration function](\ref #installExpander).
|
||||
*/
|
||||
void
|
||||
Tangible::slotExpand()
|
||||
{
|
||||
if (this->doExpand(true))
|
||||
uiBus_.note (GenNode("expand", true));
|
||||
uiBus_.note (GenNode{string{MARK_expand}, true});
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -165,24 +169,59 @@ namespace model {
|
|||
Tangible::slotCollapse()
|
||||
{
|
||||
if (this->doExpand(false))
|
||||
uiBus_.note (GenNode("expand", false));
|
||||
uiBus_.note (GenNode{string{MARK_expand}, false});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @todo 12/2015 not clear yet what needs to be done
|
||||
* @remarks the intention is to request the given child
|
||||
* to be brought into sight. We need to set up some kind
|
||||
* of children registration, but better not do this in
|
||||
* a completely generic fashion, for danger of overengineering.
|
||||
* Moreover, it is not clear yet, who will issue this request
|
||||
* and at which element the initial request can/will be targeted.
|
||||
* generic default implementation of the expand/collapse functionality.
|
||||
* Based on the #expand_ functor, which needs to be [configured](\ref installExpander())
|
||||
* explicitly to enable this functionality.
|
||||
* @return `true` if the actual expansion state has been changed.
|
||||
*/
|
||||
bool
|
||||
Tangible::doExpand (bool yes)
|
||||
{
|
||||
if (not expand_.canExpand())
|
||||
return false;
|
||||
bool oldState = expand_(yes);
|
||||
return oldState != yes; // actually changed
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cause the element to be brought into sight.
|
||||
* This is a generic Slot to connect UI signals against;
|
||||
* the same action can also be triggered by sending a **mark**
|
||||
* message over the UI-Bus with the symbol "`reveal`".
|
||||
* @note this is an optional feature and requires the actual widget or controller
|
||||
* either to override the ::doReveal() extension point, or to
|
||||
* [install a suitable closure](\ref installRevealer()). Typically this
|
||||
* is not in itself a persistent state change; however, it might incur
|
||||
* expanding some widgets, which is recorded as persistent UI state.
|
||||
* @remarks the intention is to make a specific element visible, e.g. to reveal
|
||||
* the effect of some operation, or to mark a serious error condition.
|
||||
* Implementing this is by no means trivial, since it involves the
|
||||
* possibly recursive collaboration with enclosing container widgets,
|
||||
* and maybe even to scroll to a given canvas position.
|
||||
*/
|
||||
void
|
||||
Tangible::slotReveal(ID child)
|
||||
Tangible::slotReveal()
|
||||
{
|
||||
this->doReveal(child);
|
||||
this->doRevealYourself();
|
||||
this->doReveal();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* generic default implementation of the "reveal" functionality.
|
||||
* Based on the #reveal_ functor, which needs to be [configured](\ref installRevealer())
|
||||
* explicitly to enable this functionality.
|
||||
*/
|
||||
void
|
||||
Tangible::doReveal()
|
||||
{
|
||||
if (reveal_.canReveal())
|
||||
reveal_();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -207,13 +246,13 @@ namespace model {
|
|||
void
|
||||
Tangible::mark (GenNode const& stateMark)
|
||||
{
|
||||
if (stateMark.idi.getSym() == "Flash")
|
||||
if (stateMark.idi.getSym() == MARK_Flash)
|
||||
this->doFlash();
|
||||
else
|
||||
if (stateMark.idi.getSym() == "Error")
|
||||
if (stateMark.idi.getSym() == MARK_Error)
|
||||
this->markErr (stateMark.data.get<string>());
|
||||
else
|
||||
if (stateMark.idi.getSym() == "Message")
|
||||
if (stateMark.idi.getSym() == MARK_Message)
|
||||
this->markMsg (stateMark.data.get<string>());
|
||||
else
|
||||
this->doMark(stateMark);
|
||||
|
|
@ -229,10 +268,10 @@ namespace model {
|
|||
* It is up to the concrete element to give this a tangible meaning, e.g. a track
|
||||
* might switch to detail view and a clip might reveal attached effects.
|
||||
* - *reset* restores the element to the hard wired default, by invoking `doReset()`
|
||||
* - *revealYourself* prompts the element to take the necessary actions to bring
|
||||
* itself into view. There is no requirement for an element to even implement
|
||||
* this call, but those which do typically know some kind of _"parent object"_
|
||||
* to forward this request, by invoking `doReveal(myID)` on this parent.
|
||||
* - *reveal* prompts the element to take the necessary actions to bring itself
|
||||
* into view. There is no requirement for an element to even implement this call,
|
||||
* but those which do typically know some kind of _"parent object"_ to forward
|
||||
* this request, by invoking `doReveal(myID)` on this parent.
|
||||
* For instance, a clip might ask the enclosing track, which in turn might
|
||||
* call the enclosing timeline display for help, resulting in a scroll action
|
||||
* to bring the clip into sight.
|
||||
|
|
@ -242,20 +281,23 @@ namespace model {
|
|||
void
|
||||
Tangible::doMark (GenNode const& stateMark)
|
||||
{
|
||||
if (stateMark.idi.getSym() == "expand")
|
||||
this->doExpand (stateMark.data.get<bool>());
|
||||
if (stateMark.idi.getSym() == MARK_expand)
|
||||
{
|
||||
if (this->doExpand (stateMark.data.get<bool>()))
|
||||
uiBus_.note (GenNode(string{MARK_expand}, true)); // possibly reentrant (yet harmless)
|
||||
}
|
||||
else
|
||||
if (stateMark.idi.getSym() == "reset")
|
||||
if (stateMark.idi.getSym() == MARK_reset)
|
||||
this->reset();
|
||||
else
|
||||
if (stateMark.idi.getSym() == "clearMsg")
|
||||
if (stateMark.idi.getSym() == MARK_clearMsg)
|
||||
this->clearMsg();
|
||||
else
|
||||
if (stateMark.idi.getSym() == "clearErr")
|
||||
if (stateMark.idi.getSym() == MARK_clearErr)
|
||||
this->clearErr();
|
||||
else
|
||||
if (stateMark.idi.getSym() == "revealYourself")
|
||||
this->doRevealYourself();
|
||||
if (stateMark.idi.getSym() == MARK_reveal)
|
||||
this->doReveal();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
/** @file tangible.hpp
|
||||
** Abstraction: a tangible element of the User Interface.
|
||||
** This is a generic foundation for any elements of more than local relevance
|
||||
** within the Lumiera UI.<br/> Any such element is connected to the [UI-Bus](ui-bus.hpp).
|
||||
** within the Lumiera UI.<br/> Any such element is connected to the [UI-Bus](\ref ui-bus.hpp).
|
||||
**
|
||||
** ## rationale
|
||||
**
|
||||
|
|
@ -96,7 +96,7 @@
|
|||
**
|
||||
** And finally, there are the _essential updates_ -- any changes in the model _for real._
|
||||
** These are sent as notifications just to some relevant top level element, expecting this element
|
||||
** to request a [diff](tree-diff.hpp) and to mutate contents into shape recursively.
|
||||
** to request a [diff](\ref tree-diff.hpp) and to mutate contents into shape recursively.
|
||||
**
|
||||
** ## Interactions
|
||||
**
|
||||
|
|
@ -105,7 +105,7 @@
|
|||
** - **act**: send a \ref GenNode representing the action
|
||||
** - **note**: _send_ a GenNode representing the _state mark_
|
||||
** - **mark**: _receive_ a GenNode representing the _feedback,_ a replayed _state mark_ or _generic message._
|
||||
** - **diff**: ask to retrieve a diff, which
|
||||
** - **diff**: prompt the element to retrieve a diff, which
|
||||
** - either is an incremental status update
|
||||
** - or is a from-scratch reconfiguration
|
||||
**
|
||||
|
|
@ -133,6 +133,7 @@
|
|||
#include "lib/error.hpp"
|
||||
#include "lib/nocopy.hpp"
|
||||
#include "gui/ctrl/bus-term.hpp"
|
||||
#include "gui/model/expander-revealer.hpp"
|
||||
#include "lib/diff/diff-mutable.hpp"
|
||||
#include "lib/idi/entry-id.hpp"
|
||||
#include "lib/symbol.hpp"
|
||||
|
|
@ -153,9 +154,9 @@ namespace model {
|
|||
* Interface common to all UI elements of relevance for the Lumiera application.
|
||||
* Any non-local and tangible interface interaction will at some point pass through
|
||||
* this foundation element, which forms the joint and attachment to the UI backbone,
|
||||
* which is the [UI-Bus](ui-bus.hpp). Any tangible element acquires a distinct identity
|
||||
* which is the [UI-Bus](\ref 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)
|
||||
* @see [explanation of the basic interactions](\ref tangible.hpp)
|
||||
* @warning Tangible is `NonCopyable` for good reason: the UI-Bus Nexus adds a direct
|
||||
* reference into the routing table, tied to the given Tangible's ID (identity.
|
||||
* Consequently you must not store tangibles in STL containers, since these
|
||||
|
|
@ -175,9 +176,14 @@ namespace model {
|
|||
|
||||
ctrl::BusTerm uiBus_;
|
||||
|
||||
Expander expand_;
|
||||
Revealer reveal_;
|
||||
|
||||
|
||||
Tangible(ID identity, ctrl::BusTerm& nexus)
|
||||
: uiBus_{nexus.attach(identity, *this)}
|
||||
, expand_{}
|
||||
, reveal_{}
|
||||
{ }
|
||||
|
||||
public:
|
||||
|
|
@ -196,21 +202,22 @@ namespace model {
|
|||
|
||||
void slotExpand();
|
||||
void slotCollapse();
|
||||
|
||||
void slotReveal(ID child);
|
||||
void slotReveal();
|
||||
|
||||
void markFlash();
|
||||
void markMsg (string message);
|
||||
void markErr (string error);
|
||||
void mark(GenNode const&);
|
||||
|
||||
void installExpander (Expander::ProbeFun, Expander::ChangeFun);
|
||||
void installRevealer (Revealer::RevealeItFun);
|
||||
|
||||
protected:
|
||||
virtual bool doReset() =0;
|
||||
virtual bool doClearMsg() =0;
|
||||
virtual bool doClearErr() =0;
|
||||
virtual bool doExpand (bool yes) =0;
|
||||
virtual void doReveal (ID child) =0;
|
||||
virtual void doRevealYourself () =0;
|
||||
virtual bool doExpand (bool yes);
|
||||
virtual void doReveal ();
|
||||
|
||||
virtual bool doMsg (string) =0;
|
||||
virtual bool doErr (string) =0;
|
||||
|
|
@ -222,30 +229,74 @@ namespace model {
|
|||
|
||||
|
||||
/** override default size traits for diff application.
|
||||
* @remarks this value here is hard coded, base on what
|
||||
* @remarks this value here is hard coded, based on what
|
||||
* can be expected for diff application to UI elements.
|
||||
*/
|
||||
friend constexpr size_t
|
||||
treeMutatorSize (const Tangible*)
|
||||
{
|
||||
return 256;
|
||||
return 512;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/** convenience shortcut to build a message suitable for command invocation
|
||||
* @param args... sequence of arguments to be packaged into a lib::diff::Rec for invocation
|
||||
*/
|
||||
template<typename...ARGS>
|
||||
inline lib::diff::GenNode
|
||||
commandMessage (Symbol cmdID, ARGS&&... args)
|
||||
{
|
||||
using lib::diff::Rec;
|
||||
using lib::diff::GenNode;
|
||||
using GenNodeIL = std::initializer_list<GenNode>;
|
||||
|
||||
|
||||
return GenNode (string{cmdID},
|
||||
Rec(Rec::TYPE_NIL_SYM
|
||||
,GenNodeIL{}
|
||||
,GenNodeIL {std::forward<ARGS> (args)...}));
|
||||
} // not typed, no attributes, all arguments as children
|
||||
|
||||
|
||||
/** convenience shortcut to issue a command with several arguments */
|
||||
template<typename...ARGS>
|
||||
inline void
|
||||
Tangible::invoke (Symbol cmdID, ARGS&&... args)
|
||||
{
|
||||
using GenNodeIL = std::initializer_list<GenNode>;
|
||||
|
||||
invoke (cmdID,
|
||||
Rec(Rec::TYPE_NIL_SYM
|
||||
,GenNodeIL{}
|
||||
,GenNodeIL {std::forward<ARGS> (args)...}));
|
||||
} // not typed, no attributes, all arguments as children
|
||||
uiBus_.act (commandMessage (cmdID, std::forward<ARGS> (args)...));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Configure the (optional) functionality to expand or collapse the UI-Element.
|
||||
* @param detectCurrExpansionState a lambda or function<bool()> to retrieve if the element is currently expanded
|
||||
* @param howto_expand_collapse a lambda or function<void(bool)> to switch the element's expansion state
|
||||
* @note unless this setup function is invoked, the expand/collapse functionality remains disabled;
|
||||
* invoking the #slotExpand() or sending **mark** "`expand`" messages via UI-Bus has no effect then.
|
||||
*/
|
||||
inline void
|
||||
Tangible::installExpander (Expander::ProbeFun detectCurrExpansionState,
|
||||
Expander::ChangeFun howto_expand_collapse)
|
||||
{
|
||||
expand_ = Expander{move (detectCurrExpansionState), move (howto_expand_collapse)};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Configure the (optional) functionality to bring the UI-Element into sight.
|
||||
* @param how_to_uncover_the_element a lambda or function<void()> to actually cause the necessary actions.
|
||||
* @note unless this setup function is invoked, the "`reveal`" functionality remains disabled.
|
||||
* Typically this setup will be done by an owning parent container, binding to some internals
|
||||
* and also recursively invoking the "`reveal`" action on the container.
|
||||
*/
|
||||
inline void
|
||||
Tangible::installRevealer (Revealer::RevealeItFun how_to_uncover_the_element)
|
||||
{
|
||||
reveal_ = Revealer{move (how_to_uncover_the_element)};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
244
src/gui/model/w-link.hpp
Normal file
244
src/gui/model/w-link.hpp
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
/*
|
||||
W-LINK.hpp - smart-link to GTK widget
|
||||
|
||||
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 w-link.hpp
|
||||
** A smart link to an GTK widget with automatic disconnection.
|
||||
** Allows to hold a connection to a [trackable] without taking ownership.
|
||||
** The link is statefull, can be reconnected, and automatically transitions
|
||||
** into disconnected state when the target dies. The link state can be tested
|
||||
** by `bool` conversion. WLink objects are fully copyable, and each copy has
|
||||
** its own attachment state and can be reconnected independently.
|
||||
**
|
||||
** The purpose of WLink is to support collaborations between controllers and
|
||||
** widgets or between widgets. Whenever some logic works with or relies on
|
||||
** some other UI entity -- which it might even _"allocate"_ -- without taking
|
||||
** ownership, the relation can be implemented with a WLink.
|
||||
**
|
||||
** @warning this class is not threadsafe, because lib SigC++ is not either,
|
||||
** and it can only be used reliably from within the GUI thread.
|
||||
** @remarks the lib SigC++ is used in GTKmm for handling "Signals" and invoking
|
||||
** "Slots" (i.e. functors) as result of such a signal. Such is dangerous
|
||||
** within the context of an interactive UI, since widgets might go away
|
||||
** and be destroyed due to user interaction. To deal with this problem,
|
||||
** GTKmm derives all widgets from [trackable], which offers an auto
|
||||
** deregistration callback, when the object is destroyed, so any
|
||||
** remaining signal connections can be dropped to avoid calling
|
||||
** a dead functor. We make use of the same mechanism here to
|
||||
** install a callback to invalidate this smart-handle.
|
||||
**
|
||||
** [trackable]: https://developer.gnome.org/libsigc++/stable/structsigc_1_1trackable.html#details "`sigc::trackable`"
|
||||
**
|
||||
** @see \ref WLink_test
|
||||
** @see \ref NotificationHub (usage example)
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#ifndef GUI_MODEL_W_LINK_H
|
||||
#define GUI_MODEL_W_LINK_H
|
||||
|
||||
|
||||
#include "lib/error.hpp"
|
||||
#include "lib/format-string.hpp"
|
||||
|
||||
#include <type_traits>
|
||||
#include <sigc++/trackable.h>
|
||||
|
||||
|
||||
namespace gui {
|
||||
namespace model {
|
||||
|
||||
namespace error = lumiera::error;
|
||||
using error::LUMIERA_ERROR_BOTTOM_VALUE;
|
||||
|
||||
using util::_Fmt;
|
||||
|
||||
|
||||
/**
|
||||
* Managed link to a `sigc::trackable` UI widget, without taking ownership.
|
||||
* Automatically installs a callback to switch this link into detached state
|
||||
* when the target (widget) is destroyed.
|
||||
* @tparam TAR the actual target widget type, which can be forward declared,
|
||||
* until the point where the widget is actually constructed or attached.
|
||||
* This target type must derive from `sigc::trackable`
|
||||
* @warning _not_ threadsafe
|
||||
* @note only EX_SANE, since attaching, detaching and swapping might throw.
|
||||
*/
|
||||
template<class TAR>
|
||||
class WLink
|
||||
{
|
||||
|
||||
TAR* widget_;
|
||||
|
||||
|
||||
public:
|
||||
~WLink()
|
||||
{
|
||||
try {
|
||||
this->clear();
|
||||
}
|
||||
ERROR_LOG_AND_IGNORE (gui, "Detaching managed WLink from Widget")
|
||||
}
|
||||
WLink() noexcept
|
||||
: widget_{nullptr}
|
||||
{ }
|
||||
|
||||
explicit
|
||||
WLink (TAR& targetWidget)
|
||||
: widget_{attachTo (targetWidget)}
|
||||
{ }
|
||||
|
||||
WLink (WLink const& r)
|
||||
: widget_{r.widget_? attachTo(*r.widget_) : nullptr}
|
||||
{ }
|
||||
|
||||
WLink (WLink && rr)
|
||||
: widget_{rr.widget_? attachTo(*rr.widget_) : nullptr}
|
||||
{
|
||||
rr.clear();
|
||||
}
|
||||
|
||||
WLink&
|
||||
operator= (WLink other) ///< @warning only EX_SANE
|
||||
{
|
||||
swap (*this, other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** swap the pointees, including callback registration.
|
||||
* @warning only EX_SANE.
|
||||
* Might leave the following intermediary states
|
||||
* - only r was detached
|
||||
* - both r and l are detached
|
||||
* - both detached, but only l attached to the former target of r,
|
||||
* while the former target of l is now completely detached.
|
||||
*/
|
||||
friend void
|
||||
swap (WLink& l, WLink& r)
|
||||
{
|
||||
TAR* tl = l.widget_;
|
||||
TAR* tr = r.widget_;
|
||||
if (tl == tr) return;
|
||||
r.clear();
|
||||
l.clear();
|
||||
if (tr) l.widget_ = l.attachTo (*tr);
|
||||
if (tl) r.widget_ = r.attachTo (*tl);
|
||||
}
|
||||
|
||||
|
||||
explicit
|
||||
operator bool() const
|
||||
{
|
||||
return bool{widget_};
|
||||
}
|
||||
|
||||
TAR&
|
||||
operator* () const
|
||||
{
|
||||
__ensureAlive();
|
||||
return *widget_;
|
||||
}
|
||||
|
||||
TAR*
|
||||
operator-> () const
|
||||
{
|
||||
__ensureAlive();
|
||||
return widget_;
|
||||
}
|
||||
|
||||
|
||||
bool isValid() const { return bool{*this}; }
|
||||
bool isActive() const { return bool{*this}; }
|
||||
|
||||
|
||||
/** detach and deactivate this link
|
||||
* @note EX_STRONG (but only under the assumption that also
|
||||
* sigc::trackable::remove_destroy_notify_cllback is EX_STRONG)
|
||||
*/
|
||||
void
|
||||
clear()
|
||||
{
|
||||
if (widget_)
|
||||
widget_->remove_destroy_notify_callback (&widget_);
|
||||
widget_ = nullptr;
|
||||
}
|
||||
|
||||
/** (re)connect this smart link to the given target.
|
||||
* Any previously existing link is detached beforehand
|
||||
* @note EX_SANE only (assuming `sigc::trackable` is sane)
|
||||
* Might leave this WLink in disconnected state when throwing
|
||||
* @throws error::External when registration with `sigc::trackable` fails
|
||||
*/
|
||||
void
|
||||
connect (TAR& otherTarget)
|
||||
{
|
||||
if (widget_ == &otherTarget) return;
|
||||
clear();
|
||||
widget_ = attachTo (otherTarget);
|
||||
}
|
||||
|
||||
private:
|
||||
void
|
||||
__ensureAlive() const
|
||||
{
|
||||
if (not widget_)
|
||||
throw error::State ("zombie widget encountered"
|
||||
, LERR_(BOTTOM_VALUE));
|
||||
}
|
||||
|
||||
/** @internal installs the necessary callback
|
||||
* to detach this link in case the target is destroyed
|
||||
* @note EX_STRONG
|
||||
*/
|
||||
TAR*
|
||||
attachTo (TAR& target)
|
||||
{
|
||||
static_assert (std::is_base_of<sigc::trackable, TAR>()
|
||||
,"target type required to be sigc::trackable");
|
||||
|
||||
try {
|
||||
target.add_destroy_notify_callback (&widget_
|
||||
,[](void* p)
|
||||
{
|
||||
TAR* & widgetPtr = *static_cast<TAR**>(p);
|
||||
ASSERT (widgetPtr);
|
||||
widgetPtr = nullptr;
|
||||
return p;
|
||||
});
|
||||
return ⌖
|
||||
}
|
||||
catch (std::exception& problem)
|
||||
{
|
||||
throw error::External (problem, _Fmt{"WLink could not attach to %s. Problem is %s"} % target % problem);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
ERROR (gui, "Unknown exception while attaching WLink");
|
||||
throw error::External (_Fmt{"WLink could not attach to %s due to unidentified Problems"} % target);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}} // namespace gui::model
|
||||
#endif /*GUI_MODEL_W_LINK_H*/
|
||||
|
|
@ -67,24 +67,6 @@ namespace model {
|
|||
UNIMPLEMENTED ("Widget reset");
|
||||
}
|
||||
|
||||
virtual bool
|
||||
doExpand (bool yes) override
|
||||
{
|
||||
UNIMPLEMENTED ("Widget doExpand");
|
||||
}
|
||||
|
||||
virtual void
|
||||
doReveal (ID child) override
|
||||
{
|
||||
UNIMPLEMENTED ("Widget doReveal");
|
||||
}
|
||||
|
||||
virtual void
|
||||
doRevealYourself() override
|
||||
{
|
||||
UNIMPLEMENTED ("Widget doRevealYourself");
|
||||
}
|
||||
|
||||
virtual bool
|
||||
doMsg (string text) override
|
||||
{
|
||||
|
|
|
|||
|
|
@ -32,13 +32,20 @@
|
|||
** lifespan of this service instance must exceed the running of the event loop,
|
||||
** since otherwise the event loop might invoke a lambda bound to the `this`
|
||||
** pointer of a NotificationService already decommissioned. The setup of the
|
||||
** standard Lumiera UI top-level context ensures this is the case, since the
|
||||
** standard Lumiera UI top-level context ensures these requirements, since the
|
||||
** UiManager::performMainLoop() maintains the NotificationService instance
|
||||
** and also performs the blocking `gtk_main()` call. Consequently, any
|
||||
** invocation added from other threads after leaving the GTK main loop
|
||||
** but before closing the GuiNotification facade will just be enqueued,
|
||||
** but then dropped on destruction of the UiDispatcher PImpl.
|
||||
**
|
||||
** Beyond that dispatching functionality, the NotificationService
|
||||
** just serves as entry point to send messages through the [UI-Bus]
|
||||
** (\ref ui-bus.hpp) towards [UI elements](\ref tangible.hpp)
|
||||
** identified by EntryID. Even notifications and error messages
|
||||
** are handled this way, redirecting them toward a dedicated
|
||||
** [Log display](\ref error-log-display.hpp)
|
||||
**
|
||||
** @see ui-dispatcher.hpp
|
||||
**
|
||||
*/
|
||||
|
|
@ -48,9 +55,13 @@
|
|||
#include "gui/ctrl/ui-manager.hpp"
|
||||
#include "gui/ctrl/ui-dispatcher.hpp"
|
||||
#include "gui/notification-service.hpp"
|
||||
#include "gui/interact/wizard.hpp"
|
||||
#include "include/ui-protocol.hpp"
|
||||
|
||||
#include "lib/diff/mutation-message.hpp"
|
||||
#include "lib/diff/gen-node.hpp"
|
||||
#include "include/logging.h"
|
||||
#include "lib/format-string.hpp"
|
||||
#include "lib/depend.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
|
|
@ -58,6 +69,7 @@ extern "C" {
|
|||
#include "common/interface-descriptor.h"
|
||||
}
|
||||
|
||||
#include <utility>
|
||||
#include <string>
|
||||
|
||||
|
||||
|
|
@ -66,8 +78,11 @@ using lib::diff::TreeMutator;
|
|||
using lib::diff::MutationMessage;
|
||||
using gui::ctrl::UiDispatcher;
|
||||
using gui::ctrl::BusTerm;
|
||||
using std::string;
|
||||
using util::cStr;
|
||||
using util::_Fmt;
|
||||
|
||||
using std::string;
|
||||
using std::move;
|
||||
|
||||
|
||||
namespace gui {
|
||||
|
|
@ -82,40 +97,57 @@ namespace gui {
|
|||
{
|
||||
dispatch_->event ([=]()
|
||||
{
|
||||
this->mark (uiElement, uiMessage);
|
||||
ctrl::BusTerm::mark (uiElement, uiMessage);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
void
|
||||
NotificationService::displayInfo (NotifyLevel severity, string const& text)
|
||||
{
|
||||
INFO (gui, "@GUI: display '%s' as notification message.", cStr(text));
|
||||
////////////////////////TODO actually push the information to the GUI ///////////////////////////////////TICKET #1102 : build a message display box in the UI
|
||||
////////////////////////////////////////////////TICKET #1047 : as a temporary solution, use the InfoBox panel...
|
||||
}
|
||||
{ ///////////////////////////////////TICKET #1102 : build a dedicated message display box in the UI
|
||||
ID errorLogID = interact::Wizard::getErrorLogID(); ////////////////////////////////////TICKET #1047 : as a temporary solution, use the InfoBox panel...
|
||||
switch (severity) {
|
||||
case NOTE_ERROR:
|
||||
markError (errorLogID, text);
|
||||
break;
|
||||
case NOTE_INFO:
|
||||
markNote (errorLogID, text);
|
||||
break;
|
||||
case NOTE_WARN:
|
||||
mark (errorLogID, GenNode{string{MARK_Warning}, text});
|
||||
break;
|
||||
default:
|
||||
throw lumiera::error::Logic (_Fmt{"UI Notification with invalid severity %d encountered. "
|
||||
"Given message text was '%s'"} % severity % text);
|
||||
} }
|
||||
|
||||
|
||||
void
|
||||
NotificationService::markError (ID uiElement, string const& text)
|
||||
{
|
||||
dispatchMsg (uiElement, GenNode{"Error", text});
|
||||
dispatchMsg (uiElement, GenNode{string{MARK_Error}, text});
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
NotificationService::markNote (ID uiElement, string const& text)
|
||||
NotificationService::markNote (ID uiElement, string const& text)
|
||||
{
|
||||
dispatchMsg (uiElement, GenNode{"Message", text});
|
||||
dispatchMsg (uiElement, GenNode{string{MARK_Message}, text});
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
NotificationService::mark (ID uiElement, GenNode&& stateMarkMsg)
|
||||
{
|
||||
dispatchMsg (uiElement, move (stateMarkMsg));
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
NotificationService::mutate (ID uiElement, MutationMessage&& diff)
|
||||
{
|
||||
dispatch_->event ([=]() //////////////////////////////////TODO care for error handling!!!
|
||||
{
|
||||
// apply and consume diff message stored within closure
|
||||
dispatch_->event ([=]()
|
||||
{ // apply and consume diff message stored within closure
|
||||
this->change (uiElement, move(unConst(diff)));
|
||||
});
|
||||
}
|
||||
|
|
@ -229,27 +261,35 @@ namespace gui {
|
|||
}
|
||||
)
|
||||
, LUMIERA_INTERFACE_INLINE (markError,
|
||||
void, (LumieraUid element, const char* text),
|
||||
void, (const void* element, const char* text),
|
||||
{
|
||||
if (!_instance) lumiera_error_set (LUMIERA_ERROR_LIFECYCLE, text);
|
||||
else
|
||||
_instance().markError (reinterpret_cast<ID> (*element), text);
|
||||
_instance().markError (*static_cast<lib::idi::BareEntryID const*> (element), text);
|
||||
}
|
||||
)
|
||||
, LUMIERA_INTERFACE_INLINE (markNote,
|
||||
void, (LumieraUid element, const char* text),
|
||||
void, (const void* element, const char* text),
|
||||
{
|
||||
if (!_instance) lumiera_error_set (LUMIERA_ERROR_LIFECYCLE, text);
|
||||
else
|
||||
_instance().markNote (reinterpret_cast<ID> (*element), text);
|
||||
_instance().markNote (*static_cast<lib::idi::BareEntryID const*> (element), text);
|
||||
}
|
||||
)
|
||||
, LUMIERA_INTERFACE_INLINE (mark,
|
||||
void, (const void* element, void* stateMark),
|
||||
{
|
||||
if (!_instance) lumiera_error_set (LUMIERA_ERROR_LIFECYCLE, "passing state mark");
|
||||
else
|
||||
_instance().mark (*static_cast<lib::idi::BareEntryID const*> (element), move(*reinterpret_cast<GenNode*> (stateMark)));
|
||||
}
|
||||
)
|
||||
, LUMIERA_INTERFACE_INLINE (mutate,
|
||||
void, (LumieraUid element, void* diff),
|
||||
void, (const void* element, void* diff),
|
||||
{
|
||||
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 (*static_cast<lib::idi::BareEntryID const*> (element), move(*reinterpret_cast<MutationMessage*> (diff)));
|
||||
}
|
||||
)
|
||||
, LUMIERA_INTERFACE_INLINE (triggerGuiShutdown,
|
||||
|
|
@ -262,15 +302,13 @@ namespace gui {
|
|||
)
|
||||
);
|
||||
|
||||
|
||||
|
||||
} // (END) facade implementation details
|
||||
} //(END) facade implementation details
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* When started, NotificationService connects to the [UI-Bus](ui-bus.hpp) via the provided connection.
|
||||
* When started, NotificationService connects to the [UI-Bus](\ref 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.
|
||||
|
|
|
|||
|
|
@ -27,11 +27,12 @@
|
|||
** events within the lower layers, or as result of invoking commands on the session.
|
||||
**
|
||||
** This service is the implementation of a layer separation facade interface. Clients should use
|
||||
** gui::GuiNotification#facade to access this service. This header defines the interface used
|
||||
** to \em provide this service, not to access it.
|
||||
**
|
||||
** @see gui::GuiFacade
|
||||
** @see core-sevice.hpp starting this service
|
||||
** gui::GuiNotification#facade to access this service. This header here defines the interface
|
||||
** used to _provide_ this service, not to access it.
|
||||
**
|
||||
** @see gui::GuiFacade launching the Lumiera UI
|
||||
** @see facade.hpp RAII holder to start this service and open the interface
|
||||
** @see gui::ctrl::UiManager::performMainLoop() exposes all UI façade interfaces
|
||||
*/
|
||||
|
||||
|
||||
|
|
@ -68,7 +69,7 @@ namespace gui {
|
|||
* route calls through this interface.
|
||||
*
|
||||
* @note the ctor of this class establishes an "up-link"
|
||||
* connection to the [UI-Bus](ui-bus.hpp), which
|
||||
* connection to the [UI-Bus](\ref ui-bus.hpp), which
|
||||
* enables the service implementation to talk to
|
||||
* other facilities within the UI.
|
||||
*/
|
||||
|
|
@ -100,10 +101,11 @@ namespace gui {
|
|||
void displayInfo (NotifyLevel,string const& text) override;
|
||||
void markError (ID uiElement, string const& text) override;
|
||||
void markNote (ID uiElement, string const& text) override;
|
||||
void mark (ID uiElement, GenNode&&) override;
|
||||
void mutate (ID uiElement, MutationMessage&&) override;
|
||||
void triggerGuiShutdown (string const& cause) override;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace gui
|
||||
|
|
|
|||
|
|
@ -27,43 +27,63 @@
|
|||
|
||||
#include "gui/gtk-base.hpp"
|
||||
#include "gui/panel/infobox-panel.hpp"
|
||||
#include "lib/format-string.hpp"
|
||||
#include "gui/widget/error-log-display.hpp"
|
||||
|
||||
using util::_Fmt;
|
||||
using Glib::ustring;
|
||||
|
||||
namespace gui {
|
||||
namespace panel{
|
||||
|
||||
using widget::ErrorLogDisplay;
|
||||
|
||||
|
||||
InfoBoxPanel::InfoBoxPanel (workspace::PanelManager& panelManager, Gdl::DockItem& dockItem)
|
||||
: Panel(panelManager, dockItem, getTitle(), getStockID())
|
||||
, twoParts_{Gtk::ORIENTATION_VERTICAL}
|
||||
, buttons_{}
|
||||
, frame_{"UI Integration Experiments"}
|
||||
, errorLog_{}
|
||||
, frame_{_("System Information")}
|
||||
, logExpander_{_("Error Log")}
|
||||
, theLog_{}
|
||||
{
|
||||
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_);
|
||||
// buttons to control the error log
|
||||
buttonClear_.set_label (_("_clear Log"));
|
||||
buttonClear_.set_use_underline();
|
||||
buttonClear_.set_tooltip_markup (_("Discard all contents of the error log."));
|
||||
buttonClear_.signal_clicked().connect(
|
||||
[this](){ if (theLog_) theLog_->clearAll(); });
|
||||
buttonClearErr_.set_label (_("_Error OK"));
|
||||
buttonClearErr_.set_use_underline();
|
||||
buttonClearErr_.set_tooltip_markup (_("Clear the error state and turn errors in to information entries."));
|
||||
buttonClearErr_.signal_clicked().connect(
|
||||
[this](){ if (theLog_) theLog_->turnError_into_InfoMsg(); });
|
||||
buttonClearInfo_.set_label (_("drop _Info"));
|
||||
buttonClearInfo_.set_use_underline();
|
||||
buttonClearInfo_.set_tooltip_markup (_("Discard all mere info message, retain error entries only."));
|
||||
buttonClearInfo_.signal_clicked().connect(
|
||||
[this](){ if (theLog_) theLog_->clearInfoMsg(); });
|
||||
|
||||
buttons_.add (buttonClear_);
|
||||
buttons_.add (buttonClearErr_);
|
||||
buttons_.add (buttonClearInfo_);
|
||||
//(End)buttons...
|
||||
|
||||
frame_.set_border_width (5);
|
||||
frame_.add (errorLog_);
|
||||
|
||||
// show everything....
|
||||
// show initial configuration....
|
||||
this->add (twoParts_);
|
||||
this->show_all();
|
||||
|
||||
// schedule state update to hide the error related buttons
|
||||
// after the UI is actually mapped to screen.
|
||||
Glib::signal_idle()
|
||||
.connect_once ( sigc::bind<bool>(
|
||||
sigc::mem_fun (*this, &InfoBoxPanel::reflect_LogErrorState), false)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const char*
|
||||
InfoBoxPanel::getTitle()
|
||||
{
|
||||
|
|
@ -77,16 +97,44 @@ namespace panel{
|
|||
}
|
||||
|
||||
|
||||
/** on demand allocate display of information / error log
|
||||
* @note we assume it stays alive until the panel itself is closed.
|
||||
* @remark the ErrorLogDisplay widget exposes a \ref Expander functor,
|
||||
* which is wired here with the Gtk::Expander container holding the Log.
|
||||
* This setup allows to trigger the expand/collapse functionality and
|
||||
* query the expansion state directly on the widget. Especially
|
||||
* \ref NotificationHub relies on this configuration.
|
||||
*/
|
||||
ErrorLogDisplay&
|
||||
InfoBoxPanel::getLog()
|
||||
{
|
||||
if (not theLog_)
|
||||
{
|
||||
theLog_.reset (new ErrorLogDisplay{});
|
||||
logExpander_.set_expanded (false);
|
||||
logExpander_.add (*theLog_);
|
||||
theLog_->expand = model::Expander{[&]() -> bool { return logExpander_.get_expanded(); }
|
||||
,[&](bool yes) { logExpander_.set_expanded (yes); }
|
||||
};
|
||||
theLog_->reveal = model::Revealer{[&]() { Panel::show(true);
|
||||
theLog_->expand(true);
|
||||
} };
|
||||
frame_.set_border_width (5);
|
||||
frame_.add (logExpander_);
|
||||
frame_.show_all();
|
||||
|
||||
theLog_->signalErrorChanged().connect(
|
||||
mem_fun(*this, &InfoBoxPanel::reflect_LogErrorState));
|
||||
}
|
||||
return *theLog_;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
InfoBoxPanel::experiment_1()
|
||||
InfoBoxPanel::reflect_LogErrorState (bool isError)
|
||||
{
|
||||
frame_.set_label("Experiment 1... BANG");
|
||||
|
||||
static uint bangNo{0};
|
||||
static _Fmt msgTemplate{"Bang #%d\n"};
|
||||
|
||||
errorLog_.showMsg(NOTE_WARN, msgTemplate % ++bangNo);
|
||||
buttonClearErr_.set_visible (isError);
|
||||
buttonClearInfo_.set_visible (isError);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
** 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
|
||||
** @todo as of 10/2017 this is used as space to host the information and error log
|
||||
*/
|
||||
|
||||
|
||||
|
|
@ -36,9 +36,13 @@
|
|||
|
||||
|
||||
#include "gui/panel/panel.hpp"
|
||||
#include "gui/widget/error-log-widget.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace gui {
|
||||
namespace widget {
|
||||
class ErrorLogDisplay;
|
||||
}
|
||||
namespace panel{
|
||||
|
||||
class InfoBoxPanel
|
||||
|
|
@ -49,7 +53,8 @@ namespace 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...
|
||||
* @todo just used as place for the error log as of 10/2017.
|
||||
* More to come...
|
||||
*/
|
||||
InfoBoxPanel (workspace::PanelManager&, Gdl::DockItem&);
|
||||
|
||||
|
|
@ -57,14 +62,18 @@ namespace panel{
|
|||
static const char* getTitle(); ///< @deprecated need better design of the PanelManager /////TICKET #1026
|
||||
static const gchar* getStockID();
|
||||
|
||||
widget::ErrorLogDisplay& getLog();
|
||||
|
||||
private:
|
||||
Gtk::Box twoParts_;
|
||||
Gtk::ButtonBox buttons_;
|
||||
Gtk::Button button_1_;
|
||||
Gtk::Button buttonClear_, buttonClearInfo_, buttonClearErr_;
|
||||
Gtk::Frame frame_;
|
||||
widget::ErrorLogWidget errorLog_;
|
||||
Gtk::Expander logExpander_;
|
||||
|
||||
void experiment_1();
|
||||
std::unique_ptr<widget::ErrorLogDisplay> theLog_;
|
||||
|
||||
void reflect_LogErrorState (bool);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,8 @@ namespace panel{
|
|||
,Gdl::DockItem& dockItem
|
||||
,const gchar* longName
|
||||
,const gchar* stockID)
|
||||
: panelManager_(panelManager)
|
||||
: Gtk::Box{Gtk::ORIENTATION_VERTICAL}
|
||||
, panelManager_(panelManager)
|
||||
, dockItem_(dockItem)
|
||||
, panelBar_(*this, stockID)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -36,18 +36,18 @@
|
|||
#include <gdlmm.h>
|
||||
|
||||
namespace gui {
|
||||
namespace workspace {
|
||||
class PanelManager;
|
||||
class WorkspaceWindow;
|
||||
}
|
||||
namespace panel {
|
||||
namespace workspace {
|
||||
class PanelManager;
|
||||
class WorkspaceWindow;
|
||||
}
|
||||
namespace panel {
|
||||
|
||||
|
||||
/**
|
||||
* The base class for all dockable panels.
|
||||
*/
|
||||
class Panel
|
||||
: public Gtk::VBox
|
||||
: public Gtk::Box
|
||||
{
|
||||
protected:
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
** Implementation of the dockable panel to hold the main timeline display.
|
||||
**
|
||||
** @todo as of 10/2016 this is WIP-WIP-WIP : canvas widgets experiment
|
||||
** @todo build a new timeline widget, connected to the UI-Bus
|
||||
** @todo as of 10/2018 we start to build a new timeline widget, connected to the UI-Bus
|
||||
** @see timeline-widget.hpp
|
||||
**
|
||||
*/
|
||||
|
|
@ -33,107 +33,30 @@
|
|||
|
||||
#include "gui/gtk-base.hpp"
|
||||
#include "gui/panel/timeline-panel.hpp"
|
||||
#include "gui/timeline/timeline-widget.hpp"
|
||||
#include "gui/timeline/timeline-widget-empty.hpp"
|
||||
|
||||
//#include "gui/workspace/workspace-window.hpp"
|
||||
#include "gui/ui-bus.hpp"
|
||||
#include "lib/format-string.hpp"
|
||||
#include "lib/format-cout.hpp"
|
||||
|
||||
//#include "lib/util.hpp"
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
|
||||
|
||||
|
||||
using util::_Fmt;
|
||||
//using std::shared_ptr;
|
||||
//using std::weak_ptr;
|
||||
//using util::contains;
|
||||
using Gtk::Widget;
|
||||
using sigc::mem_fun;
|
||||
using sigc::ptr_fun;
|
||||
using std::string;
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
using std::rand;
|
||||
using std::max;
|
||||
using std::make_unique;
|
||||
|
||||
|
||||
namespace gui {
|
||||
namespace panel {
|
||||
|
||||
|
||||
using timeline::TimelinePage;
|
||||
using timeline::TimelineWidget;
|
||||
using timeline::TimelineWidgetEmpty;
|
||||
|
||||
|
||||
TimelinePanel::TimelinePanel (workspace::PanelManager& panelManager,
|
||||
Gdl::DockItem& dockItem)
|
||||
: Panel(panelManager, dockItem, getTitle(), getStockID())
|
||||
, twoParts_(Gtk::ORIENTATION_VERTICAL)
|
||||
, buttons_()
|
||||
, frame_("Gtk::Layout Experiments")
|
||||
, scroller_()
|
||||
, canvas_()
|
||||
, tabs_{}
|
||||
, pages_{}
|
||||
{
|
||||
twoParts_.pack_start(buttons_, Gtk::PACK_SHRINK);
|
||||
twoParts_.pack_start(frame_);
|
||||
|
||||
buttons_.set_layout(Gtk::BUTTONBOX_START);
|
||||
|
||||
// buttons to trigger experiments
|
||||
button_1_.set_label("_place");
|
||||
button_1_.set_use_underline();
|
||||
button_1_.set_tooltip_markup("<b>Experiment 1</b>:\nplace new child widget\nat random position on the canvas");
|
||||
button_1_.signal_clicked().connect(
|
||||
mem_fun(*this, &TimelinePanel::experiment_1));
|
||||
buttons_.add(button_1_);
|
||||
|
||||
button_2_.set_label("_move");
|
||||
button_2_.set_use_underline();
|
||||
button_2_.set_tooltip_markup("<b>Experiment 2</b>:\nmove all child widgets randomly");
|
||||
button_2_.signal_clicked().connect(
|
||||
mem_fun(*this, &TimelinePanel::experiment_2));
|
||||
buttons_.add(button_2_);
|
||||
|
||||
button_3_.set_label("a_lign");
|
||||
button_3_.set_use_underline();
|
||||
button_3_.set_tooltip_markup("<b>Experiment 3</b>:\nalign all child widgets in a row\nwith silight random vertical offset");
|
||||
button_3_.signal_clicked().connect(
|
||||
mem_fun(*this, &TimelinePanel::experiment_3));
|
||||
buttons_.add(button_3_);
|
||||
|
||||
button_4_.set_label("_grow");
|
||||
button_4_.set_use_underline();
|
||||
button_4_.set_tooltip_markup("<b>Experiment 4</b>:\nextend arbitrary child widget's text");
|
||||
button_4_.signal_clicked().connect(
|
||||
mem_fun(*this, &TimelinePanel::experiment_4));
|
||||
buttons_.add(button_4_);
|
||||
|
||||
button_5_.set_label("_kill");
|
||||
button_5_.set_use_underline();
|
||||
button_5_.set_tooltip_markup("<b>Experiment 5</b>:\nkill arbitrary child widget");
|
||||
button_5_.signal_clicked().connect(
|
||||
mem_fun(*this, &TimelinePanel::experiment_5));
|
||||
buttons_.add(button_5_);
|
||||
|
||||
toggleDraw_.set_label("draw");
|
||||
toggleDraw_.signal_clicked().connect(
|
||||
[this]() {
|
||||
canvas_.enableDraw (this->toggleDraw_.get_active());
|
||||
});
|
||||
buttons_.add(toggleDraw_);
|
||||
//(End)buttons...
|
||||
|
||||
frame_.add(scroller_);
|
||||
frame_.set_border_width(5);
|
||||
|
||||
scroller_.set_shadow_type(Gtk::SHADOW_NONE);
|
||||
scroller_.set_border_width(10);
|
||||
scroller_.add(canvas_);
|
||||
|
||||
canvas_.adjustSize();
|
||||
addTimeline (PageHandle{new TimelineWidgetEmpty{}});
|
||||
|
||||
// show everything....
|
||||
this->add(twoParts_);
|
||||
this->add(tabs_);
|
||||
this->show_all();
|
||||
}
|
||||
|
||||
|
|
@ -150,231 +73,27 @@ namespace panel {
|
|||
}
|
||||
|
||||
|
||||
|
||||
void
|
||||
TimelinePanel::experiment_1()
|
||||
{
|
||||
frame_.set_label("Experiment 1... PLACE");
|
||||
|
||||
ChildEx* chld = makeChld();
|
||||
childz_.push_back(chld);
|
||||
uint x = rand() % 1000;
|
||||
uint y = rand() % 500;
|
||||
canvas_.put(*chld, x, y);
|
||||
chld->show();
|
||||
canvas_.adjustSize();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
TimelinePanel::experiment_2()
|
||||
{
|
||||
frame_.set_label("Experiment 2... MOVE");
|
||||
for (Widget* chld : childz_)
|
||||
{
|
||||
uint x = canvas_.child_property_x(*chld);
|
||||
uint y = canvas_.child_property_y(*chld);
|
||||
int deltaX = -20 + rand() % 41;
|
||||
int deltaY = -15 + rand() % 31;
|
||||
x = uint(max (0, int(x) + deltaX));
|
||||
y = uint(max (0, int(y) + deltaY));
|
||||
|
||||
canvas_.move (*chld, x,y);
|
||||
}
|
||||
canvas_.adjustSize();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
TimelinePanel::experiment_3()
|
||||
{
|
||||
frame_.set_label("Experiment 3... ALIGN");
|
||||
uint pos=0;
|
||||
for (Widget* chld : childz_)
|
||||
{
|
||||
uint y = rand() % 30;
|
||||
canvas_.move (*chld, pos, y);
|
||||
|
||||
int width = chld->get_allocated_width();
|
||||
pos += 0.6 * width;
|
||||
}
|
||||
canvas_.adjustSize();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
TimelinePanel::experiment_4()
|
||||
{
|
||||
frame_.set_label("Experiment 4... GROW");
|
||||
uint selector = rand() % childz_.size();
|
||||
ChildEx& toGrow = *childz_[selector];
|
||||
toGrow.set_label ("***"+toGrow.get_label()+"***");
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
TimelinePanel::experiment_5()
|
||||
{
|
||||
frame_.set_label("Experiment 5... KILL");
|
||||
uint killPos = rand() % childz_.size();
|
||||
ChildV::iterator killThat(&childz_[killPos]);
|
||||
ChildEx* victim = *killThat;
|
||||
childz_.erase (killThat);
|
||||
canvas_.remove (*victim);
|
||||
delete victim;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Canvas::enableDraw (bool yes)
|
||||
{
|
||||
shallDraw_ = yes;
|
||||
|
||||
// force redrawing of the visible area...
|
||||
auto win = get_window();
|
||||
if (win)
|
||||
{
|
||||
int w = get_allocation().get_width();
|
||||
int h = get_allocation().get_height();
|
||||
Gdk::Rectangle rect{0, 0, w, h};
|
||||
win->invalidate_rect(rect, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void
|
||||
Canvas::adjustSize()
|
||||
{
|
||||
recalcExtension_ = true;
|
||||
}
|
||||
|
||||
void
|
||||
Canvas::determineExtension()
|
||||
{
|
||||
if (not recalcExtension_) return;
|
||||
|
||||
uint extH=20, extV=20;
|
||||
Gtk::Container::ForeachSlot callback
|
||||
= [&](Gtk::Widget& chld)
|
||||
{
|
||||
auto alloc = chld.get_allocation();
|
||||
uint x = alloc.get_x();
|
||||
uint y = alloc.get_y();
|
||||
x += alloc.get_width();
|
||||
y += alloc.get_height();
|
||||
extH = max (extH, x);
|
||||
extV = max (extV, y);
|
||||
};
|
||||
foreach(callback);
|
||||
recalcExtension_ = false;
|
||||
set_size (extH, extV);
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Canvas::on_draw(Cairo::RefPtr<Cairo::Context> const& cox)
|
||||
{
|
||||
if (shallDraw_)
|
||||
{
|
||||
uint extH, extV;
|
||||
determineExtension();
|
||||
get_size (extH, extV);
|
||||
|
||||
auto adjH = get_hadjustment();
|
||||
auto adjV = get_vadjustment();
|
||||
double offH = adjH->get_value();
|
||||
double offV = adjV->get_value();
|
||||
|
||||
cox->save();
|
||||
cox->translate(-offH, -offV);
|
||||
|
||||
// draw red diagonal line
|
||||
cox->set_source_rgb(0.8, 0.0, 0.0);
|
||||
cox->set_line_width (10.0);
|
||||
cox->move_to(0, 0);
|
||||
cox->line_to(extH, extV);
|
||||
cox->stroke();
|
||||
cox->restore();
|
||||
|
||||
// cause child widgets to be redrawn
|
||||
bool event_is_handled = Gtk::Layout::on_draw(cox);
|
||||
|
||||
// any drawing which follows happens on top of child widgets...
|
||||
cox->save();
|
||||
cox->translate(-offH, -offV);
|
||||
|
||||
cox->set_source_rgb(0.2, 0.4, 0.9);
|
||||
cox->set_line_width (2.0);
|
||||
cox->rectangle(0,0, extH, extV);
|
||||
cox->stroke();
|
||||
cox->restore();
|
||||
|
||||
return event_is_handled;
|
||||
}
|
||||
else
|
||||
return Gtk::Layout::on_draw(cox);
|
||||
}
|
||||
|
||||
|
||||
/* === Support for Investigation === */
|
||||
|
||||
namespace {
|
||||
_Fmt childID("Chld-%02d");
|
||||
|
||||
int instanceCnt = 0;
|
||||
}
|
||||
|
||||
|
||||
uint ChildEx::childNo = 0;
|
||||
|
||||
|
||||
ChildEx::ChildEx()
|
||||
: Gtk::Button(string (childID % childNo++))
|
||||
bool
|
||||
isEmptyTimeline (auto& pages)
|
||||
{
|
||||
++instanceCnt;
|
||||
return 1 == pages.size()
|
||||
and dynamic_cast<TimelineWidgetEmpty*> (pages[0].get());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
ChildEx::on_clicked()
|
||||
TimelinePanel::addTimeline (PageHandle&& pTimelineWidget)
|
||||
{
|
||||
cout << "|=="<<get_label()<<endl;
|
||||
if (isEmptyTimeline (pages_))
|
||||
{
|
||||
tabs_.remove_page();
|
||||
pages_.clear();
|
||||
}
|
||||
pages_.push_back (move (pTimelineWidget));
|
||||
TimelinePage& addedPage = *pages_.back();
|
||||
tabs_.append_page (addedPage, addedPage.getLabel());
|
||||
}
|
||||
|
||||
|
||||
ChildEx*
|
||||
TimelinePanel::makeChld()
|
||||
{
|
||||
return Gtk::manage(new ChildEx);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////TICKET #1020 : verification code for instance management
|
||||
ChildEx::~ChildEx()
|
||||
{
|
||||
--instanceCnt;
|
||||
if (instanceCnt > 0)
|
||||
cout << " ↯↯ still "<<instanceCnt<<" children to kill..."<<endl;
|
||||
else
|
||||
if (instanceCnt == 0)
|
||||
cout << "+++ Success: all children are dead..."<<endl;
|
||||
else
|
||||
cout << "### ALARM ###"<<endl
|
||||
<< "instanceCnt == "<<instanceCnt <<endl;
|
||||
}
|
||||
|
||||
void
|
||||
__verifyDeadChildren()
|
||||
{
|
||||
if (instanceCnt == 0)
|
||||
cout << "+++ Success: all children are dead..."<<endl;
|
||||
else
|
||||
cout << "### ALARM ###"<<endl
|
||||
<< "instanceCnt == "<<instanceCnt <<endl;
|
||||
}
|
||||
////////////////////////////////////////////////////////////////////TICKET #1020 : verification code for instance management
|
||||
|
||||
|
||||
|
||||
}} // namespace gui::panel
|
||||
|
|
|
|||
|
|
@ -24,19 +24,17 @@
|
|||
/** @file timeline-panel.hpp
|
||||
** A dockable container to hold a notebook of timeline displays.
|
||||
**
|
||||
** ## planned design 2/2017
|
||||
** The architecture of the UI is in a state of transition right now, working towards the goal
|
||||
** of interconnected layers and an operative session and engine. Within the new control structure
|
||||
** about to be built, there is an UI top-level circle of managing entities to handle global concerns
|
||||
** and actions. The ctrl::InteractionDirector incorporates the role of _model root_ and thus manages
|
||||
** a collection of timelines. The timeline panel exposes this collection and allows to view and
|
||||
** interact with one of the given timelines. The build-up of the timeline display and thus the
|
||||
** initiative to expose a timeline comes from the interaction director -- but as usual with
|
||||
** graphical user interfaces, any user interaction after this point is handled autonomously
|
||||
** by the gui::timeline::TimelineWidget and gui::timeline::TimelineController
|
||||
** ## Management of Timeline display
|
||||
** Within the Lumiera GTK UI, there is an UI top-level circle of managing entities to handle global
|
||||
** concerns and actions. The ctrl::InteractionDirector incorporates the role of _model root_ and thus
|
||||
** manages a collection of timelines. The timeline panel exposes this collection and allows to view and
|
||||
** interact with one of the given timelines. The build-up of the timeline display and thus the initiative
|
||||
** to expose a timeline comes from the interaction director -- but as usual with graphical user interfaces,
|
||||
** any user interaction after this point is handled autonomously by the gui::timeline::TimelineWidget and
|
||||
** gui::timeline::TimelineController
|
||||
**
|
||||
** @todo as of 10/2016 this is WIP-WIP-WIP : canvas widgets experiment
|
||||
** @todo build a new timeline widget, connected to the UI-Bus
|
||||
** @todo as of 10/2018 we start to build a new timeline widget, connected to the UI-Bus
|
||||
*/
|
||||
|
||||
|
||||
|
|
@ -44,120 +42,46 @@
|
|||
#define GUI_PANEL_TIMELINE_PANEL_H
|
||||
|
||||
#include "gui/panel/panel.hpp"
|
||||
#include "gui/timeline/timeline-widget.hpp"
|
||||
|
||||
//#include <memory>
|
||||
#include <gtkmm/notebook.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
|
||||
|
||||
namespace gui {
|
||||
namespace model{
|
||||
class Sequence;
|
||||
}
|
||||
namespace panel {
|
||||
|
||||
//using std::shared_ptr;
|
||||
|
||||
/**
|
||||
* "experimental" child widget for investigation of Gtk::Layout
|
||||
*/
|
||||
class ChildEx
|
||||
: public Gtk::Button
|
||||
{
|
||||
static uint childNo;
|
||||
|
||||
public:
|
||||
ChildEx();
|
||||
~ChildEx();
|
||||
|
||||
private:
|
||||
void on_clicked() override;
|
||||
};
|
||||
|
||||
void __verifyDeadChildren();
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* "experimental" custom canvas, based on Gtk::Layout.
|
||||
* In addition this customised widget supports direct drawing
|
||||
*/
|
||||
class Canvas
|
||||
: public Gtk::Layout
|
||||
{
|
||||
bool shallDraw_;
|
||||
bool recalcExtension_ = false;
|
||||
|
||||
public:
|
||||
void enableDraw (bool);
|
||||
void adjustSize();
|
||||
|
||||
private:
|
||||
virtual bool on_draw (Cairo::RefPtr<Cairo::Context> const&) override;
|
||||
|
||||
void determineExtension();
|
||||
};
|
||||
|
||||
namespace panel{
|
||||
|
||||
|
||||
/**
|
||||
* Dockable panel to hold timeline widget(s).
|
||||
* @todo build the actual implementation, after finishing the investigation
|
||||
*
|
||||
* ## Investigation of gtk::GtkLayout
|
||||
* As of 10/2016, we start this task with an exploration of GTK behaviour
|
||||
*
|
||||
* \par Plan of investigation
|
||||
* 1. place some simple widgets (Buttons) ✔
|
||||
* 2. learn how to draw ✔
|
||||
* 3. place a huge number of widgets, to scrutinise scrolling and performance
|
||||
* 4. place widgets overlapping and irregularily, beyond the scrollable area ✔
|
||||
* 5. bind signals to those widgets, to verify event dispatching ✔
|
||||
* 6. bind some further signal(s) to the ~GtkLayout container
|
||||
* 7. hide and re-show a partially and a totally overlapped widget
|
||||
* 8. find a way to move a widget ✔ and delete arbitrary widgets ✔
|
||||
* 9. expand an existing widget (text change) ✔
|
||||
* 10. build a custom "clip" widget
|
||||
* 11. retrofit all preceding tests to use this "clip" widget
|
||||
* @todo WIP 10/2018 the actual re-implementation of Lumiera's Timeline display
|
||||
*/
|
||||
class TimelinePanel
|
||||
: public Panel
|
||||
{
|
||||
using PageHandle = std::unique_ptr<timeline::TimelinePage>;
|
||||
using Timelines = std::vector<PageHandle>;
|
||||
|
||||
Gtk::Notebook tabs_;
|
||||
Timelines pages_;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @param panel_manager The owner panel manager widget.
|
||||
* @param dock_item The GdlDockItem that will host this panel.
|
||||
*/
|
||||
TimelinePanel(workspace::PanelManager&, Gdl::DockItem&);
|
||||
TimelinePanel (workspace::PanelManager&, Gdl::DockItem&);
|
||||
|
||||
|
||||
static const char* getTitle();
|
||||
static const gchar* getStockID();
|
||||
|
||||
/** take ownership of the widget and place it into a new tab */
|
||||
void addTimeline (PageHandle &&);
|
||||
|
||||
private:
|
||||
Gtk::Box twoParts_;
|
||||
Gtk::ButtonBox buttons_;
|
||||
Gtk::Button button_1_;
|
||||
Gtk::Button button_2_;
|
||||
Gtk::Button button_3_;
|
||||
Gtk::Button button_4_;
|
||||
Gtk::Button button_5_;
|
||||
Gtk::CheckButton toggleDraw_;
|
||||
Gtk::Frame frame_;
|
||||
Gtk::ScrolledWindow scroller_;
|
||||
Canvas canvas_;
|
||||
|
||||
ChildEx* makeChld();
|
||||
|
||||
using ChildV = std::vector<ChildEx*>;
|
||||
ChildV childz_;
|
||||
|
||||
void experiment_1();
|
||||
void experiment_2();
|
||||
void experiment_3();
|
||||
void experiment_4();
|
||||
void experiment_5();
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -63,9 +63,6 @@ namespace setting {
|
|||
void
|
||||
AssetController::buildMutator (TreeMutator::Handle buffer)
|
||||
{
|
||||
// using Attrib = std::pair<const string,string>;
|
||||
// using lib::diff::collection;
|
||||
|
||||
buffer.create (
|
||||
TreeMutator::build()
|
||||
);
|
||||
|
|
|
|||
|
|
@ -77,14 +77,14 @@ namespace setting {
|
|||
: public model::Controller
|
||||
{
|
||||
///////TODO create UI representation for the asset subsections
|
||||
|
||||
/** content population and manipulation via UI-Bus */
|
||||
void buildMutator (lib::diff::TreeMutator::Handle) override;
|
||||
|
||||
public:
|
||||
AssetController (ID identity, ctrl::BusTerm& nexus);
|
||||
~AssetController();
|
||||
|
||||
/** content population and manipulation via UI-Bus */
|
||||
void buildMutator (lib::diff::TreeMutator::Handle) override;
|
||||
|
||||
private:
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/*
|
||||
HeaderPaneWidget - display pane for track headers within the timeline
|
||||
StyleScheme - magic keys to access uniform styling scheme
|
||||
|
||||
Copyright (C) Lumiera.org
|
||||
2016, Hermann Vosseler <Ichthyostega@web.de>
|
||||
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
|
||||
|
|
@ -21,54 +21,27 @@
|
|||
* *****************************************************/
|
||||
|
||||
|
||||
/** @file header-pane-widget.cpp
|
||||
** Implementation of the track header pane within the timeline UI.
|
||||
/** @file style-scheme.hpp
|
||||
** Definition of access keys for uniform UI styling.
|
||||
**
|
||||
** @todo WIP-WIP-WIP as of 12/2016
|
||||
** @see gui::workspace::UiStyle
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#include "gui/gtk-base.hpp"
|
||||
#include "gui/timeline/header-pane-widget.hpp"
|
||||
|
||||
//#include "gui/ui-bus.hpp"
|
||||
//#include "lib/format-string.hpp"
|
||||
//#include "lib/format-cout.hpp"
|
||||
|
||||
//#include "lib/util.hpp"
|
||||
|
||||
//#include <algorithm>
|
||||
//#include <vector>
|
||||
|
||||
|
||||
|
||||
//using util::_Fmt;
|
||||
//using util::contains;
|
||||
//using Gtk::Widget;
|
||||
//using sigc::mem_fun;
|
||||
//using sigc::ptr_fun;
|
||||
//using std::cout;
|
||||
//using std::endl;
|
||||
|
||||
#include "gui/style-scheme.hpp"
|
||||
|
||||
namespace gui {
|
||||
namespace timeline {
|
||||
|
||||
const Literal TAG_ERROR{"ERROR"}; ////////////////////////////////////////////////////////////TICKET #1168 : find a way to manage style of custom extended UI elements
|
||||
const Literal TAG_WARN{"WARN"};
|
||||
|
||||
/** CSS class added temporarily to make a widget "flash"
|
||||
* in response to the corresponding UI-Bus message
|
||||
* @see flash-deco.hpp */
|
||||
cuString CSS_CLASS_FLASH{"indication_flash"};
|
||||
const uint STYLE_FLASH_DURATION_in_ms = 1100;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
HeaderPaneWidget::HeaderPaneWidget ()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
HeaderPaneWidget::~HeaderPaneWidget()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}}// namespace gui::timeline
|
||||
}// namespace gui
|
||||
58
src/gui/style-scheme.hpp
Normal file
58
src/gui/style-scheme.hpp
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
STYLE_SCHEME.hpp - magic keys to access uniform styling scheme
|
||||
|
||||
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 style-scheme.hpp
|
||||
** Hard wired key constants to access a global uniform styling scheme.
|
||||
**
|
||||
** @todo as or 10/2018 this is mere placeholder code. The StyleManager needs rework
|
||||
**
|
||||
** @see ui-style.hpp
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef GUI_STYLE_SCHEME_H
|
||||
#define GUI_STYLE_SCHEME_H
|
||||
|
||||
|
||||
#include "lib/symbol.hpp"
|
||||
|
||||
|
||||
namespace Glib {
|
||||
class ustring;
|
||||
}
|
||||
namespace gui {
|
||||
|
||||
using lib::Literal;
|
||||
using cuString = const Glib::ustring;
|
||||
|
||||
|
||||
extern const Literal TAG_ERROR; ////////////////////////////////////////////////////////TICKET #1168 : find a way to manage style of custom extended UI elements
|
||||
extern const Literal TAG_WARN;
|
||||
|
||||
extern cuString CSS_CLASS_FLASH;
|
||||
extern const uint STYLE_FLASH_DURATION_in_ms;
|
||||
|
||||
|
||||
|
||||
}// namespace gui
|
||||
#endif /*GUI_STYLE_SCHEME_H*/
|
||||
|
|
@ -31,6 +31,7 @@
|
|||
|
||||
#include "gui/gtk-base.hpp"
|
||||
#include "gui/timeline/body-canvas-widget.hpp"
|
||||
#include "gui/timeline/track-body.hpp"
|
||||
|
||||
//#include "gui/ui-bus.hpp"
|
||||
//#include "lib/format-string.hpp"
|
||||
|
|
@ -57,17 +58,47 @@ namespace timeline {
|
|||
|
||||
|
||||
|
||||
TimelineCanvas::TimelineCanvas()
|
||||
: Gtk::Layout{}
|
||||
, rootBody_{nullptr}
|
||||
{ }
|
||||
|
||||
|
||||
|
||||
BodyCanvasWidget::~BodyCanvasWidget() { }
|
||||
|
||||
|
||||
BodyCanvasWidget::BodyCanvasWidget ()
|
||||
: Gtk::ScrolledWindow{}
|
||||
, canvas_{}
|
||||
{
|
||||
this->set_shadow_type(Gtk::SHADOW_IN);
|
||||
this->set_policy (Gtk::POLICY_ALWAYS, Gtk::POLICY_AUTOMATIC); // always need a horizontal scrollbar
|
||||
this->property_expand() = true; // dynamically grab any available additional space
|
||||
this->add(canvas_);
|
||||
|
||||
// canvas_.adjustSize();
|
||||
|
||||
// show everything....
|
||||
this->show_all();
|
||||
}
|
||||
|
||||
|
||||
BodyCanvasWidget::~BodyCanvasWidget()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* The Lumiera Timeline model does not rely on a list of tracks, as most conventional video editing software does --
|
||||
* rather, each sequence holds a _fork of nested scopes._ This recursively nested structure is reflected in the way
|
||||
* we organise and draw the timeline representation onto the TimelineCanvas: we use an intermediary entity, the TrackBody
|
||||
* as an organisational grouping device, even while we draw _all of the timeline representation_ onto a single global
|
||||
* ::canvas_ within the (scrollable) BodyCanvasWidget. Thus, adding the first TrackBody to represent the root track
|
||||
* of a Timeline, will also prepare the grounding for any other nested entities to be drawn on top.
|
||||
*/
|
||||
void
|
||||
BodyCanvasWidget::installForkRoot (TrackBody& rootTrackBody)
|
||||
{
|
||||
canvas_.rootBody_ = &rootTrackBody;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -69,18 +69,36 @@
|
|||
namespace gui {
|
||||
namespace timeline {
|
||||
|
||||
class TrackBody;
|
||||
|
||||
|
||||
class TimelineCanvas
|
||||
: public Gtk::Layout
|
||||
{
|
||||
public:
|
||||
TrackBody* rootBody_;
|
||||
|
||||
TimelineCanvas();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @todo WIP-WIP as of 12/2016
|
||||
*/
|
||||
class BodyCanvasWidget
|
||||
: public Gtk::ScrolledWindow
|
||||
{
|
||||
TimelineCanvas canvas_;
|
||||
|
||||
public:
|
||||
BodyCanvasWidget();
|
||||
~BodyCanvasWidget();
|
||||
|
||||
|
||||
/** @internal Initially install the contents corresponding to the root track fork */
|
||||
void installForkRoot (TrackBody& rootTrackBody);
|
||||
|
||||
private:/* ===== Internals ===== */
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -25,11 +25,13 @@
|
|||
** Implementation details of clip presentation management.
|
||||
**
|
||||
** @todo WIP-WIP-WIP as of 12/2016
|
||||
** @todo as of 10/2018 timeline display in the UI is rebuilt to match the architecture
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#include "gui/gtk-base.hpp"
|
||||
#include "include/ui-protocol.hpp"
|
||||
#include "gui/timeline/clip-presenter.hpp"
|
||||
#include "gui/timeline/marker-widget.hpp"
|
||||
|
||||
|
|
@ -93,57 +95,57 @@ namespace timeline {
|
|||
.attach (collection(markers_)
|
||||
.isApplicableIf ([&](GenNode const& spec) -> bool
|
||||
{ // »Selector« : require object-like sub scope with type-field "Marker"
|
||||
return "Marker" == spec.data.recordType();
|
||||
return TYPE_Marker == spec.data.recordType();
|
||||
})
|
||||
.matchElement ([&](GenNode const& spec, PMarker const& elm) -> bool
|
||||
{
|
||||
return spec.idi == ID(elm);
|
||||
return spec.idi == ID{*elm};
|
||||
})
|
||||
.constructFrom ([&](GenNode const& spec) -> PMarker
|
||||
{
|
||||
return make_unique<MarkerWidget>(spec.idi, this->uiBus_);
|
||||
return make_unique<MarkerWidget> (spec.idi, this->uiBus_);
|
||||
})
|
||||
.buildChildMutator ([&](PMarker& target, GenNode::ID const& subID, TreeMutator::Handle buff) -> bool
|
||||
{
|
||||
if (ID(target) != subID) return false;
|
||||
if (ID{*target} != subID) return false;
|
||||
target->buildMutator (buff);
|
||||
return true;
|
||||
}))
|
||||
.attach (collection(effects_)
|
||||
.isApplicableIf ([&](GenNode const& spec) -> bool
|
||||
{ // »Selector« : require object-like sub scope with type-field "Effect"
|
||||
return "Effect" == spec.data.recordType();
|
||||
return TYPE_Effect == spec.data.recordType();
|
||||
})
|
||||
.matchElement ([&](GenNode const& spec, PEffect const& elm) -> bool
|
||||
{
|
||||
return spec.idi == ID(elm);
|
||||
return spec.idi == ID{*elm};
|
||||
})
|
||||
.constructFrom ([&](GenNode const& spec) -> PEffect
|
||||
{
|
||||
return make_unique<ClipPresenter>(spec.idi, this->uiBus_);
|
||||
return make_unique<ClipPresenter> (spec.idi, this->uiBus_);
|
||||
})
|
||||
.buildChildMutator ([&](PEffect& target, GenNode::ID const& subID, TreeMutator::Handle buff) -> bool
|
||||
{
|
||||
if (ID(target) != subID) return false;
|
||||
if (ID{*target} != subID) return false;
|
||||
target->buildMutator (buff);
|
||||
return true;
|
||||
}))
|
||||
.attach (collection(channels_)
|
||||
.isApplicableIf ([&](GenNode const& spec) -> bool
|
||||
{ // »Selector« : require object-like sub scope with type-field "Channel"
|
||||
return "Channel" == spec.data.recordType();
|
||||
return TYPE_Channel == spec.data.recordType();
|
||||
})
|
||||
.matchElement ([&](GenNode const& spec, PChannel const& elm) -> bool
|
||||
{
|
||||
return spec.idi == ID(elm);
|
||||
return spec.idi == ID{*elm};
|
||||
})
|
||||
.constructFrom ([&](GenNode const& spec) -> PChannel
|
||||
{
|
||||
return make_unique<ClipPresenter>(spec.idi, this->uiBus_);
|
||||
return make_unique<ClipPresenter> (spec.idi, this->uiBus_);
|
||||
})
|
||||
.buildChildMutator ([&](PChannel& target, GenNode::ID const& subID, TreeMutator::Handle buff) -> bool
|
||||
{
|
||||
if (ID(target) != subID) return false;
|
||||
if (ID{*target} != subID) return false;
|
||||
target->buildMutator (buff);
|
||||
return true;
|
||||
})));
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@
|
|||
** clips, just to indicate some content is actually present in this part of the timeline.
|
||||
**
|
||||
** @todo WIP-WIP-WIP as of 12/2016
|
||||
** @todo as of 10/2018 timeline display in the UI is rebuilt to match the architecture
|
||||
**
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,13 @@
|
|||
** The HeaderPaneWidget aggregates those patchbay elements into a nested, collapsable tree
|
||||
** structure in accordance with the nesting of scopes. For the actual layout, it has to collaborate
|
||||
** with the timeline::LayoutManager to work out the space available for each individual track head
|
||||
** and to keep these parts aligned with the tracks in the [timeline body](BodyCanvasWidget).
|
||||
** and to keep these parts aligned with the tracks in the [timeline body](\ref BodyCanvasWidget).
|
||||
**
|
||||
** The header area is shown at the left side of the timeline display, always visible.
|
||||
** At the top of the header area, corresponding to the time ruler within the body at the right,
|
||||
** we place a compact navigation control, including also the timecode display. The space below
|
||||
** is kept in sync with the respective track entries of the timeline, and is itself built as
|
||||
** a nested structure of PatchbayWidget entries.
|
||||
**
|
||||
** @todo WIP-WIP-WIP as of 12/2016
|
||||
**
|
||||
|
|
@ -44,6 +50,8 @@
|
|||
#define GUI_TIMELINE_HEADER_PANE_WIDGET_H
|
||||
|
||||
#include "gui/gtk-base.hpp"
|
||||
#include "gui/timeline/navigator-widget.hpp"
|
||||
#include "gui/timeline/patchbay-widget.hpp"
|
||||
|
||||
//#include "lib/util.hpp"
|
||||
|
||||
|
|
@ -55,16 +63,44 @@
|
|||
namespace gui {
|
||||
namespace timeline {
|
||||
|
||||
using PAdjustment = Glib::RefPtr<Gtk::Adjustment>;
|
||||
|
||||
|
||||
class TrackHeadWidget;
|
||||
|
||||
|
||||
/**
|
||||
* @todo WIP-WIP as of 12/2016
|
||||
*/
|
||||
class HeaderPaneWidget
|
||||
: public Gtk::Box
|
||||
{
|
||||
NavigatorWidget navigator_;
|
||||
PatchbayWidget patchbay_;
|
||||
|
||||
public:
|
||||
HeaderPaneWidget();
|
||||
~HeaderPaneWidget();
|
||||
|
||||
~HeaderPaneWidget() { }
|
||||
|
||||
HeaderPaneWidget (PAdjustment const& vScroll)
|
||||
: Gtk::Box{Gtk::ORIENTATION_VERTICAL}
|
||||
, navigator_{}
|
||||
, patchbay_{vScroll}
|
||||
{
|
||||
this->pack_start (navigator_, Gtk::PACK_SHRINK);
|
||||
this->pack_start (patchbay_, Gtk::PACK_EXPAND_WIDGET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initially install the root node of the track fork,
|
||||
* which later can be extended recursively by adding nested
|
||||
* sub-forks ("Sub-Tracks").
|
||||
*/
|
||||
void
|
||||
installForkRoot (TrackHeadWidget& rootTrackHead)
|
||||
{
|
||||
patchbay_.installFork (rootTrackHead);
|
||||
}
|
||||
|
||||
private:/* ===== Internals ===== */
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -25,11 +25,13 @@
|
|||
** Implementation of marker display.
|
||||
**
|
||||
** @todo WIP-WIP-WIP as of 12/2016
|
||||
** @todo as of 10/2018 timeline display in the UI is rebuilt to match the architecture
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#include "gui/gtk-base.hpp"
|
||||
#include "include/ui-protocol.hpp"
|
||||
#include "gui/timeline/marker-widget.hpp"
|
||||
|
||||
//#include "gui/ui-bus.hpp"
|
||||
|
|
@ -81,11 +83,11 @@ namespace timeline {
|
|||
{
|
||||
buffer.create (
|
||||
TreeMutator::build()
|
||||
.change("name", [&](string val)
|
||||
.change(ATTR_name, [&](string val)
|
||||
{
|
||||
name_ = val;
|
||||
})
|
||||
.change("kind", [&](string val)
|
||||
.change(META_kind, [&](string val)
|
||||
{
|
||||
if (val == "LOOP") kind_ = LOOP;
|
||||
else kind_ = MARK;
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
** may receive messages over the UI-Bus.
|
||||
**
|
||||
** @todo WIP-WIP-WIP as of 12/2016
|
||||
** @todo as of 10/2018 timeline display in the UI is rebuilt to match the architecture
|
||||
**
|
||||
*/
|
||||
|
||||
|
|
@ -39,7 +40,7 @@
|
|||
|
||||
#include "gui/gtk-base.hpp"
|
||||
#include "gui/model/widget.hpp"
|
||||
#include "gui/widget/label-widget.hpp"
|
||||
#include "gui/widget/element-box-widget.hpp"
|
||||
|
||||
//#include "lib/util.hpp"
|
||||
|
||||
|
|
@ -64,7 +65,7 @@ namespace timeline {
|
|||
*/
|
||||
class MarkerWidget
|
||||
: public model::Widget
|
||||
, public widget::LabelWidget
|
||||
, public widget::ElementBoxWidget
|
||||
{
|
||||
MarkerKind kind_;
|
||||
string name_;
|
||||
|
|
|
|||
|
|
@ -60,14 +60,13 @@ namespace timeline {
|
|||
|
||||
|
||||
|
||||
|
||||
NavigatorWidget::~NavigatorWidget() { }
|
||||
|
||||
|
||||
NavigatorWidget::NavigatorWidget ()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
NavigatorWidget::~NavigatorWidget()
|
||||
{
|
||||
}
|
||||
: Gtk::Grid{}
|
||||
{ }
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ namespace timeline {
|
|||
* @todo WIP-WIP as of 12/2016
|
||||
*/
|
||||
class NavigatorWidget
|
||||
: public Gtk::Grid
|
||||
{
|
||||
public:
|
||||
NavigatorWidget ();
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
|
||||
#include "gui/gtk-base.hpp"
|
||||
#include "gui/timeline/patchbay-widget.hpp"
|
||||
|
||||
#include "gui/timeline/track-head-widget.hpp"
|
||||
//#include "gui/ui-bus.hpp"
|
||||
//#include "lib/format-string.hpp"
|
||||
//#include "lib/format-cout.hpp"
|
||||
|
|
@ -46,6 +46,7 @@
|
|||
|
||||
//using util::_Fmt;
|
||||
//using util::contains;
|
||||
using Gtk::Adjustment;
|
||||
//using Gtk::Widget;
|
||||
//using sigc::mem_fun;
|
||||
//using sigc::ptr_fun;
|
||||
|
|
@ -60,16 +61,34 @@ namespace timeline {
|
|||
|
||||
|
||||
|
||||
PatchbayWidget::PatchbayWidget ()
|
||||
{
|
||||
}
|
||||
PatchbayWidget::~PatchbayWidget() { }
|
||||
|
||||
|
||||
PatchbayWidget::~PatchbayWidget()
|
||||
/**
|
||||
* Set up the patchbay area of the timeline UI.
|
||||
* The patchbay is a container to hold the actual placement control widgets
|
||||
* arranged alongside with each track, according to the nested track structure.
|
||||
* The header pane and thus especially the patchbay needs to follow as a slave
|
||||
* to the vertical scrolling adjustments of the Timeline display; if the users
|
||||
* scrolls up or down over the more or less expanded tree of tracks, the header
|
||||
* has to follow this scrolled display in sync. Thus the implementation of the
|
||||
* patchbay is based on a Gtk::Viewport, which is attached to the vertical
|
||||
* Gtk::Adjustment, as exposed by the Gtk::ScrolledWindow holding the
|
||||
* timeline body canvas on the right side of the Timeline UI.
|
||||
* @note the Patchbay is not scrollable in horizontal direction,
|
||||
* thus we create a disabled Adjustment for this parameter.
|
||||
*/
|
||||
PatchbayWidget::PatchbayWidget (PAdjustment const& vScroll)
|
||||
: Gtk::Viewport{Gtk::Adjustment::create (0,0,0,0,0,0), vScroll}
|
||||
{ }
|
||||
|
||||
|
||||
void
|
||||
PatchbayWidget::installFork (TrackHeadWidget& rootTrackHead)
|
||||
{
|
||||
this->add (rootTrackHead);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}}// namespace gui::timeline
|
||||
|
|
|
|||
|
|
@ -22,22 +22,14 @@
|
|||
|
||||
|
||||
/** @file patchbay-widget.hpp
|
||||
** Widget to control the placement parameters of a fork within the timeline header pane UI.
|
||||
** Widget to hold the track head controls within the timeline header pane UI.
|
||||
** [The fork](\ref session::Fork), a recursively nested system of scopes, is rendered within
|
||||
** the timeline display as a system of nested tracks. Each of these tracks possibly holds some
|
||||
** child tracks plus some actual media clips, which all inherit parameters of placement from
|
||||
** this fork ("track"). These parameters address various aspects of how content is attached
|
||||
** ("placed") into the model at large, examples being
|
||||
** - how to route the output
|
||||
** - how to "place" this content into an _output space_, like e.g.
|
||||
** + sound panning
|
||||
** + video overlay parameters (additive, opaque, transparent)
|
||||
** + video or audio _level_ (=fader)
|
||||
** - how to locate this content in time (e.g. relative to some marker)
|
||||
** For each track, we show a patchbay in the timeline header pane, which serves to control
|
||||
** such aspects relevant for all content contained within the scope of this track.
|
||||
** the timeline display as a system of nested tracks. For each of these tracks we get a header
|
||||
** section, allowing to control its placement parameters, including start time, output routing
|
||||
** level and panning. The _Patch Bay_ is the container holding all those track header controls,
|
||||
** arranged into a recursively nested structure.
|
||||
**
|
||||
** @todo WIP-WIP-WIP as of 12/2016
|
||||
** @todo WIP-WIP-WIP as of 10/2018
|
||||
**
|
||||
*/
|
||||
|
||||
|
|
@ -57,16 +49,28 @@
|
|||
namespace gui {
|
||||
namespace timeline {
|
||||
|
||||
using PAdjustment = Glib::RefPtr<Gtk::Adjustment>;
|
||||
|
||||
class TrackHeadWidget;
|
||||
|
||||
|
||||
/**
|
||||
* Header pane control area corresponding to a Track with nested child Tracks.
|
||||
* This structure is used recursively to build up the Fork of nested Tracks.
|
||||
* - first row: Placement + Property pane
|
||||
* - second row: content or nested tracks.
|
||||
* @todo WIP-WIP as of 12/2016
|
||||
*/
|
||||
class PatchbayWidget
|
||||
: public Gtk::Viewport
|
||||
{
|
||||
public:
|
||||
PatchbayWidget ();
|
||||
PatchbayWidget (PAdjustment const& vScroll);
|
||||
~PatchbayWidget();
|
||||
|
||||
|
||||
/** @internal Initially install the contents corresponding to the track fork root */
|
||||
void installFork (TrackHeadWidget& rootTrackHead);
|
||||
|
||||
private:/* ===== Internals ===== */
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -32,13 +32,15 @@
|
|||
** - thus we get a rather simple mapping, with some fixed attributes and no
|
||||
** flexible child collection. The root track is implemented as TrackPresenter.
|
||||
**
|
||||
** @todo as of 12/2016 a complete rework of the timeline display is underway
|
||||
** @todo as of 10/2018 timeline display in the UI is rebuilt to match the architecture
|
||||
** @see TimelineWidget
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#include "gui/gtk-base.hpp"
|
||||
#include "include/ui-protocol.hpp"
|
||||
#include "gui/timeline/timeline-layout.hpp"
|
||||
#include "gui/timeline/timeline-controller.hpp"
|
||||
#include "gui/timeline/track-presenter.hpp"
|
||||
#include "gui/timeline/marker-widget.hpp"
|
||||
|
|
@ -74,18 +76,21 @@ namespace timeline {
|
|||
//const int TimelineWidget::TrackPadding = 1;
|
||||
//const int TimelineWidget::HeaderWidth = 150;
|
||||
//const int TimelineWidget::HeaderIndentWidth = 10;
|
||||
class TrackHeadWidget;
|
||||
class TrackBody;
|
||||
|
||||
|
||||
|
||||
|
||||
TimelineController::TimelineController (ID identity, ID trackID, ctrl::BusTerm& nexus)
|
||||
TimelineController::TimelineController (ID identity, ID trackID, ctrl::BusTerm& nexus, TimelineLayout& layoutManager)
|
||||
: Controller{identity, nexus}
|
||||
, name_{identity.getSym()} // fallback initialise name from human-readable ID symbol
|
||||
, markers_{}
|
||||
, fork_{new TrackPresenter{trackID, nexus}}
|
||||
, name_{identity.getSym()} // fallback initialise name from human-readable ID symbol
|
||||
{
|
||||
UNIMPLEMENTED ("how to make the controller operative...");
|
||||
}
|
||||
, fork_{new TrackPresenter{trackID, nexus, [&](TrackHeadWidget& head,TrackBody& body)
|
||||
{
|
||||
layoutManager.installRootTrack (head, body);
|
||||
}}}
|
||||
{ }
|
||||
|
||||
|
||||
TimelineController::~TimelineController()
|
||||
|
|
@ -133,6 +138,7 @@ namespace timeline {
|
|||
TimelineController::buildMutator (TreeMutator::Handle buffer)
|
||||
{
|
||||
using PMarker = unique_ptr<MarkerWidget>;
|
||||
auto rootForkID = ID{*fork_};
|
||||
|
||||
buffer.create (
|
||||
TreeMutator::build()
|
||||
|
|
@ -143,7 +149,7 @@ namespace timeline {
|
|||
})
|
||||
.matchElement ([&](GenNode const& spec, PMarker const& elm) -> bool
|
||||
{ // »Matcher« : how to know we're dealing with the right object
|
||||
return spec.idi == ID(elm);
|
||||
return spec.idi == ID{*elm};
|
||||
})
|
||||
.constructFrom ([&](GenNode const& spec) -> PMarker
|
||||
{ // »Constructor« : what to do when the diff mentions a new entity
|
||||
|
|
@ -151,17 +157,17 @@ namespace timeline {
|
|||
})
|
||||
.buildChildMutator ([&](PMarker& target, GenNode::ID const& subID, TreeMutator::Handle buff) -> bool
|
||||
{ // »Mutator« : how to apply the diff recursively to a nested scope
|
||||
if (ID(target) != subID) return false; // - require match on already existing child object
|
||||
if (ID{*target} != subID) return false; // - require match on already existing child object
|
||||
target->buildMutator (buff); // - delegate to child to build nested TreeMutator
|
||||
return true;
|
||||
}))
|
||||
.mutateAttrib("fork", [&](TreeMutator::Handle buff)
|
||||
{ // »Attribute Mutator« : how enter an object field as nested scope
|
||||
.mutateAttrib(rootForkID, [&](TreeMutator::Handle buff)
|
||||
{ // »Attribute Mutator« : how to enter the track-fork-object field as nested scope
|
||||
REQUIRE (fork_);
|
||||
fork_->buildMutator(buff);
|
||||
})
|
||||
.change("name", [&](string val)
|
||||
{ // »Attribute Setter« : how assign a new value to some object field
|
||||
.change(ATTR_name, [&](string val)
|
||||
{ // »Attribute Setter« : how to assign a new value to the name field (object member)
|
||||
name_ = val;
|
||||
}));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@
|
|||
** - these in turn manage a set of ClipPresenter entities
|
||||
** - and those presenters care for injecting suitable widgets into the TimelineWidget's parts.
|
||||
**
|
||||
** @todo as of 12/2016 a complete rework of the timeline display is underway
|
||||
** @todo as of 10/2018 timeline display in the UI is rebuilt to match the architecture
|
||||
**
|
||||
*/
|
||||
|
||||
|
|
@ -72,12 +72,13 @@ namespace timeline {
|
|||
using std::string;
|
||||
|
||||
class TrackPresenter;
|
||||
class TimelineLayout;
|
||||
class MarkerWidget;
|
||||
|
||||
|
||||
/**
|
||||
* Controller to supervise the timeline display.
|
||||
* As a [tangible element](model::Tangible), it is attached to the UI-Bus.
|
||||
* As a [tangible element](\ref model::Tangible), it is attached to the UI-Bus.
|
||||
* @todo WIP-WIP-rewrite as of 12/2016
|
||||
* @remarks a Timeline always has an attached Sequence, which in turn has
|
||||
* a single mandatory root track. This in turn might hold further child tracks,
|
||||
|
|
@ -86,10 +87,11 @@ namespace timeline {
|
|||
class TimelineController
|
||||
: public model::Controller
|
||||
{
|
||||
string name_;
|
||||
|
||||
vector<unique_ptr<MarkerWidget>> markers_;
|
||||
std::unique_ptr<TrackPresenter> fork_;
|
||||
|
||||
string name_;
|
||||
|
||||
public:
|
||||
/**
|
||||
|
|
@ -97,9 +99,9 @@ namespace timeline {
|
|||
* @param trackID the mandatory root track used in the associated Sequence
|
||||
* @param nexus some established connection to the UI-Bus, used for registration.
|
||||
*/
|
||||
TimelineController (ID identity, ID trackID, ctrl::BusTerm& nexus);
|
||||
TimelineController (ID identity, ID trackID, ctrl::BusTerm& nexus, TimelineLayout&);
|
||||
|
||||
~TimelineController();
|
||||
~TimelineController();
|
||||
|
||||
|
||||
/** set up a binding to respond to mutation messages via UiBus */
|
||||
|
|
@ -108,6 +110,12 @@ namespace timeline {
|
|||
|
||||
public: /* ===== Control interface ===== */
|
||||
|
||||
string const&
|
||||
getName() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
public: /* ===== Signals ===== */
|
||||
|
||||
private:/* ===== Events ===== */
|
||||
|
|
|
|||
123
src/gui/timeline/timeline-gui.cpp
Normal file
123
src/gui/timeline/timeline-gui.cpp
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
TimelineGui - top-level anchor point and placeholder for "the timeline"
|
||||
|
||||
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 timeline-gui.cpp
|
||||
** Implementation details a delegating anchor proxy for a TimelineWidget.
|
||||
** To reduce header include impact of global-ctx.hpp, all operations involving
|
||||
** actual object construction are defined within this dedicated translation unit.
|
||||
**
|
||||
** @see TimelineWidget
|
||||
** @see gui::interact::InteractionDirector::buildMutator
|
||||
** @see gui::timeline::TimelineController::buildMutator
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#include "gui/gtk-base.hpp"
|
||||
#include "include/ui-protocol.hpp"
|
||||
#include "gui/timeline/timeline-gui.hpp"
|
||||
#include "gui/timeline/timeline-widget.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
using lib::diff::TreeMutator;
|
||||
using std::unique_ptr;
|
||||
|
||||
|
||||
namespace gui {
|
||||
namespace timeline {
|
||||
|
||||
|
||||
/**
|
||||
* Initially, TimelineGui is just an empty placeholder handle.
|
||||
* Yet knowledge of the represented timeline's ID is required
|
||||
*/
|
||||
TimelineGui::TimelineGui (ID identity, ID trackID)
|
||||
: timelineID_{identity}
|
||||
, rootTrackID_{trackID}
|
||||
{ }
|
||||
|
||||
/**
|
||||
* @remark basically the default copy ctor, just redefined here explicitly
|
||||
* to allow using TimelineGui without the need to include timeline-widget.hpp
|
||||
*/
|
||||
TimelineGui::TimelineGui (TimelineGui const& r)
|
||||
: WLink{r}, util::Cloneable{}
|
||||
, timelineID_{r.timelineID_}
|
||||
, rootTrackID_{r.rootTrackID_}
|
||||
{ }
|
||||
|
||||
TimelineGui::TimelineGui (TimelineGui && rr)
|
||||
: WLink{std::move (rr)}
|
||||
, timelineID_{rr.timelineID_}
|
||||
, rootTrackID_{rr.rootTrackID_}
|
||||
{ }
|
||||
|
||||
TimelineGui::~TimelineGui()
|
||||
{
|
||||
if (this->isActive())
|
||||
TODO ("cascading destruction of the TimelineWidget"); /////////////////////////////////////////////TICKET 1016 : implement sane unwinding
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* actually build a TimelineWidget to enact the role represented by this smart-handle.
|
||||
* @remark some implementation twist involved here, since TimelinePanel manages a collection
|
||||
* of `unique_ptr<TimelinePage>`, in order to be able to hold an empty placeholder page.
|
||||
* Since C++ unfortunately does not support _Covariance_ proper, we need to fabricate a
|
||||
* unique_ptr<TimelinePage> right here, which can than be handed over to TimelinePanel,
|
||||
* yet still we _do inherit_ from WLink<TimelineWidget>, i.e. expose the subclass.
|
||||
* The `unque_ptr` owns and manages the TimelineWidget, which is itself non-copyable
|
||||
* and stays at a fixed location in heap memory, as is required by gui::ctrl::Nexus
|
||||
* to maintain a registration of the UI-Bus connection. WLink to the contrary
|
||||
* just connects to the widget, and is automatically disconnected when it dies.
|
||||
*/
|
||||
unique_ptr<TimelinePage>
|
||||
TimelineGui::buildTimelineWidget (BusTerm& nexus)
|
||||
{
|
||||
TimelineWidget* widget;
|
||||
unique_ptr<TimelinePage> pageHandle {widget = new TimelineWidget{timelineID_, rootTrackID_, nexus}};
|
||||
this->connect (*widget);
|
||||
return pageHandle;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* forwarding implementation of the DiffMutable interface,
|
||||
* delegating through the referred TimelineWidget if present,
|
||||
* yet silently ignoring any diff messages otherwise.
|
||||
* @see tree-mutator.hpp
|
||||
*/
|
||||
void
|
||||
TimelineGui::buildMutator (lib::diff::TreeMutator::Handle buffer)
|
||||
{
|
||||
if (this->isActive())
|
||||
operator*().buildMutator (buffer); // delegate to TimelineController
|
||||
else // else when no widget exists...
|
||||
buffer.create(
|
||||
TreeMutator::build()
|
||||
.ignoreAllChanges()); // ...consume and ignore diff
|
||||
}
|
||||
|
||||
|
||||
}}// namespace gui::timeline
|
||||
106
src/gui/timeline/timeline-gui.hpp
Normal file
106
src/gui/timeline/timeline-gui.hpp
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
TIMELINE-GUI.hpp - top-level anchor point and placeholder for "the timeline"
|
||||
|
||||
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 timeline/timeline-gui.hpp
|
||||
** Anchor point and placeholder for the UI representation of a Timeline from the session.
|
||||
** TimelineGui elements are what is managed by the InteractionDirector as children reflecting
|
||||
** the top-level collection of Timelines within the session. However, these elements are proxies,
|
||||
** implemented as [smart-handle for a widget](\ref model::WLink) -- because the fact _that_ a given
|
||||
** timeline _is indeed presented_ needs to remain at the UI's discretion, retaining the latter's total
|
||||
** authority in question of interaction materiality. Moreover, the actual TimelineWidget is not at all
|
||||
** attached within the InteractionDirector as a managing entity, it is rather installed as a child widget
|
||||
** within a notebook control at some [timeline docking panel](\ref TimelinePanel). There could be several
|
||||
** such timeline panels in various top-level windows, and it is a question within the user's workspace
|
||||
** arrangement where exactly it will be placed, if at all. So TimelineGui acts as smart-handle to access
|
||||
** such a widget, and it stands in as a proxy to receive diff messages for this given timeline. Yet when
|
||||
** this timeline is not present in the UI, such diff messages and other UI-Bus messages addressed for
|
||||
** this entity are to be silently ignored.
|
||||
**
|
||||
** \par how the timeline presentation structure is built
|
||||
** The actual UI representation of a timeline tends to be a highly complex tree of interconnected widgets.
|
||||
** There is no "master plan" knowledge within the UI how to build such a structure, rather this structure
|
||||
** evolves in response to _population diff messages_ from the core. Which in turn are either produced
|
||||
** initially, or in answer to a population request originating from within the UI, prompted by the
|
||||
** need to expose some timeline at a given place within the interface.
|
||||
**
|
||||
** @see gui::interact::InteractionDirector::buildMutator
|
||||
** @see gui::timeline::TimelineWidget
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#ifndef GUI_TIMELINE_TIMELINE_GUI_H
|
||||
#define GUI_TIMELINE_TIMELINE_GUI_H
|
||||
|
||||
#include "gui/ctrl/bus-term.hpp"
|
||||
#include "gui/model/w-link.hpp"
|
||||
#include "lib/diff/diff-mutable.hpp"
|
||||
#include "lib/nocopy.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
|
||||
namespace gui {
|
||||
namespace timeline {
|
||||
|
||||
class TimelinePage;
|
||||
class TimelineWidget;
|
||||
using ctrl::BusTerm;
|
||||
|
||||
/**
|
||||
* smart-Handle as anchor point for "the UI representation" of a timeline.
|
||||
* A collection of TimelineGui elements is managed as children of the InteractionDirector.
|
||||
* The corresponding TimelineWidget may or may not be present within the UI currently.
|
||||
*/
|
||||
class TimelineGui
|
||||
: public model::WLink<TimelineWidget>
|
||||
, public lib::diff::DiffMutable
|
||||
, util::Cloneable
|
||||
{
|
||||
using ID = BusTerm::ID;
|
||||
|
||||
ID timelineID_;
|
||||
ID rootTrackID_;
|
||||
|
||||
public:
|
||||
TimelineGui (ID identity, ID trackID);
|
||||
virtual ~TimelineGui();
|
||||
|
||||
TimelineGui (TimelineGui const&);
|
||||
TimelineGui (TimelineGui &&);
|
||||
|
||||
operator ID() const { return timelineID_; }
|
||||
|
||||
|
||||
/** actually build a TimelineWidget to enact the role
|
||||
* represented by this smart-handle */
|
||||
std::unique_ptr<TimelinePage> buildTimelineWidget (BusTerm&);
|
||||
|
||||
/** @internal this method is invoked by the UI-Bus when dispatching a MutationMessage... */
|
||||
void buildMutator (lib::diff::TreeMutator::Handle buffer) override;
|
||||
|
||||
};
|
||||
|
||||
|
||||
}}// namespace gui::timeline
|
||||
#endif /*GUI_TIMELINE_TIMELINE_GUI_H*/
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
LayoutManager - global timeline layout management and display control
|
||||
TimelineLayout - global timeline layout management and display control
|
||||
|
||||
Copyright (C) Lumiera.org
|
||||
2016, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
|
@ -21,16 +21,17 @@
|
|||
* *****************************************************/
|
||||
|
||||
|
||||
/** @file layout-manager.cpp
|
||||
/** @file timeline-layout.cpp
|
||||
** Implementation details of global timeline layout management.
|
||||
**
|
||||
** @todo WIP-WIP-WIP as of 12/2016
|
||||
** @todo as of 10/2018 timeline display in the UI is rebuilt to match the architecture
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#include "gui/gtk-base.hpp"
|
||||
#include "gui/timeline/layout-manager.hpp"
|
||||
#include "gui/timeline/timeline-layout.hpp"
|
||||
|
||||
//#include "gui/ui-bus.hpp"
|
||||
//#include "lib/format-string.hpp"
|
||||
|
|
@ -58,16 +59,35 @@ namespace timeline {
|
|||
|
||||
|
||||
|
||||
TimelineLayout::~TimelineLayout() { }
|
||||
|
||||
LayoutManager::LayoutManager ()
|
||||
TimelineLayout::TimelineLayout (Gtk::Paned& topLevelContainer)
|
||||
: paneSplitPosition_{topLevelContainer.property_position()}
|
||||
, bodyCanvas_{}
|
||||
, headerPane_{bodyCanvas_.get_vadjustment()} // wire the header pane (Gtk::Viewport) to follow the body vertical scroll movement
|
||||
{
|
||||
topLevelContainer.add1 (headerPane_);
|
||||
topLevelContainer.add2 (bodyCanvas_);
|
||||
}
|
||||
|
||||
|
||||
LayoutManager::~LayoutManager()
|
||||
/**
|
||||
* This function is invoked once for each new TimelineWidget, in order to build the
|
||||
* starting point for the track widget structure, which then can be extended recursively
|
||||
* to add further nested tracks. The central problem for this widget hierarchy is that
|
||||
* we have to build two matching structures in parallel...
|
||||
* - the track header area ("patchbay")
|
||||
* - the corresponding track body with actual content (clips)
|
||||
*/
|
||||
void
|
||||
TimelineLayout::installRootTrack(TrackHeadWidget& head, TrackBody& body)
|
||||
{
|
||||
headerPane_.installForkRoot (head);
|
||||
bodyCanvas_.installForkRoot (body);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
LAYOUT-MANAGER.hpp - global timeline layout management and display control
|
||||
TIMELINE-LAYOUT.hpp - global timeline layout management and display control
|
||||
|
||||
Copyright (C) Lumiera.org
|
||||
2016, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
*/
|
||||
|
||||
|
||||
/** @file layout-manager.hpp
|
||||
/** @file timeline-layout.hpp
|
||||
** A core service of the timeline UI to ensure consistent display and layout
|
||||
** of all components within the timeline. The content of the timeline is organised
|
||||
** into several nested collections, possibly holding several thousand individual elements.
|
||||
|
|
@ -40,11 +40,12 @@
|
|||
** # Architecture
|
||||
**
|
||||
** A naive approach would have a global layout manager drill down into some model storage
|
||||
** and reach into the components to manipulate and adjust the layout to fit. Doing so can
|
||||
** be considered, since this links together details scattered all over the model into a
|
||||
** huge global process carried out at a single code location. Any further extension or
|
||||
** evolution of details of the UI presentation are bound to be worked into this core
|
||||
** global piece of code, which soon becomes brittle, hard to understand and generally
|
||||
** and reach into the components to manipulate and adjust the layout to fit. Yet however
|
||||
** straight forward and adequate this might seem, following this routine is a recipe for
|
||||
** disaster, since this procedure now ties and links together details scattered all over
|
||||
** the model into a huge global process, carried out at a single code location. Any further
|
||||
** extension or evolution of details of the UI presentation are bound to be worked into this
|
||||
** core global piece of code, which soon becomes brittle, hard to understand and generally
|
||||
** a liability and maintenance burden. We have seen this happen in numerous existing
|
||||
** code bases (and in fact even our own initial approach started to go down that route).
|
||||
** Thus we strive to break up the whole process of controlling the layout into several
|
||||
|
|
@ -57,13 +58,14 @@
|
|||
**
|
||||
** Whenever the layout of timeline contents has to be (re)established, we trigger a recursive
|
||||
** evaluation pass, which in fact is a tree walk. The layout manager creates a DisplayEvaluation
|
||||
** record, which is passed to the [Element's allocate function](Element::allocate). The element
|
||||
** in turn has the liability to walks its children and recursively initiate a nested evaluation
|
||||
** record, which is passed to the [Element's allocate function](\ref Element::allocate). The element
|
||||
** in turn has the liability to walk its children and recursively initiate a nested evaluation
|
||||
** by invoking DisplayEvaluation::evaluateChild(Element), which in turn calls back to
|
||||
** LayoutManager::evaluate() to initiate a recursive evaluation pass. Within the recursively
|
||||
** created DisplayEvaluation elements, we are able to transport and aggregate information
|
||||
** necessary to give each element it' screen allocation. And this in turn allows us to
|
||||
** decide upon a suitable display strategy for each individual element.
|
||||
** decide upon a suitable display strategy for each individual element, within a local
|
||||
** and self-contained context.
|
||||
**
|
||||
** For this to work, the _element_ can not be the actual widget, since the result of this whole
|
||||
** process might be to create or retract an actual GTK widget. For this reason, the timeline
|
||||
|
|
@ -72,14 +74,17 @@
|
|||
** [MVP pattern](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter) here.
|
||||
**
|
||||
** @todo WIP-WIP-WIP as of 12/2016
|
||||
** @todo as of 10/2018 timeline display in the UI is rebuilt to match the architecture
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#ifndef GUI_TIMELINE_LAYOUT_MANAGER_H
|
||||
#define GUI_TIMELINE_LAYOUT_MANAGER_H
|
||||
#ifndef GUI_TIMELINE_TIMELINE_LAYOUT_H
|
||||
#define GUI_TIMELINE_TIMELINE_LAYOUT_H
|
||||
|
||||
#include "gui/gtk-base.hpp"
|
||||
#include "gui/timeline/header-pane-widget.hpp"
|
||||
#include "gui/timeline/body-canvas-widget.hpp"
|
||||
|
||||
//#include "lib/util.hpp"
|
||||
|
||||
|
|
@ -91,20 +96,35 @@
|
|||
namespace gui {
|
||||
namespace timeline {
|
||||
|
||||
class TrackHeadWidget;
|
||||
class TrackBody;
|
||||
|
||||
|
||||
/**
|
||||
* @todo WIP-WIP as of 12/2016
|
||||
* Top-level anchor point for the timeline display (widgets).
|
||||
* The central entity to organise concerns relevant for the presentation of the
|
||||
* Timeline as a whole, as opposed to rendering individual tracks as part of the Timeline.
|
||||
* @todo WIP-WIP as of 10/2018
|
||||
*/
|
||||
class LayoutManager
|
||||
class TimelineLayout
|
||||
{
|
||||
Glib::PropertyProxy<int> paneSplitPosition_;
|
||||
|
||||
BodyCanvasWidget bodyCanvas_;
|
||||
HeaderPaneWidget headerPane_;
|
||||
|
||||
|
||||
public:
|
||||
LayoutManager ();
|
||||
~LayoutManager();
|
||||
|
||||
TimelineLayout (Gtk::Paned&);
|
||||
~TimelineLayout();
|
||||
|
||||
/** @internal anchor the display of the root track into the two display panes */
|
||||
void installRootTrack (TrackHeadWidget&,TrackBody&);
|
||||
|
||||
private:/* ===== Internals ===== */
|
||||
|
||||
};
|
||||
|
||||
|
||||
}}// namespace gui::timeline
|
||||
#endif /*GUI_TIMELINE_LAYOUT_MANAGER_H*/
|
||||
#endif /*GUI_TIMELINE_TIMELINE_LAYOUT_H*/
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
/*
|
||||
TimelineWidgetEmpty - placeholder widget for a missing timeline
|
||||
|
||||
Copyright (C) Lumiera.org
|
||||
2016, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of
|
||||
the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
|
||||
* *****************************************************/
|
||||
|
||||
|
||||
/** @file timeline-widget-empty.cpp
|
||||
** Implementation of an empty timeline placeholder.
|
||||
**
|
||||
** @todo as of 12/2016 a complete rework of the timeline display is underway
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#include "gui/gtk-base.hpp"
|
||||
#include "gui/timeline/timeline-widget-empty.hpp"
|
||||
|
||||
//#include "gui/workspace/workspace-window.hpp"
|
||||
//#include "gui/ui-bus.hpp"
|
||||
//#include "lib/format-string.hpp"
|
||||
//#include "lib/format-cout.hpp"
|
||||
|
||||
//#include "lib/util.hpp"
|
||||
//#include <algorithm>
|
||||
//#include <cstdlib>
|
||||
|
||||
|
||||
|
||||
//using util::_Fmt;
|
||||
//using std::shared_ptr;
|
||||
//using std::weak_ptr;
|
||||
//using util::contains;
|
||||
//using Gtk::Widget;
|
||||
//using sigc::mem_fun;
|
||||
//using sigc::ptr_fun;
|
||||
//using std::cout;
|
||||
//using std::endl;
|
||||
|
||||
|
||||
namespace gui {
|
||||
namespace timeline {
|
||||
|
||||
//const int TimelineWidget::TrackPadding = 1;
|
||||
//const int TimelineWidget::HeaderWidth = 150;
|
||||
//const int TimelineWidget::HeaderIndentWidth = 10;
|
||||
|
||||
|
||||
|
||||
|
||||
TimelineWidgetEmpty::TimelineWidgetEmpty ()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
TimelineWidgetEmpty::~TimelineWidgetEmpty()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}}// namespace gui::timeline
|
||||
|
|
@ -23,12 +23,12 @@
|
|||
|
||||
/** @file timeline-widget-empty.hpp
|
||||
** Empty placeholder to be rendered when the UI starts without session.
|
||||
** The TimelinePannel represents the [model root](\ref mobject::session::Root) and thus
|
||||
** has the responsibility to manage the individual TimelineWidget instances. In case the
|
||||
** UI is started with no usable session loaded in Proc-Layer, this empty placeholder widget
|
||||
** can be inserted.
|
||||
**
|
||||
** @todo as of 12/2016 a complete rework of the timeline display is underway
|
||||
** A TimelinePannel is able to expose several timelines in a tabbed notebook.
|
||||
** However, the actual [model root](\ref mobject::session::Root) is represented by the
|
||||
** InteractionDirector, which receives the timeline contents pushed up from the session
|
||||
** as diff messages and injects a TimelineWidget for each of them. But when not timeline
|
||||
** is present and thus no actual TimelineWidget was yet instantiated, the TimelinePanel
|
||||
** will install this TimelineWidgetEmpty placeholder instead to fill the void.
|
||||
**
|
||||
*/
|
||||
|
||||
|
|
@ -37,12 +37,7 @@
|
|||
#define GUI_TIMELINE_TIMELINE_WIDGET_EMPTY_H
|
||||
|
||||
#include "gui/gtk-base.hpp"
|
||||
|
||||
#include "lib/time/timevalue.hpp"
|
||||
|
||||
//#include <memory>
|
||||
//#include <vector>
|
||||
|
||||
#include "gui/timeline/timeline-widget.hpp"
|
||||
|
||||
|
||||
namespace gui {
|
||||
|
|
@ -50,31 +45,52 @@ namespace timeline {
|
|||
|
||||
|
||||
/**
|
||||
* Core timeline display (custom widget).
|
||||
* @todo WIP-WIP-rewrite as of 12/2016
|
||||
* @remarks At top level, this widget is split into a header pane (left)
|
||||
* and a scrollable timeline body (right). The layout of both parts is aligned.
|
||||
* Placeholder to show when no actual Timeline is present.
|
||||
* Typically this happens when the application starts up and no session has been loaded (yet).
|
||||
* @todo This widget could offer a list of last sessions to pick from etc...
|
||||
* @remarks We just display some quote to fill the void...
|
||||
*/
|
||||
class TimelineWidgetEmpty
|
||||
: public TimelinePage
|
||||
{
|
||||
|
||||
Gtk::Frame frame_;
|
||||
Gtk::Box content_;
|
||||
Gtk::Label messageText_;
|
||||
Gtk::Label quoteText_;
|
||||
|
||||
Gtk::Image image_;
|
||||
|
||||
cuString
|
||||
getLabel() const override
|
||||
{
|
||||
return _("[virgin]");
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* @param source_state state to be used used as the
|
||||
* data source (model) for this timeline widget.
|
||||
*/
|
||||
TimelineWidgetEmpty();
|
||||
~TimelineWidgetEmpty();
|
||||
|
||||
|
||||
|
||||
public: /* ===== Control interface ===== */
|
||||
|
||||
public: /* ===== Signals ===== */
|
||||
|
||||
private:/* ===== Events ===== */
|
||||
|
||||
private:/* ===== Internals ===== */
|
||||
|
||||
TimelineWidgetEmpty()
|
||||
: TimelinePage{}
|
||||
, content_{Gtk::ORIENTATION_VERTICAL}
|
||||
, image_{Gtk::StockID{"track_enabled"}, Gtk::ICON_SIZE_LARGE_TOOLBAR}
|
||||
{
|
||||
messageText_.set_markup("To be or not to be that is the question...");
|
||||
messageText_.set_max_width_chars(80);
|
||||
messageText_.set_line_wrap();
|
||||
messageText_.set_halign(Gtk::ALIGN_FILL);
|
||||
messageText_.set_valign(Gtk::ALIGN_FILL);
|
||||
quoteText_.set_markup ("<i>-- the great actor Joseph Tura</i>");
|
||||
quoteText_.set_halign(Gtk::ALIGN_END);
|
||||
quoteText_.set_valign(Gtk::ALIGN_START);
|
||||
content_.pack_start (messageText_, Gtk::PACK_SHRINK);
|
||||
content_.pack_start (quoteText_, Gtk::PACK_EXPAND_PADDING);
|
||||
frame_.add(content_);
|
||||
frame_.set_border_width(5);
|
||||
frame_.set_halign(Gtk::ALIGN_END);
|
||||
frame_.set_valign(Gtk::ALIGN_START);
|
||||
|
||||
Gtk::Paned::add1 (image_);
|
||||
Gtk::Paned::add2 (frame_);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
/** @file timeline/timeline-widget.cpp
|
||||
** Implementation details of Lumiera's timeline display widget.
|
||||
**
|
||||
** @todo as of 12/2016 a complete rework of the timeline display is underway
|
||||
** @todo as of 10/2018 a complete rework of the timeline display is underway
|
||||
** @see timeline-controller.cpp
|
||||
**
|
||||
*/
|
||||
|
|
@ -32,6 +32,8 @@
|
|||
|
||||
#include "gui/gtk-base.hpp"
|
||||
#include "gui/timeline/timeline-widget.hpp"
|
||||
#include "gui/timeline/timeline-controller.hpp"
|
||||
#include "gui/timeline/timeline-layout.hpp"
|
||||
|
||||
//#include "gui/workspace/workspace-window.hpp"
|
||||
//#include "gui/ui-bus.hpp"
|
||||
|
|
@ -66,19 +68,29 @@ namespace timeline {
|
|||
|
||||
|
||||
TimelineWidget::TimelineWidget (BusTerm::ID identity, BusTerm::ID trackID, BusTerm& nexus)
|
||||
: Gtk::Paned{Gtk::ORIENTATION_VERTICAL}
|
||||
, control_{new TimelineController{identity, trackID, nexus}}
|
||||
, layout_{new LayoutManager}
|
||||
: TimelinePage{}
|
||||
, layout_{new TimelineLayout{*this}}
|
||||
, control_{new TimelineController{identity, trackID, nexus, *layout_}}
|
||||
{
|
||||
UNIMPLEMENTED ("build the timeline UI");
|
||||
show_all();
|
||||
}
|
||||
|
||||
TimelineWidget::~TimelineWidget() { }
|
||||
|
||||
TimelineWidget::~TimelineWidget()
|
||||
|
||||
void
|
||||
TimelineWidget::buildMutator (lib::diff::TreeMutator::Handle buff)
|
||||
{
|
||||
control_->buildMutator (buff);
|
||||
}
|
||||
|
||||
|
||||
cuString
|
||||
TimelineWidget::getLabel() const
|
||||
{
|
||||
return control_->getName();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -33,13 +33,13 @@
|
|||
** to a single session::Timeline, known by its ID. The widget creates a TimelineController
|
||||
** right away, which takes initiative to populate the display with that Timeline's contents.
|
||||
**
|
||||
** #Lifecycle
|
||||
** # Lifecycle
|
||||
** The assumption is that any element creation and deletion is triggered through messages over
|
||||
** the [UI-Bus](\ref ui-bus.hpp). So there will be a _parent element,_ corresponding to the
|
||||
** ["model root"](\ref session::Root), and this parent, in response to some mutation message,
|
||||
** will create a TimelineWidget, add it into the appropriate GTK display setup and manage it
|
||||
** as child element; the [construction parameters](TimelineWidget::TimelineWidget] ensure
|
||||
** it gets connected to the bus as well. Incidentally, this assumption also implies that
|
||||
** [model root](\ref proc::mobject::session::Root), and this parent, in response to some
|
||||
** mutation message, will create a TimelineWidget, add it into the appropriate GTK display setup
|
||||
** and manage it as child element; the [construction parameters](\ref TimelineWidget::TimelineWidget)
|
||||
** ensure it gets connected to the bus as well. Incidentally, this assumption also implies that
|
||||
** this parent element has set up a _binding for diff mutation,_ typically by implementing
|
||||
** model::Tangible::buildMutator. And further on this means that the parent will also
|
||||
** destroy the TimelineWidget, prompted by a message to that end. All deregistration
|
||||
|
|
@ -47,7 +47,7 @@
|
|||
** is `sigc::trackable`, which means after destruction any further signals
|
||||
** will be silently ignored.
|
||||
**
|
||||
** @todo as of 12/2016 a complete rework of the timeline display is underway
|
||||
** @todo as of 10/2018 a complete rework of the timeline display is underway
|
||||
**
|
||||
*/
|
||||
|
||||
|
|
@ -56,12 +56,13 @@
|
|||
#define GUI_TIMELINE_TIMELINE_WIDGET_H
|
||||
|
||||
#include "gui/gtk-base.hpp"
|
||||
#include "gui/timeline/timeline-controller.hpp" /////TODO possible to push that into the implementation?
|
||||
#include "gui/timeline/layout-manager.hpp"
|
||||
//#include "gui/timeline/timeline-controller.hpp" /////TODO possible to push that into the implementation?
|
||||
//#include "gui/timeline/layout-manager.hpp"
|
||||
#include "gui/ctrl/bus-term.hpp"
|
||||
|
||||
#include "lib/time/timevalue.hpp"
|
||||
#include "lib/diff/diff-mutable.hpp"
|
||||
#include "lib/nocopy.hpp"
|
||||
|
||||
//#include <memory>
|
||||
//#include <vector>
|
||||
|
|
@ -77,6 +78,25 @@ namespace gui {
|
|||
namespace timeline {
|
||||
|
||||
using ctrl::BusTerm;
|
||||
class TimelineController;
|
||||
class TimelineLayout;
|
||||
|
||||
/**
|
||||
* Interface: GUI page holding a timeline display
|
||||
*/
|
||||
class TimelinePage
|
||||
: public Gtk::Paned
|
||||
{
|
||||
public:
|
||||
virtual ~TimelinePage() { } ///< this is an interface
|
||||
|
||||
virtual cuString getLabel() const =0;
|
||||
|
||||
|
||||
TimelinePage()
|
||||
: Gtk::Paned{Gtk::ORIENTATION_HORIZONTAL}
|
||||
{ }
|
||||
};
|
||||
|
||||
/**
|
||||
* Core timeline display (custom widget).
|
||||
|
|
@ -86,10 +106,11 @@ namespace timeline {
|
|||
* and a scrollable timeline body (right). The layout of both parts is aligned.
|
||||
*/
|
||||
class TimelineWidget
|
||||
: public Gtk::Paned
|
||||
: public TimelinePage
|
||||
, util::NonCopyable
|
||||
{
|
||||
std::unique_ptr<TimelineLayout> layout_;
|
||||
std::unique_ptr<TimelineController> control_;
|
||||
std::unique_ptr<LayoutManager> layout_;
|
||||
|
||||
public:
|
||||
/** build a new timeline display and attach it to the UI-Bus.
|
||||
|
|
@ -113,12 +134,18 @@ namespace timeline {
|
|||
*/
|
||||
TimelineWidget (BusTerm::ID identity, BusTerm::ID trackID, BusTerm& nexus);
|
||||
|
||||
~TimelineWidget();
|
||||
~TimelineWidget();
|
||||
|
||||
|
||||
|
||||
public: /* ===== Control interface ===== */
|
||||
|
||||
/** allow for diff mutation (delegated to TimelineController */
|
||||
void buildMutator (lib::diff::TreeMutator::Handle);
|
||||
|
||||
cuString getLabel() const override;
|
||||
|
||||
|
||||
public: /* ===== Signals ===== */
|
||||
|
||||
private:/* ===== Events ===== */
|
||||
|
|
|
|||
|
|
@ -56,20 +56,47 @@
|
|||
namespace gui {
|
||||
namespace timeline {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
TrackBody::TrackBody ()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
TrackBody::~TrackBody()
|
||||
{
|
||||
namespace {
|
||||
const uint DEFAULT_OVERVIEW_HEIGHT_px = 20;
|
||||
const uint DEFAULT_CONTENT_HEIGHT_px = 80;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
TrackBody::TrackBody()
|
||||
: overviewHeight_{DEFAULT_OVERVIEW_HEIGHT_px}
|
||||
, contentHeight_{DEFAULT_OVERVIEW_HEIGHT_px}
|
||||
, subTracks_{}
|
||||
{ }
|
||||
|
||||
|
||||
TrackBody::~TrackBody()
|
||||
{
|
||||
TODO ("detach from parent; store a functor or backreference");
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
TrackBody::setTrackName (cuString& trackName)
|
||||
{
|
||||
TODO ("is the track name of any relevance for the TrackBody widget?");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* recursively calculate the height in pixels to display this track,
|
||||
* including all nested sub-tracks
|
||||
*/
|
||||
uint
|
||||
TrackBody::calcHeight()
|
||||
{
|
||||
uint heightSum = overviewHeight_ + contentHeight_;
|
||||
for (TrackBody* subTrack : subTracks_)
|
||||
heightSum += subTrack->calcHeight();
|
||||
return heightSum;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}}// namespace gui::timeline
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
** yet for coordination of a globally consistent timeline layout, each
|
||||
** track display is coordinated by a TrackPresenter, which corresponds
|
||||
** to a session::Fork and directly controls the respective display elements
|
||||
** in the [header pane](timeline::HeaderPaneWidget) and the display of the
|
||||
** in the [header pane](\ref timeline::HeaderPaneWidget) and the display of the
|
||||
** timeline body, which is actually a canvas for custom drawing.
|
||||
**
|
||||
** @todo WIP-WIP-WIP as of 12/2016
|
||||
|
|
@ -41,27 +41,50 @@
|
|||
#define GUI_TIMELINE_TRACK_BODY_H
|
||||
|
||||
#include "gui/gtk-base.hpp"
|
||||
#include "gui/ctrl/bus-term.hpp"
|
||||
|
||||
//#include "lib/util.hpp"
|
||||
|
||||
//#include <memory>
|
||||
//#include <vector>
|
||||
#include <vector>
|
||||
|
||||
|
||||
|
||||
namespace gui {
|
||||
namespace timeline {
|
||||
|
||||
using ID = ctrl::BusTerm::ID;
|
||||
|
||||
|
||||
/**
|
||||
* @todo WIP-WIP as of 12/2016
|
||||
* Helper to organise and draw the space allocated for a fork of sub-tracks.
|
||||
* TrackBody units work together with the TimelineCanvas, which arranges all
|
||||
* elements placed into the tracks and performs custom drawing to mark the
|
||||
* working space available for placing those elements (Clips, Effects, Markers).
|
||||
* A given TrackBody works with coordinates relative to its vertical starting point;
|
||||
* coordinates on the TimelineCanvas operate from top downwards. The fundamental
|
||||
* task of a TrackBody is to find out about its own overall height, including the
|
||||
* overall height require by all its nesting children. Moreover, the height of
|
||||
* the content area needs to be negotiated with the actual content elements.
|
||||
* @todo WIP-WIP as of 10/2018
|
||||
*/
|
||||
class TrackBody
|
||||
{
|
||||
uint overviewHeight_;
|
||||
uint contentHeight_;
|
||||
|
||||
using SubTracks = std::vector<TrackBody*>;
|
||||
|
||||
SubTracks subTracks_;
|
||||
|
||||
public:
|
||||
TrackBody ();
|
||||
TrackBody();
|
||||
~TrackBody();
|
||||
|
||||
|
||||
void setTrackName (cuString&);
|
||||
|
||||
uint calcHeight();
|
||||
|
||||
private:/* ===== Internals ===== */
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -22,8 +22,7 @@
|
|||
|
||||
|
||||
/** @file track-head-widget.cpp
|
||||
** Implementation details regarding display of the track heads
|
||||
** within the timeline UI.
|
||||
** Implementation details regarding display of the track heads within the timeline UI.
|
||||
**
|
||||
** @todo WIP-WIP-WIP as of 12/2016
|
||||
**
|
||||
|
|
@ -59,17 +58,44 @@ namespace timeline {
|
|||
|
||||
|
||||
|
||||
TrackHeadWidget::~TrackHeadWidget() { }
|
||||
|
||||
TrackHeadWidget::TrackHeadWidget ()
|
||||
|
||||
|
||||
TrackHeadWidget::TrackHeadWidget()
|
||||
: Gtk::Grid{}
|
||||
, nameTODO_{"?"}
|
||||
, treeTODO_{"↳"}
|
||||
{
|
||||
this->attach (nameTODO_, 0,0, 2,1);
|
||||
this->attach (treeTODO_, 0,1, 1,1);
|
||||
|
||||
this->show_all();
|
||||
}
|
||||
|
||||
|
||||
TrackHeadWidget::~TrackHeadWidget()
|
||||
void
|
||||
TrackHeadWidget::setTrackName (cuString& trackName)
|
||||
{
|
||||
nameTODO_.set_label (trackName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @remark The Lumiera Timeline model does not rely on a list of tracks, as most conventional
|
||||
* video editing software does -- rather, each sequence holds a _fork of nested scopes._
|
||||
* This recursively nested structure is reflected in the patchbay area corresponding to
|
||||
* each track in the _header pane_ of the timeline display, located to the left. The
|
||||
* patchbay for each track is a grid with four quadrants, and the 4th quadrant is the
|
||||
* _content area,_ which is recursively extended to hold nested PatchbayWidget elements,
|
||||
* corresponding to the child tracks of this track. To _fund_ this recursively extensible
|
||||
* structure, we need to set up the first four quadrants
|
||||
*/
|
||||
void
|
||||
TrackHeadWidget::injectSubFork (TrackHeadWidget& subForkHead)
|
||||
{
|
||||
UNIMPLEMENTED ("how actually to represent the track in the patchbay");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}}// namespace gui::timeline
|
||||
|
|
|
|||
|
|
@ -22,10 +22,22 @@
|
|||
|
||||
|
||||
/** @file track-head-widget.hpp
|
||||
** Widget to represent a track head with placement control patchbay
|
||||
** within Lumiera's timeline UI.
|
||||
** Widget to represent a track head with placement parameters, within the timeline header pane.
|
||||
** [The fork](\ref session::Fork), a recursively nested system of scopes, is rendered within
|
||||
** the timeline display as a system of nested tracks. Each of these tracks possibly holds some
|
||||
** child tracks plus some actual media clips, which all inherit parameters of placement from
|
||||
** this fork ("track"). These parameters address various aspects of how content is attached
|
||||
** ("placed") into the model at large, examples being
|
||||
** - how to route the output
|
||||
** - how to "place" this content into an _output space_, like e.g.
|
||||
** + sound panning
|
||||
** + video overlay parameters (additive, opaque, transparent)
|
||||
** + video or audio _level_ (=fader)
|
||||
** - how to locate this content in time (e.g. relative to some marker)
|
||||
** For each track, we show a patchbay in the timeline header pane, which serves to control
|
||||
** such aspects relevant for all content contained within the scope of this track.
|
||||
**
|
||||
** @todo WIP-WIP-WIP as of 12/2016
|
||||
** @todo WIP-WIP-WIP as of 10/2018
|
||||
**
|
||||
*/
|
||||
|
||||
|
|
@ -34,6 +46,7 @@
|
|||
#define GUI_TIMELINE_TRACK_HEAD_WIDGET_H
|
||||
|
||||
#include "gui/gtk-base.hpp"
|
||||
#include "gui/ctrl/bus-term.hpp"
|
||||
|
||||
//#include "lib/util.hpp"
|
||||
|
||||
|
|
@ -45,16 +58,30 @@
|
|||
namespace gui {
|
||||
namespace timeline {
|
||||
|
||||
using ID = ctrl::BusTerm::ID;
|
||||
|
||||
/**
|
||||
* Header pane control area corresponding to a Track with nested child Tracks.
|
||||
* This structure is used recursively to build up the Fork of nested Tracks.
|
||||
* - first row: Placement + Property pane
|
||||
* - second row: content or nested tracks.
|
||||
* @todo WIP-WIP as of 12/2016
|
||||
*/
|
||||
class TrackHeadWidget
|
||||
: public Gtk::Grid
|
||||
{
|
||||
Gtk::Label nameTODO_;
|
||||
Gtk::Label treeTODO_;
|
||||
|
||||
public:
|
||||
TrackHeadWidget ();
|
||||
TrackHeadWidget();
|
||||
~TrackHeadWidget();
|
||||
|
||||
|
||||
void setTrackName (cuString&);
|
||||
|
||||
/** Integrate the control area for a nested sub track fork. */
|
||||
void injectSubFork (TrackHeadWidget& subForkHead);
|
||||
|
||||
private:/* ===== Internals ===== */
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -25,14 +25,14 @@
|
|||
** Implementation details of track presentation management.
|
||||
**
|
||||
** @todo WIP-WIP-WIP as of 12/2016
|
||||
** @todo as of 10/2018 timeline display in the UI is rebuilt to match the architecture
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#include "gui/gtk-base.hpp"
|
||||
#include "include/ui-protocol.hpp"
|
||||
#include "gui/timeline/track-presenter.hpp"
|
||||
#include "gui/timeline/clip-presenter.hpp"
|
||||
#include "gui/timeline/marker-widget.hpp"
|
||||
|
||||
//#include "gui/ui-bus.hpp"
|
||||
//#include "lib/format-string.hpp"
|
||||
|
|
@ -64,31 +64,20 @@ namespace timeline {
|
|||
|
||||
|
||||
|
||||
TrackPresenter::TrackPresenter (ID identity, ctrl::BusTerm& nexus)
|
||||
: Controller{identity, nexus}
|
||||
, subFork_{}
|
||||
, markers_{}
|
||||
, clips_{}
|
||||
, head_{}
|
||||
, body_{}
|
||||
{
|
||||
UNIMPLEMENTED ("how to attach the TrackPresenter into the two relevant GTK display contexts");
|
||||
}
|
||||
|
||||
|
||||
TrackPresenter::~TrackPresenter()
|
||||
{
|
||||
}
|
||||
TrackPresenter::~TrackPresenter() { }
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @note we distinguish between the contents of our three nested child collections
|
||||
* based on the symbolic type field send in the Record type within the diff representation
|
||||
* based on the symbolic type field sent in the Record type within the diff representation
|
||||
* - "Marker" designates a Marker object
|
||||
* - "Clip" designates a Clip placed on this track
|
||||
* - "Fork" designates a nested sub-track
|
||||
* @see TimelineController::buildMutator() for a basic explanation of the data binding mechanism
|
||||
*/
|
||||
void
|
||||
TrackPresenter::buildMutator (TreeMutator::Handle buffer)
|
||||
|
|
@ -102,60 +91,69 @@ namespace timeline {
|
|||
.attach (collection(markers_)
|
||||
.isApplicableIf ([&](GenNode const& spec) -> bool
|
||||
{ // »Selector« : require object-like sub scope with type-field "Marker"
|
||||
return "Marker" == spec.data.recordType();
|
||||
return TYPE_Marker == spec.data.recordType();
|
||||
})
|
||||
.matchElement ([&](GenNode const& spec, PMarker const& elm) -> bool
|
||||
{
|
||||
return spec.idi == ID(elm);
|
||||
return spec.idi == ID{*elm};
|
||||
})
|
||||
.constructFrom ([&](GenNode const& spec) -> PMarker
|
||||
{
|
||||
return make_unique<MarkerWidget>(spec.idi, this->uiBus_);
|
||||
return make_unique<MarkerWidget> (spec.idi, this->uiBus_);
|
||||
})
|
||||
.buildChildMutator ([&](PMarker& target, GenNode::ID const& subID, TreeMutator::Handle buff) -> bool
|
||||
{
|
||||
if (ID(target) != subID) return false;
|
||||
if (ID{*target} != subID) return false;
|
||||
target->buildMutator (buff);
|
||||
return true;
|
||||
}))
|
||||
.attach (collection(clips_)
|
||||
.isApplicableIf ([&](GenNode const& spec) -> bool
|
||||
{ // »Selector« : require object-like sub scope with type-field "Clip"
|
||||
return "Clip" == spec.data.recordType();
|
||||
return TYPE_Clip == spec.data.recordType();
|
||||
})
|
||||
.matchElement ([&](GenNode const& spec, PClip const& elm) -> bool
|
||||
{
|
||||
return spec.idi == ID(elm);
|
||||
return spec.idi == ID{*elm};
|
||||
})
|
||||
.constructFrom ([&](GenNode const& spec) -> PClip
|
||||
{
|
||||
return make_unique<ClipPresenter>(spec.idi, this->uiBus_);
|
||||
return make_unique<ClipPresenter> (spec.idi, this->uiBus_);
|
||||
})
|
||||
.buildChildMutator ([&](PClip& target, GenNode::ID const& subID, TreeMutator::Handle buff) -> bool
|
||||
{
|
||||
if (ID(target) != subID) return false;
|
||||
if (ID{*target} != subID) return false;
|
||||
target->buildMutator (buff);
|
||||
return true;
|
||||
}))
|
||||
.attach (collection(subFork_)
|
||||
.isApplicableIf ([&](GenNode const& spec) -> bool
|
||||
{ // »Selector« : require object-like sub scope with type-field "Fork"
|
||||
return "Fork" == spec.data.recordType();
|
||||
return TYPE_Fork == spec.data.recordType();
|
||||
})
|
||||
.matchElement ([&](GenNode const& spec, PFork const& elm) -> bool
|
||||
{
|
||||
return spec.idi == ID(elm);
|
||||
return spec.idi == ID{*elm};
|
||||
})
|
||||
.constructFrom ([&](GenNode const& spec) -> PFork
|
||||
{
|
||||
return make_unique<TrackPresenter>(spec.idi, this->uiBus_);
|
||||
return make_unique<TrackPresenter> (spec.idi
|
||||
,this->uiBus_
|
||||
,[&](TrackHeadWidget& head,TrackBody& body)
|
||||
{
|
||||
display_.injectSubTrack (head, body);
|
||||
});
|
||||
})
|
||||
.buildChildMutator ([&](PFork& target, GenNode::ID const& subID, TreeMutator::Handle buff) -> bool
|
||||
{
|
||||
if (ID(target) != subID) return false;
|
||||
if (ID{*target} != subID) return false;
|
||||
target->buildMutator (buff);
|
||||
return true;
|
||||
})));
|
||||
}))
|
||||
.change(ATTR_name, [&](string val)
|
||||
{ // »Attribute Setter« : receive a new value for the track name field
|
||||
this->setTrackName (val);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
/** @file track-presenter.hpp
|
||||
** Presentation control element to model and manage a track within the timeline UI.
|
||||
** In the Lumiera timeline UI, we are mixing two different scope of concerns: For one,
|
||||
** we have the globally tangible scope of actual session elements an operations performed
|
||||
** we have the globally tangible scope of actual session elements and operations performed
|
||||
** on those. And then there are more local considerations regarding the "mechanics" of the
|
||||
** UI elements, their state and immediate feedback to user interactions. The _Presenter_ --
|
||||
** as known from the [MVP pattern](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter) --
|
||||
|
|
@ -36,9 +36,10 @@
|
|||
** part of layout building, delegating to a mostly passive GTK widget for the actual display.
|
||||
** This way it becomes possible to manage the actual UI resources on a global level, avoiding to
|
||||
** represent potentially several thousand individual elements as GTK entities, while at any time
|
||||
** only several can be visible and active as far as user interaction is concerned.
|
||||
** only a small number of elements can be visible and active as far as user interaction is concerned.
|
||||
**
|
||||
** @todo WIP-WIP-WIP as of 12/2016
|
||||
** @todo as of 10/2018 timeline display in the UI is rebuilt to match the architecture
|
||||
**
|
||||
*/
|
||||
|
||||
|
|
@ -48,12 +49,16 @@
|
|||
|
||||
#include "gui/gtk-base.hpp"
|
||||
#include "gui/model/controller.hpp"
|
||||
#include "gui/timeline/marker-widget.hpp"
|
||||
#include "gui/timeline/clip-presenter.hpp"
|
||||
#include "gui/timeline/track-head-widget.hpp"
|
||||
#include "gui/timeline/track-body.hpp"
|
||||
|
||||
#include "lib/nocopy.hpp"
|
||||
//#include "lib/util.hpp"
|
||||
|
||||
//#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
|
||||
|
|
@ -64,8 +69,43 @@ namespace timeline {
|
|||
using std::vector;
|
||||
using std::unique_ptr;
|
||||
|
||||
class ClipPresenter;
|
||||
class MarkerWidget;
|
||||
|
||||
/**
|
||||
* Reference frame to organise the display related to a specific Track in the Timeline-GUI.
|
||||
*/
|
||||
struct DisplayFrame
|
||||
: util::NonCopyable
|
||||
{
|
||||
TrackHeadWidget head;
|
||||
TrackBody body;
|
||||
|
||||
template<class FUN>
|
||||
DisplayFrame (FUN anchorDisplay)
|
||||
: head{}
|
||||
, body{}
|
||||
{
|
||||
anchorDisplay (head, body);
|
||||
}
|
||||
|
||||
~DisplayFrame()
|
||||
{
|
||||
TODO ("cause the managed presentation elements to detach from their parents");
|
||||
}
|
||||
|
||||
void
|
||||
setTrackName (cuString& name)
|
||||
{
|
||||
head.setTrackName (name);
|
||||
body.setTrackName (name); ///////////////////////////////////TICKET #1017 -- TODO 11/18 : not clear yet if TrackBody needs to know its name
|
||||
}
|
||||
|
||||
void
|
||||
injectSubTrack (TrackHeadWidget& head, TrackBody& body)
|
||||
{
|
||||
UNIMPLEMENTED ("inject the widgets to represent a nested sub-track within this timeline track display frame");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @todo WIP-WIP as of 12/2016
|
||||
|
|
@ -73,22 +113,30 @@ namespace timeline {
|
|||
class TrackPresenter
|
||||
: public model::Controller
|
||||
{
|
||||
DisplayFrame display_;
|
||||
|
||||
vector<unique_ptr<TrackPresenter>> subFork_;
|
||||
vector<unique_ptr<MarkerWidget>> markers_;
|
||||
vector<unique_ptr<ClipPresenter>> clips_;
|
||||
|
||||
TrackHeadWidget head_;
|
||||
TrackBody body_;
|
||||
|
||||
|
||||
public:
|
||||
~TrackPresenter();
|
||||
|
||||
/**
|
||||
* @param identity used to refer to a corresponding session::Fork in the Session
|
||||
* @param nexus a way to connect this Controller to the UI-Bus.
|
||||
*/
|
||||
TrackPresenter (ID identity, ctrl::BusTerm& nexus);
|
||||
|
||||
~TrackPresenter();
|
||||
template<class FUN>
|
||||
TrackPresenter (ID id, ctrl::BusTerm& nexus, FUN anchorDisplay)
|
||||
: Controller{id, nexus}
|
||||
, display_{anchorDisplay}
|
||||
, subFork_{}
|
||||
, markers_{}
|
||||
, clips_{}
|
||||
{
|
||||
setTrackName (id.getSym()); // fallback initialise track-name from human-readable ID symbol
|
||||
}
|
||||
|
||||
|
||||
/** set up a binding to respond to mutation messages via UiBus */
|
||||
|
|
@ -96,6 +144,13 @@ namespace timeline {
|
|||
|
||||
|
||||
private:/* ===== Internals ===== */
|
||||
|
||||
/** invoked via diff to show a (changed) track name */
|
||||
void
|
||||
setTrackName (string name)
|
||||
{
|
||||
display_.setTrackName (name);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -25,9 +25,9 @@
|
|||
/** @file ui-bus.cpp
|
||||
** Implementation of the UI backbone service for messaging.
|
||||
** Any globally relevant widget or controller within the Lumiera UI
|
||||
** is connected to the [UI-Bus](ui-bus.hpp), which is essentially implemented
|
||||
** is connected to the [UI-Bus](\ref ui-bus.hpp), which is essentially implemented
|
||||
** within this compilation unit. [Clients](\ref gui::model::Tangible) typically
|
||||
** use the [BusTerm-Interface](bus-term.hpp) to route generic actions and
|
||||
** use the [BusTerm-Interface](\ref bus-term.hpp) to route generic actions and
|
||||
** receive notifications, state changes and mutations.
|
||||
**
|
||||
** @see core-service.hpp
|
||||
|
|
@ -143,7 +143,7 @@ namespace ctrl {
|
|||
|
||||
|
||||
/** capture and record a "state mark" for later replay for restoring UI state.
|
||||
* @param subject the [endpoint-ID](BusTerm::endpointID_) of the emitting element
|
||||
* @param subject the [endpoint-ID](\ref BusTerm::endpointID_) of the emitting element
|
||||
* @param mark the actual state update or notification message to be remembered
|
||||
* @remarks relevant changes to presentation state, which are to be recalled and
|
||||
* restored later, are emitted from the place they occur, packaged as
|
||||
|
|
@ -162,11 +162,11 @@ namespace ctrl {
|
|||
|
||||
|
||||
/** route a state update or notification to the given subject.
|
||||
* @param subject the [endpoint-ID](BusTerm::endpointID_) of the element to address
|
||||
* @param subject the [endpoint-ID](\ref BusTerm::endpointID_) of the element to address
|
||||
* @param mark the actual state update or notification message to be delivered
|
||||
* @remarks each addressed "subject" is a gui::model::Tangible, and as such holds
|
||||
* a BusTerm of its own, which in turn ensures a registration and connection
|
||||
* from the [central routing hub](gui::ctrl::Nexus) down to the element. Thus,
|
||||
* from the [central routing hub](\ref gui::ctrl::Nexus) down to the element. Thus,
|
||||
* the default implementation is just to pass the given state mark "up",
|
||||
* assuming that it will reach the hub eventually, which in turn knows
|
||||
* how to reach the element, and invoke the Tangible::mark() operation.
|
||||
|
|
|
|||
|
|
@ -64,11 +64,11 @@
|
|||
** We distinguish between _up-link messages,_ directed towards some central service
|
||||
** (presentation state management or command invocation) and _down-link messages,_
|
||||
** directed towards individual elements. The interactions at the bus are closely interrelated
|
||||
** with the [elementary UI-Element operations](tangible.hpp).
|
||||
** with the [elementary UI-Element operations](\ref tangible.hpp).
|
||||
**
|
||||
** - *act*: send a [GenNode] representing the action
|
||||
** - the ID is either a globally registered command-ID or an explicitly
|
||||
** ["opened"](proc::control::SessionCommand::cycle(Symbol,string)) command instance ID.
|
||||
** ["opened"](\ref proc::control::SessionCommand::cycle(Symbol,string)) command instance ID.
|
||||
** - the payload is a Record<GenNode> holding the actual command arguments
|
||||
** - on reception, an _instance_ (anonymous clone copy) of the command is created, bound
|
||||
** with the arguments and handed over to the ProcDispatcher to be enqueued for execution.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
LabelWidget - display an ID label, possibly with icon
|
||||
ElementBoxWidget - fundamental UI building block to represent a placed element
|
||||
|
||||
Copyright (C) Lumiera.org
|
||||
2016, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
|
@ -24,13 +24,13 @@
|
|||
/** @file label-widget.cpp
|
||||
** Implementation details of the UI building block to display an ID label.
|
||||
**
|
||||
** @todo WIP-WIP-WIP as of 12/2016
|
||||
** @todo WIP-WIP-WIP as of 11/2018 ///////////////////////////////////////////////////////////////////////TICKET #1185
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#include "gui/gtk-base.hpp"
|
||||
#include "gui/widget/label-widget.hpp"
|
||||
#include "gui/widget/element-box-widget.hpp"
|
||||
|
||||
//#include "gui/ui-bus.hpp"
|
||||
//#include "lib/format-string.hpp"
|
||||
|
|
@ -59,12 +59,12 @@ namespace widget {
|
|||
|
||||
|
||||
|
||||
LabelWidget::LabelWidget ()
|
||||
ElementBoxWidget::ElementBoxWidget ()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
LabelWidget::~LabelWidget()
|
||||
ElementBoxWidget::~ElementBoxWidget()
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
/*
|
||||
LABEL-WIDGET.hpp - display an ID label, possibly with icon
|
||||
ELEMENT-BOX-WIDGET.hpp - fundamental UI building block to represent a placed element
|
||||
|
||||
Copyright (C) Lumiera.org
|
||||
2016, Hermann Vosseler <Ichthyostega@web.de>
|
||||
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
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
*/
|
||||
|
||||
|
||||
/** @file label-widget.hpp
|
||||
/** @file element-box-widget.hpp
|
||||
** Widget to render an ID label with associated icon.
|
||||
** This is a UI building block used at various places, e.g. to show
|
||||
** the name of a clip in the media bins or in the timeline, to indicate
|
||||
|
|
@ -32,13 +32,13 @@
|
|||
** support for picking the icon and the indicator style based on some
|
||||
** notion of _"type"._
|
||||
**
|
||||
** @todo WIP-WIP-WIP as of 12/2016
|
||||
** @todo WIP-WIP-WIP as of 11/2018 ///////////////////////////////////////////////////////////////////////TICKET #1185
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#ifndef GUI_WIDGET_LABEL_WIDGET_H
|
||||
#define GUI_WIDGET_LABEL_WIDGET_H
|
||||
#ifndef GUI_WIDGET_ELEMENT_BOX_WIDGET_H
|
||||
#define GUI_WIDGET_ELEMENT_BOX_WIDGET_H
|
||||
|
||||
#include "gui/gtk-base.hpp"
|
||||
|
||||
|
|
@ -56,11 +56,11 @@ namespace widget {
|
|||
/**
|
||||
* @todo WIP-WIP as of 12/2016
|
||||
*/
|
||||
class LabelWidget
|
||||
class ElementBoxWidget
|
||||
{
|
||||
public:
|
||||
LabelWidget ();
|
||||
~LabelWidget();
|
||||
ElementBoxWidget ();
|
||||
~ElementBoxWidget();
|
||||
|
||||
private:/* ===== Internals ===== */
|
||||
|
||||
|
|
@ -68,4 +68,4 @@ namespace widget {
|
|||
|
||||
|
||||
}}// namespace gui::widget
|
||||
#endif /*GUI_WIDGET_LABEL_WIDGET_H*/
|
||||
#endif /*GUI_WIDGET_ELEMENT_BOX_WIDGET_H*/
|
||||
337
src/gui/widget/error-log-display.hpp
Normal file
337
src/gui/widget/error-log-display.hpp
Normal file
|
|
@ -0,0 +1,337 @@
|
|||
/*
|
||||
ERROR-LOG-DISPLAY.hpp - display of error messages in a text box
|
||||
|
||||
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 error-log-display.hpp
|
||||
** Widget to display error messages to be accessible in non-modal way.
|
||||
** Notifications are shown in a simple text window with scrollbars; new
|
||||
** entries can be added with a severity level, causing the widget to scroll
|
||||
** down to the last line of the content buffer.
|
||||
**
|
||||
** ## Lifecycle considerations
|
||||
**
|
||||
** The ErrorLogDisplay is typically managed as child of a container widget.
|
||||
** However, the ctrl::NotificationHub as controller does a lookup and attaches
|
||||
** to an ErrorLogDisplay found within the InfoBoxPanel, using it as holder for
|
||||
** information and error messages pushed into the GUI. For that reason, it is
|
||||
** important really _to destroy_ the ErrorLogDisplay, when it is taken out of
|
||||
** service. Since ErrorLogDisplay inherits from `sigc::trackable`, it is
|
||||
** automatically detached from the WLink implementing this cross-attachment,
|
||||
** but only when _actually the dtor is called._
|
||||
** @note this is a special convention; usually it is sufficient just to hide()
|
||||
** a GTK widget, because it is then left alone by the event handling and
|
||||
** automatically cleaned up at shutdown. Moreover, if you ` remove()` a
|
||||
** widget from a container, the container no longer manages the widget,
|
||||
** so you are responsible to take ownership in such situations. And,
|
||||
** as said, in case of the ErrorLogDisplay, you should `delete` it.
|
||||
** @remark the standard use case is to have the ErrorLogDisplay sitting
|
||||
** within the InfoBoxPanel. If the latter is destroyed, it destroys
|
||||
** its children and all works as expected without much ado.
|
||||
**
|
||||
** @todo WIP-WIP-WIP as of 9/2017 this is a first draft of a widget to be
|
||||
** used as receiver by the GuiNotificationService.
|
||||
** @todo WIP-WIP and in 9/2018 this draft gradually shifts towards a prototype
|
||||
** how to deal with custom configured widget behaviour, and how to integrate
|
||||
** with our GUI framework (#1099)
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#ifndef GUI_WIDGET_ERROR_LOG_DISPLAY_H
|
||||
#define GUI_WIDGET_ERROR_LOG_DISPLAY_H
|
||||
|
||||
#include "gui/gtk-base.hpp"
|
||||
#include "gui/style-scheme.hpp"
|
||||
#include "gui/model/flash-deco.hpp"
|
||||
#include "gui/model/expander-revealer.hpp"
|
||||
#include "include/gui-notification-facade.h"
|
||||
#include "lib/format-string.hpp"
|
||||
#include "lib/symbol.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
|
||||
|
||||
namespace gui {
|
||||
namespace widget {
|
||||
|
||||
using util::max;
|
||||
using util::_Fmt;
|
||||
using lib::Literal;
|
||||
using std::make_pair;
|
||||
using std::vector;
|
||||
using std::move;
|
||||
|
||||
namespace {
|
||||
|
||||
using Tag = Glib::RefPtr<Gtk::TextBuffer::Tag>;
|
||||
|
||||
/**
|
||||
* @internal inject some generic standard styles for use in `TextView` components.
|
||||
* @param tagTable reference to a `TagTable` bound with an existing `Gtk::TextBuffer`.
|
||||
* @todo 9/2018 dummy placeholder code, later to be transformed into a framework /////////////////////TICKET #1168 : find a way to manage style of custom extended UI elements
|
||||
*/
|
||||
inline void
|
||||
populateStandardTextTags (Glib::RefPtr<TextBuffer::TagTable> tagTable)
|
||||
{
|
||||
Tag errorTag = Gtk::TextBuffer::Tag::create (cuString{TAG_ERROR});
|
||||
errorTag->property_background() = "Yellow"; ////////////////////////////////////////////TICKET #1168 : should be retrieved from a central location
|
||||
errorTag->property_weight() = PANGO_WEIGHT_BOLD;
|
||||
tagTable->add (errorTag);
|
||||
|
||||
Tag warnTag = Gtk::TextBuffer::Tag::create (cuString{TAG_WARN});
|
||||
warnTag->property_background() = "LightYellow"; ////////////////////////////////////////////TICKET #1168 : should be retrieved from a central location
|
||||
tagTable->add (warnTag);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*********************************************************************//**
|
||||
* Widget to display log and error messages.
|
||||
* Based on a multiline text display box with scrollbars.
|
||||
* Warning and error messages are highlighted by special formatting.
|
||||
* And [error entries are treated specially](\ref addError()), insofar
|
||||
* they are tracked by an index, allowing to build additional convenience
|
||||
* features later on...
|
||||
* @remarks this is _just a widget_ intended for direct use. By default,
|
||||
* it is managed by the \ref NotificationHub, which is a controller,
|
||||
* attached to the UI-Bus and responding to the [UI-Element protocol]
|
||||
* (\ref tangible.hpp).
|
||||
*/
|
||||
class ErrorLogDisplay
|
||||
: public Gtk::ScrolledWindow
|
||||
{
|
||||
|
||||
using Mark = Glib::RefPtr<Gtk::TextBuffer::Mark>;
|
||||
using Entry = std::pair<Mark,Mark>;
|
||||
using TextWidget = model::FlashDeco<Gtk::TextView>;
|
||||
|
||||
vector<Entry> errorMarks_;
|
||||
TextWidget textLog_;
|
||||
|
||||
sigc::signal<void, bool> errorChangedSignal_;
|
||||
|
||||
|
||||
public:
|
||||
~ErrorLogDisplay() { };
|
||||
|
||||
ErrorLogDisplay()
|
||||
: Gtk::ScrolledWindow()
|
||||
, errorMarks_{}
|
||||
, textLog_{}
|
||||
{
|
||||
set_size_request (200, 80); // must be > 50 for the scrollbar to work properly
|
||||
property_expand() = true; // always grab any available additional space
|
||||
set_border_width (10);
|
||||
set_shadow_type (Gtk::SHADOW_NONE);
|
||||
|
||||
// the vertical scrollbar will always be necessary....
|
||||
set_policy (Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS);
|
||||
textLog_.set_editable (false);
|
||||
this->add (textLog_);
|
||||
|
||||
populateStandardTextTags (textLog_.get_buffer()->get_tag_table());
|
||||
}
|
||||
|
||||
model::Expander expand;
|
||||
model::Revealer reveal;
|
||||
|
||||
|
||||
/** empty text buffer and discard all error bookmarks */
|
||||
void
|
||||
clearAll()
|
||||
{
|
||||
bool shallNotify = this->isError();
|
||||
|
||||
errorMarks_.clear();
|
||||
size_t lineCnt = max (0, textLog_.get_buffer()->get_line_count() - 1);
|
||||
string placeholder;
|
||||
if (lineCnt > 0)
|
||||
placeholder = _Fmt{_("───════ %d preceding lines removed ════───\n")} % lineCnt;
|
||||
textLog_.get_buffer()->set_text (placeholder); // discard existing content
|
||||
|
||||
if (shallNotify)
|
||||
errorChangedSignal_.emit (false);
|
||||
}
|
||||
|
||||
|
||||
/** just add normal information message to buffer,
|
||||
* without special markup and without expanding the widget */
|
||||
void
|
||||
addInfo (string text)
|
||||
{
|
||||
addEntry (text);
|
||||
}
|
||||
|
||||
/** add an information message, formatted more prominent as warning */
|
||||
void
|
||||
addWarn (string text)
|
||||
{
|
||||
addEntry ("WARNING: "+text, TAG_WARN);
|
||||
}
|
||||
|
||||
/** present an error notification prominently.
|
||||
* Adds the error text, formatted accordingly to stand out,
|
||||
* but also stores a [Mark] to bookmark the presence of this
|
||||
* error entry. And finally expand the display if collapsed.
|
||||
*
|
||||
* [Mark]: https://developer.gnome.org/gtkmm-tutorial/stable/sec-textview-buffer.html.en#textview-marks
|
||||
*/
|
||||
void
|
||||
addError (string text)
|
||||
{
|
||||
bool shallNotify = not this->isError();
|
||||
|
||||
errorMarks_.emplace_back(
|
||||
addEntry ("ERROR: "+text, TAG_ERROR));
|
||||
if (not expand.isExpanded())
|
||||
expand (true);
|
||||
|
||||
if (shallNotify)
|
||||
errorChangedSignal_.emit (true);
|
||||
}
|
||||
|
||||
/**
|
||||
* clear all mere information messages;
|
||||
* retain just the previously tagged errors
|
||||
* @remark in fact populates a new buffer
|
||||
*/
|
||||
void
|
||||
clearInfoMsg()
|
||||
{
|
||||
auto newBuff = Gtk::TextBuffer::create (textLog_.get_buffer()->get_tag_table());
|
||||
vector<Entry> newMarks;
|
||||
for (Entry& entry : errorMarks_)
|
||||
{
|
||||
newBuff->insert (newBuff->end(), "\n");
|
||||
auto pos = newBuff->end();
|
||||
--pos;
|
||||
newMarks.emplace_back(
|
||||
make_pair (
|
||||
newBuff->create_mark (pos, true), // "left gravity" : stays to the left of inserted text
|
||||
newBuff->create_mark (pos, false))); // "right gravity": sticks right behind the inserted text))
|
||||
|
||||
newBuff->insert (pos // copy from old to new buffer, complete with formatting tag
|
||||
,entry.first->get_iter()
|
||||
,entry.second->get_iter()
|
||||
);
|
||||
}
|
||||
// install the reduced new buffer
|
||||
auto oldBuff = textLog_.get_buffer();
|
||||
textLog_.set_buffer(newBuff);
|
||||
swap (errorMarks_, newMarks);
|
||||
|
||||
// add a marker line to indicate the removed old log contents
|
||||
int oldLines = oldBuff->get_line_count();
|
||||
int newLines = newBuff->get_line_count();
|
||||
ASSERT (oldLines >= newLines);
|
||||
addInfo (_Fmt{_("───════ %d old log lines removed ════───\n")} % (oldLines-newLines));
|
||||
}
|
||||
|
||||
|
||||
/** visit all errors and downgrade the markup; discard all bookmarks */
|
||||
void
|
||||
turnError_into_InfoMsg()
|
||||
{
|
||||
if (not isError()) return;
|
||||
|
||||
auto buff = textLog_.get_buffer();
|
||||
for (Entry& entry : errorMarks_)
|
||||
{
|
||||
auto begin = entry.first->get_iter();
|
||||
auto end = entry.second->get_iter();
|
||||
|
||||
buff->remove_tag_by_name(uString{TAG_ERROR}, begin,end);
|
||||
buff->apply_tag_by_name (uString{TAG_WARN}, begin,end);
|
||||
}
|
||||
errorMarks_.clear();
|
||||
errorChangedSignal_.emit (false);
|
||||
}
|
||||
|
||||
|
||||
/** temporarily change display style to prompt for attention;
|
||||
* set callback-timeout for return to normal state.
|
||||
* @see \ref gui::CSS_CLASS_FLASH
|
||||
*/
|
||||
void
|
||||
triggerFlash()
|
||||
{
|
||||
textLog_.flash();
|
||||
}
|
||||
|
||||
|
||||
/* ======= Error-State ======= */
|
||||
|
||||
bool
|
||||
isError() const
|
||||
{
|
||||
return not errorMarks_.empty();
|
||||
}
|
||||
|
||||
/** signal fired when error state changes */
|
||||
sigc::signal<void,bool>
|
||||
signalErrorChanged()
|
||||
{
|
||||
return errorChangedSignal_;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private:/* ===== Internals ===== */
|
||||
|
||||
/** add message entry to the (ever growing) text buffer.
|
||||
* @return pair of anonymous marks bracketing the content added
|
||||
* @remark an entry is content sans the following line break, which is appended automatically.
|
||||
* We inject the content _between_ two marks, which will adjust when content is altered.
|
||||
* @remark According to the [API doc], `TextView::scroll_to(iter)` is not reliable; preferably
|
||||
* we should use a text mark, which could e.g. be the [insert position][insert-mark].
|
||||
* The handling of marks and tags is described in the [GTKmm tutorial].
|
||||
* @warning Each entry creates a new pair of marks. Not sure about the impact on performance...
|
||||
*
|
||||
* [GTKmm tutorial]: https://developer.gnome.org/gtkmm-tutorial/stable/sec-textview-buffer.html.en#textview-marks
|
||||
* [insert-mark]: https://developer.gnome.org/gtkmm/3.22/classGtk_1_1TextMark.html#details
|
||||
* [API doc]: https://developer.gnome.org/gtkmm/3.22/classGtk_1_1TextView.html#a8412941c4da9a71a381052d6049164e4
|
||||
*/
|
||||
Entry
|
||||
addEntry (string const& text, Literal markupTagName =nullptr)
|
||||
{
|
||||
auto buff = textLog_.get_buffer();
|
||||
buff->insert (buff->end(), "\n");
|
||||
auto pos = buff->end();
|
||||
--pos;
|
||||
auto begin = buff->create_mark (pos, true); // "left gravity" : stays to the left of inserted text
|
||||
auto after = buff->create_mark (pos, false); // "right gravity": sticks right behind the inserted text
|
||||
if (markupTagName)
|
||||
buff->insert_with_tag(pos, text, cuString{markupTagName});
|
||||
else
|
||||
buff->insert (pos, text);
|
||||
textLog_.scroll_to (begin);
|
||||
return make_pair (move(begin), move(after));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}}// namespace gui::widget
|
||||
#endif /*GUI_WIDGET_ERROR_LOG_DISPLAY_H*/
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
/*
|
||||
ERROR-LOG-WIDGET.hpp - display of error messages in a text box
|
||||
|
||||
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 error-log-widget.hpp
|
||||
** Widget to display error messages to the use in non-modal way.
|
||||
** Notifications are shown in a simple text window with scrollbars; new
|
||||
** entries can be added with a severity level, causing the widget to scroll
|
||||
** down to the last line of the content buffer.
|
||||
**
|
||||
** @todo WIP-WIP-WIP as of 9/2017 this is a first draft of a widget to be
|
||||
** used as receiver by the GuiNotificationService.
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#ifndef GUI_WIDGET_ERROR_LOG_WIDGET_H
|
||||
#define GUI_WIDGET_ERROR_LOG_WIDGET_H
|
||||
|
||||
#include "gui/gtk-base.hpp"
|
||||
#include "include/gui-notification-facade.h"
|
||||
|
||||
//#include "lib/util.hpp"
|
||||
|
||||
//#include <memory>
|
||||
//#include <vector>
|
||||
|
||||
|
||||
|
||||
namespace gui {
|
||||
namespace widget {
|
||||
|
||||
|
||||
/**
|
||||
* @todo WIP-WIP as of 9/2017
|
||||
* Just a text display box with scrollbars.
|
||||
* Need to add formatting etc.
|
||||
*/
|
||||
class ErrorLogWidget
|
||||
: public Gtk::ScrolledWindow
|
||||
{
|
||||
public:
|
||||
~ErrorLogWidget() { };
|
||||
|
||||
ErrorLogWidget()
|
||||
: Gtk::ScrolledWindow()
|
||||
, textLog_{}
|
||||
{
|
||||
set_size_request (200, 50);
|
||||
set_border_width (10);
|
||||
set_shadow_type (Gtk::SHADOW_NONE);
|
||||
|
||||
// the vertical scrollbar will always be necessary....
|
||||
set_policy (Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS);
|
||||
this->add (textLog_);
|
||||
textLog_.set_editable (false);
|
||||
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
showMsg (NotifyLevel severity, string const& text)
|
||||
{
|
||||
//////////////////////////////////////////////////TICKET #1102 : add formatting according to the error level
|
||||
switch (severity) {
|
||||
case NOTE_ERROR:
|
||||
addEntry ("ERROR: "+text);
|
||||
break;
|
||||
case NOTE_WARN:
|
||||
addEntry ("WARN: "+text);
|
||||
break;
|
||||
default:
|
||||
addEntry (text);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private:/* ===== Internals ===== */
|
||||
|
||||
Gtk::TextView textLog_;
|
||||
|
||||
/** add message entry to the (ever growing) text buffer.
|
||||
* @remark According to the Gtkmm tutorial, TextView::scroll_to(iter) is not reliable;
|
||||
* rather we need to use a text mark and set that text mark to the insert position.
|
||||
* Actually, there is always one predefined text mark called "insert", which corresponds
|
||||
* to the text cursor. Thus it suffices to navigate to text end, insert and scroll into view.
|
||||
*/
|
||||
void
|
||||
addEntry (string const& text)
|
||||
{
|
||||
auto buff = textLog_.get_buffer();
|
||||
auto cursor = buff->get_insert();
|
||||
buff->move_mark (cursor, buff->end());
|
||||
buff->insert (buff->end(), text);
|
||||
textLog_.scroll_to (cursor);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}}// namespace gui::widget
|
||||
#endif /*GUI_WIDGET_ERROR_LOG_WIDGET_H*/
|
||||
|
|
@ -42,6 +42,7 @@
|
|||
#include "gui/panel/timeline-panel-obsolete.hpp"
|
||||
|
||||
#include "include/logging.h"
|
||||
#include "lib/util-foreach.hpp"
|
||||
|
||||
using namespace boost; ////////////////////////////////////////////////////////////////////////////////TICKET #1071 no wildcard includes please!
|
||||
using namespace std; ////////////////////////////////////////////////////////////////////////////////TICKET #1071 no wildcard includes please!
|
||||
|
|
@ -143,7 +144,16 @@ namespace workspace {
|
|||
}
|
||||
|
||||
|
||||
void
|
||||
bool
|
||||
DockArea::hasPanel (const int description_index)
|
||||
{
|
||||
return util::has_any (panels_, [=](panel::Panel* panel)
|
||||
{
|
||||
return getPanelType(panel) == description_index;
|
||||
});
|
||||
}
|
||||
|
||||
panel::Panel&
|
||||
DockArea::showPanel (const int description_index)
|
||||
{
|
||||
// Try and find the panel and present it if possible
|
||||
|
|
@ -158,7 +168,8 @@ namespace workspace {
|
|||
Gdl::DockItem &dock_item = panel->getDockItem();
|
||||
// ENSURE(dock_item);
|
||||
dock_item.present(dock_);
|
||||
return;
|
||||
ENSURE (panel);
|
||||
return *panel;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -167,6 +178,9 @@ namespace workspace {
|
|||
|
||||
// Dock the item
|
||||
dock_.add_item(new_panel->getDockItem(), Gdl::DOCK_FLOATING);
|
||||
|
||||
ENSURE (new_panel);
|
||||
return *new_panel;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -256,7 +270,7 @@ namespace workspace {
|
|||
|
||||
|
||||
int
|
||||
DockArea::findPanelDescription (const char* class_name) const
|
||||
DockArea::findPanelDescription (const char* class_name)
|
||||
{
|
||||
REQUIRE(class_name);
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@
|
|||
** @todo 2017 need to clarify the intended behaviour of panels
|
||||
** ///////////////////////////////////////////////////////////////////////////TICKET #1097 clarify the role and behaviour of Panels
|
||||
** @todo will be transformed into a Dock entity as of 6/2018 /////////////////////////////////////////////TICKET #1144 refactor dock handling
|
||||
** @todo as of 8/2018 this is a copy of the (still actively used) PanelManager;
|
||||
** the intention was to do a clean rewrite and then throw away
|
||||
** the old messy PanelManager implementation. Meanwhile I was forced to add
|
||||
** yet more cruft to PanelManager, just to keep it going. Beware!
|
||||
**
|
||||
** @see actions.hpp
|
||||
*/
|
||||
|
|
@ -117,8 +121,12 @@ namespace workspace {
|
|||
/**
|
||||
* Shows a panel given a description index.
|
||||
* @param description_index The index of the panel type to show.
|
||||
* @return existing or new Panel, docked within the realm of this PanelManager.
|
||||
*/
|
||||
void showPanel (const int description_index);
|
||||
panel::Panel& showPanel (const int description_index);
|
||||
|
||||
/** was the indicated panel already allocated within this PanelManager's realm? */
|
||||
bool hasPanel (const int description_index);
|
||||
|
||||
/**
|
||||
* Switches a panel from one type to another,
|
||||
|
|
@ -138,6 +146,14 @@ namespace workspace {
|
|||
|
||||
|
||||
public:
|
||||
/**
|
||||
* retrieve the internal type-ID corresponding to the given panel implementation type.
|
||||
* @return internal index into the #panelDescriptionList, or `-1` if not found.
|
||||
* @deprecated the whole concept of panel identification needs overhaul 8/2018
|
||||
*/
|
||||
template<class P>
|
||||
static int findPanelID();
|
||||
|
||||
/** Gets the number of panel descriptions. */
|
||||
static int getPanelDescriptionCount();
|
||||
|
||||
|
|
@ -166,7 +182,7 @@ namespace workspace {
|
|||
* @return Returns the index of the panel description found, or -1
|
||||
* if no description was found for this type.
|
||||
*/
|
||||
int findPanelDescription (const char* class_name) const;
|
||||
static int findPanelDescription (const char* class_name);
|
||||
|
||||
/**
|
||||
* Creates a panel by description index.
|
||||
|
|
@ -338,5 +354,16 @@ namespace workspace {
|
|||
};
|
||||
|
||||
|
||||
|
||||
|
||||
template<class P>
|
||||
inline int
|
||||
DockArea::findPanelID()
|
||||
{
|
||||
return DockArea::findPanelDescription (typeid(P).name());
|
||||
}
|
||||
|
||||
|
||||
|
||||
}}// namespace gui::workspace
|
||||
#endif /*GUI_WORKSPACE_DOCK_AREA_H*/
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@
|
|||
#include "gui/panel/timeline-panel-obsolete.hpp"
|
||||
|
||||
#include "include/logging.h"
|
||||
#include "lib/util-foreach.hpp"
|
||||
|
||||
using namespace boost; ////////////////////////////////////////////////////////////////////////////////TICKET #1071 no wildcard includes please!
|
||||
using namespace std; ////////////////////////////////////////////////////////////////////////////////TICKET #1071 no wildcard includes please!
|
||||
|
|
@ -136,7 +137,16 @@ namespace workspace {
|
|||
}
|
||||
|
||||
|
||||
void
|
||||
bool
|
||||
PanelManager::hasPanel (const int description_index)
|
||||
{
|
||||
return util::has_any (panels_, [=](panel::Panel* panel)
|
||||
{
|
||||
return getPanelType(panel) == description_index;
|
||||
});
|
||||
}
|
||||
|
||||
panel::Panel&
|
||||
PanelManager::showPanel (const int description_index)
|
||||
{
|
||||
// Try and find the panel and present it if possible
|
||||
|
|
@ -149,9 +159,9 @@ namespace workspace {
|
|||
if (!panel->is_shown()) panel->show();
|
||||
|
||||
Gdl::DockItem &dock_item = panel->getDockItem();
|
||||
// ENSURE(dock_item);
|
||||
dock_item.present(dock_);
|
||||
return;
|
||||
ENSURE (panel);
|
||||
return *panel;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -160,6 +170,9 @@ namespace workspace {
|
|||
|
||||
// Dock the item
|
||||
dock_.add_item(new_panel->getDockItem(), Gdl::DOCK_FLOATING);
|
||||
|
||||
ENSURE (new_panel);
|
||||
return *new_panel;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -249,7 +262,7 @@ namespace workspace {
|
|||
|
||||
|
||||
int
|
||||
PanelManager::findPanelDescription (const char* class_name) const
|
||||
PanelManager::findPanelDescription (const char* class_name)
|
||||
{
|
||||
REQUIRE(class_name);
|
||||
|
||||
|
|
|
|||
|
|
@ -114,8 +114,12 @@ namespace workspace {
|
|||
/**
|
||||
* Shows a panel given a description index.
|
||||
* @param description_index The index of the panel type to show.
|
||||
* @return existing or new Panel, docked within the realm of this PanelManager.
|
||||
*/
|
||||
void showPanel (const int description_index);
|
||||
panel::Panel& showPanel (const int description_index);
|
||||
|
||||
/** was the indicated panel already allocated within this PanelManager's realm? */
|
||||
bool hasPanel (const int description_index);
|
||||
|
||||
/**
|
||||
* Switches a panel from one type to another,
|
||||
|
|
@ -135,6 +139,14 @@ namespace workspace {
|
|||
|
||||
|
||||
public:
|
||||
/**
|
||||
* retrieve the internal type-ID corresponding to the given panel implementation type.
|
||||
* @return internal index into the #panelDescriptionList, or `-1` if not found.
|
||||
* @deprecated the whole concept of panel identification needs overhaul 8/2018
|
||||
*/
|
||||
template<class P>
|
||||
static int findPanelID();
|
||||
|
||||
/** Gets the number of panel descriptions. */
|
||||
static int getPanelDescriptionCount();
|
||||
|
||||
|
|
@ -163,7 +175,7 @@ namespace workspace {
|
|||
* @return Returns the index of the panel description found, or -1
|
||||
* if no description was found for this type.
|
||||
*/
|
||||
int findPanelDescription (const char* class_name) const;
|
||||
static int findPanelDescription (const char* class_name);
|
||||
|
||||
/**
|
||||
* Creates a panel by description index.
|
||||
|
|
@ -334,5 +346,16 @@ namespace workspace {
|
|||
};
|
||||
|
||||
|
||||
|
||||
|
||||
template<class P>
|
||||
inline int
|
||||
PanelManager::findPanelID()
|
||||
{
|
||||
return PanelManager::findPanelDescription (typeid(P).name());
|
||||
}
|
||||
|
||||
|
||||
|
||||
}}// namespace gui::workspace
|
||||
#endif /*GUI_WORKSPACE_PANEL_MANAGER_H*/
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ namespace workspace {
|
|||
}
|
||||
|
||||
Gtk::StyleContext::add_provider_for_screen (screen, css_provider,
|
||||
GTK_STYLE_PROVIDER_PRIORITY_USER);
|
||||
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
** by a better design, where rather the \em provider of an output facility
|
||||
** registers with the OutputManager in the core.
|
||||
**
|
||||
** @see [corresponding implementation](display-service.hpp)
|
||||
** @see [corresponding implementation](\ref display-service.hpp)
|
||||
** @see gui::GuiFacade
|
||||
** @see dummy-player-facade.h
|
||||
**
|
||||
|
|
|
|||
|
|
@ -44,15 +44,20 @@
|
|||
#ifdef __cplusplus /* ============== C++ Interface ================= */
|
||||
|
||||
#include "lib/depend.hpp"
|
||||
#include "lib/diff/mutation-message.hpp"
|
||||
#include "lib/idi/entry-id.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace diff {
|
||||
class GenNode;
|
||||
class MutationMessage;
|
||||
} }
|
||||
namespace gui {
|
||||
|
||||
using std::string;
|
||||
using lib::diff::GenNode;
|
||||
using lib::diff::MutationMessage;
|
||||
|
||||
using ID = lib::idi::BareEntryID const&;
|
||||
|
|
@ -69,7 +74,7 @@ namespace gui {
|
|||
* from the lower layers into the Lumiera GUI. Typically, this happens
|
||||
* asynchronously and triggered by events within the lower layers.
|
||||
*
|
||||
* This is a layer separation facade interface. Clients should use
|
||||
* This is a layer separation façade interface. Clients should use
|
||||
* the embedded #facade factory, which yields a proxy routing any
|
||||
* calls through the lumieraorg_GuiNotification interface
|
||||
* @throws lumiera::error::State when interface is not opened
|
||||
|
|
@ -93,6 +98,9 @@ namespace gui {
|
|||
/** attach an warning or state information element */
|
||||
virtual void markNote (ID uiElement, string const& text) =0;
|
||||
|
||||
/** send a generic _state mark_ message to some element */
|
||||
virtual void mark (ID uiElement, GenNode&& stateMark) =0;
|
||||
|
||||
/** push a diff message up into the user interface.
|
||||
* @remark this is the intended way how to populate or
|
||||
* manipulate the contents of the user interface from
|
||||
|
|
@ -129,9 +137,10 @@ extern "C" {
|
|||
|
||||
LUMIERA_INTERFACE_DECLARE (lumieraorg_GuiNotification, 0,
|
||||
LUMIERA_INTERFACE_SLOT (void, displayInfo, (uint, const char*)),
|
||||
LUMIERA_INTERFACE_SLOT (void, markError, (LumieraUid, const char*)),
|
||||
LUMIERA_INTERFACE_SLOT (void, markNote, (LumieraUid, const char*)),
|
||||
LUMIERA_INTERFACE_SLOT (void, mutate, (LumieraUid, void*)),
|
||||
LUMIERA_INTERFACE_SLOT (void, markError, (const void*, const char*)), ////////////TICKET #1175 : need a way to pass EntryID
|
||||
LUMIERA_INTERFACE_SLOT (void, markNote, (const void*, const char*)),
|
||||
LUMIERA_INTERFACE_SLOT (void, mark, (const void*, void*)),
|
||||
LUMIERA_INTERFACE_SLOT (void, mutate, (const void*, void*)),
|
||||
LUMIERA_INTERFACE_SLOT (void, triggerGuiShutdown, (const char*)),
|
||||
);
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue