As it turned out, the reason was a missing move-ctor.
The base of the whole DSL-Stack, TreeMutator, is defined MoveOnly,
and this is also the intended use (build an anonymous instance
through the DSL and move it into the work buffer prior to diff application)
However, C++ does *cease to define* a move ctor implicitly,
whenever /one of the "big five" is defined explicitly/.
So Detector4StructuralChanges was the culprit, it defined a dtor,
but failed to define the move ctor explicitly.
So.... well, this did cost me several hours to track down,
yet I still rather do not want to write all those ctors explicitly all the time,
and so I am still in favour of implicitly generated ctors, even if they hurt sometimes.
with the new decorator layer, we suddenly trigger a chain of template instantiation errors.
At first sight, they are almost undecipherable, yet after some experimentation, it becomes clear
that they relate down to the base class (TreeMutator), which is defined MoveOnly
This seems to indicate that, at some point in the call chain, we are
digressing from the move-construction scheme and switch over to copy construction,
which in the end failst (and shall fail).
Inconclusive, to be investigated further
basically the solution was a bit too naive and assumed everything is similar to a vector.
It is not, and this leads to some insidious problems with std::map, which hereby
are resolved by introducing ContainerTraits
All of the existing "simple" tests for the »Diff Framework« are way to much low-level;
they might indeed be elementary, but not introductory and simple to grasp.
We need a very simplistic example to show off the idea of mutation by diff,
and this simple example can then be used to build further usage test cases.
My actual goal for #1206 to have such a very basic usage demonstration and then
to attach a listener to this setup, and verify it is actually triggered.
PS: the name "GenNodeBasic_test" is somewhat pathetic, this test covers a lot
of ground and is anything but "basic". GenNode in fact became a widely used
fundamental data structure within Lumiera, and -- admittedly -- the existing
implementation might be somewhat simplistic, while the whole concept as such
is demanding, and we should accept that as the state of affairs
Basically the advice system is just some further helper component with
a lookup table, which "just works" transparently most of the time.
We'd need those diagnostic messages only when explicitly debugging
for some kind of access-after death, which should not happen as long
as we stick to the general policy not to allow any significant functionality
to be hooked up from dtors
now the lifecycle of widget and hook are tightly interwoven.
Indeed the test uncovered a situation where a call into the
already destroyed Canvas might halt the application.
basically this attempts to work around an "impedance mismatch" caused by relying on Lumiera's Diff framework.
Applying a diff might alter the structural order of components, without those componets
being aware of the change. If especially those components are attached into some
UI layout, or otherwise delegate to display widgets, we need a dedicated mechanism
to reestablish those display elements in proper order after applying the change.
The typical examples is a sequence of sub-Tracks, which might have been reordert due
to applying rules down in the Steam Layer. The resulting diff will propagate the
new order of sub-Tracks up into the UI, yet now all of the elaborate layout and
space allocation done in the presentation code needs to be adjusted or even
recomputed to accomodate the change.
By applying a Diff, the children of some timeline element (track) may be re-ordered.
This imposes specific problems, since these elements hold onto slave-Widgets,
which are already attached into some elaborated and nested widget structure.
To keep complexity under control, we can not allow the TrackPresenter to have
any knowledge regarding the implementation structure of these target widgets.
Thus I am pondering the idea to represent that relation as an abstracted ViewHook link
...which serves to solve the problem with Canvas access.
Basically we do not want each and every Clip widget to be aware of the concrete canvas implementation widget;
and in addition, automated removal of widgets from the Canvas seems desirable
This is dummy/test/diagnostics code and should be removed when the track display code is complete!
It can be activated by sending a "mark"-Message via the UI-Bus, towards the
Timeline element to be tested (Tip: use the same ID as used when injecting
the Timeline via the TestControl Dialog box). When receiving this message
(asynchronously), the TimelineControler asks each nested TrackPresnter
to inject a Button with the corresponding track name onto the BodyCanvasWidget.
This allows us to verify the coordinate calculation and size allocation --
and indeed, the numbers are not yet correct (TODO)
admittedly this is a bit sketchy, but I don't have a better framework to hinge upon right now.
Thus we store the vertical start coordinates and the offset of the content area
as a side effect, while calculating the TrackProfile
...which has the nice additional effect of exposing box-shadow on the outside of the content area too.
Thus the content area now behaves equivalent to the rulers, and adjacent
content space of simple tracks without rulers and nesting can be slightly
offset from each other through a margin in CSS
In the end, I used the profile building pass to also calculate and sum up the vertical offsets.
Seems to be the only sane approach to get really precise values, since adjacent
upwards slopes can be combined at various places (and I do not want to use the
actual drawing code for this calculation)
need to investigate and probably need to store per track offset values
already while building the track profile. The primary reason for the
observed discrepancy seems to be the rather flexible combination of
slope borders.
Especially note the tricks we need to play in order to allow for (limited) usage of CSS3 box-shadows.
The reason is, all these CSS3 effects are rendered in one shot and combinend on the StyleContext::render_background() call
Thus we need to ensure that the background is properly aligned with the frames
seemingly, the Box with PACK_SHRINK allocates a zero height to the rulerCanvas initally,
which is correct at that point, since the widgets are not yet realised.
However, when we later set_size() on the rulerCanvas, the enclosing Box should reflow.
It does indeed if the child widget is a button or something similar, however,
somehow this reflowing does not work when we set_size on the canvas.
A workaround is to place a new set_size_request().
TODO: do this more precisely, and only on the rulerCanvas. To the contrary,
the mainCanvas is placed into a scolling-pane and thus does not need a size-Request.
Moreover, the latter automatically communicates with the hadjustment() / vadjustment() of
the enclosing scrollbars.
as can be verified with the debugger, it sets the correct sizes now.
And it is called only once (unless the content size actually changes).
TODO: however, the visible display of the GTK widgets is not adjusted
Indeed I had missed to connect the new "free standing" StyleContext to
some Gdk::Screen, typically the default screen (connected to the current
top level window). But seemingly this was not really necessary, since,
somehow magically, the style context must have connected itself to some
screen, otherwise it wouldn't be able to access the CSS cascade.
Anyhow, fixing this omission does not resolve our problem.
Nor does any combination of re-connecting, invalidating etc.
I poked around in the GTK (C) code a lot, but could not spot any obvious
missing initialisation step. To much magic around here. Without massive
debugging into GTK internals, I don't see any way to further this
investigation. And, moreover there is a viable workaround
(namely to set and remove the classes explicitly, which works as intended)
I posted a question on Stackoverflow and for now
I'll file this topic as "inconclusive"
https://stackoverflow.com/q/57342478
Note however, we will not plaster our UI code and CSS with mangled-out selectors
on each and every single element. This is what cascading was meant to be used for.
DONE
- can now control the border size through a set of modifier classes
OPEN
- but context_save()/restore() does not work; seem to loose all styling
- not clear how to deal with CSS3 effects like box-shadow
...somehow does not yet work as intended...
- unable to control the border-width from code
- Gtk::StyleContext::add_class(name) does not seem to have any effect
The population message is just made up, in order to create more interesting structures
in the UI and so to further the development of the timeline display.
For the actual structure I choose to mirror my example drawing in draw/UI-TimelineLayout-1.png
which is also used in the TiddlyWiki, on the #GuiTimelineView tiddler
https://lumiera.org/wiki/renderengine.html#GuiTimelineView
We can add our custom classes to custom widgets, and we can set the
widget name, which can be used as #id selector from CSS
Unfortunately we can not set the main CSS node name for CustomWidgets defined through GTKmm (C++)
The latter is only possible when deriving the custom widget in plain-C, which is quite tedious.
On a second thought, this limitation is not so severe as it might seem, because
most of the time you actually do *not* want to change the CSS node name,
because you want to match against existing rules in the theme (e.g. box, or paned)
The actual case here would have been an exception to this rule, since here
it would be nice to anchor the whole custom timeline drawing in an "body.timeline" element
NOTE: Current state for the selector path is now:
window.background box.vertical box[2/3].horizontal widget[2/2] widget paned.vertical widget box.vertical notebook[1/1].frame paned.horizontal.timeline-page box.vertical.timeline.timeline-body fork.timeline
...and perform the initialisation once, when attaching the first timeline to the UI
Now our code produces the following Gtk::WidgetPath (note the last node, which our code added)
window:backdrop:dir-ltr.background box:backdrop:dir-ltr.vertical box:backdrop:dir-ltr[2/3].horizontal widget:backdrop:dir-ltr[2/2] widget:backdrop:dir-ltr paned:backdrop:dir-ltr.vertical widget:backdrop:dir-ltr box:backdrop:dir-ltr.vertical notebook:backdrop:dir-ltr[1/1].frame paned:backdrop:dir-ltr.horizontal box:backdrop:dir-ltr.vertical fork.timeline
For context: The »Advice System« was coined a long time ago, in 2010,
based on the vague impression that it might be useful for that kind of application
we are about to build here. And, as can be expected, none of the usage situations
envisioned at that time was brought to bear. Non the less, the facility came in
handy at times, precisely because it is cross-cutting and allows to pass
information without imposing any systematic relationship between the
communication partners.
And now we've got again such a situation.
The global style manager in the UI has to build a virtual CSS path,
which is needed by drawing code somewhere deep down, and we absolutely
do not want to pass a reference to the style manager over 20 recursive calls.
The alternatives would be
(1) to turn the style manager into a public service
(2) to have a static access function somewhere
(3) to use a global variable.
For rationale, (1) would be overblown, because we do not actually request
a service to do work for us, rather we need some global piece of information.
(2) would be equivalent to (1), just more confusing. And (3) is basically
what the Advice system does, with the added benefit of a clear-cut service
access point and a well defined lifecycle.
This changeset adds the ability to check if actual Advice has been published,
which allows us to invoke the (possibly expensive) GTK path building and
style context building code only once.
- at some (yet to be defined) location, a virtual WidgetPath is constructed
and used to build a Gtk::StyleContext in accordance to the curren CSS
- within the drawing routine, we use Lumiera's Advice-System to access this info
Mostly, std::regexp can be used as a drop-in replacement.
Note: unfortunately ECMA regexps do not support lookbehind assertions.
This lookbehind is necesary here because we want to allow parsing values
from strings with additional content, which means we need explicitly to
exclude mismatches due to invalid syntax.
We can work around that issue like "either line start, or *not* one of these characters.
Alternatively we could consider to make the match more rigid,
i.e we would require the string to conain *only* the timecode spec to be parsed.
The existing implementation created a Buffer-Type based on various traits,
including the constructor and destructor functions for the buffer content.
However, this necessitates calculating the hash_value of a std::function,
which (see #294) is generally not possible to implement.
So with this changeset we now store an additional identity hash value
right into the TypeHandler, based on the target type placed into the buffer
This was prompted by a test failing under Boost-1.65 (--> see #294)
When reviewed now, the whole idea of testing Steam-Layer Commands for
equivalence feels a bit sketchy.
Just the comparison for the command ''identity'' alone seems sufficient,
i.e. the test if a command-ID is associated with the same backend-handle
and thus the same functor binding.
<rant>
the "improved" boost::rational can no longer compute 1/x
quite brilliant
</rant>
well... the reason is again signed vs unsigned int.
FrameRate is based on unsigned int (since a negative frame rate makes no sense).
seemingly, the newer boost libraries added an internal type rational<I>::bool_type
together with an overload for the equality comparison operator.
Unfortunately this now renders a comparison ambiguous with the constant zero (i.e. int{0})
because in our use case we employ rational<uint>.
Workaround is to compare explicitly to a zero of the underlying integer type.
...when rendering this part, which shall be always visible.
And the rest of the profile needs to be rendered into a second canvas,
which is placed within a pane with scrollbar.
Implemented as a statefull iterator filter
TODO:
- actual draw operations not yet implemented
- find a way how to select the prelude / body part of the track profile
This is a consequence of subsuming the timeline ruler under the concept of an overview track
the template lib::PolymorphicValue seemingly picked the wrong
implementation strategy for "virtual copy support": In fact it is possible
to use the optimal strategy here, since our interface inherits from CloneSupport,
yet the metaprogramming logic picked the mix-in-adapter (which requires one additional "slot"
of storage plus a dynamic_cast at runtime).
The reason for this malfunction was the fact that we used META_DETECT_FUNCTION
to detect the presence of a clone-support-function. This is not correct, since
it can only detect a function in the *same* class, not an inherited function.
Thus, switching to META_DETECT_FUNCTION_NAME solves this problem
Well, this solution has some downsides, but since I intend to rewrite the
whole virtual copy support (#1197) anyway, I'll deem this acceptable for now
TODO / WIP: still some diagnostics code to clean up, plus a better solution for the EmptyBase
...which, in the end, can even be considered the more logical design choice,
since the "verb visitor" is a more elaborated and sophisiticated Verb-Token,
adding the special twist of embedded storage for variable function arguments
...but bad news on the main issue:
the workaround consumes the tuple and thus is not tenable!
And what is even worse: the textbook implementation of std::apply is
equivalent to our workaround and also consumes the argument tuple
A simple yet weird workaround (and basically equivalent to our helper function)
is to wrap the argument tuple itself into std::forward<Args> -- which has the
effect of exposing RValue references to the forwarding function, thus silencing
the compiler.
I am not happy with this result, since it contradicts the notion of perfect forwarding.
As an asside, the ressearch has sorted out some secondary suspicions..
- it is *not* the Varargs argument pack as such
- it is *not* the VerbToken type as such
The problem clearly is related to exposing tuple elements to a forwarding function.
this is a generalisation of what we use in the diff framework;
typically you'd package the VerbToken into some kind of container,
together with the concrete invocation argument.
However, the specific twist here is that we want *variable arguments*,
depending on the actual operation called on the interpreter interface.
Up to now, PolymorphicValue was always used as-is, packaged into a typedef.
Now we consider using it as building block within an adapter for visitor-like tokens.
Which requires to pass-down the ctor call directly from the subclass, at least if we
want to emplace the resulting entity directly into a stdlib container.
As an asside, PolymorphicValue also used explicit specialisations for N-arguments,
which meanwhile can be replaced by variadic templates
...which leads to a specific twist here; while in the simple version
we still could hope to get away with a simple uniform uint argument,
the situation has changed altogether now. The canvas has turned into
some generic component, since it is instantiated two times, onece for
the time ruler and once for the actual body content. Thus all of the
specifics of the drawing code need to be pushed into a new, dedicated
renderer component. And this more or less forces us to pass all the
actual presentation variations through the invocation arguments of
the visitor.
So we're now off again for a digression, we need a more generalised visitor
After thinking the whole concept over several times, it occurred to me that
a separate implementation of a time ruler would be quite redundant with the
envisioned feature of per-track overview rulers. Following this line of thought,
the time ruler would just be some specifically configured overview ruler.
This has the somewhat unfortunate consequence, that it becomes the responsibility
of the body canvas to render the overview ruler, thereby somehow delegating
to a common renderer implementation. Which makes the whole setup of the body canvas
way more complex, because now we get *two* canvas like painting areas, one
always visible at top, and the second one, the content area, fully scrollable
within the lower part.
- we got occasional hangups when waiting for disabled state
- the builder was not triggered properly, sometimes redundant, sometimes without timeout
As it turned out, the loop control logic is more like a state machine,
and the state variables need to be separated from the external influenced variables.
As a consequence, the inChange_ variable was not calculated properly when disabled in a race,
and then the loop went into infinite wait state, without propagating this to
the externally waiting client, which caused the deadlock
effectively we rely in the micro tick timescale promoted by libGAVL,
but it seems indicated to introduce our own constant definition.
And also clarify some comments and tests.
(this changeset does not change any values or functionality)
basically we can pick just any convention here, and so we should pick the convention in a way
that makes most sense informally, for a *human reader*. But what we previously did, was to pick
the condition such as to make it simple in some situations for the programmer....
With the predictable result: even with the disappointingly small number of usages we have up to now,
we got that condition backwards several times.
OK, so from now on!!!
Time::NEVER == Time::MAX, because "never" is as far as possible into the future
A classical carry-over of dirty values...
Problem arises, when starting an unconditional wait on the same object monitor,
which previously conducted a timed wait. Then the obsolete timeout from the previous
wait remained in place, causing our Sync-Wrapper (erroneously) to assume a timed wait
and then pthread to return immediately from this timed wait.
The result was permanent idle looping in the ProcDispatcher, after the first command was processed
When invoking the util::toString conversion, we indeed to want any conversion,
including explicit conversion operators. However, probing the possibility to build a string
can be dangerous, since there is a string constructor from characters, and
integral types can be converted to characters.
OTOH, leaving out explicit conversions is likewise not desirable, since there are
class types, which deliberately do not offer an implicit conversion, but allow
explicit conversion for dump and diagnostic output. The notorious example for
such a situation is the lib::idi::EntryID<TY>. We certainly do not want an
EntryID to be converted into a string without further notice, but we do want
an EntryID to be automatically rendered to string in diagnostic output, since
this will include the human readable ID part.
See especially: 8432420726
Now we'll attempt to get out of this dilemma by probing explicitly for the presence
of a string conversion operator, which will fail for any non-class types, thereby
ruling out all those nasty indirect type -> character -> string conversion paths.
The rationale is: if someone queries the predicate can_convertToString, the intention
is really to get an string rendering, and not just to invoke some random function
with an string argument.
- most notably the NOBUG logging flags have been renamed now
- but for the configuration, I'll stick to "GUI" for now,
since "Stage" would be bewildering for an occasional user
- in a similar vein, most documentation continues to refer to the GUI
the new structure causes them now to be installed into $TARGET/stage
which is simply not what I want. I still consider $TARGET/gui the better choice,
since an administrator or packager is not aware of our layer namings.
The existing solution was half baked anyway, it did not really replicate the source tree.
On the other hand, I want to retain the location of the CSS files within the GUI tree,
since I consider it a good practice, to keep "code-like" resources with the actual code,
and not far away in some arcane "data" directory.
No I've noticed, that the env.GuiResource() function is only used once, for this very task.
So, for the time being, we can keep it simple and deditaced to that task, i.e
we pick up all CSS files we find and install it into a single target directory.
NOTE: this issue has brought to my attention two further, completely unrelated issues
* Ticket #1192 (Lumiera hangs on failed GUI start)
* The ProcDispatcher does an idle wait, due to an error in timed-wait implementation
...in accordance to our general design guideline: we don't duplicate
actual model values within the controllers/presenters, since our widgets
act themselves as view-model
This change demonstrates how to deal properly with possible duplicate entities
with similar symbolic ID: define a RandomID (to guarantee a distinct hash on each instance).
In the actual implementation, this should happen already within the domain model,
not when constructing the diff (obviously of course...)
This change also adds a mutation sequence to inject the actual track name
same pattern as the existing EntryID, i.e. a human readable symbol plus a hash
but the hash is just random, instead of deriving it from the symbol text.
Use case is when we explicitly need a distinct identity, even when the
human readable symbolic name is the same. Actual example: the fork root in the timeline
...it should have been explicit from start, since there is no point
in converting an EntryID into a plain flat string without further notice
this became evident, when the compiler picked the string overload on
MakeRec().genNode(specialID)
...which is in compliance to the rules, since string is a direct match,
while BareEntryID would be an (slicing) upcast. However, obviously we
want the BareEntryID here, and not an implicit string conversion,
thereby discarding the special hash value hidden within the ID
so this seems to be the better approach for dealing with this insidious problem.
In some cases -- as here most prominently with the root track within the timeline --
we have to care within the domain model to prepare unique ids even for sub objects
treated as attributes. In the actual case, without that special attention,
all timelines would hold onto an attribute "fork" with the same ID, based
on the type of the nested object plus the string "fork". Thus all root track
representations in the GUI would end up listening to the same ID on the UI-Bus...
...rather extend the "object builder" DSL notation to allow passing in a given EntryID literally.
Rationale is, we should handle the problem of unique IDs on the level of the domain model.
If we attempt to "fix" this within GenNode, the price would be to make the ETD creation stateful
this is not a problem, strictly sepaking, locally.
But it becomes a problem once the GUI uses those attribute IDs
as Element-IDs for tangible UI entities, which need to be uniquely
addressable via the UI-Bus.
An obvious solution is to inject randomness into the Attribute ID hash
...otherwise we'll get several seemingly identical Timeline tabs in the UI,
since this fake function just sends an INS for each newly injected Timeline,
and there is no deduplication in the UI (we assume that in a real session
and timeline-IDs will be unique)
these recursively nested helper entities work together with the TimelineCanvas
and enable the latter to draw the track background in the Timeline Widget and
to find out about the vertical coordinates where to place content (Clip, Effects, Markers)
Gtk::Viewport allows to add the ability to scroll a partial view window
for a container larger than the available display area. The position
and movement of this window is controlled by Gtk::Adjustments,
which can be located elsewhere.
Here we use the existing Adjustments of the ScrolledWindow
holding the body canvas; this setup makes the header pane follow
the scroll movements of the body
bottom line is to do most autmatically, and to establish a slave-relation
navigation-area -> timeline-ruler
header-pane-content -> corresponding track-body
this can be accomplished mostly by connecting the aproprieate signals,
thus these widgets will live within the Layout-Manager, which consequently
is renamed into TimelineLayout
the solution idea is to use a helper frame, and an "anchor functor",
which is passed down from the respective parent context, and which
does the actual work of injecting the child widgets at the apropriate
position within the parent display.
seems to work surprisingly well...
the diff application poceeds in the GUI up to the point
where the TrackPresenter need to be inserted into a two-fold display context
To drive the timeline display in the UI ahead, the plan is to have
a faked action, which injects dummy population diff messages into the GUI,
resulting in the build-up of a typical simple session timeline
decision: for now we will represent *every* Timeline present in the Session.
Later it would also possible to skip some representation; however we'd need
a way to store such presentation state such that we'd be able to get at this
persisted stat right at this point here, when processing the Diff.
other than the regular way of building an object,
we do expect a minimal structure to be sent right within the INS message.
Rationale: the standard way would allow for too much leeway and created
unwanted intermediary states. The non-standard way decided upon here
is well within the limits of our diff language
As it turns out, using the functional-notation form conversion
with *parentheses* will fall back on a C-style (wild, re-interpret) cast
when the target type is *not* a class. As in the case in question here, where
it is a const& to a class. To the contrary, using *curly braces* will always
attempt to go through a constructor, and thus fail as expected, when there is
no conversion path available.
I wasn't aware of that pitfall. I noticed it since the recently introduced
class TimelineGui lacked a conversion operator to BareEntryID const& and just
happily used the TimelineGui object itself and did a reinterpret_cast into BareEntryID
Problem is, the InteractionDirector, being the representation of the model root,
needs to manage and maintain the collection of "timelines". However, these
can not be widgets, rather, they need to attach to widgets living within
the GUI widget structure proper, i.e. within the TimelinePanel
proposed solution is to build a smart handle based on WLink,
but also delegating the DiffMutable interface
This involves a fundamental decision about how to build structures in the Lumiera UI:
They shall be solely created in response to diff messages. Which leads us to
introduce a new (and quite challenging) concept: the »DiffConstituent«
...these magical strings are already spreading dangerously throughout the code base
PS: also fixup for c6b8811af0 (broken whitespace in test definition)
This marks start of actual work on this fundamental task.
Extensive planning from 2016 is available, together with an almost
complete diff binding for the entities involved into timeline display.
''a new hope''
This was quite a long way until we're back at the point of
re-building the timeline anew.
Stash the canvas research code to make room for new things to come
this turned out to be more tricky than expected.
When we initially configure the UI and invoke this->show_all(),
seemingly some draw-callbacks will be scheduled into the event loop.
Just set_visible(false) on the relevant buttons directly after that call
will have no effect (since the widget is still hidden at that point anyway,
it is not yet mapped and realised).
Thus we need to schedule a callback with the Glib::signal_idle(),
so our state detection runs after the initial mapping of the UI
NOTE: there is a minor itch, which I don't address right now:
when adding the error state and thus revealing the additional buttons,
the error log grabs some additional horizontal space, even while there
would be ample space for the additional buttons within the button bar.
When the error state is cleared and the buttons thus hidden again,
the additional horizontal space is dropped and the error log gets
narrower. Probably we'd need some special GTK call to re-allocate
the required space properly
solved by temporarily adding a CSS class.
Mostly this was an issue of writing the Stylesheet properly.
Hint: use the GTK+ inspector, i.e. run with
GTK_DEBUG=interactive target/lumiera
Even while we (still) have the goal to ship our own stylesheet and provide
the typical subdued media-aplication look, right now this porting and styling effort (#1023)
is unfinished and handled with rather low priority (writing code is more important
than toying with styles and looks).
This alternative stylesheet is meant to be used with a typical "light" desktop theme.
We'll add just the bare minimum of definitions to make lumiera work well in that setup.
And right now, I'll use that setup to continue with my development work
In GTK-2 you'd always use a theming engine.
During the transitional period, GTK-3 followed that path,
but at the point when the new CSS based system was mature enough,
this approach was discouraged, since it means to ship additional
executable code and defeats the purpose of configuring the look
of the application through one coherent set of rules.
As it turns out, debian continued to ship the 'adwaita' for some
extended transitional period, but in Stretch it is gone, which broke
our half-way ported stylesheet.
This changeset removes the use of a theming engine, and fixes tha
basic look by adding the background-color: inherit, which pushes aside
many (but not all) of the default theme definitions.
However, our stylesheet remains mostly broken and TODO for now
Basically we create a pair of marks, with left/right gravity and then
inject the content between. Unfortunately, when the insert position
is the very end of the buffer (which it always is), this trick
leads to nesting the marked regions into each other.
As a remedy, we first insert the trailing newline,
and then attach the insert position one step before
discard all other info log messages and retain only the entries marked as error.
This is also a proof-of-concept regarding position bookmarks and markup.
Implemented by populating a new buffer and swapping it into place.
== possible Scenario ==
1. Gui: sigTerm invoked
2. last Subsystem -> cleans all remaining Subsy entries
3. main-Thread wakens
4. leaves main() und undloads the GUI plug-in
5. which destroys the `DependencyFactory<LocationQuery>` placed in static memory
6. the Gui-Thread returns from sigTerm() and invokes `~UiManager()`
7. which indirectly deregisters through `InteractionDirector` the `LocationQuery` Service
8. `DependInject::Service::shutdown()` grabs the Lock ==> **BOOM**
== Solution ==
Ensure all dtors of the UI backbone are invoked ''prior'' to calling sigTerm()
and especially our provisional dummy code to execute some commands "right here"
should also check and raise captured exceptions from command invocation
As it turns out, several problems reinforce each other
- lumiera error does not properly propagate the cause message
- our test/dummy code does not check the ExecResult
- thus the exception is detected rather accidentally, when entring the next sync/wait state
- emergency shutdown is chaotic in its very nature (this is well known...)
- but especially triggerShutdown is not airtight and might die...
- causing the shutdown to hang....
And last but not least, a ZombieCheck tripwire got triggered,
but unfortunately I was unable to get hold of the zombie iteself
test_meta_markAction always produces a state mark with payload type string.
However, the model::Tangible expects a bool payload when handling the "expand" mark.
- add diagnostics to lib::variant to indicate expected and actual payload type
- attempt to fix with boost::lexical_cast; this is insufficient, since
you'd expect such a function to understand "true" and "false" etc.
Moreover, raising this exception causes emergency shutdown, which
flounders due to triggering a ZombieCheck. Interesting.
The very backbone structure of the Lumiera UI, the UI-Bus is now fully defined
and proven to be operative, including asynchronous dispatch of messages
an a generic notification mechanism
A communication chain, triggered from a button in a non-modal dialog box,
passing invocation into another thread, dispatched by the ProcDispatcher,
then again passing thread boundaries to push a response back into the UI.
This is a milestone, and integrates several components built during the last years.
- a text input field
- a trigger to invoke the showInfo function on GuiNotification
- triggers to send state mark messages via GuiNotification into the UI-Bus
- a combo box to define the action-ID within those state mark messages
With these controls, it should be possible to execute all the variations
of the Tangible element protocol and verify the respective behaviour
has been coded up properly within the receiving ErrorLogDisplay widget
Note the key point (and the next step to code up) is for #1099 to
invoke a dummy/demo command in Proc-Layer, which in turn pushes an
reaction via the GuiNotification facade back into the UI asynchronously...
wrap up the helpers and wire the connection to the UI-Bus.
Then attempt a direct invocation, still within the GTK thread.
While this might seem as just some silly experiment, in fact it is
*** THE FUCKING FIRST TIME to transmit a visible action to a real widget ***
this links together and integrates various efforts achieved during the last years
Gtk::Notebook is a quite powerful container foundation to build complex dialog widgets with multible pages on tabs.
Hower, the construction, wiring an 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 Gtkmm programming.
...which gives us already the base functionality required to run the first tests
- can be triggered from the Help menu
- non-modal dialog (Gtk::Dialog)
- attached as child / slave-Window to the current active workspace window
- window manager hint to keep it on top
- have a notebook control within the dialog
- attached (passively) to the UI-Bus
...just to decide not to follow-up too much on that topic right now.
As it turns out, GTK seems to be lacking in that respect. I have plotted
some ideas how we could work around that discrepancy in future...
And for this simple DemoGuiRoundtrip, we'll just use direct styling,
but we'll store a table of bookmarks for the error entries, allowing
us to add further features later on top
after an extended digression to fix our matcher for tests on the EventLog,
the new helper abstractions gui::model::Expander and gui::model::Revealer
are now covered and ready for use.
In this special case here, the controller uses both the Expander and Revealer
inherited from model::Tangible; yet both are wired to access the actual
display widget via the getter, and delegate to the Expander rsp. Revealer
located within the widget. Which in turn are wired when creating the widget
within the InfoboxPanel.
Bottom line -- we have a generic scheme now, and the actual implementation
is filled in as lambda, at the point where the component or widget is created
well... reduction in size of the debug build objects
turns out not to be so large as I hoped. But it is significant anyway,
about 3-4MB on the most affected test classes. Plus from now on we
do not repeat that code on other tests using the same features.
up to now, EventLog was header only, which seems to cause
a significant bloat in terms of generated code size, especially
in debug builds. One major source for this kind of "template bloat"
is the IterChainSearch, rsp. the filter and transformer iterators.
And since EventLog is not meant for performance critical application code,
but rather serves as helper for writing unit tests, an obvious remedy is
to move that problematic part of the code down into a dedicate translation
unit, instead of using inline functions. To prepare this refactoring,
some var arg (templated) API funcitons need to be segregated.
For the before / after chaining search functions,
we now do one single step in the respective direction before evaluating
the new (next) filter condition. However, we also need to *deactivate* the
previous condition, otherwise that single "step" might cause us to jump
or even exhaust the underlying filter, due to the old filter condition
still being applied.
due to the lack of real backtracking, the existing solution
relied on a quirk, and started the before / after chained search
conditions /at/ the current element, not after / before it.
Now we're able to remove this somewhat surprising behaviour, yet to do so
we also need to introduce basic "just search" variations of all search
operations, in order to define the initial condition for a chained search.
Without that, the first condition in a chain would never be able to
match on the header entry of the log
- need to use dedicated steps in the chain for every added condition now
- seems to break the logic on tests on non-match.
This doesn't come as a surprise, since backtracking can be expected
to reveal additional solutions.
NOTE: some tests broken, to be investigated
est-event-log-test.cpp:228: thread_1: verify_callLogging: (log.ensureNot("fun").after("fun").after("fun2"))
...which can be achieved by checking the backtracking loop
always right after the non-backtracking iteration, exploiting
the fact that the guard conditions of both are complimentary.
So the only case when we'd actually enter the backtracking
loop after regular iteration would precisely be when
we drop down due to exahausting an upper layer.
The result now reads
"sausage-bacon-tomato-and-spam-spam-bacon-spam-tomato-and-spam-bacon-tomato-and-bacon-tomato-and-tomato-and"
...which sounds correct, yay!
...since usually such evaluation layers are finally wrapped into
an IterableDecorator and then presented as TreeEplorer -- an exercise
we do not want to perform here, since it is pointless in the typicall
use case. The IterChainSearch is already meant to be ready-for-use.
Thus, instead of wrapping again, the pragmatic solution is simply
to overload the missing operator++ and make it call the augmented
iterNext() function. Related to this, we also need to ensure
proper operation in case no further expansion is mandated
...seems basically sane now.
Just we still need to wrap it one more time into IterableDecorator;
which means the overall scheme how to build and package the whole pipeline
is not correct yet.
Maybe it is not possible to get it packaged all into one single class?
on closer investigation it turned out that the logic of the
first design attempt was broken altogether. It did not properly
support backtracking (which was the reason to start this whole
exercise) and it caused dangling references within the lambda
closure once the produced iterator pipeline was moved out
into the target location.
Reasoning from first principles then indicated that the only sane
way to build such a search evaluation component is to use *two*
closely collaborating layers. The actual filter configuration
and evaluation logic can not reside and work from within the
expander. Rather, it must sit in a layer on top and work in
a conventional, imperative way (with a while loop).
Sometimes, functional programming is *not* the natural way
of doing things, and we should then stop attempting to force
matters against their nature.
this is an rather obvious extension to the TreeExplorer framework.
In some cases, client code wants to define its own very specific
processing layers, beyond of what can be done with filters and
transformers. Obviously, writing such a custom layer requires
intimate knowledge about the internals of TreeExplorer
the actual use case prompting this extension is IterChainSearch;
it turned out that the original design can not be implemented,
unless the resulting object is non-copyable (which violates
the basic traits of a TreeExplorer based pipeline).
Up to now, we had a very simplistic configuration option just
to search for a match, and we had the complete full-blown reconfiguration
builder option, which accepts a functor to work on and reconfigure the
embedded Filter chain.
It occurred to me that in many cases you'd rather want some intermediary
level of flexibility: you want to replace the filter predicate entirely
by some explicitly given functor, yet you don't need the full ability
to re-shape the Filter chain as a whole. In fact the intended use case
for IterChainSearch (which is the EventLog I am about to augment with
backtracking capabilities) will only ever need that intermediate level.
Thus wer're adding this intermediary level of configurability now.
The only twist is that doing so requires us to pass an "arbitrary function like thing"
(captured by universal reference) through a "layer of lambdas". Which means,
we have to capture an "arbitrary thingie" by value.
Fortunately, as I just found out today, C++14 allows something which comes
close to that requirement: the value capture of a lambda is allowe to have
an intialiser. Which means, we can std::forward into the value captured
by the intermediary lambda. I just hope I never need to know or understand
the actual type this captured "value" takes on.... :-)
with the augmented TreeExplorer, we're now able to get rid of the
spurious base layer, and we're able to discard the filter and
continue with the unfiltered sequence starting from current position.
build a special feature into the Explorer component of TreeExplorer,
causing it to "lock into" the current child sequence and discard
all previous sequences from the stack of child explorations
There is an asymetry, insofar the base layer configuration is
evaluated immediately, causing the MutableFilter to be reconfigured
and forwarded to the first match.
to the contrary, when configuring an additional layer, we just
add it to the chain, but then need to iterate once to cause
this configuration actually to be unfolded onto the stack
...which just turns the pipeline into exhausted state,
instead of raising an Assertion failure
The point is, expandChildren() does not guard itself,
since it _requires_ an non-empty iterator as precondition.
Thus, any function downstream, which invokes expandChildren(),
has to check and guard this call apropriately.
In the concrete case at hand we just stop adding further constraints
when the pipeline is already in exhausted state
...the solution built thus far was logically broken, since it retained the unfiltered
source sequence within the base layer. Thus it would backtrack into this unfiltered
sequence eventually.
The idea was to build a special treatment for attaching the first filter condition;
in fact the first one does not need to be added to the chain, but rather should be
planted directly into the base layer.
WIP: this is a solution draft, but does not work yet
- when attaching the base layer, the filter is pulled twice
- an overconstrained filter raises an Assertion failure
(instead of just transitioning into exhausted state)
So we have now a reworked version of the internals of TreeExplorer in place.
It should be easier to debug template instantation traces now, since most
of the redundancy on the type parameters could be remove. Moreover, existing
pipelines can now be re-assigned with similarily built pipelines in many cases,
since the concrete type of the functor is now erased.
The price tag for this refactoring is that we have now to perform a call
through a function pointer on each functor invocation (due to the type erasure).
And seemingly the bloat in the debugging information has been increased slightly
(this overhead is removed by stripping the binary)
Here the design trardeoff becomes clearly visiblie
- on the plus side, we removed that spurous redundant info
from the template parameter, and we simplified functor rebinding
- but as a tradeoff, we now always have two std::function objects
nested into each other, which also means that at least the outer
object resides on the heap and /inevitably/ calls through a
function pointer, even in case the target function is a lambda,
simply because some type erasure happened, and the call site
does not know the actual type anymore