From 06b3c382f3c4077dd818adbe09de1415c5e01bec Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Tue, 7 Aug 2018 05:02:13 +0200 Subject: [PATCH 001/178] DemoGuiRoundtrip: expand on that idea (#1099) and start analysis how to create that UI component The goal is to build a (in itself completely meaningless) ping-pong interaction between the UI and Proc-Layer, for the purpose of driving the integration ahead. The immediate challenge is how to create and place an apropriate "GuiComponentView", i.e. a Tangible, which is connected to the UI-Bus with an predictable EntryID. And the problem is to get that settled right now, without building the envisioned generic framework for View allocation in the UI. When this is achieved, it should be a rather small step to actually send those notifications over the UI-Bus, which is basically implemented and ready by now. --- src/gui/interact/interaction-director.cpp | 5 + src/gui/model/tangible.hpp | 2 +- src/gui/widget/error-log-widget.hpp | 2 +- wiki/renderengine.html | 24 +- wiki/thinkPad.ichthyo.mm | 1553 +++++++++++---------- 5 files changed, 852 insertions(+), 734 deletions(-) diff --git a/src/gui/interact/interaction-director.cpp b/src/gui/interact/interaction-director.cpp index 01e083c10..644fe1981 100644 --- a/src/gui/interact/interaction-director.cpp +++ b/src/gui/interact/interaction-director.cpp @@ -109,6 +109,11 @@ namespace interact { void InteractionDirector::populateContent_afterStart() { + ///////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1099 trigger creation of the UI notification display here + //// TODO : this clearly is a preliminary solution. Later it might be possible, + //// : to create the notification display on demand. But right now (8/2018) + //// : we need a GuiComponentView with predictable ID instructed to exist "somewhere" + //// invoke (cmd::test_meta_activateContentDiff); ////////////////////////////////////////////TICKET #211 prefix test_ bypasses logging and immediately executes for now (2018) } diff --git a/src/gui/model/tangible.hpp b/src/gui/model/tangible.hpp index fcaf2f1f9..393ba06b7 100644 --- a/src/gui/model/tangible.hpp +++ b/src/gui/model/tangible.hpp @@ -222,7 +222,7 @@ 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 diff --git a/src/gui/widget/error-log-widget.hpp b/src/gui/widget/error-log-widget.hpp index 4533e8add..4eebcfe7b 100644 --- a/src/gui/widget/error-log-widget.hpp +++ b/src/gui/widget/error-log-widget.hpp @@ -22,7 +22,7 @@ /** @file error-log-widget.hpp - ** Widget to display error messages to the use in non-modal way. + ** 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. diff --git a/wiki/renderengine.html b/wiki/renderengine.html index 44dd1a5e5..7d269fbe5 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -2906,14 +2906,14 @@ Command instances are like prototypes -- thus each additional level of different see the description in → CommandSetup -
+
//A view within the UI, featuring some component of relevance to »the model«.//
 While any UI is comprised of numerous widgets acting as //view of something,// only some of those views play the prominent role to act as //building block component// of the user interface.
 Such UI component views exhibit some substantial traits
 * they conform to a built-in fixed list of view types, each of which is unique and dedicated to a very specific purpose: //''Timeline'', ''Viewer'', (Asset)''Bin'', ''Infobox'', ''Playcontrol'',...//
 * each component view has a distinguishable identity and is connected to and addressable through the UI-Bus
 * it can be hosted only at a dedicated location within one or several specific [[docking panels|GuiDockingPanel]].
-* multiplicity (one, one per window, many) depends on the type of view and needs to be managed.
+* multiplicity (only one, one per window, many) depends on the type of view and needs to be managed.
 * such a view is not just //created// -- rather it needs to be //allocated//
 
 !Allocation of UI component views
@@ -3186,7 +3186,7 @@ In order to build a sensible plan for our timeline structure, we need to investi
 }}}
 
-
+
//Management of dockable content panes//
 Within each top level application window, the usable screen real estate can be split and arranged into a number of standard view building blocks. Each of this //panels// follows a specific preconfigured basic layout -- these are hard coded, yet customisable in detail. There is a finite list of such panel types available:
 * the [[timeline display|GuiTimelineView]]
@@ -3197,6 +3197,20 @@ Within the preconfigured layout it is a panel's role to host and incorporate Gui
 Please note the distinction between UI component view and panel; in many cases there is one of each for a given kind, and these need to be distinguished: e.g. there is the [[timeline view|GuiTimelineView]] and there is the timeline panel, which houses several timelines (as tabs). Or there is the viewer component, which is located within a dedicated viewer panel each.
 
 !Instantiation
+A panel as such is //an identifiable, named, delineated space within the UI.//
+* since it is just a space, instantiation of any panel //implies the creation of its content// -- at least in minimal or default form.
+* the type of the panel determines, what the possible content of such a panel might be, and what need to be created to allow the panel to exist.
+Consequently, there are two distinct ways to instantiate a panel
+;by view allocation
+:some process or action requires a specific GuiComponentView, and requests access or allocation through the ViewLocator.
+:in this case, the panel holding this view is created on demand as a by-product
+;by explicit setup
+:initiated through user interaction, or caused by default wiring, a certain panel is made to exist somewhere in the UI
+:in this case, the type of the panel determines what content view(s) need to be present to allow the panel to exist
+
+!!!requirements and consequences for panel instantiation
+Based on these considerations, and based on the fact that any GuiComponentView needs an ID and an UI-Bus connection, we can conclude that the actual panel creation need to be carried out by some core component directly related to the GuiTopLevel (to get UI-Bus access). And we need to ensure that the ID of any created or allocated view is //predictable,// since other parts need to be able to talk to this component via the bus, without any direct way to discover and retrieve the ID beforehand.
+
 !Placing and addressing of embedded contents
 A specific problem arises insofar other parts of the UI need to create, address and control some UI entities, which at the same time exist as part of a docking panel. This is a problem of crosscutting concerns: UI control and interaction structure should not be mingled with the generic concern to maintain a component as part of a screen layout. Unfortunately, the design of standard UI toolkit sets is incredibly naive when it comes to interaction design, and the only available alternative is to rely on frameworks, which come with a hard-wired philosophy.
 As a result, we're forced to build our UI from components which fall short on the distinction between //ownership and control.//
@@ -3346,9 +3360,9 @@ The most fundamental principle is that of ''subsidiarity'': we understand both &
 Based on these foundations, we shape and form the core part of the interface, which is the [[timeline display|GuiTimelineView]]
 
-
+
Starting up the GUI is optional and is considered part of the Application start/stop and lifecycle.
-* main and AppState activate the lifecyle methods on the ~GuiSubsysDescriptor, accessible via the GuiFacade
+* main and {{{lumiera::AppState}}} activate the lifecyle methods on the ~GuiSubsysDescriptor, accessible via the GuiFacade
 * loading a GuiStarterPlugin actually
 ** loads the GUI (shared lib)
 ** creates instances of all the GUI services available through LayerSeparationInterfaces
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm
index 2b3483a50..59bc81953 100644
--- a/wiki/thinkPad.ichthyo.mm
+++ b/wiki/thinkPad.ichthyo.mm
@@ -345,7 +345,7 @@
 
 
 
-
+
 
 
 
@@ -357,11 +357,11 @@
 
 
 
-
+
 
 
 
-
+
 
 
 
@@ -371,13 +371,13 @@
 
 
 
-
+
 
 
 
 
 
-
+
 
 
 
@@ -415,7 +415,7 @@
   
 
 
-
+
 
 
 
@@ -448,7 +448,7 @@
 
 
 
-
+
 
 
 
@@ -483,7 +483,7 @@
   
 
 
-
+
 
 
 
@@ -492,7 +492,7 @@
 
 
 
-
+
 
 
 
@@ -500,7 +500,7 @@
 
 
 
-
+
 
   
     
@@ -703,7 +703,7 @@
 
 
 
-
+
 
 
 
@@ -713,7 +713,7 @@
 
 
 
-
+
 
 
 
@@ -890,10 +890,10 @@
 
 
 
-
+
 
 
-
+
 
 
 
@@ -1019,19 +1019,19 @@
 
 
 
-
+
 
 
-
-
+
+
 
 
 
 
 
-
+
 
-
+
 
 
 
@@ -1048,7 +1048,7 @@
 
 
 
-
+
 
 
 
@@ -1074,12 +1074,12 @@
 
 
 
-
+
 
 
 
 
-
+
 
 
 
@@ -1193,12 +1193,12 @@
 
 
 
-
+
 
 
 
 
-
+
 
 
 
@@ -1280,7 +1280,7 @@
   
 
 
-
+
 
 
 
@@ -1290,7 +1290,7 @@
 
 
 
-
+
 
 
 
@@ -1301,7 +1301,7 @@
 
 
 
-
+
 
 
 
@@ -1341,7 +1341,7 @@
 
 
 
-
+
 
 
 
@@ -1356,7 +1356,7 @@
 
 
 
-
+
 
 
 
@@ -1376,9 +1376,9 @@
   
 
 
-
+
 
-
+
 
 
 
@@ -1411,14 +1411,14 @@
 
 
 
-
+
 
 
 
 
 
 
-
+
 
 
 
@@ -1433,7 +1433,7 @@
 
 
 
-
+
 
 
 
@@ -1445,18 +1445,29 @@
 
 
 
-
+
 
+
 
 
-
+
+
+
+
+
+
+
+
 
 
-
-
+
+
+
+
+
 
 
-
+
 
 
 
@@ -1470,8 +1481,43 @@
   
 
 
+
+
+
+
 
-
+
+
+
+
+
+  
+    
+  
+  
+    

+ es ist schon bekannt, daß dieser Service der ViewLocator sein wird, +

+

+ und daß man ihn via InteractionDirector erreicht +

+ + +
+
+ + + + + + + + + + + + + @@ -1505,7 +1551,9 @@ - + + + @@ -1565,7 +1613,7 @@ - + @@ -1659,7 +1707,7 @@
- + @@ -1674,11 +1722,11 @@ - + - + @@ -1749,21 +1797,21 @@ - + - + - + - + @@ -1774,16 +1822,16 @@ - + - + - + @@ -1805,7 +1853,7 @@ - + @@ -1835,7 +1883,7 @@ - + @@ -1848,12 +1896,12 @@ - + - + @@ -2452,7 +2500,7 @@ - + @@ -3778,7 +3826,7 @@ - + @@ -3786,7 +3834,7 @@ - + @@ -3835,7 +3883,7 @@ - + @@ -4045,7 +4093,7 @@ - + @@ -4064,21 +4112,21 @@ - + - + - + - + @@ -4106,8 +4154,8 @@ - - + + @@ -4125,15 +4173,15 @@ - + - + - + @@ -4148,12 +4196,12 @@ - + - + @@ -4161,13 +4209,13 @@ - + - + @@ -4176,7 +4224,7 @@ - + @@ -4196,11 +4244,11 @@ - + - + @@ -4209,7 +4257,7 @@ - + @@ -4217,7 +4265,7 @@ - + @@ -4270,14 +4318,14 @@ - + - + - + @@ -4286,26 +4334,26 @@ - + - + - + - + - + @@ -4443,13 +4491,13 @@ - + - + @@ -4511,7 +4559,7 @@ - + @@ -4554,11 +4602,11 @@ - + - + @@ -4573,7 +4621,7 @@ - + @@ -4702,7 +4750,7 @@ - + @@ -4792,7 +4840,7 @@ - + @@ -4800,13 +4848,13 @@ - + - + @@ -4823,26 +4871,26 @@ - + - + - + - + - + - + @@ -4850,32 +4898,32 @@ - + - + - + - + - + - + - + @@ -4909,11 +4957,11 @@ - + - + @@ -4973,7 +5021,7 @@ - + @@ -4988,7 +5036,7 @@ - + @@ -5002,7 +5050,7 @@ - + @@ -5385,9 +5433,9 @@ - + - + @@ -5396,7 +5444,7 @@ - + @@ -5444,7 +5492,7 @@ - + @@ -7065,7 +7113,7 @@ - + @@ -7919,7 +7967,7 @@ - + @@ -7930,16 +7978,16 @@ - + - + - + @@ -7955,7 +8003,7 @@ - + @@ -8251,7 +8299,7 @@ - + @@ -8335,9 +8383,9 @@ - + - + @@ -8420,7 +8468,7 @@ - + @@ -8429,7 +8477,7 @@ - + @@ -8475,7 +8523,7 @@ - + @@ -8538,13 +8586,13 @@ - + - + @@ -8557,10 +8605,10 @@ - + - + @@ -8582,21 +8630,21 @@ - + - + - + - + - + @@ -8639,7 +8687,7 @@ - + @@ -8654,7 +8702,7 @@ - + @@ -8685,7 +8733,7 @@ - + @@ -8734,7 +8782,7 @@ - + @@ -8742,7 +8790,7 @@ - + @@ -8762,13 +8810,13 @@ - + - + @@ -8805,7 +8853,7 @@ - + @@ -8818,7 +8866,7 @@ - + @@ -8826,7 +8874,7 @@ - + @@ -8840,7 +8888,7 @@ - + @@ -8849,14 +8897,14 @@ - + - + - + @@ -8870,7 +8918,7 @@ - + @@ -8885,7 +8933,7 @@ - + @@ -8911,7 +8959,7 @@ - + @@ -8930,7 +8978,7 @@ - + @@ -8946,7 +8994,7 @@ - + @@ -8972,7 +9020,7 @@ - + @@ -8985,7 +9033,7 @@ - + @@ -9011,11 +9059,11 @@ - + - + @@ -9024,7 +9072,7 @@ - + @@ -9033,7 +9081,7 @@ - + @@ -9076,7 +9124,7 @@ - + @@ -9117,7 +9165,7 @@ - + @@ -9135,7 +9183,7 @@ - + @@ -9159,7 +9207,7 @@ - + @@ -9208,7 +9256,7 @@ - + @@ -9308,7 +9356,7 @@ - + @@ -9325,9 +9373,9 @@ - + - + @@ -9493,31 +9541,31 @@ - + - + - + - + - + @@ -9538,27 +9586,27 @@ - + - + - + - - + + - + - + @@ -9589,14 +9637,14 @@ - + - + @@ -9637,18 +9685,18 @@ - + - + - + - + @@ -9708,9 +9756,9 @@ - + - + @@ -9718,7 +9766,7 @@ - + @@ -9743,7 +9791,7 @@ - + @@ -9880,13 +9928,13 @@ - + - + @@ -9983,7 +10031,7 @@ - + @@ -9993,7 +10041,7 @@ - + @@ -10025,17 +10073,17 @@ - + - - + + - + @@ -10067,8 +10115,8 @@ - - + + @@ -10095,7 +10143,7 @@ - + @@ -10109,9 +10157,9 @@ - + - + @@ -10119,7 +10167,7 @@ - + @@ -10131,9 +10179,9 @@ - + - + @@ -10173,7 +10221,7 @@ - + @@ -10193,7 +10241,7 @@ - + @@ -10204,7 +10252,7 @@ - + @@ -10220,14 +10268,14 @@ - + - - + + @@ -10235,7 +10283,7 @@ - + @@ -10299,7 +10347,7 @@ - + @@ -10339,11 +10387,11 @@ - - + + - + @@ -10351,10 +10399,10 @@ - + - + @@ -10375,12 +10423,12 @@ - + - + @@ -10390,13 +10438,13 @@ - + - + - + - + @@ -10412,7 +10460,7 @@ - + @@ -10429,7 +10477,7 @@ - + @@ -10614,7 +10662,7 @@ - + @@ -10653,9 +10701,9 @@ - + - + @@ -10682,7 +10730,7 @@ - + @@ -10691,7 +10739,7 @@ - + @@ -10711,7 +10759,7 @@ - + @@ -10734,7 +10782,7 @@ - + @@ -10743,12 +10791,12 @@ - + - + @@ -10761,7 +10809,7 @@ - + @@ -10771,7 +10819,7 @@ - + @@ -10808,7 +10856,7 @@ - + @@ -10875,7 +10923,7 @@ - + @@ -10965,7 +11013,7 @@ - + @@ -11017,7 +11065,7 @@ - + @@ -11056,13 +11104,13 @@ - + - + @@ -11071,14 +11119,14 @@ - + - + @@ -11088,7 +11136,7 @@ - + @@ -11127,15 +11175,15 @@ - + - + - + - + @@ -11155,27 +11203,27 @@ - + - + - + - + - + @@ -11239,7 +11287,7 @@ - + @@ -11247,13 +11295,13 @@ - + - + @@ -11285,7 +11333,7 @@ - + @@ -11313,7 +11361,7 @@ - + @@ -11322,15 +11370,15 @@ - + - + - + @@ -11359,16 +11407,16 @@ - + - + - + - + @@ -11416,10 +11464,10 @@ - + - + @@ -11462,7 +11510,7 @@ - + @@ -11480,7 +11528,7 @@ - + @@ -11488,7 +11536,7 @@ - + @@ -11543,15 +11591,15 @@ - + - + - + @@ -11559,7 +11607,7 @@ - + @@ -11571,9 +11619,9 @@ - + - + @@ -11611,7 +11659,7 @@ - + @@ -11708,7 +11756,7 @@ - + @@ -11782,7 +11830,7 @@ - + @@ -11868,7 +11916,7 @@ - + @@ -11934,12 +11982,12 @@ - + - - + + @@ -11962,7 +12010,7 @@ - + @@ -11971,7 +12019,7 @@ - + @@ -12033,7 +12081,7 @@ - + @@ -12068,17 +12116,17 @@ - + - + - + @@ -12095,17 +12143,17 @@ - + - + - + @@ -12130,7 +12178,7 @@ - + @@ -12165,7 +12213,7 @@ - + @@ -12250,7 +12298,7 @@ - + @@ -12271,7 +12319,7 @@ - + @@ -12307,7 +12355,7 @@ - + @@ -12316,7 +12364,7 @@ - + @@ -12375,14 +12423,14 @@ - + - + @@ -12614,7 +12662,7 @@ - + @@ -12649,7 +12697,7 @@ - + @@ -12697,15 +12745,15 @@ - + - + - + @@ -12717,7 +12765,7 @@ - + @@ -12726,7 +12774,7 @@ - + @@ -12748,19 +12796,19 @@ - + - + - + - + @@ -12770,16 +12818,16 @@ - + - + - + @@ -12790,7 +12838,7 @@ - + @@ -12809,7 +12857,7 @@ - + @@ -12852,7 +12900,7 @@ - + @@ -12870,7 +12918,7 @@ - + @@ -12922,7 +12970,7 @@ - + @@ -12957,7 +13005,7 @@ - + @@ -12978,7 +13026,7 @@ - + @@ -12986,10 +13034,10 @@ - + - + @@ -13012,15 +13060,15 @@ - + - + - + @@ -13029,10 +13077,10 @@ - + - + @@ -13054,15 +13102,15 @@ - + - + - + @@ -13071,7 +13119,7 @@ - + @@ -13353,11 +13401,11 @@ - + - + @@ -13585,10 +13633,21 @@ - + + + + + + + + + + + + @@ -13879,6 +13938,27 @@ + + + + + + + + + + + + + + + + + + + + + @@ -15907,27 +15987,42 @@ + + + + + + + + + + + - - - - + + - + + + + + + + @@ -15941,7 +16036,7 @@ - + @@ -16014,8 +16109,7 @@ - - + @@ -16113,17 +16207,16 @@ - - + - + - + @@ -16134,11 +16227,10 @@ Main erbt nicht von Gio::Application

- -
+
- + @@ -16161,13 +16253,13 @@ - + - + @@ -16196,14 +16288,14 @@ - + - + @@ -16219,7 +16311,7 @@ - + @@ -16274,7 +16366,7 @@ - + @@ -16312,8 +16404,7 @@ - - + @@ -16337,7 +16428,7 @@ - + @@ -16352,7 +16443,7 @@ - + @@ -16363,7 +16454,7 @@ - + @@ -16384,7 +16475,7 @@ - + @@ -16437,7 +16528,7 @@ - + @@ -16452,7 +16543,7 @@ - + @@ -16491,7 +16582,7 @@ - + @@ -16602,6 +16693,23 @@ + + + + + + + + + + + + + + + + + @@ -17431,7 +17539,7 @@ - + @@ -17457,7 +17565,7 @@ - + @@ -17480,7 +17588,7 @@ - + @@ -17559,12 +17667,12 @@ - + - + @@ -17589,7 +17697,7 @@ - + @@ -17681,7 +17789,7 @@ - + @@ -18304,13 +18412,13 @@ - + - + - + @@ -18398,7 +18506,7 @@ - + @@ -18430,17 +18538,17 @@ - + - + - + - + @@ -18457,8 +18565,8 @@ - - + + @@ -18550,12 +18658,12 @@ - - + + - + @@ -18928,7 +19036,7 @@ - + @@ -18942,7 +19050,7 @@ - + @@ -18969,7 +19077,7 @@ - + @@ -19039,7 +19147,7 @@ - + @@ -19060,7 +19168,7 @@ - + @@ -19238,7 +19346,7 @@ - + @@ -19259,7 +19367,7 @@ - + @@ -19290,7 +19398,7 @@ - + @@ -19298,22 +19406,22 @@ - + - + - + - + @@ -19321,11 +19429,11 @@ - + - + @@ -19340,12 +19448,12 @@ - + - + @@ -19362,7 +19470,7 @@ - + @@ -19471,7 +19579,7 @@ - + @@ -19486,7 +19594,7 @@ - + @@ -19721,7 +19829,7 @@ - + @@ -19733,7 +19841,7 @@ - + @@ -19760,7 +19868,7 @@ - + @@ -19783,7 +19891,7 @@ - + @@ -19838,7 +19946,7 @@ - + @@ -19872,7 +19980,7 @@

- + @@ -19891,8 +19999,8 @@ - - + + @@ -19926,17 +20034,17 @@ - + - + - + @@ -19996,7 +20104,7 @@ - + @@ -20012,7 +20120,7 @@ - + @@ -20043,7 +20151,7 @@

- + @@ -20067,7 +20175,7 @@ - + @@ -20078,11 +20186,11 @@ - + - + @@ -20090,14 +20198,14 @@ - + - + @@ -20108,7 +20216,7 @@ - + @@ -20143,19 +20251,19 @@ - + - + - + @@ -20172,7 +20280,7 @@ - + @@ -20189,7 +20297,7 @@ - + @@ -20212,25 +20320,25 @@

- + - + - + - + - + - + @@ -20238,18 +20346,18 @@ - + - + - + @@ -20270,21 +20378,21 @@ - + - + - + @@ -20337,10 +20445,10 @@ - + - + @@ -20389,22 +20497,22 @@ - + - + - + - + @@ -20417,9 +20525,9 @@ - + - + @@ -20488,7 +20596,7 @@ - + @@ -20503,14 +20611,14 @@ - + - + @@ -20530,7 +20638,7 @@ - + @@ -20542,7 +20650,7 @@ - + @@ -20589,7 +20697,7 @@ - + @@ -20610,7 +20718,7 @@ - + @@ -20628,13 +20736,13 @@ - - + + - + @@ -20687,7 +20795,7 @@ - + @@ -20732,9 +20840,9 @@ - + - + @@ -20756,14 +20864,14 @@ - + - + @@ -20858,7 +20966,7 @@ - + @@ -20946,7 +21054,7 @@ - + @@ -20972,7 +21080,7 @@ - + @@ -21091,22 +21199,22 @@ - - + + - + - + - + - - + + @@ -21124,7 +21232,7 @@ - + @@ -21202,9 +21310,9 @@ - + - + @@ -21244,7 +21352,7 @@ - + @@ -21252,7 +21360,7 @@ - + @@ -21321,7 +21429,7 @@ - + @@ -21487,11 +21595,11 @@ - + - + @@ -21499,9 +21607,9 @@ - + - + @@ -21513,14 +21621,14 @@ - + - + - + @@ -21583,9 +21691,9 @@ - + - + @@ -21608,7 +21716,7 @@ - + @@ -21626,13 +21734,13 @@ - + - + @@ -21679,7 +21787,7 @@ - + @@ -21726,7 +21834,7 @@ - + @@ -21738,24 +21846,24 @@ - + - + - + - + - + @@ -21796,7 +21904,7 @@ - + @@ -21947,7 +22055,7 @@ - + @@ -22472,7 +22580,7 @@ - + @@ -22483,14 +22591,14 @@ - + - + @@ -22507,11 +22615,11 @@ - + - + @@ -22588,16 +22696,16 @@ - + - + - + - + @@ -22615,7 +22723,7 @@ - + @@ -22653,7 +22761,7 @@ - + @@ -22696,7 +22804,7 @@ - + @@ -24646,13 +24754,13 @@ - + - + @@ -24823,12 +24931,12 @@ - + - + @@ -24861,11 +24969,11 @@ - + - + @@ -25000,7 +25108,7 @@ - + @@ -25008,7 +25116,7 @@ - + @@ -25290,11 +25398,11 @@ - + - + @@ -25306,11 +25414,11 @@ - + - + @@ -25324,14 +25432,14 @@ - + - + @@ -25394,13 +25502,13 @@ - + - + @@ -25659,12 +25767,12 @@ - + - + @@ -25844,7 +25952,7 @@ - + @@ -25853,7 +25961,7 @@ - + @@ -25912,7 +26020,7 @@ - + @@ -25921,7 +26029,7 @@ - + @@ -25966,7 +26074,7 @@ - + @@ -26385,15 +26493,15 @@ - + - + - + @@ -26487,12 +26595,12 @@ - + - + @@ -26539,7 +26647,7 @@ - + @@ -26841,14 +26949,14 @@ - + - + @@ -26888,7 +26996,7 @@ - + @@ -26958,7 +27066,7 @@ - + @@ -27424,8 +27532,8 @@ - - + + @@ -27480,7 +27588,7 @@ - + @@ -27514,11 +27622,11 @@ - - + + - + @@ -27572,11 +27680,11 @@ - + - + @@ -28844,7 +28952,7 @@ - + @@ -28872,7 +28980,7 @@ - + @@ -28903,12 +29011,12 @@ - + - + @@ -28918,7 +29026,7 @@ - + @@ -29123,9 +29231,9 @@ - + - + @@ -29146,7 +29254,7 @@ - + @@ -29176,7 +29284,7 @@ - + @@ -29196,7 +29304,7 @@ - + @@ -29210,7 +29318,7 @@ - + @@ -29222,12 +29330,12 @@ - + - + @@ -29237,15 +29345,15 @@ - + - + - + @@ -29263,20 +29371,20 @@ - - + + - - + + - + @@ -29286,7 +29394,7 @@ - + @@ -29294,11 +29402,11 @@ - + - + @@ -29315,7 +29423,7 @@ - + @@ -29352,20 +29460,20 @@ - - - + + + - + - + @@ -29374,27 +29482,27 @@ - + - + - + - + - + @@ -29407,7 +29515,7 @@ - + @@ -29422,7 +29530,7 @@ - + @@ -29453,11 +29561,11 @@ - + - - + + @@ -29481,12 +29589,12 @@ - + - + @@ -29494,7 +29602,7 @@ - + @@ -29546,7 +29654,7 @@ - + @@ -29554,14 +29662,14 @@ - + - + @@ -29588,7 +29696,7 @@ - + @@ -29637,7 +29745,7 @@ - + @@ -29647,14 +29755,14 @@ - + - + @@ -29695,9 +29803,9 @@ - + - + @@ -29724,10 +29832,10 @@ - + - + @@ -29765,7 +29873,7 @@ - + @@ -29793,8 +29901,8 @@ - - + + @@ -29841,9 +29949,9 @@ - + - + @@ -29929,8 +30037,8 @@ - - + + @@ -29940,7 +30048,7 @@ - + @@ -29957,7 +30065,7 @@ - + @@ -29977,27 +30085,27 @@ - + - + - + - - + + - + @@ -30024,9 +30132,9 @@ - + - + @@ -30047,9 +30155,9 @@ - + - + @@ -30060,7 +30168,7 @@ - + @@ -30080,7 +30188,7 @@ - + @@ -30125,13 +30233,13 @@ - + - + @@ -30139,9 +30247,9 @@ - + - + @@ -30157,10 +30265,10 @@ - + - - + + @@ -30171,7 +30279,7 @@ - + @@ -30179,7 +30287,7 @@ - + @@ -30191,7 +30299,7 @@ - + @@ -30210,7 +30318,7 @@ - + @@ -30266,7 +30374,7 @@ - + @@ -30291,7 +30399,7 @@ - + @@ -30335,7 +30443,7 @@ - + @@ -30347,7 +30455,7 @@ - + @@ -30431,7 +30539,7 @@ - + @@ -30439,7 +30547,7 @@ - + @@ -30551,7 +30659,7 @@ - + @@ -30575,7 +30683,7 @@ - + @@ -30584,7 +30692,7 @@ - + @@ -30616,7 +30724,7 @@ - + @@ -30630,7 +30738,7 @@ - + @@ -30639,11 +30747,11 @@ - + - + @@ -30661,20 +30769,20 @@ - + - + - + - + @@ -30685,7 +30793,7 @@ - + @@ -30694,19 +30802,19 @@ - + - + - + @@ -31002,10 +31110,10 @@ - + - + @@ -31052,9 +31160,9 @@ - + - + @@ -31069,7 +31177,7 @@ - + @@ -31084,7 +31192,7 @@ - + @@ -31108,14 +31216,14 @@ - - - + + + - + @@ -31140,13 +31248,13 @@ - + - + @@ -31154,7 +31262,7 @@ - + @@ -31231,15 +31339,15 @@ - + - + - + @@ -31249,7 +31357,7 @@ - + @@ -31258,14 +31366,14 @@ - + - - + + @@ -31282,10 +31390,10 @@ - + - + @@ -31296,7 +31404,7 @@ - + @@ -31396,7 +31504,7 @@ - + @@ -32449,10 +32557,10 @@ - - - + + + @@ -32471,8 +32579,7 @@ ...und erzeugt diesen on demand auch neu

- -
+
@@ -32488,7 +32595,7 @@
- + @@ -32529,7 +32636,7 @@ - + @@ -32551,8 +32658,7 @@ welche ggfs C++ - Exceptions fängt

- -
+
@@ -32584,8 +32690,7 @@ Nur das ist der Grund. Es geht gar nicht um die Event-Loop

- -
+
@@ -32624,8 +32729,7 @@ nicht durch gtk_main

- - + @@ -32643,13 +32747,12 @@ - - +
- + @@ -32667,8 +32770,7 @@ beachte: ruft nicht gtk_main sondern macht das Äquivalent

- - +
@@ -32688,7 +32790,7 @@ - + @@ -32705,7 +32807,7 @@ - + @@ -32766,8 +32868,7 @@ }

- -
+ @@ -32781,7 +32882,7 @@ - + @@ -32848,8 +32949,7 @@ }

- -
+ @@ -32888,7 +32988,7 @@
- + @@ -32922,8 +33022,7 @@ aber macht in etwa die gleichen Operationen

- - +
@@ -33201,7 +33300,7 @@ - + @@ -33219,7 +33318,7 @@ - + @@ -33247,11 +33346,11 @@ - + - + @@ -33260,10 +33359,10 @@ - + - + From 0c8151cb2f6d26b1920011298d01525a50549377 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 9 Aug 2018 01:23:20 +0200 Subject: [PATCH 002/178] NotificationDisplay: decide upon the architecture for handling notification messages (#1102) * have a dedicated "information hub" controller, which acts a receiver of "error log messages" on the UI-Bus * let that controller in turn allocate an apropriate view on demand --- src/gui/ctrl/notification-hub.hpp | 81 ++++++++++ src/gui/interact/wizard.cpp | 1 + src/gui/interact/wizard.hpp | 6 +- wiki/thinkPad.ichthyo.mm | 259 +++++++++++++++++++++++------- 4 files changed, 288 insertions(+), 59 deletions(-) create mode 100644 src/gui/ctrl/notification-hub.hpp diff --git a/src/gui/ctrl/notification-hub.hpp b/src/gui/ctrl/notification-hub.hpp new file mode 100644 index 000000000..0676f742e --- /dev/null +++ b/src/gui/ctrl/notification-hub.hpp @@ -0,0 +1,81 @@ +/* + NOTIFICATION-HUB.hpp - receive and reroute notification messages + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + 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 ErrorlogWidget for presentation + ** of those messages + ** + ** @see error-log-widget.hpp + ** @see notification-service.hpp + */ + + +#ifndef GUI_CTRL_NOTIFICATION_HUB_H +#define GUI_CTRL_NOTIFICATION_HUB_H + +#include "gui/gtk-base.hpp" +#include "lib/nocopy.hpp" + +#include +#include + + +namespace gui { +namespace workspace { class WorkspaceWindow; } +namespace ctrl { + + + using std::list; + + + + /** + * 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 altert the + * user, while not blocking the overall UI. + */ + class NotificationHub + : util::NonCopyable + { + + + public: + NotificationHub (); + + private: + + }; + + + + /** */ + + + +}}// namespace gui::ctrl +#endif /*GUI_CTRL_NOTIFICATION_HUB_H*/ diff --git a/src/gui/interact/wizard.cpp b/src/gui/interact/wizard.cpp index 8585092fa..7996eb744 100644 --- a/src/gui/interact/wizard.cpp +++ b/src/gui/interact/wizard.cpp @@ -33,6 +33,7 @@ #include "gui/interact/wizard.hpp" #include "gui/interact/spot-locator.hpp" #include "gui/workspace/workspace-window.hpp" +#include "gui/ctrl/notification-hub.hpp" #include "gui/ctrl/global-ctx.hpp" #include "lib/format-string.hpp" //#include "lib/util.hpp" diff --git a/src/gui/interact/wizard.hpp b/src/gui/interact/wizard.hpp index 0d541616b..2d12f9733 100644 --- a/src/gui/interact/wizard.hpp +++ b/src/gui/interact/wizard.hpp @@ -46,16 +46,17 @@ #include "lib/nocopy.hpp" //#include -//#include +#include namespace gui { namespace ctrl { class GlobalCtx; + class NotificationHub; } namespace interact { -// using std::unique_ptr; + using std::unique_ptr; // using std::string; // class GlobalCtx; @@ -73,6 +74,7 @@ namespace interact { : util::NonCopyable { ctrl::GlobalCtx& globalCtx_; + unique_ptr notificationHub_; public: Wizard (ctrl::GlobalCtx&); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 59bc81953..94882dfe8 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -949,6 +949,12 @@ + + + + + + @@ -1463,6 +1469,27 @@ + + + + + + + +

+ das ist schon die endgültige Lösung +

+ + +
+ +
+ + + + + + @@ -1480,6 +1507,13 @@

+ + + + + + + @@ -1502,8 +1536,7 @@ und daß man ihn via InteractionDirector erreicht

- - +
@@ -1560,6 +1593,87 @@
+ + + + + + + + + + + + + + + + + +

+ weil so sichergestellt ist, daß er stets existiert, +

+

+ und er trotzdem vom konkreten Widget entkoppelt bleibt +

+ + +
+
+ + + + + + +

+ besserer Name: NotificationHub +

+ + +
+
+
+ + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
@@ -3666,6 +3780,7 @@ + @@ -4303,7 +4418,7 @@ - + @@ -4463,7 +4578,7 @@ - + @@ -4476,7 +4591,7 @@ - + @@ -4597,7 +4712,7 @@ - + @@ -4724,7 +4839,7 @@ - + @@ -4809,7 +4924,7 @@ - + @@ -4832,7 +4947,7 @@ - + @@ -9203,7 +9318,7 @@ - + @@ -9234,7 +9349,7 @@ - + @@ -9248,7 +9363,7 @@ - + @@ -9375,7 +9490,7 @@ - + @@ -9536,7 +9651,7 @@ - + @@ -10239,7 +10354,7 @@ - + @@ -11275,7 +11390,7 @@ - + @@ -11368,7 +11483,7 @@ - + @@ -11378,7 +11493,7 @@ - + @@ -11428,7 +11543,7 @@ - + @@ -11900,7 +12015,7 @@ - + @@ -12234,7 +12349,7 @@ - + @@ -12620,7 +12735,7 @@ - + @@ -12850,7 +12965,7 @@ - + @@ -12896,7 +13011,7 @@ - + @@ -13613,7 +13728,8 @@ - + + @@ -13939,7 +14055,8 @@ - + + @@ -14117,6 +14234,7 @@ + @@ -14209,6 +14327,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -16941,7 +17086,7 @@ - + @@ -17365,7 +17510,7 @@ - + @@ -17385,7 +17530,7 @@ - + @@ -17411,7 +17556,7 @@ - + @@ -17479,7 +17624,7 @@ - + @@ -17535,7 +17680,7 @@ - + @@ -17610,7 +17755,7 @@ - + @@ -17639,7 +17784,7 @@ - + @@ -17710,7 +17855,7 @@ - + @@ -17798,7 +17943,7 @@ - + @@ -18195,7 +18340,7 @@ - + @@ -18213,7 +18358,7 @@ - + @@ -18340,7 +18485,7 @@ - + @@ -18757,7 +18902,7 @@ - + @@ -18982,7 +19127,7 @@ - + @@ -19023,7 +19168,7 @@ - + @@ -19270,7 +19415,7 @@ - + @@ -19316,7 +19461,7 @@ - + @@ -19353,7 +19498,7 @@ - + @@ -19529,7 +19674,7 @@ - + @@ -19747,7 +19892,7 @@ - + @@ -19806,7 +19951,7 @@ - + @@ -20442,8 +20587,8 @@ - - + + @@ -20931,7 +21076,7 @@ - + @@ -21183,7 +21328,7 @@ - + @@ -21256,7 +21401,7 @@ - + @@ -21523,7 +21668,7 @@ - + @@ -21536,7 +21681,7 @@

- + @@ -21986,7 +22131,7 @@ - + @@ -22031,7 +22176,7 @@ - + From 12244afc900455d4fbb65c8dfad2fd518fe4cef1 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 9 Aug 2018 01:42:42 +0200 Subject: [PATCH 003/178] NotificationDisplay: fill in some default implementation for the controller --- src/gui/ctrl/notification-hub.hpp | 36 +++++++++++++++++++++++-------- src/gui/model/controller.hpp | 2 ++ wiki/thinkPad.ichthyo.mm | 9 ++++++-- 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/src/gui/ctrl/notification-hub.hpp b/src/gui/ctrl/notification-hub.hpp index 0676f742e..7b58221d5 100644 --- a/src/gui/ctrl/notification-hub.hpp +++ b/src/gui/ctrl/notification-hub.hpp @@ -37,35 +37,53 @@ #ifndef GUI_CTRL_NOTIFICATION_HUB_H #define GUI_CTRL_NOTIFICATION_HUB_H -#include "gui/gtk-base.hpp" -#include "lib/nocopy.hpp" +#include "gui/widget/error-log-widget.hpp" +#include "gui/model/controller.hpp" +#include "lib/diff/tree-mutator.hpp" -#include -#include +//#include +//#include namespace gui { -namespace workspace { class WorkspaceWindow; } namespace ctrl { + using lib::diff::TreeMutator; - using std::list; /** * 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 altert the + * such a message, this controller ensures to display the message and alert the * user, while not blocking the overall UI. */ class NotificationHub - : util::NonCopyable + : public model::Controller { + /** content population and manipulation via UI-Bus */ + void + buildMutator (lib::diff::TreeMutator::Handle buffer) override + { + // using Attrib = std::pair; + // using lib::diff::collection; + + buffer.create ( + TreeMutator::build() + ); + UNIMPLEMENTED ("create a sensible binding between AssetManager in the section and AssetController in the UI"); + } + + public: public: - NotificationHub (); + NotificationHub (ID identity, ctrl::BusTerm& nexus)////////////////////////////////////TODO #1099 : define the ID hard-wired at an appropriate location, so it can be used from the NotificationService impl as well + : model::Controller{identity, nexus} + { } + + ~NotificationHub() { }; private: diff --git a/src/gui/model/controller.hpp b/src/gui/model/controller.hpp index 8ebdfea29..e9accebb8 100644 --- a/src/gui/model/controller.hpp +++ b/src/gui/model/controller.hpp @@ -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 diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 94882dfe8..93b63d0a7 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -1490,6 +1490,9 @@ + + + @@ -1620,7 +1623,7 @@ - + @@ -1632,9 +1635,11 @@ + - + + From 5475839a49d9b0f8fd8a21c0be8f8ea7f64a1720 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 10 Aug 2018 18:37:09 +0200 Subject: [PATCH 004/178] NotificationDisplay: the question where to define the entity-ID --- src/gui/ctrl/notification-hub.hpp | 2 +- src/gui/interact/wizard.hpp | 4 +- src/proc/asset/meta.cpp | 3 - src/proc/asset/meta.hpp | 21 ++--- src/proc/asset/unknown.cpp | 8 +- src/proc/asset/unknown.hpp | 4 +- wiki/renderengine.html | 8 +- wiki/thinkPad.ichthyo.mm | 133 +++++++++++++++++++++++++++--- 8 files changed, 147 insertions(+), 36 deletions(-) diff --git a/src/gui/ctrl/notification-hub.hpp b/src/gui/ctrl/notification-hub.hpp index 7b58221d5..1e447cc6f 100644 --- a/src/gui/ctrl/notification-hub.hpp +++ b/src/gui/ctrl/notification-hub.hpp @@ -73,7 +73,7 @@ namespace ctrl { buffer.create ( TreeMutator::build() ); - UNIMPLEMENTED ("create a sensible binding between AssetManager in the section and AssetController in the UI"); + UNIMPLEMENTED ("create a sensible binding between AssetManager in the session and AssetController in the UI"); } public: diff --git a/src/gui/interact/wizard.hpp b/src/gui/interact/wizard.hpp index 2d12f9733..381f1b40f 100644 --- a/src/gui/interact/wizard.hpp +++ b/src/gui/interact/wizard.hpp @@ -42,8 +42,8 @@ #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 #include @@ -62,6 +62,8 @@ namespace interact { // class GlobalCtx; // class SpotLocator; + extern lib::idi::EntryID theErrorLog_ID; + /** diff --git a/src/proc/asset/meta.cpp b/src/proc/asset/meta.cpp index 927ef2a1c..404eda461 100644 --- a/src/proc/asset/meta.cpp +++ b/src/proc/asset/meta.cpp @@ -96,9 +96,6 @@ namespace asset { /**************************************************/ #include "proc/asset/meta/time-grid.hpp" -//#include "proc/asset/procpatt.hpp" -//#include "proc/asset/timeline.hpp" -//#include "proc/asset/sequence.hpp" namespace proc { diff --git a/src/proc/asset/meta.hpp b/src/proc/asset/meta.hpp index f701a665f..4a736cf47 100644 --- a/src/proc/asset/meta.hpp +++ b/src/proc/asset/meta.hpp @@ -28,13 +28,14 @@ ** works with. Lumiera exposes this self-referential control and customisation ** aspects as a special kind of Asset. Examples being types, scales and quantisation ** grids, decision rules, control data stores (automation data), annotations attached - ** to labels, inventory entities etc. + ** to labels, inventory entities etc. ** - ** For the different Kinds of Assets, we use sub-interfaces inheriting + ** For the different _Kinds_ of Assets, we use sub-interfaces inheriting ** from the general Asset interface. To be able to get asset::Meta instances ** directly from the AssetManager, we define a specialisation of the Asset ID. ** - ** \par using meta assets + ** ## using meta assets + ** ** The usage pattern of asset::Meta entities differs from the other assets, ** insofar they aren't created as individual entries, rather added as part ** of a larger scale configuration activity, or they are derived from category. @@ -47,7 +48,7 @@ ** They are created from a descriptor, which stands for a category or sub-category ** and can be another already existing asset::Meta (inheriting from meta::Descriptor) ** Generally this yields a Builder object, which can be used for outfitting the new - ** or changed metadata entry, finally \em committing this builder to yield a new + ** or changed metadata entry, finally *committing* this builder to yield a new ** asset::Meta (which, in case of a mutation, might supersede an existing one). ** ** @ingroup asset @@ -97,9 +98,9 @@ namespace asset { * thus, on creation or when changing / superseding a * meta asset, the client gets a special builder instance, * which is a value object for configuring the specific details - * to set. When done, the client invokes a \c commit() function, + * to set. When done, the client invokes a `commit()` function, * which yields a smart-ptr to the new meta asset. - * Individual meta asset subclasses are bound to define a + * Individual meta asset subclasses are bound to define a * specialisation of this Builder template, which will then be * instantiated and provided by the MetaFactory. */ @@ -120,8 +121,8 @@ namespace asset { static MetaFactory create; /** @return ID of kind Meta */ - virtual const ID& getID() const - { + virtual const ID& getID() const + { return static_cast& > (Asset::getID()); } @@ -142,9 +143,9 @@ namespace asset { - /** + /** * Factory specialised for creating Metadata Asset objects. - */ + */ class MetaFactory : util::NonCopyable { diff --git a/src/proc/asset/unknown.cpp b/src/proc/asset/unknown.cpp index 7f6af5083..da79d941f 100644 --- a/src/proc/asset/unknown.cpp +++ b/src/proc/asset/unknown.cpp @@ -41,19 +41,19 @@ namespace asset { /** create a placeholder for a media with the given identity. * the denoted original media (identity) can be accessed later * on using the Unknown::getOrg() function. - * @note we don't depend on the refered media... + * @note we don't depend on the referred media... */ Unknown::Unknown (const Asset::Ident& idi, string name, Duration length) : Media (idi, name, length) { TODO ("implement some sensible behaviour for the »unknown media« placeholder..."); - } + } - /** using the information stored in this placeholder asset, + /** using the information stored in this placeholder asset, * try to access the "real" media it stands for. */ - Media::PMedia + Media::PMedia Unknown::getOrg() { UNIMPLEMENTED ("how to get at the original media from a »Unknown« placeholder"); diff --git a/src/proc/asset/unknown.hpp b/src/proc/asset/unknown.hpp index 9f588030d..4116ebfbb 100644 --- a/src/proc/asset/unknown.hpp +++ b/src/proc/asset/unknown.hpp @@ -48,8 +48,8 @@ namespace asset { /** * Placeholder Asset for unknown or unavailable media source. * @todo maybe do special handling of the media length, allowing - * it to support existing clips even if the media length - * is not known? + * it to support existing clips even if the media length + * is not known? */ class Unknown : public Media { diff --git a/wiki/renderengine.html b/wiki/renderengine.html index 7d269fbe5..ccaa9b485 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -860,7 +860,7 @@ Even if the low-level memory manager(s) may use raw storage, we require that the → see MemoryManagement
-
+
Asset management is a subsystem on its own. Assets are "things" that can be loaded into a session, like Media, Clips, Effects, Transitions. It is the "bookkeeping view", while the Objects in the Session relate to the "manipulation and process view". Some Assets can be //loaded// and a collection of Assets is saved with each Session. Besides, there is a collection of basic Assets always available by default.
 
 The Assets are important reference points holding the information needed to access external resources. For example, an Clip asset can reference a Media asset, which in turn holds the external filename from which to get the media stream. For Effects, the situation is similar. Assets thus serve two quite distinct purposes. One is to load, list, group search and browse them, and to provide an entry point to create new or get at existing MObject in the Session, while the other purpose is to provide attribute and property information to the inner parts of the engine, while at the same time isolating and decoupling them from environmental details. 
@@ -888,7 +888,7 @@ Some of the building blocks providing the framework for the objects placed into
 → StructAsset {{red{still a bit vague...}}}
 
 !Meta Asset
-Any resources related to the //reflective recurse of the application on itself,// including parametrisation and customisation aspects and similar metadata, are categorised and tracked apart of the primary entities. Examples being types, scales and quantisation grids, decision rules, control data stores (automation data), annotations attached to labels, inventory entities etc.
+Any resources related to the //reflective recurse of the application on itself,// including parametrisation and customisation aspects and similar metadata, are categorised and tracked apart of the primary entities. Examples being types, scales and quantisation grids, decision rules, control data stores (automation data), annotations attached to labels, inventory entities, error items etc.
 * __outward interface operations__ include...
 * __inward interface operations__ include...
 → MetaAsset {{red{just emerging as of 12/2010}}}
@@ -4210,8 +4210,8 @@ For the case here in question this seems to be the __R__esource __A__llocation _
 And, last but not least, doing large scale allocations is the job of the backend. Exceptions being long-lived objects, like the session or the sequences, which are created once and don't bear the danger of causing memory pressure. Generally speaking, client code shouldn't issue "new" and "delete" when it comes in handy. Questions of setup and lifecycle should allways be delegated, typically through the usage of some [[factory|Factories]], which might return the product conveniently wrapped into a RAII style handle. Memory allocation is crucial for performance, and needs to be adapted to the actual platform -- which is impossible unless abstracted and treated as a separate concern.
 
-
-
This category encompasses the various aspects of the way the application controls and manages its own behaviour. They are more related to the way the application behaves, as opposed to the way the edited data is structured and organised (which is the realm of [[structural assets|StructAsset]]
+
+
This category encompasses the various aspects of the way the application controls and manages its own behaviour. They are more related to the way the application behaves, as opposed to the way the edited data is structured and organised (which is the realm of [[structural assets|StructAsset]])
 * StreamType → a type system for describing and relating media data streams
 * ScaleGrid → to manage time scales and frame alignment
 
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm
index 93b63d0a7..60e39ec73 100644
--- a/wiki/thinkPad.ichthyo.mm
+++ b/wiki/thinkPad.ichthyo.mm
@@ -1480,18 +1480,106 @@
       das ist schon die endgültige Lösung
     

- - + - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...es könnte auch den Feedback des Users meinen +

+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +

+ Schlußfolgerung: Wizzard wird ein Interface +

+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + @@ -1510,8 +1598,8 @@

- - + + @@ -1620,8 +1708,7 @@ und er trotzdem vom konkreten Widget entkoppelt bleibt

- - +
@@ -1633,8 +1720,7 @@ besserer Name: NotificationHub

- -
+
@@ -31988,6 +32074,31 @@
+ + + + + + + + + + + + + + + + + + + + + + + + + From 1d69bb9050648da96a0b927824f4d2d9db2670d6 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 11 Aug 2018 16:16:13 +0200 Subject: [PATCH 005/178] NotificationDisplay: define a new ErrorLog asset ...for now to serve as placeholder type, used as anchor for the corresponding UI display widget --- src/gui/interact/wizard.hpp | 8 +- src/proc/asset/meta/error-log.cpp | 76 ++++++++++++++++ src/proc/asset/meta/error-log.hpp | 101 +++++++++++++++++++++ wiki/thinkPad.ichthyo.mm | 145 +++++++++++++++++++++++++++++- 4 files changed, 327 insertions(+), 3 deletions(-) create mode 100644 src/proc/asset/meta/error-log.cpp create mode 100644 src/proc/asset/meta/error-log.hpp diff --git a/src/gui/interact/wizard.hpp b/src/gui/interact/wizard.hpp index 381f1b40f..31cd7304f 100644 --- a/src/gui/interact/wizard.hpp +++ b/src/gui/interact/wizard.hpp @@ -49,6 +49,12 @@ #include +namespace proc { + namespace asset { + namespace meta { + class ErrorLog; +} } } + namespace gui { namespace ctrl { class GlobalCtx; @@ -62,7 +68,7 @@ namespace interact { // class GlobalCtx; // class SpotLocator; - extern lib::idi::EntryID theErrorLog_ID; + extern lib::idi::EntryID theErrorLog_ID; diff --git a/src/proc/asset/meta/error-log.cpp b/src/proc/asset/meta/error-log.cpp new file mode 100644 index 000000000..6ef173013 --- /dev/null +++ b/src/proc/asset/meta/error-log.cpp @@ -0,0 +1,76 @@ +/* + ErrorLog - Entity to collect and persist incident records + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + 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.cpp + ** Implementation details of incident logging and persistence. + */ + + +#include "proc/asset/meta/error-log.hpp" +//#include "proc/asset/struct-scheme.hpp" +//#include "proc/assetmanager.hpp" +//#include "lib/time/timevalue.hpp" +//#include "lib/format-string.hpp" +//#include "lib/util.hpp" + +//#include + +//using util::_Fmt; +//using util::cStr; +//using util::isnil; +//using std::string; + + +namespace proc { +namespace asset { +namespace meta { + + namespace error = lumiera::error; + + + + /** */ + ErrorLog::ErrorLog (LogID const& nameID) + : Meta{nameID} + { } + + +// using lib::time::Time; +// using lib::time::TimeValue; + + + /** Setup of an ErrorLog: validate the settings within this builder instance, + * then create an appropriately configured ErrorLog instance. + * @return shared_ptr holding onto the new asset::Meta, which has already been + * registered with the AssetManager. + * @todo currently (8/2018) this is a mere placeholder, we just need an EntryID. + */ + lib::P + Builder::commit() + { +// return new ErrorLog; + UNIMPLEMENTED ("how to build and properly register an error log entity"); + } + + +}}} // namespace asset::meta diff --git a/src/proc/asset/meta/error-log.hpp b/src/proc/asset/meta/error-log.hpp new file mode 100644 index 000000000..235db70b8 --- /dev/null +++ b/src/proc/asset/meta/error-log.hpp @@ -0,0 +1,101 @@ +/* + ERROR-LOG.hpp - Entity to collect and persist incident records + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + 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.hpp + ** An entity to collect, possibly filter and persist incident records. + ** + ** @todo 8/2018 created as mere placeholder; for now we just need an EntryID + ** in order to mark the corresponding receiver widget in the UI. The idea is eventually + ** to persist relevant messages, filtering them out as time passes. Such an incident log + ** would be part of the session model, thus replicating its contents into the corresponding + ** gui::widget::ErrorLogView -- which displays notifications without blocking the UI. + ** + ** @see MetaFactory creating concrete asset::Meta instances + ** @see gui::ctrl::NotificationHub corresponding UI controller + ** @see gui::interact::Wizard information service in the UI + ** + */ + +#ifndef ASSET_META_ERROR_LOG_H +#define ASSET_META_ERROR_LOG_H + +#include "proc/asset/meta.hpp" +#include "lib/idi/entry-id.hpp" +//#include "lib/time/timevalue.hpp" +//#include "lib/symbol.hpp" + + + +namespace proc { +namespace asset { +namespace meta { + +// using lib::Symbol; +// using lib::time::Time; +// using lib::time::TimeVar; +// using lib::time::TimeValue; + + + class ErrorLog; + using PLog = lib::P; + using LogID = lib::idi::EntryID; + + + /** + * Receive, collect, filter and possibly persist incident records. + * @todo 8/2018 mere placeholder type for now, to allow defining an EntyID. + * We conceptually need "the" ErrorLog entity as correspondence to the ErrorLogView in the GUI. + */ + class ErrorLog + : public Meta + { + + public: + + protected: + ErrorLog (LogID const&); + }; + + + + + + template<> + struct Builder + { + + /** + * @todo what is "the default log"?? + */ + Builder() + { } + + /** create a specific error log + * configured as defined through this builder + */ + lib::P commit(); + + }; + +}}} // namespace proc::asset::meta +#endif /*ASSET_META_ERROR_LOG_H*/ diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 60e39ec73..b4a62fd18 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -14325,7 +14325,9 @@ - + + + @@ -32085,14 +32087,153 @@ + - + + + + + + + + +

+ Meta-Assets sind per Definition "immutable"
Es gibt einen Builder +

+ +
+ + + + + + + + + + + + + +

+ das ErrorLog +

+ + +
+ + + + + + + +

+ ....weil hier ein allgemeines Schema entsteht: +

+

+ jede Aktion, die in das UI "reflektiert" wird, erfolgt, indem man eine Nachricht +

+

+ über den UI-Bus schickt, an einen Empfänger mit bekannter ID. +

+ + +
+ +
+
+ + + + + + + + +

+ dort gibt es eine ErrorLogView +

+ + +
+
+ + + + + + +

+ und den Controller: NotificationHub +

+ + +
+ +
+
+ + + +
+ + + + + + + + + +

+ das ist diese Idee, daß eine Struktur sich selbst meta-repräsentiert; +

+

+ dadurch werden Meta-Operationen auf gleiche Ebene gestellt wie normale Struktur-Manipulationen +

+ + +
+ +
+ + + + + + +

+ ...und dadruch würden History-Operationen wie +

+
    +
  • + undo +
  • +
  • + redo +
  • +
  • + repeat command +
  • +
+

+ ...zu ganz normalen Manipulationen der Session, würden ihrerseits geloggt und historisiert +

+

+ und verlieren ihren magischen Charakter außerhalb der Event-Sourcing-Struktur +

+ + +
+
+
From 928b4372e00237d4d2ab719b9c4963d1f0e0aeec Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 11 Aug 2018 18:06:09 +0200 Subject: [PATCH 006/178] Assets: investigating the unclear distinction between asset::Struct and asset::Meta (#1156) including a kind-of Bugfix: the ctor of TimeGrid erroneously categorised it as asset::Kind STRUCT --- src/proc/asset/category.hpp | 2 +- src/proc/asset/meta.hpp | 11 +++++- src/proc/asset/meta/time-grid.cpp | 9 +++-- src/proc/asset/meta/time-grid.hpp | 5 +-- src/proc/asset/struct-scheme.hpp | 6 ++-- src/proc/asset/struct.hpp | 1 + tests/41asset.tests | 2 +- wiki/renderengine.html | 9 +++-- wiki/thinkPad.ichthyo.mm | 57 ++++++++++++++++++++++++++++++- 9 files changed, 87 insertions(+), 15 deletions(-) diff --git a/src/proc/asset/category.hpp b/src/proc/asset/category.hpp index ce6028921..aca63b08f 100644 --- a/src/proc/asset/category.hpp +++ b/src/proc/asset/category.hpp @@ -57,7 +57,7 @@ namespace asset { , VIDEO , EFFECT , CODEC - , STRUCT + , STRUCT //////////////////////////////////TICKET #1156 : do we need the distinction between STRUCT and META? , META }; diff --git a/src/proc/asset/meta.hpp b/src/proc/asset/meta.hpp index 4a736cf47..245d13da9 100644 --- a/src/proc/asset/meta.hpp +++ b/src/proc/asset/meta.hpp @@ -61,6 +61,7 @@ #define ASSET_META_H #include "proc/asset.hpp" +#include "proc/asset/struct-scheme.hpp" #include "lib/idi/entry-id.hpp" #include "lib/nocopy.hpp" @@ -112,6 +113,7 @@ namespace asset { /** * key abstraction: metadata, parametrisation, customisation and similar organisational traits. * @todo just a stub, still have to figure out the distinctive properties of asset::Meta + * //////////////////////////////////TICKET #1156 : do we need the distinction between STRUCT and META? */ class Meta : public Asset @@ -127,7 +129,14 @@ namespace asset { } protected: - Meta (Asset::Ident const& idi) : Asset(idi) {} //////////////TODO + Meta (Asset::Ident const& idi) + : Asset{idi} + { } + + template + Meta (lib::idi::EntryID const& nameID) + : Meta{idi::getAssetIdent (nameID, META)} + { } // friend class MetaFactory; ///////////////////////////////////TODO still necessary? }; diff --git a/src/proc/asset/meta/time-grid.cpp b/src/proc/asset/meta/time-grid.cpp index 4c374e573..6d173db2d 100644 --- a/src/proc/asset/meta/time-grid.cpp +++ b/src/proc/asset/meta/time-grid.cpp @@ -51,9 +51,12 @@ namespace meta { - /** */ + /** + * @internal to be invoked indirectly by means of the Builder. + * @todo drafted in 2010 -- usage of the builder feels unsatisfactory as of 2018 + */ TimeGrid::TimeGrid (GridID const& nameID) - : Meta (idi::getAssetIdent (nameID)) + : Meta{nameID} { } @@ -76,7 +79,7 @@ namespace meta { /** @internal helper to retrieve the smart-ptr * from the AssetManager, then attach a further * smart-ptr-to-Quantiser to that, which then can be - * published via the \link advice.hpp "advice system"\endlink + * published via the [advice system](\ref advice.hpp) * @note this allows to use a time grid just "by name", * without explicit dependence to the Session / Assets */ diff --git a/src/proc/asset/meta/time-grid.hpp b/src/proc/asset/meta/time-grid.hpp index 2cf593edb..6b00a69de 100644 --- a/src/proc/asset/meta/time-grid.hpp +++ b/src/proc/asset/meta/time-grid.hpp @@ -22,7 +22,7 @@ /** @file time-grid.hpp - ** Establishing a reference scale for quantised time values. + ** To establish a reference scale for quantised time values. ** Contrary to the continuous Time values (Lumiera internal time scale), ** quantised time can only take on some discrete values corresponding ** to blocks or frames, according to the respective \em timecode format. @@ -34,7 +34,8 @@ ** Time grid specifications are integrated into Lumiera's framework ** for meta assets, automation, configuration and similar metadata. ** - ** \par using time grids + ** ## using time grids + ** ** TimeGrid is an interface (ABC), but provides some actual factory ** functions, which can be used as a convenience shortcut to fabricate ** the kind of simple time grid used most often. diff --git a/src/proc/asset/struct-scheme.hpp b/src/proc/asset/struct-scheme.hpp index 15db48dbf..fd1f05860 100644 --- a/src/proc/asset/struct-scheme.hpp +++ b/src/proc/asset/struct-scheme.hpp @@ -155,9 +155,9 @@ namespace asset{ */ template inline Asset::Ident - getAssetIdent (lib::idi::EntryID const& entryID) - { - Category cat (STRUCT, idi::StructTraits::catFolder()); + getAssetIdent (lib::idi::EntryID const& entryID, asset::Kind assetKind =STRUCT) + { //////////////////////////////////////////TICKET #1156 : do we need the distinction between STRUCT and META? + Category cat (assetKind, idi::StructTraits::catFolder()); return Asset::Ident (entryID.getSym(), cat); } diff --git a/src/proc/asset/struct.hpp b/src/proc/asset/struct.hpp index 4ed21312e..bf5e58a2f 100644 --- a/src/proc/asset/struct.hpp +++ b/src/proc/asset/struct.hpp @@ -108,6 +108,7 @@ namespace asset { * The idea is to have a generic mechanism for attaching properties and relations. * This will become relevant once we build the real query subsystem. * Right now (as of 1/2015), asset::Struct is just an ID provider. + * //////////////////////////////////TICKET #1156 : do we need the distinction between STRUCT and META? */ class Struct : public Asset { diff --git a/tests/41asset.tests b/tests/41asset.tests index 3bfa9db35..b88b05b67 100644 --- a/tests/41asset.tests +++ b/tests/41asset.tests @@ -58,6 +58,6 @@ END TEST "Simple TimeGrid" TimeGridBasics_test <
-
+
Asset management is a subsystem on its own. Assets are "things" that can be loaded into a session, like Media, Clips, Effects, Transitions. It is the "bookkeeping view", while the Objects in the Session relate to the "manipulation and process view". Some Assets can be //loaded// and a collection of Assets is saved with each Session. Besides, there is a collection of basic Assets always available by default.
 
 The Assets are important reference points holding the information needed to access external resources. For example, an Clip asset can reference a Media asset, which in turn holds the external filename from which to get the media stream. For Effects, the situation is similar. Assets thus serve two quite distinct purposes. One is to load, list, group search and browse them, and to provide an entry point to create new or get at existing MObject in the Session, while the other purpose is to provide attribute and property information to the inner parts of the engine, while at the same time isolating and decoupling them from environmental details. 
@@ -900,6 +900,8 @@ is how to implement the relationship between [[MObject]]s and Assets. Do we use
 
   //9/07: currently implementing it as follows: use a refcounting-ptr from Clip-~MObject to asset::Media while maintaining a dependency network between Asset objects. We'll see if this approach is viable//
 
+{{red{NOTE 8/2018}}} there seems to be a fuzziness surrounding the distinction between StructAsset and MetaAsset.
+I am suspicious this is a distinction //merely derived from first principles...// &rarr; {{red{Ticket #1156}}}
 
@@ -4210,10 +4212,11 @@ For the case here in question this seems to be the __R__esource __A__llocation _ And, last but not least, doing large scale allocations is the job of the backend. Exceptions being long-lived objects, like the session or the sequences, which are created once and don't bear the danger of causing memory pressure. Generally speaking, client code shouldn't issue "new" and "delete" when it comes in handy. Questions of setup and lifecycle should allways be delegated, typically through the usage of some [[factory|Factories]], which might return the product conveniently wrapped into a RAII style handle. Memory allocation is crucial for performance, and needs to be adapted to the actual platform -- which is impossible unless abstracted and treated as a separate concern.
-
-
This category encompasses the various aspects of the way the application controls and manages its own behaviour. They are more related to the way the application behaves, as opposed to the way the edited data is structured and organised (which is the realm of [[structural assets|StructAsset]])
+
+
This category encompasses the various aspects of the way the application controls and manages its own behaviour. They are more related to the way the application behaves, as opposed to the way the edited data is structured and organised (which is the realm of [[structural assets|StructAsset]]) &rarr; {{red{Ticket #1156}}}
 * StreamType &rarr; a type system for describing and relating media data streams
 * ScaleGrid &rarr; to manage time scales and frame alignment
+* {{{ErrorLog}}} &rarr; collect incident records
 
 !accessing meta assets
 It turns out that all meta assets follow a distinct usage pattern: //they aren't built as individual entities.// Either, they are introduced into the system as part of a larger scale configuration activity, or they are //derived from category.// The latter fits in with a prototype-like approach; initially, the individual entry just serves to keep track of a categorisation, while at some point, such a link into a describing category may evolve into a local differentiation of some settings.
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm
index b4a62fd18..cbb1e9430 100644
--- a/wiki/thinkPad.ichthyo.mm
+++ b/wiki/thinkPad.ichthyo.mm
@@ -32085,6 +32085,57 @@
 
 
 
+
+
+
+
+
+
+
+
+
+
+
+  
+    
+  
+  
+    

+ ...obwohl die betreffenden Assets in die Kategorie "Medien" fallen, +

+

+ und wir einen eigenen StreamType definieren wollen, also explizit die Art der Medien offen lassen. +

+

+ Ganz prominent fehlt hier also z.B: MIDI +

+ + +
+
+ + + + + + + + + + + +

+ ...nämlich genau nicht für alle Assets, +

+

+ die Aufgrund von Klassifikationen automatisch bereits existieren +

+ + +
+
+
+
@@ -32183,7 +32234,11 @@ - + + + + + From f872e222169125d28ddcc06ec945aff99411a405 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 11 Aug 2018 19:43:57 +0200 Subject: [PATCH 007/178] Assets: draft how the global ErrorLog asset could be created --- src/proc/asset.hpp | 2 + src/proc/asset/meta.cpp | 7 ++- src/proc/asset/meta/error-log.cpp | 40 ++++++++++---- src/proc/asset/meta/error-log.hpp | 16 +++++- src/proc/asset/meta/time-grid.cpp | 16 +++--- src/proc/asset/meta/time-grid.hpp | 18 +++---- src/proc/asset/struct-scheme.hpp | 2 +- src/proc/assetmanager.cpp | 3 ++ tests/core/proc/asset/create-asset-test.cpp | 53 +++++++++++++++---- .../proc/asset/meta/time-grid-basics-test.cpp | 10 ++-- 10 files changed, 123 insertions(+), 44 deletions(-) diff --git a/src/proc/asset.hpp b/src/proc/asset.hpp index 5e1cec4c2..1a0905176 100644 --- a/src/proc/asset.hpp +++ b/src/proc/asset.hpp @@ -101,6 +101,8 @@ namespace asset { * @see AssetManager#getID generating ID values * @see asset::Media * @see ID + * + * @todo it would be desirable to use EntryID instead ///////////////////////////////////////////TICKET #739 : make identification schemes compatible */ template class ID diff --git a/src/proc/asset/meta.cpp b/src/proc/asset/meta.cpp index 404eda461..b30eb1590 100644 --- a/src/proc/asset/meta.cpp +++ b/src/proc/asset/meta.cpp @@ -96,6 +96,7 @@ namespace asset { /**************************************************/ #include "proc/asset/meta/time-grid.hpp" +#include "proc/asset/meta/error-log.hpp" namespace proc { @@ -106,7 +107,11 @@ namespace asset { using meta::TimeGrid; template Builder MetaFactory::operator() (EntryID); - template Builder MetaFactory::operator() (Descriptor const&, EntryID); + using meta::ErrorLog; + + template Builder MetaFactory::operator() (EntryID); + template Builder MetaFactory::operator() (Descriptor const&, EntryID); + }} // namespace proc::asset diff --git a/src/proc/asset/meta/error-log.cpp b/src/proc/asset/meta/error-log.cpp index 6ef173013..81d9db22f 100644 --- a/src/proc/asset/meta/error-log.cpp +++ b/src/proc/asset/meta/error-log.cpp @@ -27,18 +27,19 @@ #include "proc/asset/meta/error-log.hpp" -//#include "proc/asset/struct-scheme.hpp" -//#include "proc/assetmanager.hpp" +#include "proc/asset/struct-scheme.hpp" +#include "proc/assetmanager.hpp" //#include "lib/time/timevalue.hpp" //#include "lib/format-string.hpp" -//#include "lib/util.hpp" +#include "lib/util.hpp" //#include //using util::_Fmt; //using util::cStr; -//using util::isnil; +using util::isnil; //using std::string; +using std::dynamic_pointer_cast; namespace proc { @@ -48,13 +49,34 @@ namespace meta { namespace error = lumiera::error; - + /** storage for an unique, globally known ID. + * Used to address the corresponding ErrorLogView in the UI + * @todo 8/2018 for now this is a mere placeholder, but someone + * need to build a singleton asset to incorporate this eventually ///////////////////////////////////TICKET #1157 + */ + LogID theErrorLog_ID{"global_ErrorLog"}; + + /** */ ErrorLog::ErrorLog (LogID const& nameID) : Meta{nameID} { } + PLog + ErrorLog::global() + { + Asset::Ident ident = asset::idi::getAssetIdent (theErrorLog_ID, META); + ID globalLogID = AssetManager::instance().getID(ident); + /////////////////////////////////////////////////////////////////////////////////TICKET #739 sort out this mess with asset::ID vs EntryID + + if (not AssetManager::instance().known (globalLogID)) + return asset::Meta::create (theErrorLog_ID) + .commit(); + else + return dynamic_pointer_cast (AssetManager::instance().getAsset (globalLogID)); + } + // using lib::time::Time; // using lib::time::TimeValue; @@ -63,13 +85,13 @@ namespace meta { * then create an appropriately configured ErrorLog instance. * @return shared_ptr holding onto the new asset::Meta, which has already been * registered with the AssetManager. - * @todo currently (8/2018) this is a mere placeholder, we just need an EntryID. + * @todo currently (8/2018) this is a mere placeholder, we just need an EntryID. ///////////////TICKET #1157 */ lib::P Builder::commit() - { -// return new ErrorLog; - UNIMPLEMENTED ("how to build and properly register an error log entity"); + { + ASSERT (isnil (nameID), "only the single global Error Log is implemented for now"); ////////////////////TICKET #1157 : and the entity created here is bare of any functionality + return AssetManager::wrap (*new ErrorLog{theErrorLog_ID}); } diff --git a/src/proc/asset/meta/error-log.hpp b/src/proc/asset/meta/error-log.hpp index 235db70b8..6f600ce95 100644 --- a/src/proc/asset/meta/error-log.hpp +++ b/src/proc/asset/meta/error-log.hpp @@ -44,12 +44,15 @@ //#include "lib/time/timevalue.hpp" //#include "lib/symbol.hpp" +#include + namespace proc { namespace asset { namespace meta { + using std::string; // using lib::Symbol; // using lib::time::Time; // using lib::time::TimeVar; @@ -60,6 +63,8 @@ namespace meta { using PLog = lib::P; using LogID = lib::idi::EntryID; + extern LogID theErrorLog_ID; + /** * Receive, collect, filter and possibly persist incident records. @@ -71,9 +76,15 @@ namespace meta { { public: + /** retrieve (possibly create) the global singleton asset + * corresponding to "the" global error log, as shown in the UI. + */ + static PLog global(); protected: ErrorLog (LogID const&); + + friend class Builder; }; @@ -83,11 +94,12 @@ namespace meta { template<> struct Builder { - + string nameID; /** * @todo what is "the default log"?? */ - Builder() + Builder(string name) + : nameID{name} { } /** create a specific error log diff --git a/src/proc/asset/meta/time-grid.cpp b/src/proc/asset/meta/time-grid.cpp index 6d173db2d..52b000553 100644 --- a/src/proc/asset/meta/time-grid.cpp +++ b/src/proc/asset/meta/time-grid.cpp @@ -145,19 +145,19 @@ namespace meta { lib::P Builder::commit() { - if (predecessor_) + if (predecessor) throw error::Invalid("compound and variable time grids are a planned feature" , error::LUMIERA_ERROR_UNIMPLEMENTED); - ENSURE (fps_, "infinite grid should have been detected by FrameRate ctor"); + ENSURE (fps, "infinite grid should have been detected by FrameRate ctor"); - if (isnil (id_)) + if (isnil (id)) { _Fmt gridIdFormat("grid(%f_%d)"); - id_ = string(gridIdFormat % fps_ % _raw(origin_)); + id = string(gridIdFormat % fps % _raw(origin)); } - GridID nameID (id_); + GridID nameID (id); - return publishWrapped (*new SimpleTimeGrid(origin_, fps_, nameID)); + return publishWrapped (*new SimpleTimeGrid(origin, fps, nameID)); } @@ -175,8 +175,8 @@ namespace meta { { string name(gridID); Builder spec(name); - spec.fps_ = frames_per_second; - spec.origin_ = origin; + spec.fps = frames_per_second; + spec.origin = origin; return spec.commit(); } diff --git a/src/proc/asset/meta/time-grid.hpp b/src/proc/asset/meta/time-grid.hpp index 6b00a69de..dd341b5b6 100644 --- a/src/proc/asset/meta/time-grid.hpp +++ b/src/proc/asset/meta/time-grid.hpp @@ -104,17 +104,17 @@ namespace meta { template<> struct Builder { - string id_; + string id; - FrameRate fps_; - TimeVar origin_; + FrameRate fps; + TimeVar origin; /** when building a compound or variable grid, - * the predecessor is the grid active \em before + * the predecessor is the grid active _before_ * the origin of this (local) grid. * @todo currently not supported (as of 12/2010) */ - lib::P predecessor_; + lib::P predecessor; /** * initialise to blank (zero). @@ -122,10 +122,10 @@ namespace meta { * in order to create a usable TimeGrid */ Builder(string const& nameID ="") - : id_(nameID) - , fps_(1) - , origin_(TimeValue(0)) - , predecessor_() + : id {nameID} + , fps{1} + , origin{TimeValue(0)} + , predecessor{} { } /** create a time grid diff --git a/src/proc/asset/struct-scheme.hpp b/src/proc/asset/struct-scheme.hpp index fd1f05860..c807ed5b4 100644 --- a/src/proc/asset/struct-scheme.hpp +++ b/src/proc/asset/struct-scheme.hpp @@ -151,7 +151,7 @@ namespace asset{ * The remaining fields are filled in with hardwired defaults. * @note there is a twist, as this asset identity tuple generates * a different hash as the EntryID. It would be desirable - * to make those two addressing systems interchangeable. /////////////TICKET #739 + * to make those two addressing systems interchangeable. ////////////////////////////////TICKET #739 : make identification schemes compatible */ template inline Asset::Ident diff --git a/src/proc/assetmanager.cpp b/src/proc/assetmanager.cpp index 509b6b98c..8b572f72a 100644 --- a/src/proc/assetmanager.cpp +++ b/src/proc/assetmanager.cpp @@ -254,6 +254,7 @@ namespace asset { #include "proc/asset/timeline.hpp" #include "proc/asset/sequence.hpp" #include "proc/asset/meta/time-grid.hpp" +#include "proc/asset/meta/error-log.hpp" namespace proc { namespace asset { @@ -278,7 +279,9 @@ namespace asset { template P AssetManager::wrap (const Sequence& asset); using meta::TimeGrid; + using meta::ErrorLog; template P AssetManager::wrap (const TimeGrid& asset); + template P AssetManager::wrap (const ErrorLog& asset); }} // namespace proc::asset diff --git a/tests/core/proc/asset/create-asset-test.cpp b/tests/core/proc/asset/create-asset-test.cpp index c81140b08..8152d4bb6 100644 --- a/tests/core/proc/asset/create-asset-test.cpp +++ b/tests/core/proc/asset/create-asset-test.cpp @@ -27,18 +27,24 @@ #include "include/logging.h" #include "lib/test/run.hpp" -#include "lib/util.hpp" +#include "lib/test/test-helper.hpp" #include "proc/assetmanager.hpp" #include "proc/asset/media.hpp" #include "proc/asset/proc.hpp" +#include "proc/asset/meta/time-grid.hpp" +#include "proc/asset/meta/error-log.hpp" #include "proc/asset/asset-diagnostics.hpp" #include "backend/media-access-mock.hpp" +#include "lib/time/timevalue.hpp" #include "lib/depend-inject.hpp" +#include "lib/util.hpp" using util::isnil; using std::string; +using lib::time::FrameRate; +using lib::test::randStr; namespace proc { @@ -63,6 +69,7 @@ namespace test { createMedia(); factoryVariants(); + createMetaAssets(); if (!isnil (arg)) dumpAssetManager(); @@ -71,7 +78,7 @@ namespace test { ////////////////////////////////////TICKET #589 - typedef lib::P PM; /////TODO: transition to P<> + using PM = lib::P; /** @test Creating and automatically registering Asset instances. * Re-Retrieving the newly created objects from AssetManager. @@ -79,7 +86,8 @@ namespace test { * different kinds of Assets by ID, querying with the * wrong Category and querying unknown IDs. */ - void createMedia() + void + createMedia() { Category cat(VIDEO,"bin1"); Asset::Ident key("test-1", cat, "ichthyo", 5); @@ -89,7 +97,7 @@ namespace test { // Assets have been registered and can be retrieved by ID AssetManager& aMang = AssetManager::instance(); - PM registered; /////TODO: transition to P<> + PM registered; registered = aMang.getAsset (mm1->getID()); CHECK (registered == mm1); registered = aMang.getAsset (mm2->getID()); @@ -167,7 +175,8 @@ namespace test { * creation machinery. Covers filling out Asset's * datafields, amending missing pieces of information. */ - void factoryVariants() + void + factoryVariants() { PM candi; @@ -185,19 +194,19 @@ namespace test { candi = asset::Media::create(string("test-3.wav"), Category(AUDIO)); CHECK ( checkProperties (candi, Asset::Ident("test-3", Category(AUDIO), "lumi", 1) - , "test-3.wav")); + , "test-3.wav")); candi = asset::Media::create("some/path/test-4.wav", Category(AUDIO)); CHECK ( checkProperties (candi, Asset::Ident("test-4", Category(AUDIO), "lumi", 1) - , "some/path/test-4.wav")); + , "some/path/test-4.wav")); candi = asset::Media::create("", Category(AUDIO,"sub/bin")); CHECK ( checkProperties (candi, Asset::Ident("nil", Category(AUDIO,"sub/bin"), "lumi", 1) - , "")); + , "")); candi = asset::Media::create("", AUDIO); CHECK ( checkProperties (candi, Asset::Ident("nil", Category(AUDIO), "lumi", 1) - , "")); + , "")); } bool checkProperties (PM object, Asset::Ident identity, string filename) @@ -205,6 +214,32 @@ namespace test { return identity == object->ident && filename == object->getFilename(); } + + + void + createMetaAssets() + { + using asset::meta::GridID; + using asset::meta::PGrid; + + GridID myGrID (randStr(8)); + auto gridSpec = asset::Meta::create (myGrID); + gridSpec.fps = FrameRate{23}; + PGrid myGrid = gridSpec.commit(); + + CHECK (myGrid); + CHECK (myGrid->ident.name == myGrID.getSym()); ////////////////////////////////TICKET #739 : assets should use EntryID instead of asset::ID + + CHECK (AssetManager::instance().known (myGrid->getID())); + CHECK (myGrid == AssetManager::instance().getAsset (myGrid->getID())); + + // for the ErrorLog assert, as of 8/2018 there is just one single global placeholder entity available + asset::meta::PLog globalLog = asset::meta::ErrorLog::global(); /////////////////////////////////TICKET #1157 : what's the purpose of this ErrorLog Asset after all?? + + + CHECK (globalLog->ident.name == meta::theErrorLog_ID.getSym()); + CHECK (AssetManager::instance().known (globalLog->getID())); + } }; diff --git a/tests/core/proc/asset/meta/time-grid-basics-test.cpp b/tests/core/proc/asset/meta/time-grid-basics-test.cpp index a5cae3e7c..82d2d93c6 100644 --- a/tests/core/proc/asset/meta/time-grid-basics-test.cpp +++ b/tests/core/proc/asset/meta/time-grid-basics-test.cpp @@ -85,12 +85,12 @@ namespace test { GridID myGrID (randStr(8)); GridBuilder spec = asset::Meta::create (myGrID); - CHECK ( spec.fps_ == 1); - CHECK ( spec.origin_ == TimeValue(0)); - CHECK (!spec.predecessor_); + CHECK ( spec.fps == 1); + CHECK ( spec.origin == TimeValue(0)); + CHECK (!spec.predecessor); - spec.fps_ = testFps; - spec.origin_ = testOrigin; + spec.fps = testFps; + spec.origin = testOrigin; PGrid myGrid = spec.commit(); CHECK (myGrid); From 53c47a6fccb8eb79119fc522d2e326e4fd509603 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 11 Aug 2018 23:55:42 +0200 Subject: [PATCH 008/178] Assets: verify creation of ErrorLog meta-Asset --- src/proc/asset/meta/error-log.cpp | 6 +++--- tests/core/proc/asset/asset-diagnostics.hpp | 7 ++++--- tests/core/proc/asset/create-asset-test.cpp | 6 +++++- tests/core/proc/asset/typed-id-test.cpp | 2 +- wiki/thinkPad.ichthyo.mm | 14 ++++++++++++-- 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/proc/asset/meta/error-log.cpp b/src/proc/asset/meta/error-log.cpp index 81d9db22f..9f6cd9ef3 100644 --- a/src/proc/asset/meta/error-log.cpp +++ b/src/proc/asset/meta/error-log.cpp @@ -31,13 +31,13 @@ #include "proc/assetmanager.hpp" //#include "lib/time/timevalue.hpp" //#include "lib/format-string.hpp" -#include "lib/util.hpp" +//#include "lib/util.hpp" //#include //using util::_Fmt; //using util::cStr; -using util::isnil; +//using util::isnil; //using std::string; using std::dynamic_pointer_cast; @@ -90,7 +90,7 @@ namespace meta { lib::P Builder::commit() { - ASSERT (isnil (nameID), "only the single global Error Log is implemented for now"); ////////////////////TICKET #1157 : and the entity created here is bare of any functionality + ASSERT (nameID == theErrorLog_ID.getSym(), "only the single global Error Log is implemented for now");///TICKET #1157 : and the entity created here is bare of any functionality return AssetManager::wrap (*new ErrorLog{theErrorLog_ID}); } diff --git a/tests/core/proc/asset/asset-diagnostics.hpp b/tests/core/proc/asset/asset-diagnostics.hpp index 4f93636cb..2e91b5e71 100644 --- a/tests/core/proc/asset/asset-diagnostics.hpp +++ b/tests/core/proc/asset/asset-diagnostics.hpp @@ -54,14 +54,14 @@ namespace proc { namespace asset { inline void - dump (PcAsset& aa) + dump (PcAsset const& aa) { if (!aa) cout << "Asset(NULL)\n"; else { _Fmt fmt("%s %|50T.| id=%s adr=%p smart-ptr=%p use-count=%u"); - cout << fmt % aa % aa->getID() % aa.get() % &aa % (aa.use_count() - 1) << "\n"; + cout << fmt % aa % aa->getID() % (void*)aa.get() % &aa % (aa.use_count() - 1) << "\n"; } } @@ -70,7 +70,8 @@ namespace asset { { list assets (AssetManager::instance().listContent()); cout << "----all-registered-Assets----\n"; - for_each (assets, bind (&dump, _1)); + for (auto const& pA : assets) + dump (pA); } diff --git a/tests/core/proc/asset/create-asset-test.cpp b/tests/core/proc/asset/create-asset-test.cpp index 8152d4bb6..ece46790f 100644 --- a/tests/core/proc/asset/create-asset-test.cpp +++ b/tests/core/proc/asset/create-asset-test.cpp @@ -236,9 +236,13 @@ namespace test { // for the ErrorLog assert, as of 8/2018 there is just one single global placeholder entity available asset::meta::PLog globalLog = asset::meta::ErrorLog::global(); /////////////////////////////////TICKET #1157 : what's the purpose of this ErrorLog Asset after all?? - CHECK (globalLog->ident.name == meta::theErrorLog_ID.getSym()); CHECK (AssetManager::instance().known (globalLog->getID())); + CHECK (2 == globalLog.use_count()); // AssetManager also holds a reference + + PAsset furtherRef = asset::meta::ErrorLog::global(); + CHECK (3 == globalLog.use_count()); + CHECK (furtherRef == globalLog); } }; diff --git a/tests/core/proc/asset/typed-id-test.cpp b/tests/core/proc/asset/typed-id-test.cpp index 7fdc569f3..737b71901 100644 --- a/tests/core/proc/asset/typed-id-test.cpp +++ b/tests/core/proc/asset/typed-id-test.cpp @@ -111,7 +111,7 @@ namespace test{ * - re-access the instances just by symbolic ID * - verify the registration is cleaned up automatically. * - * @todo partially unimplemented and thus commented out ////////////////////TICKET #599 + * @todo draft from 2010 -- partially unimplemented and thus commented out //////////////////////////////////////////STICKET #599 * * @see typed-id.hpp interface covered here * @see typed-lookup.cpp implementation diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index cbb1e9430..f4079ede1 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -14325,7 +14325,7 @@ - + @@ -32053,6 +32053,10 @@ + + + + @@ -32134,6 +32138,12 @@ + + + + + + @@ -32215,7 +32225,7 @@ - + From c2c25f813474b96b805c856166524b648dff83ff Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 12 Aug 2018 00:38:32 +0200 Subject: [PATCH 009/178] NotificationDisplay: define and include the unique error-Log ID --- src/gui/id-scheme.hpp | 6 +++++- src/gui/interact/wizard.hpp | 9 ++++++--- src/gui/notification-service.cpp | 3 +++ src/proc/asset/meta/error-log.cpp | 2 +- src/proc/asset/meta/error-log.hpp | 2 +- wiki/thinkPad.ichthyo.mm | 30 +++++++++++++++++++++++++++--- 6 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/gui/id-scheme.hpp b/src/gui/id-scheme.hpp index 94bb0ca48..6b225ac70 100644 --- a/src/gui/id-scheme.hpp +++ b/src/gui/id-scheme.hpp @@ -63,6 +63,10 @@ namespace proc { namespace asset { class Timeline; + + namespace meta { + class ErrorLog; + } } } @@ -103,7 +107,7 @@ namespace gui { template<> struct Descriptor { - ///////////////////////////////////////////////////////////////////////////////////////////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 // diff --git a/src/gui/interact/wizard.hpp b/src/gui/interact/wizard.hpp index 31cd7304f..213115302 100644 --- a/src/gui/interact/wizard.hpp +++ b/src/gui/interact/wizard.hpp @@ -53,9 +53,13 @@ namespace proc { namespace asset { namespace meta { class ErrorLog; + + extern lib::idi::EntryID theErrorLog_ID; } } } namespace gui { + using ID = lib::idi::BareEntryID const&; + namespace ctrl { class GlobalCtx; class NotificationHub; @@ -65,11 +69,8 @@ namespace interact { using std::unique_ptr; // using std::string; -// class GlobalCtx; // class SpotLocator; - extern lib::idi::EntryID theErrorLog_ID; - /** @@ -89,6 +90,8 @@ namespace interact { ~Wizard (); void show_HelpAbout(); + + static ID getErrorLogID() { return proc::asset::meta::theErrorLog_ID; } private: }; diff --git a/src/gui/notification-service.cpp b/src/gui/notification-service.cpp index 0807df973..f3cdc5032 100644 --- a/src/gui/notification-service.cpp +++ b/src/gui/notification-service.cpp @@ -48,6 +48,8 @@ #include "gui/ctrl/ui-manager.hpp" #include "gui/ctrl/ui-dispatcher.hpp" #include "gui/notification-service.hpp" +#include "gui/interact/wizard.hpp" + #include "lib/diff/mutation-message.hpp" #include "lib/diff/gen-node.hpp" #include "include/logging.h" @@ -91,6 +93,7 @@ namespace gui { NotificationService::displayInfo (NotifyLevel severity, string const& text) { INFO (gui, "@GUI: display '%s' as notification message.", cStr(text)); + ID errorLogID = interact::Wizard::getErrorLogID(); ////////////////////////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... } diff --git a/src/proc/asset/meta/error-log.cpp b/src/proc/asset/meta/error-log.cpp index 9f6cd9ef3..60639a7f2 100644 --- a/src/proc/asset/meta/error-log.cpp +++ b/src/proc/asset/meta/error-log.cpp @@ -43,7 +43,7 @@ using std::dynamic_pointer_cast; namespace proc { -namespace asset { +namespace asset{ namespace meta { namespace error = lumiera::error; diff --git a/src/proc/asset/meta/error-log.hpp b/src/proc/asset/meta/error-log.hpp index 6f600ce95..cd856528e 100644 --- a/src/proc/asset/meta/error-log.hpp +++ b/src/proc/asset/meta/error-log.hpp @@ -49,7 +49,7 @@ namespace proc { -namespace asset { +namespace asset{ namespace meta { using std::string; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index f4079ede1..6f308c239 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -1493,7 +1493,7 @@ - + @@ -1561,12 +1561,36 @@ - + + + + + + + + +

+ in proc::asset::meta::theErrorLog_ID +

+ + +
+
+
+ + + + + + + + - + + From b7c3764ae8dbad83ba427b7b15051f99474cabc3 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 13 Aug 2018 00:44:52 +0200 Subject: [PATCH 010/178] NotificationDisplay: wire NotificationHub-Controller with the global ID --- src/gui/ctrl/notification-hub.hpp | 5 ++--- src/gui/interact/wizard.cpp | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/gui/ctrl/notification-hub.hpp b/src/gui/ctrl/notification-hub.hpp index 1e447cc6f..1aa58a043 100644 --- a/src/gui/ctrl/notification-hub.hpp +++ b/src/gui/ctrl/notification-hub.hpp @@ -76,13 +76,12 @@ namespace ctrl { UNIMPLEMENTED ("create a sensible binding between AssetManager in the session and AssetController in the UI"); } - public: public: - NotificationHub (ID identity, ctrl::BusTerm& nexus)////////////////////////////////////TODO #1099 : define the ID hard-wired at an appropriate location, so it can be used from the NotificationService impl as well + NotificationHub (ID identity, ctrl::BusTerm& nexus) : model::Controller{identity, nexus} { } - + ~NotificationHub() { }; private: diff --git a/src/gui/interact/wizard.cpp b/src/gui/interact/wizard.cpp index 7996eb744..49921c692 100644 --- a/src/gui/interact/wizard.cpp +++ b/src/gui/interact/wizard.cpp @@ -56,6 +56,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; @@ -67,6 +68,7 @@ namespace interact { Wizard::Wizard (GlobalCtx& globals) : globalCtx_{globals} + , notificationHub_{new NotificationHub{getErrorLogID(), globals.uiBus_.getAccessPoint()}} { } From 36abe4567e23231699be4114ded1248eac65fea6 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 13 Aug 2018 02:52:31 +0200 Subject: [PATCH 011/178] NotificationDisplay: define the actual controller behaviour to be implemented this is specification work; for now the stubs are marked UNIMPLEMENTED --- src/gui/ctrl/notification-hub.hpp | 78 +++++++++++++++++++ src/gui/model/controller.hpp | 7 +- tests/gui/abstract-tangible-test.cpp | 4 +- tests/gui/test/mock-elm.hpp | 4 +- wiki/thinkPad.ichthyo.mm | 107 ++++++++++++++++++++++++++- 5 files changed, 193 insertions(+), 7 deletions(-) diff --git a/src/gui/ctrl/notification-hub.hpp b/src/gui/ctrl/notification-hub.hpp index 1aa58a043..8c809f414 100644 --- a/src/gui/ctrl/notification-hub.hpp +++ b/src/gui/ctrl/notification-hub.hpp @@ -29,6 +29,33 @@ ** On first use, an InfoboxPanel is allocated to hold an ErrorlogWidget 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 (*TODO* reflect this in the presentation), + ** and they cause the display widget to be expanded + ** (*TODO* 8/18 consider also call the doRevealYourself() function) + ** - 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-widget.hpp ** @see notification-service.hpp */ @@ -77,6 +104,57 @@ namespace ctrl { } + /* ==== Tangible interface ==== */ + + virtual bool + doReset() override + { + UNIMPLEMENTED ("Clear log contents, collapse widget, clear error state"); + } + + virtual bool + doExpand (bool yes) override + { + UNIMPLEMENTED ("Allocate Widget if necessary, expand widget"); + } + + virtual void + doRevealYourself() override + { + UNIMPLEMENTED ("TODO: how can the Log-Widget be 'revealed'?? Possibly allocate Widget, expand it. But how to make it visible?"); + } + + virtual bool + doMsg (string text) override + { + UNIMPLEMENTED ("Possibly allocate Widget, place text into its buffer. No need to expand"); + } + + virtual bool + doClearMsg () override + { + UNIMPLEMENTED ("remove all mere information messages"); + } + + virtual bool + doErr (string text) override + { + UNIMPLEMENTED ("Set error state. Allocate Widget if necessary, expand widget, place error message into its buffer"); + } + + virtual bool + doClearErr () override + { + UNIMPLEMENTED ("clear error state. If widget exists, turn all error entries into mere information entries"); + } + + virtual void + doFlash() override + { + UNIMPLEMENTED ("If widget exists: expand it, trigger its flash function (paint with timeout). TODO also doRevealYourself."); + } + + public: NotificationHub (ID identity, ctrl::BusTerm& nexus) : model::Controller{identity, nexus} diff --git a/src/gui/model/controller.hpp b/src/gui/model/controller.hpp index e9accebb8..7d4d5a085 100644 --- a/src/gui/model/controller.hpp +++ b/src/gui/model/controller.hpp @@ -118,10 +118,15 @@ namespace model { UNIMPLEMENTED ("Controller doFlash"); } + /** 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); } diff --git a/tests/gui/abstract-tangible-test.cpp b/tests/gui/abstract-tangible-test.cpp index fdd077f52..7e1f178dd 100644 --- a/tests/gui/abstract-tangible-test.cpp +++ b/tests/gui/abstract-tangible-test.cpp @@ -639,11 +639,11 @@ namespace test { .beforeEvent("create", "a") .beforeEvent("diff","create child \"b\"") // insert second child .beforeEvent("create", "b") - .beforeEvent("diff","set Attib α <-quadrant") // assign value to existing attribute α + .beforeEvent("diff","set Attrib α <-quadrant") // assign value to existing attribute α .beforeCall("buildMutator").on(&childB) // establish nested mutator for second child .beforeEvent("diff","b accepts mutation...") .beforeEvent("diff",">>Scope>> b") // recursively mutate second child - .beforeEvent("diff","++Attib++ π = 3.1415927")); // insert new attribute π within nested scope + .beforeEvent("diff","++Attrib++ π = 3.1415927")); // insert new attribute π within nested scope CHECK (nexusLog.verifyCall("routeAdd").arg(rootMock.getID(), memLocation(rootMock)) // rootMock was attached to Nexus diff --git a/tests/gui/test/mock-elm.hpp b/tests/gui/test/mock-elm.hpp index 374e70fc6..7489534bb 100644 --- a/tests/gui/test/mock-elm.hpp +++ b/tests/gui/test/mock-elm.hpp @@ -281,14 +281,14 @@ namespace test{ { string key{spec.idi.getSym()}, val{render(spec.data)}; - log_.event("diff", "++Attib++ "+key+" = "+val); + log_.event("diff", "++Attrib++ "+key+" = "+val); return {key, val}; }) .assignElement ([&](Attrib& target, GenNode const& spec) -> bool { string key{spec.idi.getSym()}, newVal{render (spec.data)}; - log_.event("diff", "set Attib "+key+" <-"+newVal); + log_.event("diff", "set Attrib "+key+" <-"+newVal); target.second = newVal; return true; }))); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 6f308c239..64260aa10 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -1715,7 +1715,7 @@ - + @@ -1761,11 +1761,13 @@ - + + + @@ -1779,6 +1781,107 @@ + + + + + + + + + + + + + + + + + + + + + + +

+ TODO: how can the Log-Widget be 'revealed'?? +

+

+ Possibly allocate Widget, expand it. But how to make it visible? +

+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + +
    +
  • + Set error state. +
  • +
  • + Allocate Widget if necessary +
  • +
  • + expand widget +
  • +
  • + place error message into its buffer" +
  • +
+ + +
+ +
+
+ + + + + + + + + + + + + + +

+ "If widget exists: expand it, trigger its flash function (paint with timeout). +

+

+ TODO also doRevealYourself." +

+ + +
+ +
+
+
+
From e829a74edff6d9171f193bf8c83f9f8d05efb72f Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 15 Aug 2018 15:32:20 +0200 Subject: [PATCH 012/178] NotificationDisplay: draft idea of managed link-to-widget a smart-reference based on sigc::trackable --- src/gui/model/w-link.hpp | 92 ++++++++++++++++++++++++++ tests/gui/model/w-link-test.cpp | 106 ++++++++++++++++++++++++++++++ wiki/thinkPad.ichthyo.mm | 113 ++++++++++++++++++++++++-------- 3 files changed, 282 insertions(+), 29 deletions(-) create mode 100644 src/gui/model/w-link.hpp create mode 100644 tests/gui/model/w-link-test.cpp diff --git a/src/gui/model/w-link.hpp b/src/gui/model/w-link.hpp new file mode 100644 index 000000000..8273a3d67 --- /dev/null +++ b/src/gui/model/w-link.hpp @@ -0,0 +1,92 @@ +/* + W-LINK.hpp - smart-link to GTK widget + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + 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 `sigc::trackable` without ownership. + ** The is statefull, can be reconnected, and automatically transitions into disconnected + ** state when the target dies. + ** + ** @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/symbol.hpp" + +#include +#include +//#include +//#include + + +namespace gui { +namespace model { + +// using std::string; +// using lib::Symbol; + + + /** + * Managed link to a `sigc::trackable` UI widget, without taking ownership. + */ + template + class WLink + { + static_assert (std::is_base_of() + ,"target type required to be sigc::trackable"); + + TAR* widget_; + + + public: + ~WLink() + { + + } + WLink() + { + + } + explicit + WLink(TAR& targetWidget) + : widget_{&targetWidget} + { } + + protected: + }; + + + + /** c */ + + + +}} // namespace gui::model +#endif /*GUI_MODEL_W_LINK_H*/ diff --git a/tests/gui/model/w-link-test.cpp b/tests/gui/model/w-link-test.cpp new file mode 100644 index 000000000..369f06610 --- /dev/null +++ b/tests/gui/model/w-link-test.cpp @@ -0,0 +1,106 @@ +/* + WLink(Test) - verify managed link to sigc::trackable widget + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + 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-test.cpp + ** unit test \ref WLink_test + */ + + +#include "lib/test/run.hpp" +#include "lib/test/test-helper.hpp" +#include "gui/model/w-link.hpp" +//#include "gen-node-location-query.hpp" +#include "lib/format-cout.hpp" +//#include "lib/idi/entry-id.hpp" +//#include "lib/diff/gen-node.hpp" +#include "lib/util.hpp" + +//#include +//#include + + +//using std::string; +//using lib::diff::MakeRec; +//using lib::diff::Rec; +//using lib::idi::EntryID; +//using lib::diff::GenNode; +using util::isSameObject; +using util::isnil; + + +namespace gui { +namespace model{ +namespace test { + +// using lumiera::error::LUMIERA_ERROR_WRONG_TYPE; +// using lib::test::showSizeof; + + + + + /******************************************************************************//** + * @test verify the usage pattern of low-level UI element access, based on a + * mock implementation of the accessor directory. + * @see w-link.hpp + */ + class WLink_test : public Test + { + + virtual void + run (Arg) + { + verify_standardUsage(); + verify_reconnect(); + verify_copy(); + } + + + /** @test the standard use case is to hold onto a widget + * owned by _someone else_, with automatic disconnection + */ + void + verify_standardUsage() + { + UNIMPLEMENTED ("demonstrate a simple usage scenario"); + } + + + void + verify_reconnect() + { + UNIMPLEMENTED ("change connection state"); + } + + + void + verify_copy() + { + UNIMPLEMENTED ("copy with magic"); + } + }; + + + /** Register this test class... */ + LAUNCHER (WLink_test, "unit gui"); + + +}}} // namespace gui::model::test diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 64260aa10..2e9348cca 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -22,7 +22,7 @@
- + @@ -1573,8 +1573,7 @@ in proc::asset::meta::theErrorLog_ID

- -
+
@@ -1768,6 +1767,10 @@ + + + + @@ -1810,8 +1813,7 @@ Possibly allocate Widget, expand it. But how to make it visible?

- - +
@@ -1849,8 +1851,7 @@ - - +
@@ -1875,12 +1876,14 @@ TODO also doRevealYourself."

- - +
+ + +
@@ -3156,8 +3159,68 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Name: WLink +

+ + +
+ +
+ + +
@@ -32241,8 +32304,7 @@ Ganz prominent fehlt hier also z.B: MIDI

- - +
@@ -32262,8 +32324,7 @@ die Aufgrund von Klassifikationen automatisch bereits existieren

- - +
@@ -32313,8 +32374,7 @@ das ErrorLog

- - + @@ -32332,8 +32392,7 @@ über den UI-Bus schickt, an einen Empfänger mit bekannter ID.

- -
+
@@ -32349,8 +32408,7 @@ dort gibt es eine ErrorLogView

- - +
@@ -32362,8 +32420,7 @@ und den Controller: NotificationHub

- -
+
@@ -32391,8 +32448,7 @@ dadurch werden Meta-Operationen auf gleiche Ebene gestellt wie normale Struktur-Manipulationen

- - +
@@ -32422,8 +32478,7 @@ und verlieren ihren magischen Charakter außerhalb der Event-Sourcing-Struktur

- - +
@@ -34112,7 +34167,7 @@ - + @@ -34129,7 +34184,7 @@

- +
From c47e3d0210a71c0a0ffdea4f66d23a659060e474 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 15 Aug 2018 16:24:03 +0200 Subject: [PATCH 013/178] WLink: draft basic behaviour --- src/gui/model/w-link.hpp | 32 ++++++++++++++++++++++++++- tests/gui/model/w-link-test.cpp | 35 +++++++++++++++++++++++++----- wiki/thinkPad.ichthyo.mm | 38 ++++++++++++++++++++++----------- 3 files changed, 86 insertions(+), 19 deletions(-) diff --git a/src/gui/model/w-link.hpp b/src/gui/model/w-link.hpp index 8273a3d67..be203c318 100644 --- a/src/gui/model/w-link.hpp +++ b/src/gui/model/w-link.hpp @@ -51,6 +51,7 @@ namespace model { // using std::string; // using lib::Symbol; + using lumiera::error::LUMIERA_ERROR_BOTTOM_VALUE; /** @@ -62,6 +63,7 @@ namespace model { static_assert (std::is_base_of() ,"target type required to be sigc::trackable"); + //mutable TAR* widget_; @@ -79,7 +81,35 @@ namespace model { : widget_{&targetWidget} { } - protected: + explicit + operator bool() const + { + return bool(widget_); + } + + + TAR& + operator* () const + { + __ensureAlive(); + return *widget_; + } + + TAR* + operator-> () const + { + __ensureAlive(); + return widget_; + } + + private: + void + __ensureAlive() const + { + if (not widget_) + throw lumiera::error::State ("zombie widget encountered" + , LERR_(BOTTOM_VALUE)); + } }; diff --git a/tests/gui/model/w-link-test.cpp b/tests/gui/model/w-link-test.cpp index 369f06610..eb11e1d79 100644 --- a/tests/gui/model/w-link-test.cpp +++ b/tests/gui/model/w-link-test.cpp @@ -32,10 +32,10 @@ #include "lib/format-cout.hpp" //#include "lib/idi/entry-id.hpp" //#include "lib/diff/gen-node.hpp" -#include "lib/util.hpp" +//#include "lib/util.hpp" //#include -//#include +#include //using std::string; @@ -43,8 +43,9 @@ //using lib::diff::Rec; //using lib::idi::EntryID; //using lib::diff::GenNode; -using util::isSameObject; -using util::isnil; +using std::make_unique; +//using util::isSameObject; +//using util::isnil; namespace gui { @@ -54,6 +55,16 @@ namespace test { // using lumiera::error::LUMIERA_ERROR_WRONG_TYPE; // using lib::test::showSizeof; + namespace { // Test fixture... + + template + struct DummyWidget + : public sigc::trackable + { + X val; + }; + } + @@ -80,7 +91,21 @@ namespace test { void verify_standardUsage() { - UNIMPLEMENTED ("demonstrate a simple usage scenario"); + using Wint = DummyWidget; + int r{rand() % 100}; + + auto widget = make_unique(); + widget->val = r; + + WLink link{*widget}; + CHECK (link); + link->val += 23; + CHECK (r+23 == widget->val); + + // kill widget + widget.reset(); + CHECK (not link); + VERIFY_ERROR (BOTTOM_VALUE, link->val ); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 2e9348cca..621b75948 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -1770,6 +1770,12 @@ + + + + + + @@ -3168,25 +3174,19 @@ - + - - - - - - - + - - + + - - + + @@ -3218,9 +3218,21 @@ - + + + + + + + + + + + + + From 0c29bd7c9214224b0ce669ffab2c2a72187bafb1 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 15 Aug 2018 17:02:39 +0200 Subject: [PATCH 014/178] WLink: implement the basic tracking functionality --- src/gui/model/w-link.hpp | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/src/gui/model/w-link.hpp b/src/gui/model/w-link.hpp index be203c318..9e62c98be 100644 --- a/src/gui/model/w-link.hpp +++ b/src/gui/model/w-link.hpp @@ -27,6 +27,9 @@ ** The is statefull, can be reconnected, and automatically transitions into disconnected ** state when the target dies. ** + ** @warning this class is not threadsafe, because lib SigC++ is not either, + ** and it can only be used reliably from within the GUI thread. + ** ** @see \ref WLink_test ** @see \ref NotificationHub (usage example) ** @@ -56,6 +59,7 @@ namespace model { /** * Managed link to a `sigc::trackable` UI widget, without taking ownership. + * @warning _not_ threadsafe */ template class WLink @@ -70,16 +74,21 @@ namespace model { public: ~WLink() { - + if (widget_) + disconnect (*widget_); } WLink() - { - - } + : widget_{nullptr} + { } + explicit WLink(TAR& targetWidget) : widget_{&targetWidget} - { } + { + connect (targetWidget); + } + + ////////////////////////////TODO copy operations explicit operator bool() const @@ -110,6 +119,25 @@ namespace model { throw lumiera::error::State ("zombie widget encountered" , LERR_(BOTTOM_VALUE)); } + + void + connect (sigc::trackable& target) + { + target.add_destroy_notify_callback (&widget_ + ,[](void* p) + { + TAR* & widgetPtr = *static_cast(p); + ASSERT (widgetPtr); + widgetPtr = nullptr; + return p; + }); + } + + void + disconnect (sigc::trackable& target) + { + target.remove_destroy_notify_callback (&widget_); + } }; From a15951b036c1ec6cc0bcd1ead2e7e57b54a09430 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 16 Aug 2018 02:10:31 +0200 Subject: [PATCH 015/178] WLink: implement connecting / reconnecting --- src/gui/model/w-link.hpp | 38 +++++++++++++---------- tests/gui/model/w-link-test.cpp | 53 ++++++++++++++++++++++++++++----- 2 files changed, 69 insertions(+), 22 deletions(-) diff --git a/src/gui/model/w-link.hpp b/src/gui/model/w-link.hpp index 9e62c98be..fce69b5f7 100644 --- a/src/gui/model/w-link.hpp +++ b/src/gui/model/w-link.hpp @@ -74,8 +74,7 @@ namespace model { public: ~WLink() { - if (widget_) - disconnect (*widget_); + this->clear(); } WLink() : widget_{nullptr} @@ -83,10 +82,8 @@ namespace model { explicit WLink(TAR& targetWidget) - : widget_{&targetWidget} - { - connect (targetWidget); - } + : widget_{attachTo (targetWidget)} + { } ////////////////////////////TODO copy operations @@ -96,7 +93,6 @@ namespace model { return bool(widget_); } - TAR& operator* () const { @@ -111,6 +107,23 @@ namespace model { return widget_; } + + void + clear() + { + if (widget_) + widget_->remove_destroy_notify_callback (&widget_); + widget_ = nullptr; + } + + void + connect (TAR& otherTarget) + { + if (widget_ == &otherTarget) return; + clear(); + widget_ = attachTo (otherTarget); + } + private: void __ensureAlive() const @@ -120,8 +133,8 @@ namespace model { , LERR_(BOTTOM_VALUE)); } - void - connect (sigc::trackable& target) + TAR* + attachTo (TAR& target) { target.add_destroy_notify_callback (&widget_ ,[](void* p) @@ -131,12 +144,7 @@ namespace model { widgetPtr = nullptr; return p; }); - } - - void - disconnect (sigc::trackable& target) - { - target.remove_destroy_notify_callback (&widget_); + return ⌖ } }; diff --git a/tests/gui/model/w-link-test.cpp b/tests/gui/model/w-link-test.cpp index eb11e1d79..2c9177904 100644 --- a/tests/gui/model/w-link-test.cpp +++ b/tests/gui/model/w-link-test.cpp @@ -32,7 +32,7 @@ #include "lib/format-cout.hpp" //#include "lib/idi/entry-id.hpp" //#include "lib/diff/gen-node.hpp" -//#include "lib/util.hpp" +#include "lib/util.hpp" //#include #include @@ -44,7 +44,7 @@ //using lib::idi::EntryID; //using lib::diff::GenNode; using std::make_unique; -//using util::isSameObject; +using util::isSameObject; //using util::isnil; @@ -61,7 +61,7 @@ namespace test { struct DummyWidget : public sigc::trackable { - X val; + X val = 1 + rand() % 100; }; } @@ -92,10 +92,8 @@ namespace test { verify_standardUsage() { using Wint = DummyWidget; - int r{rand() % 100}; - auto widget = make_unique(); - widget->val = r; + int r = widget->val; WLink link{*widget}; CHECK (link); @@ -112,7 +110,48 @@ namespace test { void verify_reconnect() { - UNIMPLEMENTED ("change connection state"); + using Wint = DummyWidget; + + auto w1 = make_unique(); + auto w2 = make_unique(); + int r1 = w1->val; + int r2 = w2->val; + + WLink l1; + WLink l2{*w1}; + CHECK (not l1); + CHECK ( l2); + + l2->val = r1; + l1.connect(*l2); + ++l1->val; + CHECK (w1->val == r1+1); + CHECK (isSameObject (*l1, *l2)); + + l2.connect(*w2); + CHECK (not isSameObject (*l1, *l2)); + w2->val = r2; + CHECK (r1+1 == l1->val); + CHECK (r2 == l2->val); + + w1.reset(); // kill first widget + CHECK (not l1); + CHECK ( l2); + l2->val *= -10; + l2.clear(); + CHECK (not l1); + CHECK (not l2); + CHECK (-10*r2 == w2->val); + l1.connect(*w2); + l2.connect(*l1); + CHECK (-10*r2 == l2->val); + CHECK (isSameObject (*l1, *l2)); + CHECK (isSameObject (*l1, *w2)); + + // implicitly kill second widget + *w2 = Wint{}; + CHECK (not l1); + CHECK (not l2); } From 22cdfbff2c8771597a0537c9dbf51c8c64f11b54 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 16 Aug 2018 02:34:47 +0200 Subject: [PATCH 016/178] WLink: fill in some documentation --- src/gui/model/w-link.hpp | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/gui/model/w-link.hpp b/src/gui/model/w-link.hpp index fce69b5f7..5fa4d419d 100644 --- a/src/gui/model/w-link.hpp +++ b/src/gui/model/w-link.hpp @@ -23,12 +23,23 @@ /** @file w-link.hpp ** A smart link to an GTK widget with automatic disconnection. - ** Allows to hold a connection to a `sigc::trackable` without ownership. - ** The is statefull, can be reconnected, and automatically transitions into disconnected - ** state when the target dies. + ** 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. ** ** @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) @@ -59,6 +70,8 @@ namespace model { /** * 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. * @warning _not_ threadsafe */ template @@ -67,7 +80,6 @@ namespace model { static_assert (std::is_base_of() ,"target type required to be sigc::trackable"); - //mutable TAR* widget_; @@ -108,6 +120,7 @@ namespace model { } + /** detach and deactivate this link */ void clear() { @@ -116,6 +129,8 @@ namespace model { widget_ = nullptr; } + /** (re)connect this smart link to the given target. + * Any previously existing link is detached beforehand */ void connect (TAR& otherTarget) { @@ -133,6 +148,9 @@ namespace model { , LERR_(BOTTOM_VALUE)); } + /** @internal installs the necessary callback + * to detach this link in case the target is destroyed + */ TAR* attachTo (TAR& target) { @@ -149,10 +167,5 @@ namespace model { }; - - /** c */ - - - }} // namespace gui::model #endif /*GUI_MODEL_W_LINK_H*/ From baa3d2bff0baeb1f2f66b6e7dfe7cce66fce577b Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 16 Aug 2018 03:31:22 +0200 Subject: [PATCH 017/178] WLink: define expected copy behaviour --- tests/gui/model/w-link-test.cpp | 57 +++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/tests/gui/model/w-link-test.cpp b/tests/gui/model/w-link-test.cpp index 2c9177904..fa157337c 100644 --- a/tests/gui/model/w-link-test.cpp +++ b/tests/gui/model/w-link-test.cpp @@ -34,7 +34,7 @@ //#include "lib/diff/gen-node.hpp" #include "lib/util.hpp" -//#include +#include #include @@ -43,8 +43,9 @@ //using lib::diff::Rec; //using lib::idi::EntryID; //using lib::diff::GenNode; -using std::make_unique; using util::isSameObject; +using std::make_unique; +using std::move; //using util::isnil; @@ -158,7 +159,57 @@ namespace test { void verify_copy() { - UNIMPLEMENTED ("copy with magic"); + using Wint = DummyWidget; + auto w1 = make_unique(); + auto w2 = make_unique(); + + WLink l1; + WLink l2{l1}; + CHECK (not l2); + l2.connect(*w1); + + WLink l3{l2}; + CHECK (l3); + CHECK (w1->val == l3->val); + + CHECK (not l1); // they are statefull and independent + l1 = move(l2); + CHECK (not l2); + CHECK (l1); + CHECK (isSameObject (*l1, *l3)); + + l2 = WLink{WLink{*w2}}; + CHECK (w2->val == l2->val); + + l1 = l3; + CHECK (w1->val == l1->val); + WLink& ref{l1}; + l1 = move(ref); + CHECK (w1->val == l1->val); + CHECK (w1->val == l3->val); + + std::swap (l2, l3); + CHECK (w1->val == l1->val); + CHECK (w1->val == l2->val); + CHECK (w2->val == l3->val); + + w1.reset(); + CHECK (not l1); + CHECK (not l2); + CHECK (w2->val == l3->val); + + using Wuint = DummyWidget; + auto uu = make_unique(); + WLink lu{*uu}; + +///////////////does not compile... +// l1 = uu; +// l1.connect(*uu); + + // but it is a compile time check... + l1 = reinterpret_cast&&> (uu); + CHECK ((int)uu->val == l1->val); + CHECK (not lu); } }; From ae26012bf56618f2f9f48314e03d9b30805775d4 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 16 Aug 2018 04:57:55 +0200 Subject: [PATCH 018/178] WLink: implement copy operations swap-based implementation not sure if attachTo() should be noexcept --- src/gui/model/w-link.hpp | 72 ++++++++++++++++++++++++--------- tests/gui/model/w-link-test.cpp | 20 ++------- wiki/thinkPad.ichthyo.mm | 28 ++++++------- 3 files changed, 70 insertions(+), 50 deletions(-) diff --git a/src/gui/model/w-link.hpp b/src/gui/model/w-link.hpp index 5fa4d419d..552f2e9c9 100644 --- a/src/gui/model/w-link.hpp +++ b/src/gui/model/w-link.hpp @@ -52,19 +52,14 @@ #include "lib/error.hpp" -//#include "lib/symbol.hpp" #include #include -//#include -//#include namespace gui { namespace model { -// using std::string; -// using lib::Symbol; using lumiera::error::LUMIERA_ERROR_BOTTOM_VALUE; @@ -93,11 +88,39 @@ namespace model { { } explicit - WLink(TAR& targetWidget) + WLink (TAR& targetWidget) : widget_{attachTo (targetWidget)} { } - ////////////////////////////TODO copy operations + 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) + { + swap (*this, other); + return *this; + } + + friend void + swap (WLink& l, WLink& r) + { + TAR* tl = l.widget_; + TAR* tr = r.widget_; + if (tl == tr) return; + l.clear(); + r.clear(); + if (tr) l.widget_ = l.attachTo (*tr); + if (tl) r.widget_ = r.attachTo (*tl); + } + explicit operator bool() const @@ -122,17 +145,20 @@ namespace model { /** detach and deactivate this link */ void - clear() + clear() noexcept { if (widget_) - widget_->remove_destroy_notify_callback (&widget_); + try { + widget_->remove_destroy_notify_callback (&widget_); + } + catch(...) { } widget_ = nullptr; } /** (re)connect this smart link to the given target. * Any previously existing link is detached beforehand */ void - connect (TAR& otherTarget) + connect (TAR& otherTarget) noexcept { if (widget_ == &otherTarget) return; clear(); @@ -152,17 +178,23 @@ namespace model { * to detach this link in case the target is destroyed */ TAR* - attachTo (TAR& target) + attachTo (TAR& target) noexcept { - target.add_destroy_notify_callback (&widget_ - ,[](void* p) - { - TAR* & widgetPtr = *static_cast(p); - ASSERT (widgetPtr); - widgetPtr = nullptr; - return p; - }); - return ⌖ + try { + target.add_destroy_notify_callback (&widget_ + ,[](void* p) + { + TAR* & widgetPtr = *static_cast(p); + ASSERT (widgetPtr); + widgetPtr = nullptr; + return p; + }); + return ⌖ + } + catch(...) + { + return nullptr; + } } }; diff --git a/tests/gui/model/w-link-test.cpp b/tests/gui/model/w-link-test.cpp index fa157337c..6ae177ed0 100644 --- a/tests/gui/model/w-link-test.cpp +++ b/tests/gui/model/w-link-test.cpp @@ -28,33 +28,21 @@ #include "lib/test/run.hpp" #include "lib/test/test-helper.hpp" #include "gui/model/w-link.hpp" -//#include "gen-node-location-query.hpp" -#include "lib/format-cout.hpp" -//#include "lib/idi/entry-id.hpp" -//#include "lib/diff/gen-node.hpp" #include "lib/util.hpp" #include #include -//using std::string; -//using lib::diff::MakeRec; -//using lib::diff::Rec; -//using lib::idi::EntryID; -//using lib::diff::GenNode; using util::isSameObject; using std::make_unique; using std::move; -//using util::isnil; namespace gui { namespace model{ namespace test { -// using lumiera::error::LUMIERA_ERROR_WRONG_TYPE; -// using lib::test::showSizeof; namespace { // Test fixture... @@ -70,8 +58,7 @@ namespace test { /******************************************************************************//** - * @test verify the usage pattern of low-level UI element access, based on a - * mock implementation of the accessor directory. + * @test verify proper behaviour of a smart-link to a `sigc::trackable` GTK widget. * @see w-link.hpp */ class WLink_test : public Test @@ -108,6 +95,7 @@ namespace test { } + /** @test registration is automatically maintained when re-assigning targets */ void verify_reconnect() { @@ -202,12 +190,12 @@ namespace test { auto uu = make_unique(); WLink lu{*uu}; -///////////////does not compile... +///////////////does not compile... // l1 = uu; // l1.connect(*uu); // but it is a compile time check... - l1 = reinterpret_cast&&> (uu); + l1 = reinterpret_cast&&> (lu); CHECK ((int)uu->val == l1->val); CHECK (not lu); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 621b75948..cc4018dac 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -3188,20 +3188,20 @@ - - + + - - + + - - + + - - + + @@ -3223,14 +3223,14 @@ - - + + - - + + - - + + From a74dc596ce258cefc1f9c38543b210f866b4c392 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 16 Aug 2018 18:22:32 +0200 Subject: [PATCH 019/178] WLink: finished incl. exception handling guarantees and documentation --- src/gui/model/w-link.hpp | 71 ++++++++++++++++++++++++--------- tests/51-gui-model.tests | 10 +++++ tests/gui/model/w-link-test.cpp | 14 ++++++- wiki/thinkPad.ichthyo.mm | 25 ++++++++---- 4 files changed, 91 insertions(+), 29 deletions(-) diff --git a/src/gui/model/w-link.hpp b/src/gui/model/w-link.hpp index 552f2e9c9..923f0657f 100644 --- a/src/gui/model/w-link.hpp +++ b/src/gui/model/w-link.hpp @@ -25,7 +25,14 @@ ** 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. + ** 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. @@ -52,6 +59,7 @@ #include "lib/error.hpp" +#include "lib/format-string.hpp" #include #include @@ -60,7 +68,10 @@ namespace gui { namespace model { - using lumiera::error::LUMIERA_ERROR_BOTTOM_VALUE; + namespace error = lumiera::error; + using error::LUMIERA_ERROR_BOTTOM_VALUE; + + using util::_Fmt; /** @@ -68,6 +79,7 @@ namespace model { * Automatically installs a callback to switch this link into detached state * when the target (widget) is destroyed. * @warning _not_ threadsafe + * @note only EX_SANE, since attaching, detaching and swapping might throw. */ template class WLink @@ -81,9 +93,12 @@ namespace model { public: ~WLink() { - this->clear(); + try { + this->clear(); + } + ERROR_LOG_AND_IGNORE (gui, "Detaching managed WLink from Widget") } - WLink() + WLink() noexcept : widget_{nullptr} { } @@ -103,20 +118,28 @@ namespace model { } WLink& - operator= (WLink other) + 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; - l.clear(); r.clear(); + l.clear(); if (tr) l.widget_ = l.attachTo (*tr); if (tl) r.widget_ = r.attachTo (*tl); } @@ -143,22 +166,26 @@ namespace model { } - /** detach and deactivate this link */ + /** 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() noexcept + clear() { if (widget_) - try { - widget_->remove_destroy_notify_callback (&widget_); - } - catch(...) { } + widget_->remove_destroy_notify_callback (&widget_); widget_ = nullptr; } /** (re)connect this smart link to the given target. - * Any previously existing link is detached beforehand */ + * 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) noexcept + connect (TAR& otherTarget) { if (widget_ == &otherTarget) return; clear(); @@ -170,15 +197,16 @@ namespace model { __ensureAlive() const { if (not widget_) - throw lumiera::error::State ("zombie widget encountered" - , LERR_(BOTTOM_VALUE)); + 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) noexcept + attachTo (TAR& target) { try { target.add_destroy_notify_callback (&widget_ @@ -191,9 +219,14 @@ namespace model { }); return ⌖ } - catch(...) + catch (std::exception& problem) { - return nullptr; + 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); } } }; diff --git a/tests/51-gui-model.tests b/tests/51-gui-model.tests index 22f7cf813..01bf86b76 100644 --- a/tests/51-gui-model.tests +++ b/tests/51-gui-model.tests @@ -17,6 +17,16 @@ return: 0 END +PLANNED "low-level widget access" ElementAccess_test <&&> (lu); CHECK ((int)uu->val == l1->val); - CHECK (not lu); + CHECK (not lu); // assignment was actually a move + + // even the subversively attached link is managed properly + uu.reset(); + CHECK (not l1); + + // others unaffected... + CHECK (not l2); + CHECK (l3); } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index cc4018dac..302e53143 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -3174,13 +3174,13 @@ - + - - - - - + + + + + @@ -3218,8 +3218,8 @@ - - + + @@ -3233,6 +3233,15 @@ + + + + + + + + + From 42cbc9219fd4c3a1946be056036c05798660b73b Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 17 Aug 2018 13:36:31 +0200 Subject: [PATCH 020/178] NotificateDisplay: link to a widget for error log display The ErrorLogWidget is allocated and placed elsewhere and not owned by the NotificationHub controller. --- src/gui/ctrl/notification-hub.hpp | 14 ++++++++++---- src/gui/id-scheme.hpp | 4 ++-- src/gui/panel/infobox-panel.hpp | 4 ++-- ...rror-log-widget.hpp => error-log-display.hpp} | 16 ++++++++-------- wiki/thinkPad.ichthyo.mm | 10 ++++++---- 5 files changed, 28 insertions(+), 20 deletions(-) rename src/gui/widget/{error-log-widget.hpp => error-log-display.hpp} (92%) diff --git a/src/gui/ctrl/notification-hub.hpp b/src/gui/ctrl/notification-hub.hpp index 8c809f414..67a87f63e 100644 --- a/src/gui/ctrl/notification-hub.hpp +++ b/src/gui/ctrl/notification-hub.hpp @@ -56,7 +56,7 @@ ** 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-widget.hpp + ** @see error-log-display.hpp ** @see notification-service.hpp */ @@ -64,9 +64,10 @@ #ifndef GUI_CTRL_NOTIFICATION_HUB_H #define GUI_CTRL_NOTIFICATION_HUB_H -#include "gui/widget/error-log-widget.hpp" -#include "gui/model/controller.hpp" +#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 //#include @@ -76,6 +77,7 @@ namespace gui { namespace ctrl { using lib::diff::TreeMutator; + using model::WLink; @@ -90,6 +92,7 @@ namespace ctrl { : public model::Controller { + /** content population and manipulation via UI-Bus */ void buildMutator (lib::diff::TreeMutator::Handle buffer) override @@ -100,7 +103,7 @@ namespace ctrl { buffer.create ( TreeMutator::build() ); - UNIMPLEMENTED ("create a sensible binding between AssetManager in the session and AssetController in the UI"); + UNIMPLEMENTED ("define and implement what need to be reflected from asset::ErrorLog"); } @@ -164,6 +167,9 @@ namespace ctrl { private: + /** collaboration with a log display allocated elsewhere */ + WLink widget_; + }; diff --git a/src/gui/id-scheme.hpp b/src/gui/id-scheme.hpp index 6b225ac70..ff10add1b 100644 --- a/src/gui/id-scheme.hpp +++ b/src/gui/id-scheme.hpp @@ -75,7 +75,7 @@ namespace gui { class TimelineController; } namespace widget { - class ErrorLogWidget; + class ErrorLogDisplay; } namespace idi { @@ -88,7 +88,7 @@ namespace gui { /* === primary component view types === */ using TimelineView = timeline::TimelineController; - using ErrorLogView = widget::ErrorLogWidget; + using ErrorLogView = widget::ErrorLogDisplay; /** diff --git a/src/gui/panel/infobox-panel.hpp b/src/gui/panel/infobox-panel.hpp index 3b508bcba..b97060852 100644 --- a/src/gui/panel/infobox-panel.hpp +++ b/src/gui/panel/infobox-panel.hpp @@ -36,7 +36,7 @@ #include "gui/panel/panel.hpp" -#include "gui/widget/error-log-widget.hpp" +#include "gui/widget/error-log-display.hpp" namespace gui { namespace panel{ @@ -62,7 +62,7 @@ namespace panel{ Gtk::ButtonBox buttons_; Gtk::Button button_1_; Gtk::Frame frame_; - widget::ErrorLogWidget errorLog_; + widget::ErrorLogDisplay errorLog_; void experiment_1(); }; diff --git a/src/gui/widget/error-log-widget.hpp b/src/gui/widget/error-log-display.hpp similarity index 92% rename from src/gui/widget/error-log-widget.hpp rename to src/gui/widget/error-log-display.hpp index 4eebcfe7b..2c7d436bb 100644 --- a/src/gui/widget/error-log-widget.hpp +++ b/src/gui/widget/error-log-display.hpp @@ -1,5 +1,5 @@ /* - ERROR-LOG-WIDGET.hpp - display of error messages in a text box + ERROR-LOG-DISPLAY.hpp - display of error messages in a text box Copyright (C) Lumiera.org 2017, Hermann Vosseler @@ -21,7 +21,7 @@ */ -/** @file error-log-widget.hpp +/** @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 @@ -33,8 +33,8 @@ */ -#ifndef GUI_WIDGET_ERROR_LOG_WIDGET_H -#define GUI_WIDGET_ERROR_LOG_WIDGET_H +#ifndef GUI_WIDGET_ERROR_LOG_DISPLAY_H +#define GUI_WIDGET_ERROR_LOG_DISPLAY_H #include "gui/gtk-base.hpp" #include "include/gui-notification-facade.h" @@ -55,13 +55,13 @@ namespace widget { * Just a text display box with scrollbars. * Need to add formatting etc. */ - class ErrorLogWidget + class ErrorLogDisplay : public Gtk::ScrolledWindow { public: - ~ErrorLogWidget() { }; + ~ErrorLogDisplay() { }; - ErrorLogWidget() + ErrorLogDisplay() : Gtk::ScrolledWindow() , textLog_{} { @@ -118,4 +118,4 @@ namespace widget { }}// namespace gui::widget -#endif /*GUI_WIDGET_ERROR_LOG_WIDGET_H*/ +#endif /*GUI_WIDGET_ERROR_LOG_DISPLAY_H*/ diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 302e53143..b617519ed 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -1773,20 +1773,22 @@ - + + - + + - + - + From e7e09a642bd5cc2a4c1957730b80005c278d676e Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 17 Aug 2018 14:19:37 +0200 Subject: [PATCH 021/178] NotificateDisplay: delegate view allocation through a functor --- src/gui/ctrl/notification-hub.hpp | 11 ++++++++++- src/gui/interact/wizard.cpp | 20 +++++++++++++++++++- src/gui/interact/wizard.hpp | 6 +++++- wiki/thinkPad.ichthyo.mm | 8 ++++---- 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/gui/ctrl/notification-hub.hpp b/src/gui/ctrl/notification-hub.hpp index 67a87f63e..16d25de80 100644 --- a/src/gui/ctrl/notification-hub.hpp +++ b/src/gui/ctrl/notification-hub.hpp @@ -69,6 +69,7 @@ #include "gui/model/controller.hpp" #include "gui/model/w-link.hpp" +#include //#include //#include @@ -159,14 +160,22 @@ namespace ctrl { public: - NotificationHub (ID identity, ctrl::BusTerm& nexus) + using WidgetAllocator = std::function; + + + NotificationHub (ID identity, ctrl::BusTerm& nexus, WidgetAllocator wa) : model::Controller{identity, nexus} + , allocateWidget_{wa} + , widget_{} { } ~NotificationHub() { }; private: + /** external operation to find or allocate an log display widget */ + WidgetAllocator allocateWidget_; + /** collaboration with a log display allocated elsewhere */ WLink widget_; diff --git a/src/gui/interact/wizard.cpp b/src/gui/interact/wizard.cpp index 49921c692..fec5d3eab 100644 --- a/src/gui/interact/wizard.cpp +++ b/src/gui/interact/wizard.cpp @@ -68,7 +68,12 @@ namespace interact { Wizard::Wizard (GlobalCtx& globals) : globalCtx_{globals} - , notificationHub_{new NotificationHub{getErrorLogID(), globals.uiBus_.getAccessPoint()}} + , notificationHub_{new NotificationHub{getErrorLogID() + ,globals.uiBus_.getAccessPoint() + ,[&]() -> widget::ErrorLogDisplay& + { + return allocateErrorLogView(); + }} } { } @@ -107,4 +112,17 @@ namespace interact { } + + /** @internal reach out to attach or allocate a display widget for the error log. + * @todo as of 8/2018 we use "inside knowledge" to create this view in a hard wired location. + * But in the end, this allocation task should be delegated to the ViewLocator + */ + widget::ErrorLogDisplay& + Wizard::allocateErrorLogView() + { + UNIMPLEMENTED("reach out to attach or allocate a display widget for the error log"); + } + + + }}// namespace gui::interact diff --git a/src/gui/interact/wizard.hpp b/src/gui/interact/wizard.hpp index 213115302..8fb24532f 100644 --- a/src/gui/interact/wizard.hpp +++ b/src/gui/interact/wizard.hpp @@ -64,6 +64,9 @@ namespace ctrl { class GlobalCtx; class NotificationHub; } +namespace widget { + class ErrorLogDisplay; +} namespace interact { using std::unique_ptr; @@ -92,8 +95,9 @@ namespace interact { void show_HelpAbout(); static ID getErrorLogID() { return proc::asset::meta::theErrorLog_ID; } - private: + private: + widget::ErrorLogDisplay& allocateErrorLogView(); }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index b617519ed..b4da59e58 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -1767,11 +1767,11 @@ - + - - - + + + From 987aad44c1dcee36f38359c364bfb895fa696b1f Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 17 Aug 2018 15:59:52 +0200 Subject: [PATCH 022/178] DockAccess: add ability to retrieve a panel via PanelManager (#1144) This is only a premature hack, since the whole structure of PanelManager is somewhat broken. Moreover, the ViewLocator is not really ready for use yet, so this hack at least allows us to "reach into" a top-level window and "grab" the pannel we need. --- src/gui/workspace/panel-manager.cpp | 9 +- src/gui/workspace/panel-manager.hpp | 3 +- wiki/thinkPad.ichthyo.mm | 180 ++++++++++++++++++++++++---- 3 files changed, 162 insertions(+), 30 deletions(-) diff --git a/src/gui/workspace/panel-manager.cpp b/src/gui/workspace/panel-manager.cpp index 9df74186c..6d5ab5bc9 100644 --- a/src/gui/workspace/panel-manager.cpp +++ b/src/gui/workspace/panel-manager.cpp @@ -136,7 +136,7 @@ namespace workspace { } - void + panel::Panel& PanelManager::showPanel (const int description_index) { // Try and find the panel and present it if possible @@ -149,9 +149,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 +160,9 @@ namespace workspace { // Dock the item dock_.add_item(new_panel->getDockItem(), Gdl::DOCK_FLOATING); + + ENSURE (new_panel); + return *new_panel; } diff --git a/src/gui/workspace/panel-manager.hpp b/src/gui/workspace/panel-manager.hpp index 3608618a5..e78a87716 100644 --- a/src/gui/workspace/panel-manager.hpp +++ b/src/gui/workspace/panel-manager.hpp @@ -114,8 +114,9 @@ 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); /** * Switches a panel from one type to another, diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index b4da59e58..7699076f0 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -1467,8 +1467,8 @@ - - + + @@ -1489,7 +1489,7 @@ - + @@ -1608,7 +1608,7 @@ - + @@ -1621,7 +1621,7 @@

- + @@ -1629,6 +1629,12 @@ + + + + + + @@ -1662,21 +1668,29 @@ + + + + + - + + + + - + - + @@ -1686,7 +1700,7 @@ - + @@ -1889,7 +1903,8 @@ - + + @@ -2219,7 +2234,7 @@ - + @@ -2248,7 +2263,7 @@ - + @@ -3345,14 +3360,15 @@ - + + - + @@ -3387,7 +3403,7 @@ - + @@ -3431,7 +3447,7 @@ - + @@ -13967,7 +13983,7 @@ - + @@ -14345,7 +14361,7 @@ - + @@ -14356,9 +14372,28 @@ - + + - + + + + + + + + + + + + + + + + + + + @@ -14606,7 +14641,7 @@ - + @@ -33201,7 +33236,7 @@ - + @@ -33226,6 +33261,7 @@ + @@ -33266,7 +33302,8 @@ - + + @@ -34225,7 +34262,7 @@ - + @@ -34276,7 +34313,98 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ A dock item is a container widget that can be docked at different place. +

+

+ It accepts a single child and adds a grip allowing the user to click on it +

+

+ to drag and drop the widget. +

+

+ +

+

+ The grip is implemented as a GdlDockItemGrip +

+ + +
+
+
+ + + + +
+ + + + + + + + + + + @@ -34639,7 +34767,7 @@ - + From c0dca2d9784b3fdee903e81e7747317de4ae10c9 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 17 Aug 2018 16:27:51 +0200 Subject: [PATCH 023/178] DockAccess: add lookup-by-Type function to PanelManager ...now the mess multiplies --- src/gui/workspace/dock-area.cpp | 10 +++++++--- src/gui/workspace/dock-area.hpp | 28 ++++++++++++++++++++++++++-- src/gui/workspace/panel-manager.cpp | 2 +- src/gui/workspace/panel-manager.hpp | 21 ++++++++++++++++++++- wiki/thinkPad.ichthyo.mm | 28 ++++++++++++++++++++++++++-- 5 files changed, 80 insertions(+), 9 deletions(-) diff --git a/src/gui/workspace/dock-area.cpp b/src/gui/workspace/dock-area.cpp index 54df374aa..67918d2fc 100644 --- a/src/gui/workspace/dock-area.cpp +++ b/src/gui/workspace/dock-area.cpp @@ -143,7 +143,7 @@ namespace workspace { } - void + panel::Panel& DockArea::showPanel (const int description_index) { // Try and find the panel and present it if possible @@ -158,7 +158,8 @@ namespace workspace { Gdl::DockItem &dock_item = panel->getDockItem(); // ENSURE(dock_item); dock_item.present(dock_); - return; + ENSURE (panel); + return *panel; } } @@ -167,6 +168,9 @@ namespace workspace { // Dock the item dock_.add_item(new_panel->getDockItem(), Gdl::DOCK_FLOATING); + + ENSURE (new_panel); + return *new_panel; } @@ -256,7 +260,7 @@ namespace workspace { int - DockArea::findPanelDescription (const char* class_name) const + DockArea::findPanelDescription (const char* class_name) { REQUIRE(class_name); diff --git a/src/gui/workspace/dock-area.hpp b/src/gui/workspace/dock-area.hpp index eb4b50f84..aaf63275d 100644 --- a/src/gui/workspace/dock-area.hpp +++ b/src/gui/workspace/dock-area.hpp @@ -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,9 @@ 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); /** * Switches a panel from one type to another, @@ -138,6 +143,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 + static int findPanelID(); + /** Gets the number of panel descriptions. */ static int getPanelDescriptionCount(); @@ -166,7 +179,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 +351,16 @@ namespace workspace { }; + + + template + inline int + DockArea::findPanelID() + { + return DockArea::findPanelDescription (typeid(P).name()); + } + + + }}// namespace gui::workspace #endif /*GUI_WORKSPACE_DOCK_AREA_H*/ diff --git a/src/gui/workspace/panel-manager.cpp b/src/gui/workspace/panel-manager.cpp index 6d5ab5bc9..73a2619fb 100644 --- a/src/gui/workspace/panel-manager.cpp +++ b/src/gui/workspace/panel-manager.cpp @@ -252,7 +252,7 @@ namespace workspace { int - PanelManager::findPanelDescription (const char* class_name) const + PanelManager::findPanelDescription (const char* class_name) { REQUIRE(class_name); diff --git a/src/gui/workspace/panel-manager.hpp b/src/gui/workspace/panel-manager.hpp index e78a87716..d1ed12fce 100644 --- a/src/gui/workspace/panel-manager.hpp +++ b/src/gui/workspace/panel-manager.hpp @@ -136,6 +136,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 + static int findPanelID(); + /** Gets the number of panel descriptions. */ static int getPanelDescriptionCount(); @@ -164,7 +172,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. @@ -335,5 +343,16 @@ namespace workspace { }; + + + template + inline int + PanelManager::findPanelID() + { + return PanelManager::findPanelDescription (typeid(P).name()); + } + + + }}// namespace gui::workspace #endif /*GUI_WORKSPACE_PANEL_MANAGER_H*/ diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 7699076f0..b3d7cff79 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -1668,9 +1668,15 @@ - + + - + + + + + + @@ -14378,6 +14384,24 @@ + + + + + + + + + + + + + + + + + + From 8755153d9509ca923846202e3e422a0250ff28b9 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 17 Aug 2018 18:12:45 +0200 Subject: [PATCH 024/178] DockAccess: consider a preliminary lookup implementation within PanelLocator PanelLocator is a sub component of the WindowLocator (top-level GUI service). Eventually this shall become a mere widget/component access service, with the actual lookup and allocation logic layered on top through ViewLocator, configurable via ViewSpec-DSL. We can not implement the full scheme right now, since we're lacking knowledge about internals of a typical Lumiera UI widget --- src/gui/ctrl/panel-locator.hpp | 63 +++++++++++++++++++++-- src/gui/ctrl/window-locator.cpp | 16 +++++- wiki/thinkPad.ichthyo.mm | 91 +++++++++++++++++++++++++++++++-- 3 files changed, 160 insertions(+), 10 deletions(-) diff --git a/src/gui/ctrl/panel-locator.hpp b/src/gui/ctrl/panel-locator.hpp index a618e4901..2331dc664 100644 --- a/src/gui/ctrl/panel-locator.hpp +++ b/src/gui/ctrl/panel-locator.hpp @@ -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 #include #include 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 + PAN& find_or_create(); + + private: + panel::Panel& preliminary_impl_PanelLookup (int typeID); }; - /** */ + /** @remark delegating front-end for type safety and error handling. */ + template + inline PAN& + PanelLocator::find_or_create() + { + static_assert (std::is_base_of(), "Lumiera Panel subclass expected"); + + int typeID = workspace::PanelManager::findPanelID(); + if (-1 == typeID) + throw error::Invalid (_Fmt{"Requested type «%s» not usable as docking panel."} + % util::typeStr() + ,error::LERR_(WRONG_TYPE)); + + return dynamic_cast (preliminary_impl_PanelLookup (typeID)); + } diff --git a/src/gui/ctrl/window-locator.cpp b/src/gui/ctrl/window-locator.cpp index 9dc8d7dda..c1152bd45 100644 --- a/src/gui/ctrl/window-locator.cpp +++ b/src/gui/ctrl/window-locator.cpp @@ -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,20 @@ 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) + { + UNIMPLEMENTED ("search for existing panel in all windows, possibly create"); + } + + + }}// namespace gui::ctrl diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index b3d7cff79..e3352f331 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -1641,7 +1641,8 @@ - + + @@ -1668,7 +1669,7 @@ - + @@ -1681,6 +1682,18 @@ + + + + + + + + + + + + @@ -14373,8 +14386,9 @@ - + + @@ -14384,7 +14398,7 @@ - + @@ -14467,13 +14481,80 @@ - + + + + + + + + + + +

+ ...weil ich Stand 8/2018 nicht im Stande bin, +

+

+ den ViewLocator wirklich soweit fertig zu coden, daß er schon einsetzbar ist. +

+

+ Problem ist die ganze abstrahierte Widget-Access-Schicht, die sich erst sinnvoll bauen läßt, +

+

+ wenn es schon wirkliche und funktionierende Widgets im System gibt. +

+ + +
+ + +
+ + + + + + + + + + + + + + + + + +

+ ...bisher erzeugt die lookup-Operation automatisch +

+ + +
+ +
+ + + + + + + + + +
+ + + +
+
From dcde80c4cdbbd2e7409b4727d71f0c831a1efd40 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 17 Aug 2018 18:17:13 +0200 Subject: [PATCH 025/178] DockAccess: wire the new service through the Wizard --- src/gui/interact/wizard.cpp | 2 ++ wiki/thinkPad.ichthyo.mm | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/gui/interact/wizard.cpp b/src/gui/interact/wizard.cpp index fec5d3eab..35f288963 100644 --- a/src/gui/interact/wizard.cpp +++ b/src/gui/interact/wizard.cpp @@ -33,6 +33,7 @@ #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/ctrl/notification-hub.hpp" #include "gui/ctrl/global-ctx.hpp" #include "lib/format-string.hpp" @@ -120,6 +121,7 @@ namespace interact { widget::ErrorLogDisplay& Wizard::allocateErrorLogView() { + auto& infoBox = globalCtx_.windowLoc_.locatePanel().find_or_create(); UNIMPLEMENTED("reach out to attach or allocate a display widget for the error log"); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index e3352f331..60a3d07f4 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -14526,7 +14526,7 @@ - + @@ -14540,7 +14540,7 @@ - + @@ -14550,8 +14550,8 @@ - - + + From 855944eff3345be2d134a092cbfad56c7f37ba25 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 17 Aug 2018 18:54:13 +0200 Subject: [PATCH 026/178] NotificationDisplay: turn the error log into an optional display --- src/gui/interact/wizard.cpp | 2 +- src/gui/panel/infobox-panel.cpp | 30 +++++++++++++++---- src/gui/panel/infobox-panel.hpp | 11 +++++-- wiki/thinkPad.ichthyo.mm | 52 +++++++++++++++++++++++++++++++-- 4 files changed, 83 insertions(+), 12 deletions(-) diff --git a/src/gui/interact/wizard.cpp b/src/gui/interact/wizard.cpp index 35f288963..16aee2056 100644 --- a/src/gui/interact/wizard.cpp +++ b/src/gui/interact/wizard.cpp @@ -122,7 +122,7 @@ namespace interact { Wizard::allocateErrorLogView() { auto& infoBox = globalCtx_.windowLoc_.locatePanel().find_or_create(); - UNIMPLEMENTED("reach out to attach or allocate a display widget for the error log"); + return infoBox.getLog(); } diff --git a/src/gui/panel/infobox-panel.cpp b/src/gui/panel/infobox-panel.cpp index 0f4330e50..7e3d2c0d4 100644 --- a/src/gui/panel/infobox-panel.cpp +++ b/src/gui/panel/infobox-panel.cpp @@ -27,6 +27,7 @@ #include "gui/gtk-base.hpp" #include "gui/panel/infobox-panel.hpp" +#include "gui/widget/error-log-display.hpp" #include "lib/format-string.hpp" using util::_Fmt; @@ -35,12 +36,15 @@ 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_{} + , theLog_{} { twoParts_.pack_start(frame_); twoParts_.pack_start(buttons_, Gtk::PACK_SHRINK); @@ -56,10 +60,7 @@ namespace panel{ buttons_.add (button_1_); //(End)buttons... - frame_.set_border_width (5); - frame_.add (errorLog_); - - // show everything.... + // show initial configuration.... this->add (twoParts_); this->show_all(); } @@ -77,6 +78,23 @@ namespace panel{ } + /** on demand allocate display of information / error log + * @note we assume it stays alive forever, once allocated + * @todo add possibility to collapse it + */ + ErrorLogDisplay& + InfoBoxPanel::getLog() + { + if (not theLog_) + { + theLog_.reset (new ErrorLogDisplay{}); + frame_.set_border_width (5); + frame_.add (*theLog_); + frame_.show_all(); ///////////////TODO necessary? + } + return *theLog_; + } + void InfoBoxPanel::experiment_1() @@ -86,7 +104,7 @@ namespace panel{ static uint bangNo{0}; static _Fmt msgTemplate{"Bang #%d\n"}; - errorLog_.showMsg(NOTE_WARN, msgTemplate % ++bangNo); + getLog().showMsg(NOTE_WARN, msgTemplate % ++bangNo); } diff --git a/src/gui/panel/infobox-panel.hpp b/src/gui/panel/infobox-panel.hpp index b97060852..eeaa43df9 100644 --- a/src/gui/panel/infobox-panel.hpp +++ b/src/gui/panel/infobox-panel.hpp @@ -36,9 +36,13 @@ #include "gui/panel/panel.hpp" -#include "gui/widget/error-log-display.hpp" + +#include namespace gui { +namespace widget { + class ErrorLogDisplay; +} namespace panel{ class InfoBoxPanel @@ -57,12 +61,15 @@ 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::Frame frame_; - widget::ErrorLogDisplay errorLog_; + + std::unique_ptr theLog_; void experiment_1(); }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 60a3d07f4..c7d05857e 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -1746,6 +1746,26 @@ + + + + + + + + + + + + + + + + + + + + @@ -1811,8 +1831,9 @@
- + + @@ -16243,7 +16264,23 @@
- + + + + + + + + + + + + + + + + +
@@ -16486,7 +16523,16 @@ - + + + + + + + + + + From 76e79c02eea6577221d84ac4fd7d3b8e605abcc4 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 17 Aug 2018 19:03:00 +0200 Subject: [PATCH 027/178] NotificationDisplay: view allocation can be pushed into the access functor no need to define a private function on Wizard anymore, it just forwards the call to the service actually implementing the view allocation. For now this is the PanelLocator (and eventually this will be the ViewLocator / ViewSpecDSL) --- src/gui/interact/wizard.cpp | 17 +++-------------- src/gui/interact/wizard.hpp | 4 ---- wiki/thinkPad.ichthyo.mm | 4 ++-- 3 files changed, 5 insertions(+), 20 deletions(-) diff --git a/src/gui/interact/wizard.cpp b/src/gui/interact/wizard.cpp index 16aee2056..2c7fe3b29 100644 --- a/src/gui/interact/wizard.cpp +++ b/src/gui/interact/wizard.cpp @@ -73,7 +73,9 @@ namespace interact { ,globals.uiBus_.getAccessPoint() ,[&]() -> widget::ErrorLogDisplay& { - return allocateErrorLogView(); + return globalCtx_.windowLoc_.locatePanel() + .find_or_create() + .getLog(); }} } { } @@ -114,17 +116,4 @@ namespace interact { - /** @internal reach out to attach or allocate a display widget for the error log. - * @todo as of 8/2018 we use "inside knowledge" to create this view in a hard wired location. - * But in the end, this allocation task should be delegated to the ViewLocator - */ - widget::ErrorLogDisplay& - Wizard::allocateErrorLogView() - { - auto& infoBox = globalCtx_.windowLoc_.locatePanel().find_or_create(); - return infoBox.getLog(); - } - - - }}// namespace gui::interact diff --git a/src/gui/interact/wizard.hpp b/src/gui/interact/wizard.hpp index 8fb24532f..9a5a28e75 100644 --- a/src/gui/interact/wizard.hpp +++ b/src/gui/interact/wizard.hpp @@ -64,9 +64,6 @@ namespace ctrl { class GlobalCtx; class NotificationHub; } -namespace widget { - class ErrorLogDisplay; -} namespace interact { using std::unique_ptr; @@ -97,7 +94,6 @@ namespace interact { static ID getErrorLogID() { return proc::asset::meta::theErrorLog_ID; } private: - widget::ErrorLogDisplay& allocateErrorLogView(); }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index c7d05857e..1153e5086 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -1831,10 +1831,10 @@ - + - + From 67ac8601d835e80856362797bb00c883d3dead5c Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 17 Aug 2018 19:18:28 +0200 Subject: [PATCH 028/178] DockAccess: implement preliminary simplistic lookup and allocation --- src/gui/ctrl/window-locator.cpp | 14 +++++++++++++- src/gui/workspace/panel-manager.cpp | 6 ++++++ src/gui/workspace/panel-manager.hpp | 3 +++ wiki/thinkPad.ichthyo.mm | 12 ++++++------ 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/gui/ctrl/window-locator.cpp b/src/gui/ctrl/window-locator.cpp index c1152bd45..0338b6b1e 100644 --- a/src/gui/ctrl/window-locator.cpp +++ b/src/gui/ctrl/window-locator.cpp @@ -185,7 +185,19 @@ namespace ctrl { panel::Panel& PanelLocator::preliminary_impl_PanelLookup (int typeID) { - UNIMPLEMENTED ("search for existing panel in all windows, possibly create"); + 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); } diff --git a/src/gui/workspace/panel-manager.cpp b/src/gui/workspace/panel-manager.cpp index 73a2619fb..8691cac6c 100644 --- a/src/gui/workspace/panel-manager.cpp +++ b/src/gui/workspace/panel-manager.cpp @@ -136,6 +136,12 @@ namespace workspace { } + bool + PanelManager::hasPanel (const int description_index) + { + UNIMPLEMENTED ("search for a specific panel"); + } + panel::Panel& PanelManager::showPanel (const int description_index) { diff --git a/src/gui/workspace/panel-manager.hpp b/src/gui/workspace/panel-manager.hpp index d1ed12fce..1c136edd0 100644 --- a/src/gui/workspace/panel-manager.hpp +++ b/src/gui/workspace/panel-manager.hpp @@ -118,6 +118,9 @@ namespace workspace { */ 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, * without touching the underlying GdlDockItem. diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 1153e5086..beaab6ce8 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -14539,14 +14539,14 @@ - - + + - - + + - - + + From a151f28d86ed8e10d74fd8cc569b71025bcf36a6 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 17 Aug 2018 19:38:50 +0200 Subject: [PATCH 029/178] NotificationDisplay: solved that nasty topic of dock access for now phew... --- src/gui/ctrl/notification-hub.hpp | 9 ++++++ src/gui/workspace/dock-area.cpp | 10 +++++++ src/gui/workspace/dock-area.hpp | 3 ++ src/gui/workspace/panel-manager.cpp | 6 +++- wiki/thinkPad.ichthyo.mm | 44 ++++++++++++++--------------- 5 files changed, 49 insertions(+), 23 deletions(-) diff --git a/src/gui/ctrl/notification-hub.hpp b/src/gui/ctrl/notification-hub.hpp index 16d25de80..4e84afa0e 100644 --- a/src/gui/ctrl/notification-hub.hpp +++ b/src/gui/ctrl/notification-hub.hpp @@ -179,6 +179,15 @@ namespace ctrl { /** collaboration with a log display allocated elsewhere */ WLink widget_; + + widget::ErrorLogDisplay& + getWidget() + { + if (not widget_) + widget_.connect (allocateWidget_()); + return *widget_; + } + }; diff --git a/src/gui/workspace/dock-area.cpp b/src/gui/workspace/dock-area.cpp index 67918d2fc..e4e5abaff 100644 --- a/src/gui/workspace/dock-area.cpp +++ b/src/gui/workspace/dock-area.cpp @@ -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,6 +144,15 @@ namespace workspace { } + 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) { diff --git a/src/gui/workspace/dock-area.hpp b/src/gui/workspace/dock-area.hpp index aaf63275d..683d4cf91 100644 --- a/src/gui/workspace/dock-area.hpp +++ b/src/gui/workspace/dock-area.hpp @@ -125,6 +125,9 @@ namespace workspace { */ 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, * without touching the underlying GdlDockItem. diff --git a/src/gui/workspace/panel-manager.cpp b/src/gui/workspace/panel-manager.cpp index 8691cac6c..87194261c 100644 --- a/src/gui/workspace/panel-manager.cpp +++ b/src/gui/workspace/panel-manager.cpp @@ -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! @@ -139,7 +140,10 @@ namespace workspace { bool PanelManager::hasPanel (const int description_index) { - UNIMPLEMENTED ("search for a specific panel"); + return util::has_any (panels_, [=](panel::Panel* panel) + { + return getPanelType(panel) == description_index; + }); } panel::Panel& diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index beaab6ce8..b277797fe 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -1608,8 +1608,8 @@ - - + + @@ -1629,9 +1629,9 @@ - + - + @@ -1669,9 +1669,9 @@ - + - + @@ -1682,9 +1682,9 @@ - + - + @@ -1696,10 +1696,10 @@ - + - - + + @@ -1746,7 +1746,7 @@ - + @@ -1820,15 +1820,15 @@ - + - + - + - + @@ -1943,8 +1943,8 @@ - - + + @@ -14536,9 +14536,9 @@ - + - + @@ -14547,7 +14547,7 @@ - + @@ -14559,7 +14559,7 @@ - + From 7846460530beb0872376a7cdf04e8436a45a884c Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 20 Aug 2018 02:58:41 +0200 Subject: [PATCH 030/178] NotificationDisplay: code up controller protocol in NotificationHub ...mostly by delegating to (stubbed) functions in ErrorLogDisplay --- src/gui/ctrl/notification-hub.hpp | 34 +- src/gui/interact/interaction-director.cpp | 3 - src/gui/setting/asset-controller.cpp | 3 - src/gui/widget/error-log-display.hpp | 49 +++ wiki/thinkPad.ichthyo.mm | 509 ++++++++++++++++++++-- 5 files changed, 551 insertions(+), 47 deletions(-) diff --git a/src/gui/ctrl/notification-hub.hpp b/src/gui/ctrl/notification-hub.hpp index 4e84afa0e..5b3a5b685 100644 --- a/src/gui/ctrl/notification-hub.hpp +++ b/src/gui/ctrl/notification-hub.hpp @@ -98,9 +98,6 @@ namespace ctrl { void buildMutator (lib::diff::TreeMutator::Handle buffer) override { - // using Attrib = std::pair; - // using lib::diff::collection; - buffer.create ( TreeMutator::build() ); @@ -113,49 +110,64 @@ namespace ctrl { virtual bool doReset() override { - UNIMPLEMENTED ("Clear log contents, collapse widget, clear error state"); + if (not widget_) return false; + + widget_->clearAll(); + widget_->expand (false); + return true; } virtual bool doExpand (bool yes) override { - UNIMPLEMENTED ("Allocate Widget if necessary, expand widget"); + if (widget_ or yes) + getWidget().expand (yes); + return false; // expand state not sticky } virtual void doRevealYourself() override { - UNIMPLEMENTED ("TODO: how can the Log-Widget be 'revealed'?? Possibly allocate Widget, expand it. But how to make it visible?"); + getWidget().revealYourself(); // implies also expand } virtual bool doMsg (string text) override { - UNIMPLEMENTED ("Possibly allocate Widget, place text into its buffer. No need to expand"); + getWidget().addMsg (text); + return false; // logging is no persistent state } virtual bool doClearMsg () override { - UNIMPLEMENTED ("remove all mere information messages"); + if (widget_) + widget_->clearInfoMsg(); + return false; // not persistent (sticky) } virtual bool doErr (string text) override { - UNIMPLEMENTED ("Set error state. Allocate Widget if necessary, expand widget, place error message into its buffer"); + getWidget().addError (text); + widget_->expand (true); + return false; } virtual bool doClearErr () override { - UNIMPLEMENTED ("clear error state. If widget exists, turn all error entries into mere information entries"); + if (widget_) + widget_->turnError_into_InfoMsg(); + return false; // not persistent (sticky) } virtual void doFlash() override { - UNIMPLEMENTED ("If widget exists: expand it, trigger its flash function (paint with timeout). TODO also doRevealYourself."); + if (not widget_) return; + widget_->revealYourself(); + widget_->triggerFlash(); } diff --git a/src/gui/interact/interaction-director.cpp b/src/gui/interact/interaction-director.cpp index 644fe1981..3b63432e0 100644 --- a/src/gui/interact/interaction-director.cpp +++ b/src/gui/interact/interaction-director.cpp @@ -131,9 +131,6 @@ namespace interact { void InteractionDirector::buildMutator (TreeMutator::Handle buffer) { -// using Attrib = std::pair; -// using lib::diff::collection; - buffer.create ( TreeMutator::build() ); diff --git a/src/gui/setting/asset-controller.cpp b/src/gui/setting/asset-controller.cpp index 352bd751d..bda517cdd 100644 --- a/src/gui/setting/asset-controller.cpp +++ b/src/gui/setting/asset-controller.cpp @@ -63,9 +63,6 @@ namespace setting { void AssetController::buildMutator (TreeMutator::Handle buffer) { -// using Attrib = std::pair; -// using lib::diff::collection; - buffer.create ( TreeMutator::build() ); diff --git a/src/gui/widget/error-log-display.hpp b/src/gui/widget/error-log-display.hpp index 2c7d436bb..0e16dab30 100644 --- a/src/gui/widget/error-log-display.hpp +++ b/src/gui/widget/error-log-display.hpp @@ -94,6 +94,55 @@ namespace widget { } } + void + expand (bool yes) + { + UNIMPLEMENTED ("Maintain expansion state, build a collapsed representation."); + } + + void + clearAll() + { + UNIMPLEMENTED ("empty buffer and discard all error bookmarks"); + } + + void + addMsg (string text) + { + UNIMPLEMENTED ("add normal information message to buffer"); + } + + void + addError (string text) + { + UNIMPLEMENTED ("add error with special tag and bookmark"); + } + + void + clearInfoMsg() + { + UNIMPLEMENTED ("clear all mere information messages; then re-insert the errors with tags"); + } + + void + turnError_into_InfoMsg() + { + UNIMPLEMENTED ("visit all errors and downgrade the markup; discard all bookmarks"); + } + + void + triggerFlash() + { + UNIMPLEMENTED ("paint and set callback-timeout to return to normal state"); + } + + void + revealYourself() + { + UNIMPLEMENTED ("store a link to parent. Ask parent to reveal us. Switch to expanded display"); + } + + private:/* ===== Internals ===== */ diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index b277797fe..7aa9681dc 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -1734,6 +1734,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1832,36 +1902,307 @@ - + - + + + + + + + + + + + + +

+ GTK-Konzepte: "Schließen" +

+ + +
+ + +
+
+ + + + + + + +

+ wenn Log-Anzeige verworfen wird +

+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ er ist nämlich auch aufgerufen worden, nachdem die GTK-Loop bereits beendet war. +

+

+ Dieser Code hat früher C-mäßig direkt die GObjects manipuliert (Anfangs hatten wir nur GDL, nicht GDLmm) +

+ + +
+ +
+ + + + + + +

+ Erläuterung: DockItem ist ein 1-elementiger GTK-Container, +

+

+ der sichtbare Inhalt liegt in diesem Container. Bei uns ist das das Panel. +

+ + +
+
+
+ + + + + + + + + + +

+ Removes @widget from @container. @widget must be inside @container. +

+

+ Note that @container will own a reference to @widget, and that this +

+

+ may be the last reference held; so removing a widget from its      +

+

+ container can destroy that widget. If you want to use @widget      +

+

+ again, you need to add a reference to it before removing it from   +

+

+ a container, using g_object_ref(). If you don’t want to use @widget +

+

+ again it’s usually more efficient to simply destroy it directly    +

+

+ using gtk_widget_destroy() since this will remove it from the      +

+

+ container and help break any circular reference count cycles.      +

+ + +
+ +
+
+ + + + + + + + + + + + + + + + + + + +

+ das ist das Fazit meiner Untersuchung. +

+

+ "destroy" ist kein relevantes Konzept für GTK. Es gibt nur show()/hide(). +

+

+ +

+

+ Also müssen wir explizit +

+
    +
  • + eine Destroy-Signal bauen +
  • +
  • + dafür sorgen, daß das Infobox-Panel das auch aufruft +
  • +
+ + +
+ +
+ + + + + + + + + + + +
+ + + + + + + + + + +

+ ...und nicht bloß inaktiv schalten. +

+ + +
+ + + + + + + + + + +
+ + + + + + + + +

+ GTK bietet nur Anzeigen von top-down: +

+
    +
  • + show_all() auf Containern zeigt rekursiv alle Kinder +
  • +
  • + show() zeigt nur ein bestimmtes Widget +
  • +
+ + +
+ +
+ + + + + + + +
+ + + + + + + + + + + + + + + + - - - - + + + + + + - - + + - - + + @@ -1878,20 +2219,20 @@ - - + + - - + + - - + + @@ -1917,14 +2258,14 @@ - - + + - - + + @@ -1957,6 +2298,23 @@ + + + + + + + + + + + + + + + + + @@ -3271,8 +3629,7 @@ Name: WLink

- -
+
@@ -3401,8 +3758,8 @@
- + @@ -14531,8 +14888,7 @@ wenn es schon wirkliche und funktionierende Widgets im System gibt.

- - +
@@ -14557,8 +14913,7 @@ ...bisher erzeugt die lookup-Operation automatisch

- - +
@@ -33377,7 +33732,101 @@
- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ das Konzept gibts nicht +

+ + +
+ +
+ + + + + + +

+ GTK-Widget: hide() +

+ + +
+ + + + + + + + + + + + +

+ GObject: unref() +

+ + +
+ + + + + + + + + + + + + +

+ ...in der Praxis aber entsteht indirekt eine Auswirkung, +

+

+ insofern Container auf das DESTROY-Signal ihrer Kinder reagieren +

+ + +
+
+
+
+ @@ -33415,6 +33864,7 @@ + @@ -34536,8 +34986,7 @@ The grip is implemented as a GdlDockItemGrip

- - +
From 65bbc45e025f6ee42d969cf17367260683f2822a Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 20 Aug 2018 03:55:06 +0200 Subject: [PATCH 031/178] NotificationDisplay: care for lifecycle issues and expansion state persistence --- src/gui/ctrl/nexus.hpp | 2 +- src/gui/ctrl/notification-hub.hpp | 5 +++-- src/gui/ctrl/state-recorder.hpp | 8 +++++++ src/gui/widget/error-log-display.hpp | 23 ++++++++++++++++++- wiki/thinkPad.ichthyo.mm | 33 +++++++++++++++++++++------- 5 files changed, 59 insertions(+), 12 deletions(-) diff --git a/src/gui/ctrl/nexus.hpp b/src/gui/ctrl/nexus.hpp index 3d6a93a9f..eab41a8ea 100644 --- a/src/gui/ctrl/nexus.hpp +++ b/src/gui/ctrl/nexus.hpp @@ -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 */ diff --git a/src/gui/ctrl/notification-hub.hpp b/src/gui/ctrl/notification-hub.hpp index 5b3a5b685..7726bbe18 100644 --- a/src/gui/ctrl/notification-hub.hpp +++ b/src/gui/ctrl/notification-hub.hpp @@ -121,8 +121,9 @@ namespace ctrl { doExpand (bool yes) override { if (widget_ or yes) - getWidget().expand (yes); - return false; // expand state not sticky + if (getWidget().expand (yes)) + uiBus_.note (GenNode("expand", yes)); + return false; // state persisted if necessary } virtual void diff --git a/src/gui/ctrl/state-recorder.hpp b/src/gui/ctrl/state-recorder.hpp index 47d15ee1e..edf89dd44 100644 --- a/src/gui/ctrl/state-recorder.hpp +++ b/src/gui/ctrl/state-recorder.hpp @@ -187,6 +187,14 @@ namespace ctrl { } 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) { diff --git a/src/gui/widget/error-log-display.hpp b/src/gui/widget/error-log-display.hpp index 0e16dab30..383677e44 100644 --- a/src/gui/widget/error-log-display.hpp +++ b/src/gui/widget/error-log-display.hpp @@ -27,6 +27,26 @@ ** 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::tangible`, it is + ** automatically detached from the WLink implementing this cross-attachment, + ** 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. ** @@ -94,10 +114,11 @@ namespace widget { } } - void + bool expand (bool yes) { UNIMPLEMENTED ("Maintain expansion state, build a collapsed representation."); + return false; //////TODO true if state change happened; this makes the state persistent } void diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 7aa9681dc..c06db4fa3 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -1888,8 +1888,8 @@ - - + + @@ -1906,7 +1906,7 @@ - + @@ -1946,7 +1946,7 @@ - + @@ -2101,11 +2101,28 @@ - - + + + + + + + + +

+ entweder sie laufen über den Getter, +

+

+ oder sie prüfen den Zustand und machen nichts, wenn das Widget nicht da ist +

+ + +
+ +
@@ -2122,8 +2139,8 @@ - - + + From a0b80d8a4681c8e04af95ab4ca2d3e1b17f2fc6f Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Tue, 21 Aug 2018 13:27:39 +0200 Subject: [PATCH 032/178] NotificationDisplay: preliminary plans regarding information display in the UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ...while traveling with the train over the Schwäbische Alb to Karlsruhe; on my way to FrOSCon 2018 --- wiki/thinkPad.ichthyo.mm | 109 ++++++++++++++++++++++++++++----------- 1 file changed, 79 insertions(+), 30 deletions(-) diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index c06db4fa3..729416187 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -1605,7 +1605,7 @@
- + @@ -1696,6 +1696,12 @@ + + + + + + @@ -1922,8 +1928,7 @@ GTK-Konzepte: "Schließen"

- - +
@@ -1939,8 +1944,7 @@ wenn Log-Anzeige verworfen wird

- - + @@ -1981,8 +1985,7 @@ Dieser Code hat früher C-mäßig direkt die GObjects manipuliert (Anfangs hatten wir nur GDL, nicht GDLmm)

- - +
@@ -1998,8 +2001,7 @@ der sichtbare Inhalt liegt in diesem Container. Bei uns ist das das Panel.

- - +
@@ -2040,8 +2042,7 @@ container and help break any circular reference count cycles.     

- - +
@@ -2085,8 +2086,7 @@ - - +
@@ -2119,8 +2119,7 @@ oder sie prüfen den Zustand und machen nichts, wenn das Widget nicht da ist

- - +
@@ -2133,8 +2132,7 @@ ...und nicht bloß inaktiv schalten.

- - + @@ -2169,8 +2167,7 @@ - - + @@ -16584,7 +16581,7 @@ - + @@ -16649,8 +16646,11 @@ + + - + + @@ -16869,6 +16869,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -16896,8 +16927,30 @@ - + + + + + + + + + + + + + + + + + + + + + + + @@ -33784,8 +33837,7 @@ das Konzept gibts nicht

- - +
@@ -33798,8 +33850,7 @@ GTK-Widget: hide()

- - + @@ -33816,8 +33867,7 @@ GObject: unref()

- - + @@ -33838,8 +33888,7 @@ insofern Container auf das DESTROY-Signal ihrer Kinder reagieren

- - +
From bc3eb7f8da17c0de7e4026c46360dab6d1328e35 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 29 Aug 2018 19:25:06 +0200 Subject: [PATCH 033/178] NotificationDisplay: experiment to build a collapsed display basically Gtk::Expander will do the trick. However, resizing of the enclosing panel is not handled properly, the log does not expand to take up the available space, as it did automaticall when just added directly into the frame --- src/gui/panel/infobox-panel.cpp | 9 ++++-- src/gui/panel/infobox-panel.hpp | 1 + src/gui/widget/error-log-display.hpp | 3 +- wiki/thinkPad.ichthyo.mm | 47 +++++++++++++++++++++++++--- 4 files changed, 52 insertions(+), 8 deletions(-) diff --git a/src/gui/panel/infobox-panel.cpp b/src/gui/panel/infobox-panel.cpp index 7e3d2c0d4..f36ba7382 100644 --- a/src/gui/panel/infobox-panel.cpp +++ b/src/gui/panel/infobox-panel.cpp @@ -44,6 +44,7 @@ namespace panel{ , twoParts_{Gtk::ORIENTATION_VERTICAL} , buttons_{} , frame_{"UI Integration Experiments"} + , logExpander_{"Error Log"} , theLog_{} { twoParts_.pack_start(frame_); @@ -88,9 +89,13 @@ namespace panel{ if (not theLog_) { theLog_.reset (new ErrorLogDisplay{}); + logExpander_.set_expanded (true); + logExpander_.add (*theLog_); frame_.set_border_width (5); - frame_.add (*theLog_); - frame_.show_all(); ///////////////TODO necessary? + frame_.add (logExpander_); +// frame_.add (*theLog_); +// frame_.check_resize(); + frame_.show_all(); } return *theLog_; } diff --git a/src/gui/panel/infobox-panel.hpp b/src/gui/panel/infobox-panel.hpp index eeaa43df9..e000252a1 100644 --- a/src/gui/panel/infobox-panel.hpp +++ b/src/gui/panel/infobox-panel.hpp @@ -68,6 +68,7 @@ namespace panel{ Gtk::ButtonBox buttons_; Gtk::Button button_1_; Gtk::Frame frame_; + Gtk::Expander logExpander_; std::unique_ptr theLog_; diff --git a/src/gui/widget/error-log-display.hpp b/src/gui/widget/error-log-display.hpp index 383677e44..44d654cad 100644 --- a/src/gui/widget/error-log-display.hpp +++ b/src/gui/widget/error-log-display.hpp @@ -85,7 +85,7 @@ namespace widget { : Gtk::ScrolledWindow() , textLog_{} { - set_size_request (200, 50); + set_size_request (200, 80); // must be > 50 for the scrollbar to work properly set_border_width (10); set_shadow_type (Gtk::SHADOW_NONE); @@ -93,7 +93,6 @@ namespace widget { set_policy (Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS); this->add (textLog_); textLog_.set_editable (false); - } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 729416187..378919733 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -1744,8 +1744,46 @@ - - + + + + + + + + + + + + + + +

+ ...ob beim Expand/Collapse das umschließende Widget resized werden soll +

+ + +
+
+
+ + + + + + + + + + + + + + +

...wesentliches Struktur-Element:

+

der Parent-Container ist für das expand/collapse zuständig

+ +
@@ -2106,7 +2144,7 @@ - + @@ -2119,7 +2157,8 @@ oder sie prüfen den Zustand und machen nichts, wenn das Widget nicht da ist

-
+ +
From 837c6d11ff73eba474ecffa9933f7d72984a077e Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 30 Aug 2018 14:58:07 +0200 Subject: [PATCH 034/178] NotificationDisplay: solve the problem with space allocation as it turns out, we need to set the property_expand() on the child widget within Gtk::Expander explicitly, to cause the child to grab and additional available screen space (which obviously is what we want in case of a log display with scrollbars) --- src/gui/panel/infobox-panel.cpp | 4 +- src/gui/widget/error-log-display.hpp | 3 +- wiki/thinkPad.ichthyo.mm | 140 +++++++++++++++++++++++++-- 3 files changed, 137 insertions(+), 10 deletions(-) diff --git a/src/gui/panel/infobox-panel.cpp b/src/gui/panel/infobox-panel.cpp index f36ba7382..a0980b279 100644 --- a/src/gui/panel/infobox-panel.cpp +++ b/src/gui/panel/infobox-panel.cpp @@ -89,12 +89,10 @@ namespace panel{ if (not theLog_) { theLog_.reset (new ErrorLogDisplay{}); - logExpander_.set_expanded (true); + logExpander_.set_expanded (false); logExpander_.add (*theLog_); frame_.set_border_width (5); frame_.add (logExpander_); -// frame_.add (*theLog_); -// frame_.check_resize(); frame_.show_all(); } return *theLog_; diff --git a/src/gui/widget/error-log-display.hpp b/src/gui/widget/error-log-display.hpp index 44d654cad..66c431d0a 100644 --- a/src/gui/widget/error-log-display.hpp +++ b/src/gui/widget/error-log-display.hpp @@ -85,7 +85,8 @@ namespace widget { : Gtk::ScrolledWindow() , textLog_{} { - set_size_request (200, 80); // must be > 50 for the scrollbar to work properly + 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); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 378919733..7304dde48 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -1744,12 +1744,13 @@ - - + + - + + @@ -1762,17 +1763,20 @@ ...ob beim Expand/Collapse das umschließende Widget resized werden soll

- - +
- + + + + + @@ -33931,6 +33935,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...was der Fall sein kann in einer Box oder einem Grid, +

+

+ sofern das Widget mit entsprechendem Modus eingefügt wurde +

+ + +
+
+ + + + + + + + + + + @@ -34567,6 +34620,81 @@ + + + + + + + + + +

+ Gtk::Expander +

+ + +
+ + + + + + + + + + + + + + + + +

+ ...ob beim Expand/Collapse das umschließende Widget resized werden soll +

+ + +
+
+ + + + + + +

+ ob eingeklappt oder ausgeklappt +

+ + +
+
+ + + + + + + + + +

+ ein Frame setzt beim Kind property_expand() = true, +

+

+ was dazu führt, daß das Kind stets allen verfügbaren Platz nimmt +

+ + +
+
+
+
+
+
+
From 17bcdd952a83388f2a449516c99f12ae6ad34ea6 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 30 Aug 2018 19:54:40 +0200 Subject: [PATCH 035/178] UiElement: design of helper abstractions (#1162) to strive at a generic implementation for - expanding/collapsing a widget - revealing a widget which obviously somehow involes storing a closure --- wiki/renderengine.html | 27 +++- wiki/thinkPad.ichthyo.mm | 310 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 321 insertions(+), 16 deletions(-) diff --git a/wiki/renderengine.html b/wiki/renderengine.html index 6394605e7..92b89d1d0 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -2664,7 +2664,7 @@ Typically, when starting the draw operation, we retrieve our //allocation.// Thi Thus, if we want to use absolute canvas coordinates for our drawing, we need to adjust the cairo context prior to any drawing operations: we translate it in the opposite direction of the values retrieved from the scrollbar {{{Gtk::Adjustment}}}s. This causes the //user coordinates// to coincede with the absolute canvas coordinates.
-
+
//This is the canvas widget of ~GTK-3//
 It allows not only custom drawing, but also to embed other widgets at defined coordinates.
 
@@ -2681,6 +2681,9 @@ we need a test setup for this investigation.
 * easy to launch
 * don't waste much time, it is disposable
 * realistic: shall reflect the situation in our actual UI
+As starting point, {{red{in winter 2016/17}}} the old (broken) timeline panel was moved aside and a new panel was attached for GTK experiments. These basically confirmed the envisioned approach; it is possible to place widgets freely onto the canvas; they are drawn in insert order, which allows for overlapped widgets (and mouse events are dispatched properly, as you'd expect). Moreover, it is also possible to draw directly onto the canvas, by overriding the {{{on_draw()}}}-function. However, some (rather trivial) adjustments need to be done to get a virtual canvas, which moves along with the placed widgets. That is, GtkLayoutWidget handles scrolling of embedded widgets automatically, but you need to adjust the Cairo drawing context manually to move along. The aforementioned experimental code shows how.
+
+After that initial experiments, my focus shifted to the still unsatisfactory top-level UI structure, and I am working towards an initial integration with Proc-Layer since then.
 
@@ -3238,7 +3241,7 @@ More specifically, the integration is based on ''messaging''. To start with, the ---- In a preliminary attempt to establish an integration between the GUI and the lower layers, in 1/2009 we created an PlayerDummy, which "pulls" dummy frames from the (not yet existing) engine and displays them within an XV viewer widget. This highlighted the problems we're about to encounter and made us think about the more radically decoupled approach we followed thereafter...
-
+
Building a layered architecture is a challenge, since the lower layer //really// needs to be self-contained, while prepared for usage by the higher layer.
 A major fraction of all desktop applications is written in a way where operational logic is built around the invocation from UI events -- what should be a shell turns into a backbone. One possible way to escape from this common anti pattern is to introduce a mediating entity, to translate between two partially incompatible demands and concerns: Sure, the "tangible stuff" is what matters, but you can not build any significant piece of technology if all you want is to "serve" the user.
 
@@ -3250,11 +3253,25 @@ The GUI model is largely comprised of immutable ID elements, which can be treate
 
 !{{red{WARNING 2/2017}}} more questionable than ever
 The whole Idea to have a "UI model" appears more questionable than ever. It leads to tight coupling with the session and a lot of thread synchronisation headaches, without any clear benefit -- beyond just being the obvious no-brainer solution. During the last months, step by step, several presentation related structures emerged, which //indeed are structured to parallel the outline of the session.// But those structures are widgets and controllers, and it might well be that we do not need a model, beyond the data already present within the widget implementation. Rather it seems we'll get a nested structure of //presenters,// which are linked to the session with the help of the UI-Bus and the [[diff framework|TreeDiffModel]].
+* {{red{as of 8/2018}}}, matters on a large scale converge towards a structure //without a classical UI model.// Seemingly we can do without...
+* there is a working demonstration in {{{BusTerm_test}}}, which pushes mutation diffs against a mock UI-Element. The immediate response to receiving such a diff notification via the UI-Bus has now been coded; it incurs invoking a passed callback (functor), which performs within the originating context, but within the ~UI-Thread, and produces the actual diff //on-demand.// ("pull principle")
 
 !synchronisation guarantees
 We acknowledge that the gui model is typically used from within the GUI event dispatch thread. This is //not// the thread where any session state is mutated. Thus it is the responsibility of this proxying model within the GUI to ensure that the retrieved structure is a coherent snapshot of the session state. Especially the {{{gui::model::SessionFacade}}} ensures that there was a read barrier between the state retrieval and any preceding mutation command. Actually, this is implemented down in Proc-Layer, with the help of the ProcDispatcher.
 
-The forwarding of model changes to the GUI widgets is another concern, since notifications from session mutations arrive asynchronous after each [[Builder]] run. In this case, we send a notification to the widgets registered as listeners, but wait for //them// to call back and fetch the [[diffed state|TreeDiffModel]]. This callback will be scheduled by the  widgets to perform in the GUI event  thread.
+The forwarding of model changes to the GUI widgets is another concern, since notifications from session mutations arrive asynchronous after each [[Builder]] run. In this case, we send a notification to the widgets registered as listeners, but wait for //them// to call back and fetch the [[diffed state|TreeDiffModel]]. The notification will be dispatched into the GUI event thread (by the {{{GuiNotification}}} façade), which implies that also the callback embedded within the notification will be invoked by the  widgets to perform within the GUI  thread.
+
+!generic model tree building blocks
+According {{red{to the current plan (2018)}}}, the GuiModel will not be a dedicated and isolated data structure -- rather it will be interwoven with the actual structure of the widgets and controllers. Some controllers and also some widgets additionally implement the {{{gui::model::Tangible}}}-interface and thus act as "tangible" UI-Element, where each such "tangible element" directly corresponds to a component within the session model. The interface UI-Element requires such elements to provide an attachment point to receive mutations in the form of diff messages, while it remains a local implementation detail within each such element //how actually to respond and reflect// the changes indicated by the diff message. Typically the adding of child elements will result in creation of several GTK widgets, some of which are in turn also again UI-Element implementations, and only the latter count conceptually as "children".
+
+While most actual details are abstracted away by this approach, it can be expected that the handling of typical diff changes is somewhat similar in most actual widgets. For this reason we provide a selection of generic adapters and building blocks to simplify the assembly of actual widget implementations.
+
+!!!expanding and collapsing
+Several UI elements offer the ability to be collapsed into a minimal representation to save screen real estate. The UI-Element protocol defines this to happen either by invoking a dedicated signal slot on the element, or by sending an appropriate message over the UI-Bus. The actual implementation is quite simple, but unfortunately requires knowledge regarding the specific widget configuration. A commonly used approach is to wrap the expandable/collapsible element into a {{{Gtk::Expandable}}}, but there are notable exceptions, where the widget is bound to handle the expanding or reducing of information display all by itself. We bridge this discrepancy by introducing an {{{Expander}}} interface to act as adapter. 
+
+!!!revealing an element
+The UI-Element protocol also includes the ability to //reveal an element// -- which means actively to bring this element into sight, in case it is hidden, collapsed or obscured by scrolling away.
+{{red{As of 8/2018 this is just an idea}}}, and many details still need to be considered. Yet at least one point is clear: implementing such a feature requires the help of the container widget actually holding the element to be revealed. It might even happen that also the collaboration of the container holding aforementioned immediate parent container is necessary -- indicating a recursive implementation scheme.
 
@@ -9817,7 +9834,7 @@ The UI-Bus has a star shaped topology, with a central "bus master" hub :direct a MutationMessage towards the designated UI-Element, causing the latter to build a TreeMutator to receive the embedded [[diff-sequence|TreeDiffModel]]
-
+
While our UI widgets are implemented the standard way as proposed by [[GTKmm|http://www.gtkmm.org/en/documentation.html]], some key elements -- which are especially relevant for the anatomy and mechanics of the interface at a whole -- are made to conform to a common interface and behaviour protocol. {{red{WIP 11/15 work out gradually what this protocol is all about}}}. #975
 As a starting point, we know
 * there is a backbone structure known as the UI-Bus
@@ -9890,7 +9907,7 @@ It is clear by now (9/2016) how shape and content changes are to be represented
 !!!questions
 * how to integrate "diff calls" into UI-Bus and {{{Tangible}}}
 * how to ensure that diff processing happens within the event thread
-* what is the exact relation to the GuiModel -- this remained nebulous up to now
+* what is the exact relation to the GuiModel -- this remained nebulous up to now ({{red{8/2018}}} &rArr; seems to be obsolete)
 
 !!!Diff messages
 The dispatch of //diff messages// is directly integrated into the UI-Bus -- which is not just some abstract messaging system, but deliberately made to act as backbone for the kind of operations and collaborations detailed here. This seamless integration helps to overcome several technical (detail) problems. For one, the diff framework itself is //generic,// which allows to apply diff sequences to a wide variety of different target data structures. And, secondly, choose the whole approach of diff messages delivered over a bus system for a very specific, architectural reason: we want to decouple from internal structures, while still allowing low-level collaboration over this abstracted interconnection channel. On both counts, we are thus bound to encapsulate the actual diff at interface level. So the MutationMessage marks the purpose to alter the designated {{{Tangible}}}, while offering an embedded, generic implementation to apply the (hidden) diff message, relying in turn on the interface {{{DiffMutatble}}}, which -- surprise -- is offered by {{{Tangible}}}. Basically this means that each tangible interface element offers the ability to be reshaped, by creating a TreeMutator on demand, internally wired to effect the changes inscribed within the diff.
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm
index 7304dde48..0d18cd4e4 100644
--- a/wiki/thinkPad.ichthyo.mm
+++ b/wiki/thinkPad.ichthyo.mm
@@ -7,8 +7,8 @@
 
 
 
-
-
+
+
 
 
 
@@ -1605,8 +1605,9 @@
 
 
 
-
+
 
+
 
 
 
@@ -1788,6 +1789,130 @@
     

der Parent-Container ist für das expand/collapse zuständig

+ + + + + + + + + + + + + + + + + + + + + +

+ als denjenigen Container... +

+

+ ...der das eigentliche Widget expandieren kann +

+ + +
+
+ + + + + + + + + + + +

+ ggfs müssen alle anderen Funktionen nach unten delegieren +

+ + +
+
+ + + + + + +

+ wenn nämlich das "Expandieren" in einen weiter reichenden Zusammenhang integriert ist, +

+

+ wie z.B. Darstellung in verschiedenen Detailierungs-Graden, oder ein generelles +

+

+ Fokus-/Layout- Management mehrerer Komponenten zusammen +

+

+ +

+

+ Standard-Beispiel: Property-Panel +

+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +

+ ...wenn wir eine Mix-in -Implementierung wählen +

+ + +
+ +
+
+ + + + + + + + + + +
+
+
+ + + +
@@ -16899,7 +17024,8 @@
- + + @@ -16912,7 +17038,7 @@ - + @@ -18046,7 +18172,7 @@ - + @@ -18278,7 +18404,7 @@ - + @@ -18408,6 +18534,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...denn dort fehlt noch die konkrete Implementierung, +

+

+ welche die Monaden-Baum-Repräsentation schließlich +

+

+ auf konkrete Widgets abbildet. Das ist viel Arbeit, und es ließe sich vereinfachen, +

+

+ wenn für gewisse Knoten erkannt werden kann, daß sie einen NavScope darstellen; +

+

+ denn dann gäbe es eine Implementierung "von der Stange" +

+ + +
+
+
+
@@ -24018,7 +24249,7 @@ - + @@ -24058,7 +24289,7 @@ - + @@ -24268,8 +24499,8 @@ - - + + @@ -33984,6 +34215,63 @@ + + + + + + + + +

+ ein Widget kann einem GDK-Window zugeordnet sein +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + +

+ 8/2018: +

+

+ ist das eine Sequenz von konkreten Objekten, die ineinander verschachtelt sind? +

+

+ oder ist es eine Vererbungs-Hierarchie, wie sie für das CSS-Styling benötigt wird? +

+ +
+ +
+ + + + + + +
+
From 1f98895a893a846ec67224a7d4fcea63a235a755 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 31 Aug 2018 17:41:42 +0200 Subject: [PATCH 036/178] DOC: start a documentation page with coding policies for the UI-Layer --- doc/technical/gui/CodePolicy.txt | 59 ++++++++++++++++++++++++++++++++ doc/technical/gui/guiTheme.txt | 4 ++- 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 doc/technical/gui/CodePolicy.txt diff --git a/doc/technical/gui/CodePolicy.txt b/doc/technical/gui/CodePolicy.txt new file mode 100644 index 000000000..cc1fa7cf4 --- /dev/null +++ b/doc/technical/gui/CodePolicy.txt @@ -0,0 +1,59 @@ +Coding Policies within UI-Layer +=============================== + +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`, and install the corresponding service +via `lib::DependInject::ServiceInstance`. + + +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.] + diff --git a/doc/technical/gui/guiTheme.txt b/doc/technical/gui/guiTheme.txt index 8a0c2cab5..92fb9b3da 100644 --- a/doc/technical/gui/guiTheme.txt +++ b/doc/technical/gui/guiTheme.txt @@ -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 ------------ From 551920e9520480e4cfafe1582130792e36fb816d Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 31 Aug 2018 18:30:21 +0200 Subject: [PATCH 037/178] UiElement: decide upon the design variant to use for expand() / reveal() (#1162) --- doc/technical/gui/CodePolicy.txt | 35 ++++ src/gui/model/expander-revealer.hpp | 93 ++++++++++ src/gui/model/tangible.hpp | 3 +- wiki/thinkPad.ichthyo.mm | 263 +++++++++++++++++++++++----- 4 files changed, 348 insertions(+), 46 deletions(-) create mode 100644 src/gui/model/expander-revealer.hpp diff --git a/doc/technical/gui/CodePolicy.txt b/doc/technical/gui/CodePolicy.txt index cc1fa7cf4..dbf547ad9 100644 --- a/doc/technical/gui/CodePolicy.txt +++ b/doc/technical/gui/CodePolicy.txt @@ -1,5 +1,6 @@ Coding Policies within UI-Layer =============================== +:Toc: NOTE: for the time being, this is a loose collection of Conventions and Policies deemed adequate for work @@ -40,6 +41,40 @@ should prefer using `lib::Depend`, and install the correspondi via `lib::DependInject::ServiceInstance`. +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. diff --git a/src/gui/model/expander-revealer.hpp b/src/gui/model/expander-revealer.hpp new file mode 100644 index 000000000..d61fc3857 --- /dev/null +++ b/src/gui/model/expander-revealer.hpp @@ -0,0 +1,93 @@ +/* + EXPANDER-REVEALER.hpp - functor components for standard UI element actions + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + 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. + ** + ** ## Interactions + ** + ** @see [UI-Element protocol](\ref tangible.hpp) + ** @see \ref BusTerm_test + ** + */ + + +#ifndef GUI_MODEL_EXPANDER_REVEALER_H +#define GUI_MODEL_EXPANDER_REVEALER_H + + +#include "lib/error.hpp" +//#include "lib/idi/entry-id.hpp" +//#include "lib/symbol.hpp" + +#include +//#include +//#include + + +namespace gui { +namespace model { + +// using std::string; +// using lib::Symbol; + + + /** + * Functor component to support the default implementation of expanding/collapsing. + */ + class Expander + { + public: + protected: + + + Expander() + { } + + public: + + protected: + }; + + + /** + * Functor component to support the default implementation of revealing an UI-Element. + */ + class Revealer + { + public: + protected: + + + Revealer() + { } + + public: + + protected: + }; + + + +}} // namespace gui::model +#endif /*GUI_MODEL_EXPANDER_REVEALER_H*/ diff --git a/src/gui/model/tangible.hpp b/src/gui/model/tangible.hpp index 393ba06b7..9ca3c0c99 100644 --- a/src/gui/model/tangible.hpp +++ b/src/gui/model/tangible.hpp @@ -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" diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 0d18cd4e4..f3f2a9ee7 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -1789,15 +1789,15 @@

der Parent-Container ist für das expand/collapse zuständig

- - + + - + @@ -1908,10 +1908,38 @@ + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + @@ -1966,13 +1994,25 @@ + + + + + - + - + + + + + + + + @@ -18544,17 +18584,97 @@ - - + + - - - - + + + + + + +

+ nicht bool-Testbar +

+ + +
+ + + + + +

+ ganz bewußt verzichten wir darauf, +

+

+ festzustellen, ob ein Kind gegenwärtig zugänglich (revealed) ist. +

+

+ Denn dies auf dem API zu exponieren bringt keinen Mehrwehrt. +

+

+ Letztlich ist das ein lokales Detail des Layout-Managers. +

+

+ +

+

+ Es ist nicht Sache des client-Codes, auf einem Widget ein anderes Verhalten auszulösen, +

+

+ wenn es nicht sichtbar ist. Denn Sichtbarkeit gehört zur UI-Mechanik und geht den Client nix an +

+ + +
- - + + + + + + + + + + + + + + + + + + + +

+ ...denn dort fehlt noch die konkrete Implementierung, +

+

+ welche die Monaden-Baum-Repräsentation schließlich +

+

+ auf konkrete Widgets abbildet. Das ist viel Arbeit, und es ließe sich vereinfachen, +

+

+ wenn für gewisse Knoten erkannt werden kann, daß sie einen NavScope darstellen; +

+

+ denn dann gäbe es eine Implementierung "von der Stange" +

+ + +
+
+ + + + +
+ + @@ -18562,8 +18682,26 @@ + + + + + + +

+ aber die Operationen sind der Sache nach nicht symmetrisch +

+ + +
+
- + + + + + + @@ -18602,40 +18740,75 @@ + + + + + + + + - - - - + + + + + + + + + + + + + + - - + + + + + - - - - - - -

- ...denn dort fehlt noch die konkrete Implementierung, -

-

- welche die Monaden-Baum-Repräsentation schließlich -

-

- auf konkrete Widgets abbildet. Das ist viel Arbeit, und es ließe sich vereinfachen, -

-

- wenn für gewisse Knoten erkannt werden kann, daß sie einen NavScope darstellen; -

-

- denn dann gäbe es eine Implementierung "von der Stange" -

- - -
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 04424fb8df18692766ddd46903326ef6696c5db4 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 31 Aug 2018 19:52:24 +0200 Subject: [PATCH 038/178] UiElement: code and document the functor components --- src/gui/model/expander-revealer.hpp | 132 +++++++++++++++++++++++----- wiki/thinkPad.ichthyo.mm | 21 +++-- 2 files changed, 125 insertions(+), 28 deletions(-) diff --git a/src/gui/model/expander-revealer.hpp b/src/gui/model/expander-revealer.hpp index d61fc3857..c2d7b435e 100644 --- a/src/gui/model/expander-revealer.hpp +++ b/src/gui/model/expander-revealer.hpp @@ -23,11 +23,37 @@ /** @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. ** - ** ## Interactions + ** ## Usage in the default implementation + ** + ** The base class of all [tangible UI elements](\ref Tangible) provides a default implementation + ** for these generic interaction mechanism: It offers slots to connect UI signals against, and + ** it understands the _`mark`_ messages "`expand`" and "`revealYourself`". 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 + ** _`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 \ref BusTerm_test + ** @see [usage example](\ref error-log-display.hpp) ** */ @@ -37,54 +63,118 @@ #include "lib/error.hpp" -//#include "lib/idi/entry-id.hpp" -//#include "lib/symbol.hpp" #include -//#include -//#include +#include namespace gui { namespace model { - -// using std::string; -// using lib::Symbol; + + 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: - protected: + using ProbeFun = std::function; + using ChangeFun = std::function; - - Expander() - { } + ProbeFun probeState_; + ChangeFun changeState_; public: + Expander() { } + Expander(ProbeFun detectCurrExpansionState, ChangeFun expand_collapse) + : probeState_{move (detectCurrExpansionState)} + , changeState_{move (expand_collapse)} + { + ENSURE (canExpand()); + } - protected: + bool + canExpand() const + { + return bool{probeState_} + and bool{changeState_}; + } + + 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: - protected: + using RevealeItFun = std::function; - - Revealer() - { } + RevealeItFun revealIt_; public: + Revealer() { } + Revealer(RevealeItFun how_to_uncover_the_element) + : revealIt_{move (how_to_uncover_the_element)} + { + ENSURE (canReveal()); + } - protected: + bool + canReveal() const + { + return bool{revealIt_}; + } + + void + operator()() + { + REQUIRE (canReveal()); + revealIt_(); + } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index f3f2a9ee7..92feff65a 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -1779,7 +1779,7 @@ - + @@ -1789,6 +1789,7 @@

der Parent-Container ist für das expand/collapse zuständig

+ @@ -1917,15 +1918,15 @@
- + - - - + + + - - + + @@ -1941,6 +1942,9 @@ + + + @@ -2015,6 +2019,9 @@ + + + From 51a7670425862365c8128c179a7eea612c564a29 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 31 Aug 2018 21:38:33 +0200 Subject: [PATCH 039/178] UiElement: integrate a default implementation based on the Expander functor --- src/gui/model/controller.hpp | 6 ------ src/gui/model/expander-revealer.hpp | 4 ++++ src/gui/model/tangible.cpp | 19 +++++++++++++++++++ src/gui/model/tangible.hpp | 25 ++++++++++++++++++++++++- src/gui/model/widget.hpp | 6 ------ wiki/renderengine.html | 16 ++++++++++------ wiki/thinkPad.ichthyo.mm | 27 +++++++++++++++++++++------ 7 files changed, 78 insertions(+), 25 deletions(-) diff --git a/src/gui/model/controller.hpp b/src/gui/model/controller.hpp index 7d4d5a085..f44df49d0 100644 --- a/src/gui/model/controller.hpp +++ b/src/gui/model/controller.hpp @@ -70,12 +70,6 @@ namespace model { UNIMPLEMENTED ("Controller reset"); } - virtual bool - doExpand (bool yes) override - { - UNIMPLEMENTED ("Controller doExpand"); - } - virtual void doReveal (ID child) override { diff --git a/src/gui/model/expander-revealer.hpp b/src/gui/model/expander-revealer.hpp index c2d7b435e..662918b2d 100644 --- a/src/gui/model/expander-revealer.hpp +++ b/src/gui/model/expander-revealer.hpp @@ -83,9 +83,11 @@ namespace model { */ class Expander { + public: using ProbeFun = std::function; using ChangeFun = std::function; + private: ProbeFun probeState_; ChangeFun changeState_; @@ -151,8 +153,10 @@ namespace model { */ class Revealer { + public: using RevealeItFun = std::function; + private: RevealeItFun revealIt_; public: diff --git a/src/gui/model/tangible.cpp b/src/gui/model/tangible.cpp index 1c5f4de5e..f435174b0 100644 --- a/src/gui/model/tangible.cpp +++ b/src/gui/model/tangible.cpp @@ -148,6 +148,9 @@ 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() @@ -169,6 +172,22 @@ namespace model { } + /** + * 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 + } + + /** * @todo 12/2015 not clear yet what needs to be done * @remarks the intention is to request the given child diff --git a/src/gui/model/tangible.hpp b/src/gui/model/tangible.hpp index 9ca3c0c99..137e9ea3e 100644 --- a/src/gui/model/tangible.hpp +++ b/src/gui/model/tangible.hpp @@ -176,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: @@ -205,11 +210,13 @@ namespace model { void markErr (string error); void mark(GenNode const&); + void installExpander (Expander::ProbeFun, Expander::ChangeFun); + protected: virtual bool doReset() =0; virtual bool doClearMsg() =0; virtual bool doClearErr() =0; - virtual bool doExpand (bool yes) =0; + virtual bool doExpand (bool yes); virtual void doReveal (ID child) =0; virtual void doRevealYourself () =0; @@ -249,6 +256,22 @@ namespace model { } // not typed, no attributes, all arguments as children + /** + * Configure the (optional) functionality to expand or collapse the UI-Element. + * @param detectCurrExpansionState a lambda or function to retrieve if the element is currently expanded + * @param howto_expand_collapse a lambda or function 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)}; + } + + + }} // namespace gui::model #endif /*GUI_MODEL_TANGIBLE_H*/ diff --git a/src/gui/model/widget.hpp b/src/gui/model/widget.hpp index 5302cfbd5..d5b303725 100644 --- a/src/gui/model/widget.hpp +++ b/src/gui/model/widget.hpp @@ -67,12 +67,6 @@ namespace model { UNIMPLEMENTED ("Widget reset"); } - virtual bool - doExpand (bool yes) override - { - UNIMPLEMENTED ("Widget doExpand"); - } - virtual void doReveal (ID child) override { diff --git a/wiki/renderengine.html b/wiki/renderengine.html index 92b89d1d0..af92192b7 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -1523,10 +1523,10 @@ Lumiera agrees to this common understanding (of most film editing and sound hand &rarr; PipeHandling
-
+
//mediating entity used to guide and control the presentation of a clip in the UI.//
 
-The clip representation in the UI links together two distinct realms and systems of concerns. For one, there are the properties and actions performed on the clip for sake of editing a movie. Everything which has a tangible effect on the resulting render. These information and operations are embodied into the HighLevelModel and can be manipulated script driven, without relying on an UI. But beyond that, there is also the local mechanics of the interface, everything which makes working on the edit and interacting with the model into a smooth experience and workflow. With respect to this concerns, a clip is a self-contained entity which owns its own state and behaviour. The ClipPresenter is the pivotal element to link together those two realms.
+The clip representation in the UI links together two distinct realms and systems of concerns. For one, there are the properties and actions performed on the clip for sake of editing a movie. Everything which has a tangible effect on the resulting render. These information and operations are embodied into the HighLevelModel and can be manipulated script driven, without relying on an UI. But beyond that, there is also the local mechanics of the interface, everything which makes working on the edit and interacting with the model into a smooth experience and workflow. With respect to these concerns, a clip is a self-contained entity which owns its own state and behaviour. The ClipPresenter is the pivotal element to link together those two realms.
 
 [>img[Clip presentation control|uml/Timeline-clip-display.png]]
 Regarding the global angle, the ClipPresenter is an UI-Element connected to the UI-Bus, and it is added as child element to some parent entity, a TrackPresenter, which likewise serves as UI representation of a [[fork ("track")|Fork]], and controls widgets to render a track like working scope (in the header pane and in the timeline contents pane). Commands manipulating the clip can be sent via the embedded bus terminal, and status regarding clip properties and nested child elements (effects, transitions) is received as messages via the bus, which insofar plays the role of model and controller.
@@ -3241,7 +3241,7 @@ More specifically, the integration is based on ''messaging''. To start with, the
 ----
 In a preliminary attempt to establish an integration between the GUI and the lower layers, in 1/2009 we created an PlayerDummy, which "pulls" dummy frames from the (not yet existing) engine and displays them within an XV viewer widget. This highlighted the problems we're about to encounter and made us think about the more radically decoupled approach we followed thereafter...
-
+
Building a layered architecture is a challenge, since the lower layer //really// needs to be self-contained, while prepared for usage by the higher layer.
 A major fraction of all desktop applications is written in a way where operational logic is built around the invocation from UI events -- what should be a shell turns into a backbone. One possible way to escape from this common anti pattern is to introduce a mediating entity, to translate between two partially incompatible demands and concerns: Sure, the "tangible stuff" is what matters, but you can not build any significant piece of technology if all you want is to "serve" the user.
 
@@ -3267,7 +3267,11 @@ According {{red{to the current plan (2018)}}}, the GuiModel will not be a dedica
 While most actual details are abstracted away by this approach, it can be expected that the handling of typical diff changes is somewhat similar in most actual widgets. For this reason we provide a selection of generic adapters and building blocks to simplify the assembly of actual widget implementations.
 
 !!!expanding and collapsing
-Several UI elements offer the ability to be collapsed into a minimal representation to save screen real estate. The UI-Element protocol defines this to happen either by invoking a dedicated signal slot on the element, or by sending an appropriate message over the UI-Bus. The actual implementation is quite simple, but unfortunately requires knowledge regarding the specific widget configuration. A commonly used approach is to wrap the expandable/collapsible element into a {{{Gtk::Expandable}}}, but there are notable exceptions, where the widget is bound to handle the expanding or reducing of information display all by itself. We bridge this discrepancy by introducing an {{{Expander}}} interface to act as adapter. 
+Several UI elements offer the ability to be collapsed into a minimal representation to save screen real estate. The UI-Element protocol defines this to happen either by invoking a dedicated signal slot on the element, or by sending an appropriate message over the UI-Bus. The actual implementation is quite simple, but unfortunately requires knowledge regarding the specific widget configuration. A commonly used approach is to wrap the expandable/collapsible element into a {{{Gtk::Expandable}}}, but there are notable exceptions, where the widget is bound to handle the expanding or reducing of information display all by itself. We bridge this discrepancy by introducing an {{{Expander}}} interface to act as adapter.
+* the default implementation holds an {{{Expander}}} functor. In default state, this functor as well as expanding / collapsing functionality remains disabled
+* to enable it, two lambdas need to be provided, to configure
+** how to find out about the expansion state of the widget
+** how to change this expansion state (i.e. how to expand or collapse the widget)
 
 !!!revealing an element
 The UI-Element protocol also includes the ability to //reveal an element// -- which means actively to bring this element into sight, in case it is hidden, collapsed or obscured by scrolling away.
@@ -3465,7 +3469,7 @@ In the most general case, there can be per-track content and nested content at t
 &rarr; important question: how to [[organise the widgets|GuiTimelineWidgetStructure]]
 
-
+
The Timeline is probably the most prominent place in the GUI where we need to come up with a custom UI design.
 Instead of combining standard components in one of the well-known ways, here we need to come up with our own handling solution -- which also means to write one or several custom GTK widgets. Thus the question of layout and screen space division and organisation becomes a crucial design decision. The ~GTK-2 Gui, as implemented currently, did already take some steps along this route, yet this kind of decision should be cast and documented explicitly (be it after the fact).
 
@@ -3474,7 +3478,7 @@ Right away, this topic touches a tricky design and architectural challenge: the
 In a nutshell, ~GTKmm offers several degrees of customisation, namely to build a custom widget class, to build a custom container widget, and to use the [[Gtk::Layout "canvas widget"|GtkLayoutWidget]], possibly combined with //custom drawing.// In addition to assembling a timeline widget class by combining several nested panes, the timeline display needs to rely on the latter approach to allow for the necessary flexible arrangement of [[clip widgets|GuiClipWidget]] within the [[track fork|Fork]].
 
 !the header pane problem
-From general considerations we draw the conclusion to have an track header pane area always visible to the left. For one this means that our top level widget organisation in the timeline will be a horizontal split. And then this means that we get two distinct sub widgets, whose vertical layout needs to be kept in sync. And even more so, it is likely the most adequate implementation technique is different in both parts: the header pane looks like a classical fit for the paradigm of nested boxes and grid layout within those boxes, while the right part, the actual track contents impose very specific layout constraints not served by any of the pre-existing layout containers, which means we have to resort to custom drawing on a canvas widget. Following this line of thought, we need an overarching layout manager to coordinate these two disjoint technologies. Any viable alternatives?
+From general considerations we draw the conclusion to have a track header pane area always visible to the left. For one this means that our top level widget organisation in the timeline will be a horizontal split. And then this means that we get two distinct sub widgets, whose vertical layout needs to be kept in sync. And even more so, it is likely the most adequate implementation technique is different in both parts: the header pane looks like a classical fit for the paradigm of nested boxes and grid layout within those boxes, while the right part, the actual track contents impose very specific layout constraints not served by any of the pre-existing layout containers, which means we have to resort to custom drawing on a canvas widget. Following this line of thought, we need an overarching layout manager to coordinate these two disjoint technologies. Any viable alternatives?
 
 !!!considering a table grid layout
 The layout mechanics we try to establish here by explicit implementation would be more or less a given, if instead we'd build the whole timeline display from one base widget, which needs to be a table layout, i.e. {{{Gtk::Grid}}}. We'd use two columns, one for the header pane area, one for the timeline display, and we'd use N+1 rows, with the head row holding the time ruler and the additional rows holding individual tracks. But to get the specific UI mechanics desirable for a timeline display, we had to introduce some twists:
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm
index 92feff65a..31decc366 100644
--- a/wiki/thinkPad.ichthyo.mm
+++ b/wiki/thinkPad.ichthyo.mm
@@ -1929,18 +1929,24 @@
 
 
 
-
-
-
-
+
+
+
+
 
-
-
+
+
+
+
+
 
 
 
 
 
+
+
+
 
 
 
@@ -2009,6 +2015,9 @@
 
 
 
+
+
+
 
 
 
@@ -2018,6 +2027,12 @@
 
 
 
+
+
+
+
+
+
 
 
 

From 3f327b335a1fbd58c61ed8045a10ba9d7fa4e79c Mon Sep 17 00:00:00 2001
From: Ichthyostega 
Date: Fri, 31 Aug 2018 22:15:57 +0200
Subject: [PATCH 040/178] UiElement: switch MockElement to rely on the new
 functor based default impl

...which is implicit verified through AbstractTangible_test::markState()
---
 tests/gui/test/mock-elm.hpp | 15 ++++++++-------
 wiki/thinkPad.ichthyo.mm    | 18 +++++++++++++-----
 2 files changed, 21 insertions(+), 12 deletions(-)

diff --git a/tests/gui/test/mock-elm.hpp b/tests/gui/test/mock-elm.hpp
index 7489534bb..b0cec0865 100644
--- a/tests/gui/test/mock-elm.hpp
+++ b/tests/gui/test/mock-elm.hpp
@@ -142,13 +142,7 @@ namespace test{
       doExpand (bool yes)  override
         {
           log_.call(this->identify(), "expand", yes);
-          if (expanded_ == yes)
-            return false; // nothing to change
-          
-          virgin_ = false;
-          expanded_ = yes;
-          log_.event (expanded_? "expanded" : "collapsed");
-          return true; // record a state change
+          return Tangible::doExpand (yes);
         }
       
       virtual void
@@ -317,6 +311,13 @@ namespace test{
         {
           log_.call (this->identify(), "ctor", identity, string(nexus));
           log_.create (getID().getSym());
+          installExpander ([&](){ return this->expanded_; }
+                          ,[&](bool yes)
+                                {
+                                  virgin_ = false;
+                                  expanded_ = yes;
+                                  log_.event (expanded_? "expanded" : "collapsed");
+                                });
         }
       
       
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm
index 31decc366..7a744cd16 100644
--- a/wiki/thinkPad.ichthyo.mm
+++ b/wiki/thinkPad.ichthyo.mm
@@ -1943,13 +1943,21 @@
 
 
 
-
-
-
+
+
+
+
+
 
 
-
-
+
+
+
+
+
+
+
+
 
 
 

From 41e04965769f3b38d779fa7f3661b649d093cdd6 Mon Sep 17 00:00:00 2001
From: Ichthyostega 
Date: Fri, 31 Aug 2018 22:55:23 +0200
Subject: [PATCH 041/178] NotificationDisplay: now able to build the expand
 functionality

...by delegating to an Expander placed into the ErrorLogDisplay widget
---
 src/gui/ctrl/notification-hub.hpp    | 19 +++++++------
 src/gui/model/tangible.cpp           |  5 +++-
 src/gui/panel/infobox-panel.cpp      |  9 ++++++-
 src/gui/widget/error-log-display.hpp | 10 +++----
 wiki/thinkPad.ichthyo.mm             | 40 ++++++++++++++++------------
 5 files changed, 47 insertions(+), 36 deletions(-)

diff --git a/src/gui/ctrl/notification-hub.hpp b/src/gui/ctrl/notification-hub.hpp
index 7726bbe18..3c75f17f9 100644
--- a/src/gui/ctrl/notification-hub.hpp
+++ b/src/gui/ctrl/notification-hub.hpp
@@ -117,15 +117,6 @@ namespace ctrl {
           return true;
         }
       
-      virtual bool
-      doExpand (bool yes)  override
-        {
-          if (widget_ or yes)
-             if (getWidget().expand (yes))
-               uiBus_.note (GenNode("expand", yes));
-          return false; // state persisted if necessary
-        }
-      
       virtual void
       doRevealYourself()  override
         {
@@ -180,7 +171,15 @@ namespace ctrl {
         : model::Controller{identity, nexus}
         , allocateWidget_{wa}
         , widget_{}
-        { }
+        {
+          installExpander([&](){ return widget_ and widget_->expand; }
+                         ,[&](bool yes)
+                               {
+                                 if (widget_ or yes)
+                                   getWidget().expand (yes);
+                               }
+                         );
+        }
       
      ~NotificationHub() { };
       
diff --git a/src/gui/model/tangible.cpp b/src/gui/model/tangible.cpp
index f435174b0..581912e7a 100644
--- a/src/gui/model/tangible.cpp
+++ b/src/gui/model/tangible.cpp
@@ -262,7 +262,10 @@ namespace model {
   Tangible::doMark (GenNode const& stateMark)
   {
     if (stateMark.idi.getSym() == "expand")
-      this->doExpand (stateMark.data.get());
+      {
+        if (this->doExpand (stateMark.data.get()))
+          uiBus_.note (GenNode("expand", true));    // possibly reentrant (yet harmless)
+      }
     else
     if (stateMark.idi.getSym() == "reset")
       this->reset();
diff --git a/src/gui/panel/infobox-panel.cpp b/src/gui/panel/infobox-panel.cpp
index a0980b279..2c8d86996 100644
--- a/src/gui/panel/infobox-panel.cpp
+++ b/src/gui/panel/infobox-panel.cpp
@@ -81,7 +81,11 @@ namespace panel{
   
   /** on demand allocate display of information / error log
    * @note we assume it stays alive forever, once allocated
-   * @todo add possibility to collapse it
+   * @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()
@@ -91,6 +95,9 @@ namespace panel{
         theLog_.reset (new ErrorLogDisplay{});
         logExpander_.set_expanded (false);
         logExpander_.add (*theLog_);
+        theLog_->expand = model::Expander{[&]()         { return logExpander_.get_expanded(); }
+                                         ,[&](bool yes) { logExpander_.set_expanded (yes); }
+                                         };
         frame_.set_border_width (5);
         frame_.add (logExpander_);
         frame_.show_all();
diff --git a/src/gui/widget/error-log-display.hpp b/src/gui/widget/error-log-display.hpp
index 66c431d0a..376ceea2f 100644
--- a/src/gui/widget/error-log-display.hpp
+++ b/src/gui/widget/error-log-display.hpp
@@ -57,6 +57,7 @@
 #define GUI_WIDGET_ERROR_LOG_DISPLAY_H
 
 #include "gui/gtk-base.hpp"
+#include "gui/model/expander-revealer.hpp"
 #include "include/gui-notification-facade.h"
 
 //#include "lib/util.hpp"
@@ -96,6 +97,8 @@ namespace widget {
           textLog_.set_editable (false);
         }
       
+      model::Expander expand;
+      
       
       void
       showMsg (NotifyLevel severity, string const& text)
@@ -114,13 +117,6 @@ namespace widget {
           }
         }
       
-      bool
-      expand (bool yes)
-        {
-          UNIMPLEMENTED ("Maintain expansion state, build a collapsed representation.");
-          return false; //////TODO true if state change happened; this makes the state persistent
-        }
-      
       void
       clearAll()
         {
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm
index 7a744cd16..8ae6c0337 100644
--- a/wiki/thinkPad.ichthyo.mm
+++ b/wiki/thinkPad.ichthyo.mm
@@ -1741,10 +1741,10 @@
 
 
 
-
-
-
-
+
+
+
+
 
 
 
@@ -1779,7 +1779,7 @@
 
 
 
-
+
 
   
     
@@ -1789,7 +1789,7 @@
     

der Parent-Container ist für das expand/collapse zuständig

- + @@ -1941,8 +1941,8 @@ - - + + @@ -2441,19 +2441,19 @@ - - - - - - - - + + + + - + + + + + @@ -2471,6 +2471,12 @@ + + + + + + From 026049a13c2cfbb634dad9342cd1d62c55ba412b Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 31 Aug 2018 23:42:55 +0200 Subject: [PATCH 042/178] UiElement: likewise integrate the Revealer functor (#1162) --- src/gui/ctrl/notification-hub.hpp | 7 +----- src/gui/model/controller.hpp | 12 ---------- src/gui/model/tangible.cpp | 37 ++++++++++++++++++++++-------- src/gui/model/tangible.hpp | 21 +++++++++++++---- src/gui/model/widget.hpp | 12 ---------- tests/gui/test/mock-elm.hpp | 9 ++------ wiki/renderengine.html | 4 ++-- wiki/thinkPad.ichthyo.mm | 38 +++++++++++++++---------------- 8 files changed, 69 insertions(+), 71 deletions(-) diff --git a/src/gui/ctrl/notification-hub.hpp b/src/gui/ctrl/notification-hub.hpp index 3c75f17f9..638e6bc90 100644 --- a/src/gui/ctrl/notification-hub.hpp +++ b/src/gui/ctrl/notification-hub.hpp @@ -117,12 +117,6 @@ namespace ctrl { return true; } - virtual void - doRevealYourself() override - { - getWidget().revealYourself(); // implies also expand - } - virtual bool doMsg (string text) override { @@ -179,6 +173,7 @@ namespace ctrl { getWidget().expand (yes); } ); + installRevealer([&](){ getWidget().revealYourself(); }); // implementation implies also expand } ~NotificationHub() { }; diff --git a/src/gui/model/controller.hpp b/src/gui/model/controller.hpp index f44df49d0..802c47d44 100644 --- a/src/gui/model/controller.hpp +++ b/src/gui/model/controller.hpp @@ -70,18 +70,6 @@ namespace model { UNIMPLEMENTED ("Controller reset"); } - virtual void - doReveal (ID child) override - { - UNIMPLEMENTED ("Controller doReveal"); - } - - virtual void - doRevealYourself() override - { - UNIMPLEMENTED ("Controller doRevealYourself"); - } - virtual bool doMsg (string text) override { diff --git a/src/gui/model/tangible.cpp b/src/gui/model/tangible.cpp index 581912e7a..dc6e95d39 100644 --- a/src/gui/model/tangible.cpp +++ b/src/gui/model/tangible.cpp @@ -189,22 +189,41 @@ namespace model { /** - * @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. + * 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 "`revealYourself`". + * @note this is an optional feature and requires the actual widget or controller + * either to override the ::doRevealYourself() 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(); } + /** + * 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::doRevealYourself() + { + if (reveal_.canReveal()) + reveal_(); + } + + /** * Perform a command or action, once the execution context has been established. diff --git a/src/gui/model/tangible.hpp b/src/gui/model/tangible.hpp index 137e9ea3e..170d14e62 100644 --- a/src/gui/model/tangible.hpp +++ b/src/gui/model/tangible.hpp @@ -202,8 +202,7 @@ namespace model { void slotExpand(); void slotCollapse(); - - void slotReveal(ID child); + void slotReveal(); void markFlash(); void markMsg (string message); @@ -211,14 +210,14 @@ namespace model { 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); - virtual void doReveal (ID child) =0; - virtual void doRevealYourself () =0; + virtual void doRevealYourself(); virtual bool doMsg (string) =0; virtual bool doErr (string) =0; @@ -269,6 +268,20 @@ namespace model { { 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 to actually cause the necessary actions. + * @note unless this setup function is invoked, the "`revealYourself`" functionality remains disabled. + * Typically this setup will be done by an owning parent container, binding to some internals + * and also recursively invoking the "`revealYourself`" action on the container. + */ + inline void + Tangible::installRevealer (Revealer::RevealeItFun how_to_uncover_the_element) + { + reveal_ = Revealer{move (how_to_uncover_the_element)}; + } diff --git a/src/gui/model/widget.hpp b/src/gui/model/widget.hpp index d5b303725..2abfa875e 100644 --- a/src/gui/model/widget.hpp +++ b/src/gui/model/widget.hpp @@ -67,18 +67,6 @@ namespace model { UNIMPLEMENTED ("Widget reset"); } - virtual void - doReveal (ID child) override - { - UNIMPLEMENTED ("Widget doReveal"); - } - - virtual void - doRevealYourself() override - { - UNIMPLEMENTED ("Widget doRevealYourself"); - } - virtual bool doMsg (string text) override { diff --git a/tests/gui/test/mock-elm.hpp b/tests/gui/test/mock-elm.hpp index b0cec0865..132fe7ddc 100644 --- a/tests/gui/test/mock-elm.hpp +++ b/tests/gui/test/mock-elm.hpp @@ -145,16 +145,11 @@ namespace test{ return Tangible::doExpand (yes); } - virtual void - doReveal (ID child) override - { - UNIMPLEMENTED ("mock doReveal"); - } - virtual void doRevealYourself() override { - UNIMPLEMENTED ("mock doRevealYourself"); + log_.call(this->identify(), "revealYourself"); + Tangible::doRevealYourself(); // NOTE: without specific configuration this is NOP } virtual bool diff --git a/wiki/renderengine.html b/wiki/renderengine.html index af92192b7..9a14cf2a4 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -3241,7 +3241,7 @@ More specifically, the integration is based on ''messaging''. To start with, the ---- In a preliminary attempt to establish an integration between the GUI and the lower layers, in 1/2009 we created an PlayerDummy, which "pulls" dummy frames from the (not yet existing) engine and displays them within an XV viewer widget. This highlighted the problems we're about to encounter and made us think about the more radically decoupled approach we followed thereafter...
-
+
Building a layered architecture is a challenge, since the lower layer //really// needs to be self-contained, while prepared for usage by the higher layer.
 A major fraction of all desktop applications is written in a way where operational logic is built around the invocation from UI events -- what should be a shell turns into a backbone. One possible way to escape from this common anti pattern is to introduce a mediating entity, to translate between two partially incompatible demands and concerns: Sure, the "tangible stuff" is what matters, but you can not build any significant piece of technology if all you want is to "serve" the user.
 
@@ -3275,7 +3275,7 @@ Several UI elements offer the ability to be collapsed into a minimal representat
 
 !!!revealing an element
 The UI-Element protocol also includes the ability to //reveal an element// -- which means actively to bring this element into sight, in case it is hidden, collapsed or obscured by scrolling away.
-{{red{As of 8/2018 this is just an idea}}}, and many details still need to be considered. Yet at least one point is clear: implementing such a feature requires the help of the container widget actually holding the element to be revealed. It might even happen that also the collaboration of the container holding aforementioned immediate parent container is necessary -- indicating a recursive implementation scheme.
+{{red{As of 8/2018 this is just an idea}}}, and many details still need to be considered. Yet at least one point is clear: implementing such a feature requires the help of the container widget actually holding the element to be revealed. It might even happen that also the collaboration of the container holding aforementioned immediate parent container is necessary -- indicating a recursive implementation scheme. The default implementation is based on a similar scheme as the expand/collapse functionality: here we embed a {{{Revealer}}} functor, which then needs to be outfitted with a lambda binding into the internals of the parent container to effect the necessary presentation changes.
 
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 8ae6c0337..6ffe5f950 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -2009,22 +2009,19 @@ - + - - - - - + + + + + - - + + - - - - - + + @@ -2033,18 +2030,21 @@ - - - - + + + + - - + + + + + From 1ab42b298d66c095a9085de60fe2203d370ff9fa Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 1 Sep 2018 15:42:36 +0200 Subject: [PATCH 043/178] UiElement: add unit test to cover the doRevealYourself functionality ...which is now kind-of specified and we're providing a default implementation, so it should be documented in AbstractTangible_test --- tests/gui/abstract-tangible-test.cpp | 83 ++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 6 deletions(-) diff --git a/tests/gui/abstract-tangible-test.cpp b/tests/gui/abstract-tangible-test.cpp index 7e1f178dd..735b14c18 100644 --- a/tests/gui/abstract-tangible-test.cpp +++ b/tests/gui/abstract-tangible-test.cpp @@ -179,8 +179,9 @@ namespace test { verify_mockManipulation(); invokeCommand(); markState(); - notify(); - mutate(); + revealer(); +// notify(); +// mutate(); } @@ -362,10 +363,6 @@ namespace test { * * The second part of this test _replays_ such a state mark, which causes * the`doMark()` operation on the UI element to be invoked. - * @todo maybe we'll even provide a default implementation for expand/collapse - * which then means that, by replaying the mentioned state marks, the - * `doExpand()` or `doCollapse()` should be re-invoked, of course - * without issuing a further notification * @note this test does not cover or even emulate the operation of the * "state manager", since the goal is to cover the _UI element_ * protocol. We'll just listen at the bus and replay messages. @@ -473,6 +470,80 @@ namespace test { } + /** @test configure a handler for the (optional) "revealYourself" functionality. + * We install a lambda to supply the actual implementation action, which can then + * either be triggered by a signal/slot invocation, or by sending a "state mark". + */ + void + revealer () + { + MARK_TEST_FUN + EventLog nexusLog = gui::test::Nexus::startNewLog(); + + MockElm mock("target"); + ID targetID = mock.getID(); + + sigc::signal trigger_reveal; + trigger_reveal.connect (sigc::mem_fun(mock, &Tangible::slotReveal)); + + CHECK (not mock.isTouched()); + CHECK (not mock.isExpanded()); + CHECK (mock.ensureNot("reveal")); + CHECK (mock.ensureNot("expanded")); + CHECK (nexusLog.ensureNot("state-mark")); + + bool revealed = false; + mock.installRevealer([&]() + { // NOTE: our mock "implementation" of the revealYourself functionality + mock.slotExpand(); // explicitly prompts the element to expand itself, + revealed = true; // and then via closure sets a flag we can verify. + }); + + trigger_reveal(); + + CHECK (true == revealed); + CHECK (mock.isExpanded()); + CHECK (mock.verifyEvent("create","target") + .beforeCall("revealYourself") + .beforeCall("expand").arg(true) + .beforeEvent("expanded")); + + // invoking the slotExpand() also emitted a state mark to persist that expansion state... + CHECK (nexusLog.verifyCall("note").arg(targetID, GenNode{"expand", true}) + .before("handling state-mark")); + + + // second test: the same can be achieved via UI-Bus message... + revealed = false; + auto stateMark = GenNode{"revealYourself", 47}; // (payload argument irrelevant) + auto& uiBus = gui::test::Nexus::testUI(); + CHECK (nexusLog.ensureNot("revealYourself")); + + uiBus.mark (targetID, stateMark); // send the state mark message to reveal the element + + CHECK (true == revealed); + CHECK (mock.verifyMark("revealYourself", 47) + .afterEvent("expanded") + .beforeCall("revealYourself") + .beforeCall("expand").arg(true)); + + CHECK (nexusLog.verifyCall("mark").arg(targetID, stateMark) + .after("handling state-mark") + .before("revealYourself") + .beforeEvent("delivered mark")); + + + + cout << "____Event-Log_________________\n" + << util::join(mock.getLog(), "\n") + << "\n───╼━━━━━━━━━╾────────────────"< Date: Sat, 1 Sep 2018 17:30:20 +0200 Subject: [PATCH 044/178] EventLog: investigate failed match in EventLog seemingly my quick-n-dirty implementation was to naiive. We need real backtracking, if we want to support switches in the search direction (match("y").after("x").before("z") Up to now, I have cheated myself around this obvious problem :-/ --- src/lib/test/event-log.hpp | 10 ++-- tests/gui/abstract-tangible-test.cpp | 7 +++ tests/library/test/test-event-log-test.cpp | 3 ++ wiki/thinkPad.ichthyo.mm | 57 ++++++++++------------ 4 files changed, 44 insertions(+), 33 deletions(-) diff --git a/src/lib/test/event-log.hpp b/src/lib/test/event-log.hpp index 8e63f37e3..443d8802d 100644 --- a/src/lib/test/event-log.hpp +++ b/src/lib/test/event-log.hpp @@ -111,7 +111,7 @@ namespace test{ bool foundSolution() { - return !isnil (solution_); + return not isnil (solution_); } /** this is actually called after each refinement of @@ -129,7 +129,7 @@ namespace test{ if (look_for_match_ and not isnil (violation_)) return; // already failed, no further check necessary - if (foundSolution()) + if (foundSolution()) // NOTE this pulls the filter { lastMatch_ = matchSpec+" @ "+string(*solution_) + (isnil(lastMatch_)? "" @@ -898,10 +898,14 @@ namespace test{ return matcher; } - /** start a query to ensure the given expression does \em not match. + /** start a query to ensure the given expression does _not_ match. * @remarks the query expression is built similar to the other queries, * but the logic of evaluation is flipped: whenever we find any match * the overall result (when evaluating to `bool`) will be `false`. + * @warning this is not an proper exhaustive negation, since the implementation + * does not proper backtracking with a stack of choices. This becomes + * evident, when you combine `ensureNot()` with a switch in search + * direction, like e.g. using `afterXXX` at some point in the chain. */ EventMatch ensureNot (string match) const diff --git a/tests/gui/abstract-tangible-test.cpp b/tests/gui/abstract-tangible-test.cpp index 735b14c18..29a42bfe0 100644 --- a/tests/gui/abstract-tangible-test.cpp +++ b/tests/gui/abstract-tangible-test.cpp @@ -532,6 +532,13 @@ namespace test { .before("revealYourself") .beforeEvent("delivered mark")); + // Note the fine point: the target element /was/ already expanded + // and thus there is no second "expanded" event, nor is there a + // second state mark emitted into the UI-Bus... + CHECK (mock.ensureNot("expand") +// .afterCall("revealYourself") + .afterEvent("expanded")); + cout << "____Event-Log_________________\n" diff --git a/tests/library/test/test-event-log-test.cpp b/tests/library/test/test-event-log-test.cpp index 18ac7c2ac..c6ad29d90 100644 --- a/tests/library/test/test-event-log-test.cpp +++ b/tests/library/test/test-event-log-test.cpp @@ -108,6 +108,9 @@ namespace test{ CHECK (log.verify("ham").after("spam").after("beans")); CHECK (log.verify("ham").after("beans").before("spam").before("ham")); CHECK (not log.verify("spam").after("beans").after("ham")); + + log.event("beans"); + CHECK (log.verify("beans").after("spam")); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 6ffe5f950..f49937dbd 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -1818,8 +1818,7 @@ ...der das eigentliche Widget expandieren kann

- - +
@@ -1836,8 +1835,7 @@ ggfs müssen alle anderen Funktionen nach unten delegieren

- - +
@@ -1861,8 +1859,7 @@ Standard-Beispiel: Property-Panel

- -
+
@@ -1892,8 +1889,7 @@ ...wenn wir eine Mix-in -Implementierung wählen

- - +
@@ -2041,6 +2037,10 @@
+ + + + @@ -2149,7 +2149,7 @@ - + @@ -2327,7 +2327,7 @@ - + @@ -2356,8 +2356,7 @@ oder sie prüfen den Zustand und machen nichts, wenn das Widget nicht da ist

- - +
@@ -18634,8 +18633,7 @@ nicht bool-Testbar

- - + @@ -18663,8 +18661,7 @@ wenn es nicht sichtbar ist. Denn Sichtbarkeit gehört zur UI-Mechanik und geht den Client nix an

- -
+
@@ -18701,8 +18698,7 @@ denn dann gäbe es eine Implementierung "von der Stange"

- - +
@@ -18728,8 +18724,7 @@ aber die Operationen sind der Sache nach nicht symmetrisch

- - +
@@ -25100,6 +25095,13 @@ + + + + + + +
@@ -34410,8 +34412,7 @@ sofern das Widget mit entsprechendem Modus eingefügt wurde

- - +
@@ -35130,8 +35131,7 @@ Gtk::Expander

- - + @@ -35152,8 +35152,7 @@ ...ob beim Expand/Collapse das umschließende Widget resized werden soll

- - +
@@ -35165,8 +35164,7 @@ ob eingeklappt oder ausgeklappt

- -
+
@@ -35184,8 +35182,7 @@ was dazu führt, daß das Kind stets allen verfügbaren Platz nimmt

- - +
From 3fc5a94b87005fe02bd79a7004d6de18a4643c4d Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 2 Sep 2018 16:01:07 +0200 Subject: [PATCH 045/178] TreeExplorer: investigate the backtracking abilities There is a bug or shortcoming in the existing ErrorLog matcher implementation. It is not really difficult to fix, however doing so would require us to intersperse yet another helper facility into the log matcher. And it occurred to me, that this helper would effectively re-implement the stack based backtracking ability, which is already present in TreeExplorer (and was created precisely to support this kind of recursive evaluation strategies). Thus I intend to switch the implementation of the EventLog matcher from the old IterTool framework to the newer TreeExplorer framework. And this intention made me re-read the code, fixing several comments and re-thinking the design --- src/lib/iter-tree-explorer.hpp | 6 +- tests/library/iter-tree-explorer-test.cpp | 82 +-- wiki/thinkPad.ichthyo.mm | 705 ++++++++++++++++------ 3 files changed, 557 insertions(+), 236 deletions(-) diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index 6aa6ea8a4..870747646 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -574,7 +574,7 @@ namespace lib { * yielding a compatible value type). Now, this _sequence of children_ effectively * replaces the expanded source element in the overall resulting sequence; which * means, the nested sequence was _flattened_ into the results. Since this expand() - * operation can again invoked on the results, the implementation of such an evaluation + * operation can again be invoked on the results, the implementation of such an evaluation * requires a stack datastructure, so the nested iterator from each expand() invocation * can be pushed to become the new active source for iteration. Thus the primary purpose * of this Expander (decorator) is to integrate those "nested child iterators" seamlessly @@ -583,7 +583,7 @@ namespace lib { * the source iterator wrapped by this decorator. * @remark since we allow a lot of leeway regarding the actual form and definition of the * _expansion functor_, there is a lot of minute technical details, mostly confined - * within the _BoundFunctorTraits. + * within the _BoundFunctor traits. * @tparam SRC the wrapped source iterator, typically a TreeExplorer or nested decorator. * @tparam FUN the concrete type of the functor passed. Will be dissected to find the signature */ @@ -1108,7 +1108,7 @@ namespace lib { /* ==== Builder functions ==== */ /** preconfigure this TreeExplorer to allow for _"expansion of children"_. - * The resulting iterator exposes an `expand()` function, which consumes + * The resulting iterator exposes an `expandChildren()` function, which consumes * the current head element of this iterator and feeds it through the * _expansion functor_, which was provided to this builder function here. * The _expansion functor_ is expected to yield a sequence of "child" elements, diff --git a/tests/library/iter-tree-explorer-test.cpp b/tests/library/iter-tree-explorer-test.cpp index 3425789c6..02c5d4021 100644 --- a/tests/library/iter-tree-explorer-test.cpp +++ b/tests/library/iter-tree-explorer-test.cpp @@ -218,42 +218,46 @@ namespace test{ /*******************************************************************//** * @test use a simple source iterator yielding numbers - * to build various functional evaluation structures, - * based on the \ref IterExplorer template. - * - the [state adapter](\ref verifyStateAdapter() ) - * iterator construction pattern - * - helper to [chain iterators](\ref verifyChainedIterators() ) - * - building [tree exploring structures](\ref verifyDepthFirstExploration()) - * - the [monadic nature](\ref verifyMonadOperator()) of IterExplorer - * - a [recursively self-integrating](\ref verifyRecrusiveSelfIntegration()) - * evaluation pattern + * to build various functional evaluation pipelines, + * based on the \ref TreeExplorer template. + * - the adapter to wrap the source, which can either + * [be a "state core"](\ref verify_wrappedState() ), or can + * [be a "Lumiera Forward Iterator"](\ref verify_wrappedIterator() ) + * - the defining use case for TreeExplorer is to build a + * [pipeline for depth-first exploration](\ref verify_expandOperation() ) + * of a (functional) tree structure. This "tree" is created by invoking + * a "expand functor", which can be defined in various ways. + * - the usual building blocks for functional evaluation pipelines, that is + * [filtering](\ref verify_FilterIterator() ) and + * [transforming](\ref verify_transformOperation() ) of + * the elements yielded by the wrapped source iterator. + * - building complex pipelines by combining the aforementioned building blocks + * - using an opaque source, hidden behind the IterSource interface, and + * an extension (sub interface) to allow for "tree exploration" without + * any knowledge regarding the concrete implementation of the data source. * * ## Explanation * - * Both this test and the IterExplorer template might be bewildering - * and cryptic, unless you know the *Monad design pattern*. »Monads« - * are heavily used in functional programming, actually they originate - * from Category Theory. Basically, Monad is a pattern where we combine - * several computation steps in a specific way; but instead of intermingling - * the individual computation steps and their combination, the goal is to - * isolate and separate the _mechanics of combination_, so we can focus on - * the actual _computation steps:_ The mechanics of combination are embedded - * into the Monad type, which acts as a kind of container, holding some entities - * to be processed. The actual processing steps are then attached to the monad as - * "function object" parameters. It is up to the monad to decide if, and when - * those processing steps are applied to the embedded values and how to combine - * the results into a new monad. + * These tests build a evaluation pipeline by _wrapping_ some kind of data source + * and then layering some evaluation stages on top. There are two motivations why + * one might want to build such a _filter pipeline:_ + * - on demand processing ("pull principle") + * - separation of source computation and "evaluation mechanics" + * when building complex search and backtracking algorithms. * - * Using the monad pattern is well suited when both the mechanics of - * combination and the individual computation steps tend to be complex. - * In such a situation, it is beneficial to develop and test both - * in isolation. The IterExplorer template applies this pattern - * to the task of processing a source sequence. Typically we use - * this in situations where we can't afford building elaborate - * data structures in (global) memory, but rather strive at - * doing everything on-the-fly. A typical example is the - * processing of a variably sized data set without - * using heap memory for intermediary results. + * This usage style is inspired from the *Monad design pattern*. In our case here, + * the Iterator pipeline would be the monad, and can be augmented and reshaped by + * attaching further processing steps. How those processing steps are to be applied + * remains an internal detail, defined by the processing pipeline. »Monads« are heavily + * used in functional programming, actually they originate from Category Theory. Basically, + * Monad is a pattern where we combine several computation steps in a specific way; but + * instead of intermingling the individual computation steps and their combination, + * the goal is to isolate and separate the _mechanics of combination_, so we can focus + * on the actual _computation steps:_ The mechanics of combination are embedded into the + * Monad type, which acts as a kind of container, holding some entities to be processed. + * The actual processing steps are then attached to the monad as "function object" parameters. + * It is up to the monad to decide if, and when those processing steps are applied to the + * embedded values and how to combine the results into a new monad. * * @see TreeExplorer * @see IterAdapter @@ -440,7 +444,7 @@ namespace test{ template void - verify_treeExpandingIterator(EXP ii) + verify_treeExpandingIterator (EXP ii) { CHECK (!isnil (ii)); CHECK (5 == *ii); @@ -620,7 +624,7 @@ namespace test{ * matching algorithms. Even more so, when other operations like filtering are intermingled; * in that case it might even happen that the downstream consumer does not even see the * items resulting from child expansion, because they are evaluated and then filtered - * away by a transformer and filter placed in between. + * away by transformers and filters placed in between. * @note as a consequence of the flexible automatic adapting of bound functors, it is * possible for bound functors within different "layers" to collaborate, based on * additional knowledge regarding the embedded data source internals. This test @@ -779,6 +783,16 @@ namespace test{ + // contrived example to verify interplay of filtering and child expansion; + // especially note that the filter is re-evaluated after expansion happened. + cout << "VERIFY->" + <
- + @@ -5509,7 +5509,7 @@
- + @@ -5921,7 +5921,7 @@ - + @@ -5943,7 +5943,7 @@ - + @@ -5979,15 +5979,12 @@ - - + + - - - - + @@ -6110,7 +6107,7 @@ - + @@ -6139,7 +6136,7 @@ - + @@ -6171,7 +6168,7 @@ - + @@ -6337,7 +6334,7 @@ - + @@ -6351,16 +6348,8 @@

+ - - - - - - - - - @@ -6582,10 +6571,10 @@ - + @@ -9056,9 +9045,10 @@ - + + @@ -9067,7 +9057,7 @@ - + @@ -9076,7 +9066,7 @@ - + @@ -9092,7 +9082,7 @@ - + @@ -9111,8 +9101,8 @@ - - + + @@ -9219,7 +9209,7 @@ - + @@ -9331,7 +9321,7 @@ - + @@ -9373,7 +9363,7 @@ - + @@ -9472,7 +9462,7 @@ - + @@ -9681,7 +9671,7 @@ - + @@ -10109,7 +10099,7 @@ - + @@ -15311,7 +15301,7 @@ - + @@ -17293,7 +17283,7 @@ - + @@ -25101,6 +25091,45 @@ + + + + + + +

+ Beispiel: +

+

+ a-b-c-a +

+

+ match("a").after("b") scheitert, weil sich die Suche am ersten "a" festbeißt. +

+ + +
+ +
+ + + + + + + + + + + + + + + + + + + @@ -30432,6 +30461,436 @@
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Thema: Monaden +

+ +
+ + + + + + + + + +

+ sind Monaden +

+

+ wirklich hilfreich? +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...bindet die Betrachtung auf einen technischen Level, +

+

+ und führt dazu, daß die Abstraktion undicht wird +

+ +
+
+ + + + + + + + + + + + +

+ genau der Umstand, +

+

+ daß funktionale Sprachen von einer Zustands-Phobie getrieben sind, +

+

+ macht Monaden nützlich, um inhärenten Zustand wegzuabstrahieren. +

+

+ Das kann genutzt werden, um den Zustand einer Wechselwirkung +

+

+ nach einer Seite der Glieder auszukoppeln. +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ gehört zu dem Themenkomplex "itertools" +

+

+ Python hat das auch, Java neuerdings auch +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...will sagen, es ist klar, wie man sowas machen kann. +

+

+ Seinerzeit war mir das auch klar, aber ich wollte es nicht gleich ausprogrammieren. +

+

+ Inzwischen kam dann das Thema UI-Coordinaten, und dort habe ich es ausprogrammiert, +

+

+ und zwar direkt in die Low-Level-Schicht integriert, was nicht schlecht ist, +

+

+ da eine Abstraktion hier sehr technisch werden würde +

+ +
+
+
+
+ + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...von der entsprechenden Methode im Transformer +

+ + +
+ +
+ + + + + + +

+ ...hatte die Notwendigkeit hierfür seinerzeit während der Tests entdeckt, +

+

+ und im Unit-Test nur für die Kombination Transformer + Explorer abgedeckt.... +

+ + +
+ +
+ + + +
+
+ + + + + + + + +
    +
  • + Suche wird geleitet durch die ViewSpecDSL +
  • +
  • + hinter dem (opaquen) TreeExplorer steckt die konkrete UI-Topologie +
  • +
+ + +
+
+ + + + + + +

+ EventLog ist ein Test-Hilfsmittel, +

+

+ um Unit-Tests über UI-Bus-Interaktionen schreiben zu können. +

+

+ Es gibt hierzu Test-Varianten, die jeden Aufruf in ein internes Log notieren. +

+

+ Im Test verwendet man dann eine Pattern-Such-DSL, +

+

+ hinter der sich eine verkettete Suche mit Backtracking verbirgt +

+ + +
+ + + + + + + + + + + + + + + + + + + +

+ sofern längerfristig Itertools durch TreeExplorer abgelöst werden könnte +

+ + +
+
+
+
+ + + + + + + + + + + +

+ Invariante: pullMatch() +

+ + +
+ + + +
+ + + + + + + + + +
+
+
+
@@ -33154,7 +33613,7 @@
- + @@ -33529,7 +33988,7 @@ - + @@ -34090,166 +34549,14 @@ - - - - - - - - -

- Thema: Monaden -

- -
- - - - - - - - - - - - - - - - -

- gehört zu dem Themenkomplex "itertools" -

-

- Python hat das auch, Java neuerdings auch -

- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- ...will sagen, es ist klar, wie man sowas machen kann. -

-

- Seinerzeit war mir das auch klar, aber ich wollte es nicht gleich ausprogrammieren. -

-

- Inzwischen kam dann das Thema UI-Coordinaten, und dort habe ich es ausprogrammiert, -

-

- und zwar direkt in die Low-Level-Schicht integriert, was nicht schlecht ist, -

-

- da eine Abstraktion hier sehr technisch werden würde -

- -
-
-
-
- - - - - - - -
-
- - - - - - - - - - - - - - -

- ...bindet die Betrachtung auf einen technischen Level, -

-

- und führt dazu, daß die Abstraktion undicht wird -

- -
-
- - - - - - - - - - - - -

- genau der Umstand, -

-

- daß funktionale Sprachen von einer Zustands-Phobie getrieben sind, -

-

- macht Monaden nützlich, um inhärenten Zustand wegzuabstrahieren. -

-

- Das kann genutzt werden, um den Zustand einer Wechselwirkung -

-

- nach einer Seite der Glieder auszukoppeln. -

- -
-
- - - - - - - - - - - + + + + + + + From 757258fb3ac9507a71156b1385a4761efd64aa42 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 2 Sep 2018 16:27:56 +0200 Subject: [PATCH 046/178] TreeExplorer: fix bug in Filter layer Fillter needs to be re-evaluated, when an downstream entity requests expandChildren() onto an upstream source. And obviously the ordering of the chained calls was wrong here. As it turns out, I had discovered that necessity to re-evaluate with the Transformer layer. There is a dedicated test case for that, but I cut short on verifying the filter in that situation as well, so that piece of broken copy-n-paste code went through undetected. This is in fact a rather esoteric corner case, because it is only triggered when the expandChildren() call is passed through the filter. When otoh the filter sits /after/ the entity generating the expandChildren() calls, everything works as intended. And the latter is the typical standard usage situation of an recursive evalutation algorithm: the filter is here used as final part to drive the evaluation ahead and pick the solutions. --- src/lib/iter-tree-explorer.hpp | 2 +- tests/library/iter-tree-explorer-test.cpp | 14 ++++++++------ wiki/thinkPad.ichthyo.mm | 8 ++++---- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index 870747646..650db48cc 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -885,8 +885,8 @@ namespace lib { void expandChildren() { - pullFilter(); SRC::expandChildren(); + pullFilter(); } public: /* === Iteration control API for IterableDecorator === */ diff --git a/tests/library/iter-tree-explorer-test.cpp b/tests/library/iter-tree-explorer-test.cpp index 02c5d4021..39e9e08d3 100644 --- a/tests/library/iter-tree-explorer-test.cpp +++ b/tests/library/iter-tree-explorer-test.cpp @@ -785,13 +785,15 @@ namespace test{ // contrived example to verify interplay of filtering and child expansion; // especially note that the filter is re-evaluated after expansion happened. - cout << "VERIFY->" - < 10-9-8-7-6-5-4-3-2-1-3-2-1-7-6-5-4-3-2-1-3-2-1 + .filter([](uint i){ return i%2 == 0; }) + .expandAll() // Note: sends the expandChildren down through the filter + ) + == "10-8-6-4-2-2-6-4-2-2"); + + // another convoluted example to demonstrate // - a filter predicate with side-effect diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 172ad8055..c4a24a536 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -30745,8 +30745,8 @@
- - + + @@ -30780,8 +30780,8 @@ - - + + From 94da0f627f3af36d09ef92cdd2ca87ce70f7b6ac Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 2 Sep 2018 18:42:58 +0200 Subject: [PATCH 047/178] TreeExplorer: draft ability to remould the filter The intention is to switch from the itertools-based filter to the filter available in the TreeExplorer framework. Thus "basically" we just need to copy the solution over, since both are conceptually equivalent. However...... :-( The TreeExplorer framework is designed to be way more generic and accepts basically everything as argument and tries to adapt apropriately. This means we have to use a lot of intricate boilerplate code, just to get the same effect that was possible in Itertools with a simple and elegant in-place lambda assignment --- src/lib/iter-tree-explorer.hpp | 27 +++- tests/library/iter-tree-explorer-test.cpp | 23 ++++ wiki/thinkPad.ichthyo.mm | 149 +++++++++++++++++++++- 3 files changed, 197 insertions(+), 2 deletions(-) diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index 650db48cc..9e4227c5f 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -526,7 +526,7 @@ namespace lib { template Res - operator() (ARG& arg) + operator() (ARG& arg) const { auto accessArg = ArgAccessor::build(); @@ -910,6 +910,31 @@ namespace lib { pullFilter(); } + + /* === Remould the Filter condition underway === */ + + template + void + andFilter (COND conjunctiveClause) + { + using _ChainTraits = _BoundFunctor; + using Res = typename _ChainTraits::Res; + static_assert(std::is_constructible::value, "Chained Functor must be a predicate"); + + using ChainPredicate = typename _ChainTraits::Functor; + + FilterPredicate& firstClause = this->predicate_; + ChainPredicate chainClause{forward (conjunctiveClause)}; + + + predicate_ = FilterPredicate{[firstClause, chainClause] (auto val) + { + return firstClause(val) + and chainClause(val); + } }; + pullFilter(); + } + private: SRC& srcIter() const diff --git a/tests/library/iter-tree-explorer-test.cpp b/tests/library/iter-tree-explorer-test.cpp index 39e9e08d3..5ef65bf60 100644 --- a/tests/library/iter-tree-explorer-test.cpp +++ b/tests/library/iter-tree-explorer-test.cpp @@ -276,6 +276,7 @@ namespace test{ verify_combinedExpandTransform(); verify_scheduledExpansion(); verify_FilterIterator(); + verify_FilterChanges(); verify_asIterSource(); verify_IterSource(); @@ -847,6 +848,28 @@ namespace test{ + /** @test the filter layer can be re-configured on the fly */ + void + verify_FilterChanges() + { + auto seq = treeExplore(CountDown{20}) + .filter([](uint){ return true; }); + + auto takeEve = [](uint i){ return i%2 == 0; }; + auto takeTrd = [](uint i){ return i%3 == 0; }; + + CHECK (20 == *seq); + ++seq; + CHECK (19 == *seq); + CHECK (19 == *seq); + + seq.andFilter (takeEve); + CHECK (18 == *seq); + } + + + + /** @test package the resulting Iterator as automatically managed, * polymorphic opaque entity implementing the IterSource interface. * The builder operations on TreeExplorer each generate a distinct, implementation diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index c4a24a536..cff9e76e8 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -30840,7 +30840,8 @@ - + + @@ -30855,6 +30856,152 @@ + + + + + + + + + + + + + + +

+ ...und das hängt an einem hauchdünnen Faden, +

+

+ und ist subtil bis zum geht nicht mehr.... +

+

+ +

+

+ Wenn man das Lambda einfach per [=] schreibt, und das Feld this->predicate_ verwendet, +

+

+ dann wird this gecaptured (und das ist effektiv per Referenz). Wenn ich dann den +

+

+ konstruierten Funktor an this->predicate_ zuweise, haben wir eine Endlos-Rekursion. +

+

+ +

+

+ Lösung: man muß im lokalen Frame eine Referenz auf this->predicate definieren und binden. +

+

+ Diese wird dann per Value gecaptured, was die gewünschte Kopie bewirkt. +

+ + +
+
+ + + + + + +

+ ...muß den Chain-Funktor aus dem Template-Argument erzeugen, +

+

+ und ihn dann in die per-Value-Closure des erzeugten neuen Lambda binden (=Kopie). +

+

+ +

+

+ Erst ab C++17 kann man Lambda-Captures pre move machen +

+

+ (und auch dafür ist die Syntax grausam) +

+ + +
+
+
+ + + + + + + +

+ ...es soll bloß einfach funktionieren!!!!!!!!!!! +

+ + +
+ +
+ + + + + + + + + +

+ keine gute Idee. +

+

+ Dann verwenden wir im einen Funktor-Framework eine Filter-Komponente +

+

+ aus dem anderen Framework, obwohl es direkt hier auch eine Filter-Komponente gäbe. +

+

+ +

+

+ Außerdem habe ich immer noch die Hoffnung, irgendwann mal +

+

+ die Itertools komplett durch den TreeExplorer ablösen zu können +

+ + +
+ +
+ + + + + + + + + + + + + + + + + + + + + +

+ ...so daß es nur noch wenige Zugangs-Punkte zum unterliegenden Iterator gibt +

+ + +
+
From b4edf8e33cb67b4a2dd1ce09dbee940ca9c5f984 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 2 Sep 2018 19:28:11 +0200 Subject: [PATCH 048/178] TreeExplorer: find a way to extract the boilerplate ...based on generic lambdas, which are effectively template classes themselves --- src/lib/iter-tree-explorer.hpp | 14 +++++++++----- wiki/thinkPad.ichthyo.mm | 22 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index 9e4227c5f..be6f25da0 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -926,12 +926,16 @@ namespace lib { FilterPredicate& firstClause = this->predicate_; ChainPredicate chainClause{forward (conjunctiveClause)}; + auto buildCombinedClause = [](auto first, auto second) + { + return [=](auto val) + { + return first(val) + and second(val); + }; + }; - predicate_ = FilterPredicate{[firstClause, chainClause] (auto val) - { - return firstClause(val) - and chainClause(val); - } }; + predicate_ = FilterPredicate{buildCombinedClause (firstClause, chainClause)}; pullFilter(); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index cff9e76e8..d5494fb5d 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -30977,10 +30977,32 @@ + + + + + + + +

+ Name: mutableFilter() +

+ + +
+ +
+ + + + + + + From 8f70b4e902b543f7e5f8a9d10304d5a9c590c7ac Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 2 Sep 2018 20:27:19 +0200 Subject: [PATCH 049/178] TreeExplorer: prototype for the extracted boilerplate helper a quite convoluted construct built from several nested generic lambdas. When investigated in the debugger, the observed addresses and the invoked code looks sane and as expected. --- src/lib/iter-tree-explorer.hpp | 66 +++++++++++++---------- tests/library/iter-tree-explorer-test.cpp | 2 + wiki/thinkPad.ichthyo.mm | 52 ++++++++++++++++-- 3 files changed, 88 insertions(+), 32 deletions(-) diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index be6f25da0..da1d676b5 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -911,34 +911,6 @@ namespace lib { } - /* === Remould the Filter condition underway === */ - - template - void - andFilter (COND conjunctiveClause) - { - using _ChainTraits = _BoundFunctor; - using Res = typename _ChainTraits::Res; - static_assert(std::is_constructible::value, "Chained Functor must be a predicate"); - - using ChainPredicate = typename _ChainTraits::Functor; - - FilterPredicate& firstClause = this->predicate_; - ChainPredicate chainClause{forward (conjunctiveClause)}; - - auto buildCombinedClause = [](auto first, auto second) - { - return [=](auto val) - { - return first(val) - and second(val); - }; - }; - - predicate_ = FilterPredicate{buildCombinedClause (firstClause, chainClause)}; - pullFilter(); - } - private: SRC& srcIter() const @@ -955,6 +927,44 @@ namespace lib { while (srcIter() and not predicate_(srcIter())) ++srcIter(); } + + + + public: /* === API to Remould the Filter condition underway === */ + + template + void + andFilter (COND&& conjunctiveClause) + { + remouldFilter (forward (conjunctiveClause) + ,[](auto first, auto chain) + { + return [=](auto val) + { + return first(val) + and chain(val); + }; + }); + } + + + private: + template + void + remouldFilter (COND&& additionalClause, COMB buildCombinedClause) + { + using _ChainTraits = _BoundFunctor; + using Res = typename _ChainTraits::Res; + static_assert(std::is_constructible::value, "Chained Functor must be a predicate"); + + using ChainPredicate = typename _ChainTraits::Functor; + + FilterPredicate& firstClause = this->predicate_; + ChainPredicate chainClause{forward (additionalClause)}; + + predicate_ = FilterPredicate{buildCombinedClause (firstClause, chainClause)}; + pullFilter(); + } }; diff --git a/tests/library/iter-tree-explorer-test.cpp b/tests/library/iter-tree-explorer-test.cpp index 5ef65bf60..6f25ed072 100644 --- a/tests/library/iter-tree-explorer-test.cpp +++ b/tests/library/iter-tree-explorer-test.cpp @@ -865,6 +865,8 @@ namespace test{ seq.andFilter (takeEve); CHECK (18 == *seq); + ++seq; + CHECK (16 == *seq); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index d5494fb5d..13adccc8c 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -30974,7 +30974,9 @@ - + + + @@ -30988,12 +30990,12 @@ Name: mutableFilter()

- - +
- + + @@ -31002,6 +31004,48 @@ + + + + + + +

+ ...sieht gut aus. +

+

+ +

+

+ Folgendes habe ich gesehen +

+
    +
  • + zu Beginn zeigt das eingebettete Funktor-Objekt auf eine Position auf dem Stack +
  • +
  • + beim Aufrufen der andFilter()-Funktion werden diverse Funktoren kopiert,
    wobei nacheinander die (zu erwartenden) Argumente als Quelle auftauchen
    +
  • +
  • + danach hat sich der Funktor geändert: er zeigt nun auf eine Position auf dem Heap
    +
  • +
  • + der bisherige Funktor wurde mit der Closure des zusammengesetzten Funktors kollabiert (hat gleiche Addresse)
    +
  • +
  • + der Chain-Funktor hat eine Closure bekommen, die ebenfalls Heap-alloziert ist.
    das deutet darauf hin, daß das capturen per copy funktioniert hat
    +
  • +
  • + Beim Aufruf steppen wir nacheinander erst in den kombinierten Funktor,
    und von dort wie erwartet in die beiden Lambdas. +
  • +
  • +
    +
  • +
+ + +
+
From e29d9ae19e48dab84449f70d090c4ebc1dad3a78 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 2 Sep 2018 20:40:49 +0200 Subject: [PATCH 050/178] TreeExplorer: better package this very specific code as subclass while this is basically just code code cosmetics, at least it marks this as a very distinct special case, and keeps the API for the standard Filter layer clean. --- src/lib/iter-tree-explorer.hpp | 33 +++++++++++++++++++---- tests/library/iter-tree-explorer-test.cpp | 2 +- wiki/thinkPad.ichthyo.mm | 11 ++++++-- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index da1d676b5..621f7b5be 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -863,6 +863,7 @@ namespace lib { using Res = typename _Traits::Res; static_assert(std::is_constructible::value, "Functor must be a predicate"); + protected: using FilterPredicate = typename _Traits::Functor; FilterPredicate predicate_; @@ -911,7 +912,7 @@ namespace lib { } - private: + protected: SRC& srcIter() const { @@ -927,7 +928,17 @@ namespace lib { while (srcIter() and not predicate_(srcIter())) ++srcIter(); } - + }; + + + + template + class MutableFilter + : public Filter + { + using _Base = Filter; + public: + using _Base::Filter; public: /* === API to Remould the Filter condition underway === */ @@ -957,13 +968,14 @@ namespace lib { using Res = typename _ChainTraits::Res; static_assert(std::is_constructible::value, "Chained Functor must be a predicate"); + using FilterPredicate = typename _Base::FilterPredicate; using ChainPredicate = typename _ChainTraits::Functor; - FilterPredicate& firstClause = this->predicate_; + FilterPredicate& firstClause = Filter::predicate_; ChainPredicate chainClause{forward (additionalClause)}; - predicate_ = FilterPredicate{buildCombinedClause (firstClause, chainClause)}; - pullFilter(); + _Base::predicate_ = FilterPredicate{buildCombinedClause (firstClause, chainClause)}; + _Base::pullFilter(); } }; @@ -1265,6 +1277,17 @@ namespace lib { } + template + auto + mutableFilter (FUN&& filterPredicate) + { + using ResCore = iter_explorer::MutableFilter; + using ResIter = typename _DecoratorTraits::SrcIter; + + return TreeExplorer (ResCore {move(*this), forward(filterPredicate)}); + } + + /** _terminal builder_ to package the processing pipeline as IterSource. * Invoking this function moves the whole iterator compound, as assembled by the preceding * builder calls, into heap allocated memory and returns a [iterator front-end](\ref IterExploreSource). diff --git a/tests/library/iter-tree-explorer-test.cpp b/tests/library/iter-tree-explorer-test.cpp index 6f25ed072..ea4bda8b5 100644 --- a/tests/library/iter-tree-explorer-test.cpp +++ b/tests/library/iter-tree-explorer-test.cpp @@ -853,7 +853,7 @@ namespace test{ verify_FilterChanges() { auto seq = treeExplore(CountDown{20}) - .filter([](uint){ return true; }); + .mutableFilter([](uint){ return true; }); auto takeEve = [](uint i){ return i%2 == 0; }; auto takeTrd = [](uint i){ return i%3 == 0; }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 13adccc8c..38890073b 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -30977,7 +30977,8 @@
- + + @@ -31051,10 +31052,16 @@ - + + + + + + + From 90c0f43cfda28853e3d858f3e7068cbe1204a1c7 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 2 Sep 2018 22:34:30 +0200 Subject: [PATCH 051/178] TreeExplorer: code all the combination cases ...this solution works, but has a shortcoming: the type of the passed lambdas is effectively pinned to conform with the signature of the first lambda used initially when building the filter. Well, this is the standard use case, but it kind of turns all the tricky warpping and re-binding into a nonsense excercise; in this form the filter can only be used in the monadic case (value -> bool). Especially this rules out all the advanced usages, where the filter collaborates with the internals of the source. --- src/lib/iter-tree-explorer.hpp | 132 +++++++++++++++++++++- tests/library/iter-tree-explorer-test.cpp | 31 +++++ wiki/thinkPad.ichthyo.mm | 39 ++++++- 3 files changed, 196 insertions(+), 6 deletions(-) diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index 621f7b5be..2c70fdff4 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -725,7 +725,7 @@ namespace lib { /** - * @internal extension to the Expander decorator to perform expansion delayed on next iteration. + * @internal extension to the Expander decorator to perform expansion delayed on next iteration. */ template class ScheduledExpander @@ -932,6 +932,17 @@ namespace lib { + /** + * @internal Special variant of the \ref Filter Decorator to allow for dynamic remoulding. + * This covers a rather specific use case, where we want to remould or even exchange the + * Filter predicate in the middle of an ongoing iteration. Such a remoulding can be achieved + * by binding the existing (opaque) filter predicate into a new combined lambda, thereby + * capturing it _by value._ After building, this remoulded version can be assigned to the + * original filter functor, under the assumption that both are roughly compatible. Moreover, + * since we wrap the actual lambda into an adapter, allowing for generic lambdas to be used + * as filter predicates, this setup allows for a lot of leeway regarding the concrete predicates. + * @note whenever the filter is remoulded, the invariant is immediately [re-established](\ref Filter::pullFilter() ) + */ template class MutableFilter : public Filter @@ -943,6 +954,7 @@ namespace lib { public: /* === API to Remould the Filter condition underway === */ + /** remould existing predicate to require in addition the given clause to hold */ template void andFilter (COND&& conjunctiveClause) @@ -958,8 +970,101 @@ namespace lib { }); } + /** remould existing predicate to require in addition the negation of the given clause to hold */ + template + void + andNotFilter (COND&& conjunctiveClause) + { + remouldFilter (forward (conjunctiveClause) + ,[](auto first, auto chain) + { + return [=](auto val) + { + return first(val) + and not chain(val); + }; + }); + } + + /** remould existing predicate to require either the old _OR_ the given new clause to hold */ + template + void + orFilter (COND&& disjunctiveClause) + { + remouldFilter (forward (disjunctiveClause) + ,[](auto first, auto chain) + { + return [=](auto val) + { + return first(val) + or chain(val); + }; + }); + } + + /** remould existing predicate to require either the old _OR_ the negation of a new clause to hold */ + template + void + orNotFilter (COND&& disjunctiveClause) + { + remouldFilter (forward (disjunctiveClause) + ,[](auto first, auto chain) + { + return [=](auto val) + { + return first(val) + or not chain(val); + }; + }); + } + + /** remould existing predicate to negate the meaning of the existing clause */ + void + flipFilter() + { + auto dummy = [](auto){ return false; }; + remouldFilter (dummy + ,[](auto currentFilter, auto) + { + return [=](auto val) + { + return not currentFilter(val); + }; + }); + } + + /** replace the existing predicate with the given, entirely different predicate */ + template + void + setNewFilter (COND&& entirelyDifferentPredicate) + { + remouldFilter (forward (entirelyDifferentPredicate) + ,[](auto, auto chain) + { + return [=](auto val) + { + return chain(val); + }; + }); + } + private: + /** @internal boilerplate to remould the filter predicate in-place + * @param additionalClause additional functor object to combine + * @param buildCombinedClause a _generic lambda_ (important!) to define + * how exactly the old and the new predicate are to be combined + * @note the actual combination logic is handed in as generic lambda, which + * essentially is a template class, and this allows to bind to any kind + * of function objects or lambdas. This combination closure requires a + * specific setup: when invoked with the existing and the new functor + * as argument, it needs to build a new _likewise generic_ lambda + * to perform the combined evaluation. + * @warning the handed-in lambda `buildCombinedClause` must capture its + * arguments, the existing functors _by value._ This is the key piece + * in the puzzle, since it effectively moves the existing functor into + * a new heap allocated storage. + */ template void remouldFilter (COND&& additionalClause, COMB buildCombinedClause) @@ -971,15 +1076,17 @@ namespace lib { using FilterPredicate = typename _Base::FilterPredicate; using ChainPredicate = typename _ChainTraits::Functor; - FilterPredicate& firstClause = Filter::predicate_; - ChainPredicate chainClause{forward (additionalClause)}; + FilterPredicate& firstClause = _Base::predicate_; // pick up the existing filter predicate + ChainPredicate chainClause{forward (additionalClause)}; // wrap the extension predicate in a similar way _Base::predicate_ = FilterPredicate{buildCombinedClause (firstClause, chainClause)}; - _Base::pullFilter(); + _Base::pullFilter(); // pull to re-establish the Invariant } }; + + /** * Interface to indicate and expose the ability for _child expansion_. * This interface is used when packaging a TreeExplorer pipeline opaquely into IterSource. @@ -1222,7 +1329,7 @@ namespace lib { } - /** extension functionality to be used on top of expand(), to perform expansion on next iteration. + /** extension functionality to be used on top of expand(), to perform expansion on next iteration. * When configured, an expandChildren() call will not happen immediately, but rather in place of * the next iteration step. Basically child expansion _is kind of a special iteration step,_ and * thus all we need to do is add another layer with a boolean state flag, which catches the @@ -1277,6 +1384,21 @@ namespace lib { } + /** attach a special filter adapter, allowing to change the filter predicate while iterating. + * While otherwise this filter layer behaves exactly like the [standard version](\ref #filter), + * it exposes a special API to augment or even completely switch the filter predicate while + * in the middle of iterator evaluation. Of course, the underlying iterator is not re-evaluated + * from start (our iterators can not be reset), and so the new filter logic takes effect starting + * from the current element. Whenever the filter is remoulded, it is immediately re-evaluated, + * possibly causing the underlying iterator to be pulled until an element matching the condition + * is found. + * @see MutableFilter::andFilter + * @see MutableFilter::andNotFilter + * @see MutableFilter::orFilter + * @see MutableFilter::orNotFilter + * @see MutableFilter::flipFilter + * @see MutableFilter::setNewFilter + */ template auto mutableFilter (FUN&& filterPredicate) diff --git a/tests/library/iter-tree-explorer-test.cpp b/tests/library/iter-tree-explorer-test.cpp index ea4bda8b5..efbce194c 100644 --- a/tests/library/iter-tree-explorer-test.cpp +++ b/tests/library/iter-tree-explorer-test.cpp @@ -867,6 +867,37 @@ namespace test{ CHECK (18 == *seq); ++seq; CHECK (16 == *seq); + + seq.andFilter (takeTrd); + CHECK (12 == *seq); + + seq.flipFilter(); + CHECK (11 == *seq); // not divisible (by 2 AND by 3) + ++seq; + CHECK (10 == *seq); + + seq.setNewFilter (takeTrd); + CHECK ( 9 == *seq); + ++seq; + CHECK ( 6 == *seq); + + seq.orNotFilter (takeEve); + CHECK ( 6 == *seq); + ++seq; + CHECK ( 5 == *seq); + ++seq; + CHECK ( 3 == *seq); + +// string buff{"."}; +// seq.andNotFilter ([&](CountDown& core) +// { +// buff += util::toString(core.p) + "."; +// --core.p; +// return core.p % 2; +// }); +// cout << "URGS: "<<*seq<< " ..."< + + + + + + + + + + + +

+ ...für das dort hineingereichte Funktor-Objekt wird der Argument-Accessor ausgewählt (Metaprogrammierung). +

+

+ Er ist dann im Typ des Wrappers == _Traits::Functor codiert. +

+

+ +

+

+ Wir können zwar den im Wrapper enthaltenen Funktor neu zuweisen (in gewissen Grenzen), +

+

+ aber er wird stets den zu Beginn gewählten Argument-Accessor nehmen. +

+

+ Typischerweise wird dieser ja sogar beim Aufruf des getemplateteten Funtions-Operators geInlined +

+ + +
+
+ + + +
- + From be7f47d5b7a16fa644d28f5e3a1e693495394ac0 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 3 Sep 2018 00:27:21 +0200 Subject: [PATCH 052/178] TreeExplorer: rework the solution to allow for arbitrary functor types outift the Filter base class with the most generic form of the Functor wrapper, and rather wrap each functor argument individually. This allows then to combine various kinds of functors --- src/lib/iter-tree-explorer.hpp | 40 +++++++++++++++++++---- tests/library/iter-tree-explorer-test.cpp | 23 +++++++------ wiki/thinkPad.ichthyo.mm | 22 ++++++++++++- 3 files changed, 67 insertions(+), 18 deletions(-) diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index 2c70fdff4..eaad1db79 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -941,15 +941,41 @@ namespace lib { * original filter functor, under the assumption that both are roughly compatible. Moreover, * since we wrap the actual lambda into an adapter, allowing for generic lambdas to be used * as filter predicates, this setup allows for a lot of leeway regarding the concrete predicates. - * @note whenever the filter is remoulded, the invariant is immediately [re-established](\ref Filter::pullFilter() ) + * @note whenever the filter is remoulded, the invariant is immediately + * [re-established](\ref Filter::pullFilter() ), possibly forwarding the sequence + * to the next element approved by the new version of the filter. + * @remarks filter predicates can be specified in a wide variety of forms, and will be adapted + * automatically. This flexibility also holds for any of the additional clauses provided + * for remoulding the filter. Especially this means that functors of different kinds can + * be mixed and combined. To allow for this flexibility, we need to drive the _base class_ + * with the most general form of a predicate functor, corresponding to `bool`. + * Hereby we exploit the fact that the _wrapped filter,_ i.e. the Functor type constructed + * in the _BoundFunctor traits, boils down to that most generic form, adapting the arguments + * automatically. Thus the initial functor, the one passed to the ctor of this class, is + * effectively wrapped twice, so it can be combined later on with any other form and + * shape of functor. And since everything is inline, the compiler will remove any + * overhead resulting from this redundant wrapping. */ template class MutableFilter - : public Filter + : public Filter::Functor> { - using _Base = Filter; + using _Traits = _BoundFunctor; + using FilterPredicate = typename _Traits::Functor; + using _Base = Filter; + + static_assert(can_IterForEach::value, "Lumiera Iterator required as source"); + using Res = typename _Traits::Res; + static_assert(std::is_constructible::value, "Functor must be a predicate"); + + public: - using _Base::Filter; + MutableFilter() =default; + // inherited default copy operations + + MutableFilter (SRC&& dataSrc, FUN&& filterFun) + : _Base{move (dataSrc), FilterPredicate{forward (filterFun)}} + { } public: /* === API to Remould the Filter condition underway === */ @@ -1073,13 +1099,13 @@ namespace lib { using Res = typename _ChainTraits::Res; static_assert(std::is_constructible::value, "Chained Functor must be a predicate"); - using FilterPredicate = typename _Base::FilterPredicate; + using WrappedPredicate = typename _Base::FilterPredicate; using ChainPredicate = typename _ChainTraits::Functor; - FilterPredicate& firstClause = _Base::predicate_; // pick up the existing filter predicate + WrappedPredicate& firstClause = _Base::predicate_; // pick up the existing filter predicate ChainPredicate chainClause{forward (additionalClause)}; // wrap the extension predicate in a similar way - _Base::predicate_ = FilterPredicate{buildCombinedClause (firstClause, chainClause)}; + _Base::predicate_ = WrappedPredicate{buildCombinedClause (firstClause, chainClause)}; _Base::pullFilter(); // pull to re-establish the Invariant } }; diff --git a/tests/library/iter-tree-explorer-test.cpp b/tests/library/iter-tree-explorer-test.cpp index efbce194c..48f4b951b 100644 --- a/tests/library/iter-tree-explorer-test.cpp +++ b/tests/library/iter-tree-explorer-test.cpp @@ -888,16 +888,19 @@ namespace test{ ++seq; CHECK ( 3 == *seq); -// string buff{"."}; -// seq.andNotFilter ([&](CountDown& core) -// { -// buff += util::toString(core.p) + "."; -// --core.p; -// return core.p % 2; -// }); -// cout << "URGS: "<<*seq<< " ..."< + + + + + + + + + + + + + + + + + + + + @@ -31116,7 +31136,7 @@ - + From 604ffbf73c123715f6bfe2e426e23f3f87596618 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 3 Sep 2018 01:52:07 +0200 Subject: [PATCH 053/178] TreeExplorer: fix a bug and finish the feature we did an unnecessary copy of the argument, which was uncovered by the test case manipulating the state core. Whew. Now we have a beautiful new overengineered solution --- src/lib/iter-tree-explorer.hpp | 12 +++--- tests/library/iter-tree-explorer-test.cpp | 27 +++++++----- wiki/thinkPad.ichthyo.mm | 50 ++++++++++++++--------- 3 files changed, 54 insertions(+), 35 deletions(-) diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index eaad1db79..07d9c9a75 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -988,7 +988,7 @@ namespace lib { remouldFilter (forward (conjunctiveClause) ,[](auto first, auto chain) { - return [=](auto val) + return [=](auto& val) { return first(val) and chain(val); @@ -1004,7 +1004,7 @@ namespace lib { remouldFilter (forward (conjunctiveClause) ,[](auto first, auto chain) { - return [=](auto val) + return [=](auto& val) { return first(val) and not chain(val); @@ -1020,7 +1020,7 @@ namespace lib { remouldFilter (forward (disjunctiveClause) ,[](auto first, auto chain) { - return [=](auto val) + return [=](auto& val) { return first(val) or chain(val); @@ -1036,7 +1036,7 @@ namespace lib { remouldFilter (forward (disjunctiveClause) ,[](auto first, auto chain) { - return [=](auto val) + return [=](auto& val) { return first(val) or not chain(val); @@ -1052,7 +1052,7 @@ namespace lib { remouldFilter (dummy ,[](auto currentFilter, auto) { - return [=](auto val) + return [=](auto& val) { return not currentFilter(val); }; @@ -1067,7 +1067,7 @@ namespace lib { remouldFilter (forward (entirelyDifferentPredicate) ,[](auto, auto chain) { - return [=](auto val) + return [=](auto& val) { return chain(val); }; diff --git a/tests/library/iter-tree-explorer-test.cpp b/tests/library/iter-tree-explorer-test.cpp index 48f4b951b..601cf5f3c 100644 --- a/tests/library/iter-tree-explorer-test.cpp +++ b/tests/library/iter-tree-explorer-test.cpp @@ -848,7 +848,7 @@ namespace test{ - /** @test the filter layer can be re-configured on the fly */ + /** @test a special filter layer which can be re-configured on the fly */ void verify_FilterChanges() { @@ -869,7 +869,7 @@ namespace test{ CHECK (16 == *seq); seq.andFilter (takeTrd); - CHECK (12 == *seq); + CHECK (12 == *seq); // is divisible (by 2 AND by 3) seq.flipFilter(); CHECK (11 == *seq); // not divisible (by 2 AND by 3) @@ -884,22 +884,29 @@ namespace test{ seq.orNotFilter (takeEve); CHECK ( 6 == *seq); ++seq; - CHECK ( 5 == *seq); + CHECK ( 5 == *seq); // disjunctive condition actually weakens the filter ++seq; CHECK ( 3 == *seq); + // NOTE: arbitrary functors can be used/combined, + // since they are adapted individually. + // To demonstrate this, we use a functor accessing and + // manipulating the state core by side effect... string buff{"."}; seq.andNotFilter ([&](CountDown& core) { buff += util::toString(core.p) + "."; - --core.p; - return core.p % 2; + --core.p; // manipulate state core + return core.p % 2; // return a number, not bool }); - cout << "URGS: "<<*seq<< " ..."< - - + + @@ -30856,7 +30856,7 @@ - + @@ -30927,7 +30927,7 @@ - + @@ -30977,7 +30977,7 @@ - + @@ -30995,7 +30995,7 @@ - + @@ -31051,9 +31051,10 @@ - - - + + + + @@ -31081,15 +31082,26 @@ Typischerweise wird dieser ja sogar beim Aufruf des getemplateteten Funtions-Operators geInlined

- - +
- - + + - - + + + + + + +

+ der Aufruf vom äußeren in den inneren Wrapper ist 1:1 und sollte vom Compiler wegoptimiert werden +

+ +
+
+ + @@ -31109,13 +31121,13 @@
- - + + - - + + From b3f328f28d143b79005ff8862301699003f96c84 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 3 Sep 2018 02:27:32 +0200 Subject: [PATCH 054/178] EventLog: consolidate existing calls to configure the search create a narrow configuration API for the underlying search mechanism. Simplifies the task of turning that search into a real backtracking evaluation. --- src/lib/test/event-log.hpp | 79 ++++++++++++++++++++++++-------------- wiki/thinkPad.ichthyo.mm | 10 ++++- 2 files changed, 58 insertions(+), 31 deletions(-) diff --git a/src/lib/test/event-log.hpp b/src/lib/test/event-log.hpp index 443d8802d..e02493545 100644 --- a/src/lib/test/event-log.hpp +++ b/src/lib/test/event-log.hpp @@ -308,6 +308,37 @@ namespace test{ } + /* === configure the underlying search engine === */ + + enum Direction { + FORWARD, BACKWARD + }; + + template + void + attachNextSerchStep (COND&& filter, Direction direction) + { + switch (direction) + { + case FORWARD: + solution_.underlying().switchForwards(); + break; + case BACKWARD: + solution_.underlying().switchBackwards(); + break; + } + solution_.setNewFilter (forward (filter)); + } + + template + void + refineSerach (COND&& additionalFilter) + { + solution_.andFilter (forward (additionalFilter)); + } + + + public: /** final evaluation of the match query, * usually triggered from the unit test `CHECK()`. @@ -335,8 +366,7 @@ namespace test{ EventMatch& before (string match) { - solution_.underlying().switchForwards(); - solution_.setNewFilter(find(match)); + attachNextSerchStep (find(match), FORWARD); evaluateQuery ("match(\""+match+"\")"); return *this; } @@ -345,8 +375,7 @@ namespace test{ EventMatch& beforeMatch (string regExp) { - solution_.underlying().switchForwards(); - solution_.setNewFilter(findRegExp(regExp)); + attachNextSerchStep (findRegExp(regExp), FORWARD); evaluateQuery ("find-RegExp(\""+regExp+"\")"); return *this; } @@ -367,8 +396,7 @@ namespace test{ EventMatch& beforeEvent (string match) { - solution_.underlying().switchForwards(); - solution_.setNewFilter(findEvent(match)); + attachNextSerchStep (findEvent(match), FORWARD); evaluateQuery ("match-event(\""+match+"\")"); return *this; } @@ -376,8 +404,7 @@ namespace test{ EventMatch& beforeEvent (string classifier, string match) { - solution_.underlying().switchForwards(); - solution_.setNewFilter(findEvent(classifier,match)); + attachNextSerchStep (findEvent(classifier,match), FORWARD); evaluateQuery ("match-event(ID=\""+classifier+"\", \""+match+"\")"); return *this; } @@ -389,8 +416,7 @@ namespace test{ EventMatch& beforeCall (string match) { - solution_.underlying().switchForwards(); - solution_.setNewFilter(findCall(match)); + attachNextSerchStep (findCall(match), FORWARD); evaluateQuery ("match-call(\""+match+"\")"); return *this; } @@ -398,8 +424,7 @@ namespace test{ EventMatch& after (string match) { - solution_.underlying().switchBackwards(); - solution_.setNewFilter(find(match)); + attachNextSerchStep (find(match), BACKWARD); evaluateQuery ("match(\""+match+"\")", "before"); return *this; } @@ -407,8 +432,7 @@ namespace test{ EventMatch& afterMatch (string regExp) { - solution_.underlying().switchBackwards(); - solution_.setNewFilter(findRegExp(regExp)); + attachNextSerchStep (findRegExp(regExp), BACKWARD); evaluateQuery ("find-RegExp(\""+regExp+"\")", "before"); return *this; } @@ -416,8 +440,7 @@ namespace test{ EventMatch& afterEvent (string match) { - solution_.underlying().switchBackwards(); - solution_.setNewFilter(findEvent(match)); + attachNextSerchStep (findEvent(match), BACKWARD); evaluateQuery ("match-event(\""+match+"\")", "before"); return *this; } @@ -425,8 +448,7 @@ namespace test{ EventMatch& afterEvent (string classifier, string match) { - solution_.underlying().switchBackwards(); - solution_.setNewFilter(findEvent(classifier,match)); + attachNextSerchStep (findEvent(classifier,match), BACKWARD); evaluateQuery ("match-event(ID=\""+classifier+"\", \""+match+"\")", "before"); return *this; } @@ -435,8 +457,7 @@ namespace test{ EventMatch& afterCall (string match) { - solution_.underlying().switchBackwards(); - solution_.setNewFilter(findCall(match)); + attachNextSerchStep (findCall(match), BACKWARD); evaluateQuery ("match-call(\""+match+"\")", "before"); return *this; } @@ -454,7 +475,7 @@ namespace test{ ArgSeq argSeq(stringify (args...)); string argList(util::join(argSeq)); - solution_.andFilter (matchArguments(move(argSeq))); + refineSerach (matchArguments(move(argSeq))); evaluateQuery ("match-arguments("+argList+")"); return *this; } @@ -473,7 +494,7 @@ namespace test{ EventMatch& argMatch (ARGS const& ...regExps) { - solution_.andFilter (matchArgsRegExp (stringify (regExps...))); + refineSerach (matchArgsRegExp (stringify (regExps...))); evaluateQuery ("match-args-RegExp("+util::join(stringify(regExps...))+")"); return *this; } @@ -482,7 +503,7 @@ namespace test{ EventMatch& type (string typeID) { - solution_.andFilter (matchType(typeID)); + refineSerach (matchType(typeID)); evaluateQuery ("match-type("+typeID+")"); return *this; } @@ -491,7 +512,7 @@ namespace test{ EventMatch& key (string key) { - solution_.andFilter (ensureAttribute(key)); + refineSerach (ensureAttribute(key)); evaluateQuery ("ensure-attribute("+key+")"); return *this; } @@ -500,7 +521,7 @@ namespace test{ EventMatch& attrib (string key, string valueMatch) { - solution_.andFilter (matchAttribute(key,valueMatch)); + refineSerach (matchAttribute(key,valueMatch)); evaluateQuery ("match-attribute("+key+"=\""+valueMatch+"\")"); return *this; } @@ -509,7 +530,7 @@ namespace test{ EventMatch& id (string classifier) { - solution_.andFilter (matchAttribute("ID",classifier)); + refineSerach (matchAttribute("ID",classifier)); evaluateQuery ("match-ID(\""+classifier+"\")"); return *this; } @@ -518,7 +539,7 @@ namespace test{ EventMatch& on (string targetID) { - solution_.andFilter (matchAttribute("this",targetID)); + refineSerach (matchAttribute("this",targetID)); evaluateQuery ("match-this(\""+targetID+"\")"); return *this; } @@ -526,7 +547,7 @@ namespace test{ EventMatch& on (const char* targetID) { - solution_.andFilter (matchAttribute("this",targetID)); + refineSerach (matchAttribute("this",targetID)); evaluateQuery ("match-this(\""+string(targetID)+"\")"); return *this; } @@ -536,7 +557,7 @@ namespace test{ on (const X *const targetObj) { string targetID = idi::instanceTypeID (targetObj); - solution_.andFilter (matchAttribute("this",targetID)); + refineSerach (matchAttribute("this",targetID)); evaluateQuery ("match-this(\""+targetID+"\")"); return *this; } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 309294c49..0413b81c3 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -30840,7 +30840,7 @@
- + @@ -31131,7 +31131,9 @@ - + + + @@ -31143,9 +31145,13 @@ + + + + From 75e1eab4bb7aafd9dd175414323c753ef11ae3c1 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 3 Sep 2018 03:32:18 +0200 Subject: [PATCH 055/178] EventLog: drop-in the new TreeExplorer::mutableFilter while this is basically a drop-in replacement, it marks the switch to the monadic evaluation technology, which is prerequisite for building real backtracking into the search. --- src/lib/iter-cursor.hpp | 7 ++++++- src/lib/iter-tree-explorer.hpp | 11 +++++++++-- src/lib/test/event-log.hpp | 36 +++++++++++++++++++++------------- wiki/thinkPad.ichthyo.mm | 24 +++++++++++++++-------- 4 files changed, 53 insertions(+), 25 deletions(-) diff --git a/src/lib/iter-cursor.hpp b/src/lib/iter-cursor.hpp index 66b81523e..259057956 100644 --- a/src/lib/iter-cursor.hpp +++ b/src/lib/iter-cursor.hpp @@ -170,7 +170,12 @@ namespace lib { using _Parent = IterStateWrapper; public: - IterCursor() { } + IterCursor() =default; + IterCursor (IterCursor&&) =default; + IterCursor (IterCursor const&) =default; + IterCursor& operator= (IterCursor&&) =default; + IterCursor& operator= (IterCursor const&) =default; + template explicit diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index 07d9c9a75..abfa7c684 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -557,8 +557,8 @@ namespace lib { : SRC { BaseAdapter() = default; - BaseAdapter(SRC const& src) : SRC(src) { } - BaseAdapter(SRC && src) : SRC(src) { } + BaseAdapter(SRC const& src) : SRC{src} { } + BaseAdapter(SRC && src) : SRC{forward (src)} { } void expandChildren() { } size_t depth() const { return 0; } @@ -1436,6 +1436,13 @@ namespace lib { } + auto + mutableFilter() + { + return mutableFilter ([](auto){ return true; }); + } + + /** _terminal builder_ to package the processing pipeline as IterSource. * Invoking this function moves the whole iterator compound, as assembled by the preceding * builder calls, into heap allocated memory and returns a [iterator front-end](\ref IterExploreSource). diff --git a/src/lib/test/event-log.hpp b/src/lib/test/event-log.hpp index e02493545..8717fbd0c 100644 --- a/src/lib/test/event-log.hpp +++ b/src/lib/test/event-log.hpp @@ -45,7 +45,7 @@ #include "lib/error.hpp" #include "lib/idi/entry-id.hpp" -#include "lib/iter-adapter.hpp" +#include "lib/iter-tree-explorer.hpp" #include "lib/iter-cursor.hpp" #include "lib/format-util.hpp" #include "lib/format-cout.hpp" @@ -70,6 +70,18 @@ namespace test{ using lib::Symbol; using std::string; + namespace { + using Entry = lib::diff::Record; + using Log = std::vector; + + auto + buildSearchFilter(Log const& srcSeq) + { + using Iter = lib::IterCursor; + return treeExplore(Iter (srcSeq)) + .mutableFilter(); + } + } /** @@ -83,10 +95,7 @@ namespace test{ */ class EventMatch { - using Entry = lib::diff::Record; - using Log = std::vector; - using Iter = lib::IterCursor; - using Filter = ExtensibleFilterIter; + using Filter = decltype( buildSearchFilter (std::declval()) ); using ArgSeq = lib::diff::RecordSetup::Storage; using RExSeq = std::vector; @@ -151,10 +160,10 @@ namespace test{ /** @internal for creating EventLog matchers */ EventMatch(Log const& srcSeq) - : solution_(Iter(srcSeq)) - , lastMatch_("HEAD "+ solution_->get("this")) - , look_for_match_(true) - , violation_() + : solution_{buildSearchFilter (srcSeq)} + , lastMatch_{"HEAD "+ solution_->get("this")} + , look_for_match_{true} + , violation_{} { } friend class EventLog; @@ -321,10 +330,10 @@ namespace test{ switch (direction) { case FORWARD: - solution_.underlying().switchForwards(); + solution_.switchForwards(); break; case BACKWARD: - solution_.underlying().switchBackwards(); + solution_.switchBackwards(); break; } solution_.setNewFilter (forward (filter)); @@ -577,9 +586,6 @@ namespace test{ */ class EventLog { - using Entry = lib::diff::Record; - using Log = std::vector; - using Iter = lib::RangeIter; std::shared_ptr log_; @@ -835,6 +841,8 @@ namespace test{ } + using Iter = lib::RangeIter; + typedef Iter const_iterator; typedef const Entry value_type; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 0413b81c3..ff40029c9 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -31131,8 +31131,8 @@ - - + + @@ -31147,12 +31147,17 @@ - + + + + + + - - + + @@ -31173,11 +31178,14 @@ - - + + + + - + + From 9d7ce1e6a4ac3eedacf709a2dae42ccc7f11a541 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 3 Sep 2018 03:49:23 +0200 Subject: [PATCH 056/178] EventLog: now able to use the CursorGear immediately as state core ...since TreeExplorer automatically does the iterator wrapping for us. As added benefit, we have now a direct API to control the search direction --- src/lib/test/event-log.hpp | 14 +++----------- wiki/thinkPad.ichthyo.mm | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/lib/test/event-log.hpp b/src/lib/test/event-log.hpp index 8717fbd0c..c4e9d9686 100644 --- a/src/lib/test/event-log.hpp +++ b/src/lib/test/event-log.hpp @@ -77,8 +77,8 @@ namespace test{ auto buildSearchFilter(Log const& srcSeq) { - using Iter = lib::IterCursor; - return treeExplore(Iter (srcSeq)) + using Cursor = lib::iter::CursorGear; + return treeExplore (Cursor{srcSeq.begin(), srcSeq.end()}) .mutableFilter(); } } @@ -327,15 +327,7 @@ namespace test{ void attachNextSerchStep (COND&& filter, Direction direction) { - switch (direction) - { - case FORWARD: - solution_.switchForwards(); - break; - case BACKWARD: - solution_.switchBackwards(); - break; - } + solution_.reverse (BACKWARD == direction); solution_.setNewFilter (forward (filter)); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index ff40029c9..83bc81ce0 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -30715,6 +30715,8 @@ + + @@ -31150,6 +31152,23 @@ + + + + + + +

+ TreeExplorer macht das Wrappen für uns automatisch, +

+

+ und außerdem haben wir nun ein direktes API für die Laufrichtung +

+ + +
+ +
From ec8d0557e8c65c85c63062b48cbee0a0aa5190e3 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Tue, 4 Sep 2018 02:02:26 +0200 Subject: [PATCH 057/178] ChainSearch: draft interface and possible implementation approach The intention is to augment the iterator based (linear) search used in EventLog to allow for real backtracking, based on a evaluation tree. This should be rather staight forward to implement, relying on the exploreChildren() functionality of TreeExplorer. The trick is to package the chained search step as a monadic flatMap operation --- src/lib/iter-chain-search.hpp | 181 +++++++++++++++++++++++ src/lib/iter-tree-explorer.hpp | 2 +- tests/library/iter-chain-search-test.cpp | 145 ++++++++++++++++++ wiki/thinkPad.ichthyo.mm | 105 ++++++++++++- 4 files changed, 427 insertions(+), 6 deletions(-) create mode 100644 src/lib/iter-chain-search.hpp create mode 100644 tests/library/iter-chain-search-test.cpp diff --git a/src/lib/iter-chain-search.hpp b/src/lib/iter-chain-search.hpp new file mode 100644 index 000000000..fd4fc98af --- /dev/null +++ b/src/lib/iter-chain-search.hpp @@ -0,0 +1,181 @@ +/* + ITER-CHAIN-SEARCH.hpp - chained search with backtracking based on (bidirectional) iterator + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + 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 iter-cursor.hpp + ** An iterator with the ability to switch direction. + ** This wrapper relies on the ability of typical STL container iterators + ** to work in both directions, similar to std::reverse_iterator. + ** Yet it is a single, self-contained element and in compliance to the + ** ["Lumiera Forward Iterator"](iter-adapter.hpp) concept. But it has + ** the additional ability to [switch the working direction](\ref IterCursor::switchDir). + ** + ** @see IterCursor_test + ** @see iter-adapter.hpp + ** @see [usage example](event-log.hpp) + ** + */ + + +#ifndef SRC_LIB_ITER_CHAIN_SEARCH_H +#define SRC_LIB_ITER_CHAIN_SEARCH_H + + +#include "lib/error.hpp" +//#include "lib/iter-adapter.hpp" + +//#include +//#include +#include +#include + + +namespace lib { + + using std::move; + using std::string; + + namespace iter { + + /** + * @internal implementation for.... + */ + template + class Cur + { + + + public: + }; + + + } // namespace iter + + + + + /** + * Iterator based linear search mechanism, with the ability to perform consecutive search with backtracking. + * The IterChainSearch can be configured with a sequence of search goals (filter conditions), and will + * apply these in succession on the underlying iterator. It will search for the first hit of the first + * condition, and then continue to search _from there_ matching on the second condition, and so on. + * After the first combination of matches is exhausted, the search will backtrack and try to evaluate + * the next combination, leading to a tree of search solutions. + */ +// template + class IterChainSearch + { +// using _Core = iter::CursorGear; +// using _Parent = IterStateWrapper; + + public: +// IterChainSearch() =default; +// IterChainSearch (IterChainSearch&&) =default; +// IterChainSearch (IterChainSearch const&) =default; +// IterChainSearch& operator= (IterChainSearch&&) =default; +// IterChainSearch& operator= (IterChainSearch const&) =default; + + +// template +// explicit +// IterChainSearch (CON& container) +//// : _Parent(_Core(container.begin(), container.end())) +// { } +// +// IterChainSearch (IT&& begin, IT&& end) +//// : _Parent(_Core(std::forward(begin), std::forward(end))) +// { } + + + operator bool() const + { + UNIMPLEMENTED ("solution test"); + } + + bool + empty() const + { + return not(*this); + } + + + /** */ + IterChainSearch&& + search (string target) + { + UNIMPLEMENTED ("configure additional chained search condition"); + return move(*this); + } + + IterChainSearch&& + clearFilter() + { + UNIMPLEMENTED ("drop all search condition frames"); + return move(*this); + } + + + /////////////////////////TODO dummy placeholder code. Replace by real iterator access + string& + operator*() const + { + static string boo{"bääääh"}; + return boo; + } + + IterChainSearch& + operator++() + { + return *this; + } + /////////////////////////TODO dummy placeholder code. Replace by real iterator access + }; + + + + + /* ==== convenient builder free function ==== */ + + /** setup a chain search configuration by suitably wrapping the given container. + * @return a TreeEplorer, which is an Iterator to yield all the source elements, + * but may also be used to build an processing pipeline. + * @warning if you capture the result of this call by an auto variable, + * be sure to understand that invoking any further builder operation on + * TreeExplorer will invalidate that variable (by moving it into the + * augmented iterator returned from such builder call). + */ + template + inline auto + chainSearch (CON&& srcData) + { +// using SrcIter = typename _DecoratorTraits::SrcIter; +// using Base = iter_explorer::BaseAdapter; + + UNIMPLEMENTED ("figure out the Iterator type and build a suitable IterChainSearch instance"); + return IterChainSearch(); +// return TreeExplorer (std::forward (srcSeq)); + } + + + +} // namespace lib +#endif /*SRC_LIB_ITER_CHAIN_SEARCH_H*/ diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index abfa7c684..45ea45e40 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -1445,7 +1445,7 @@ namespace lib { /** _terminal builder_ to package the processing pipeline as IterSource. * Invoking this function moves the whole iterator compound, as assembled by the preceding - * builder calls, into heap allocated memory and returns a [iterator front-end](\ref IterExploreSource). + * builder calls, into heap allocated memory and returns an [iterator front-end](\ref IterExploreSource). * Any iteration and manipulation on that front-end is passed through virtual function calls into * the back-end, thereby concealing all details of the processing pipeline. */ diff --git a/tests/library/iter-chain-search-test.cpp b/tests/library/iter-chain-search-test.cpp new file mode 100644 index 000000000..0967a50c9 --- /dev/null +++ b/tests/library/iter-chain-search-test.cpp @@ -0,0 +1,145 @@ +/* + IterChainSearch(Test) - verify chained search operations with backtracking + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + 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 iter-chain-search-test.cpp + ** unit test \ref IterChainSearch_test + */ + + + +#include "lib/test/run.hpp" +#include "lib/test/test-helper.hpp" +#include "lib/format-util.hpp" +#include "lib/iter-chain-search.hpp" +#include "lib/util.hpp" + +#include +#include + + + +namespace lib { +namespace test{ + + using ::Test; + using util::join; + using util::isnil; + using util::isSameObject; + using std::vector; + using std::string; + + using lumiera::error::LERR_(ITER_EXHAUST); + + + namespace { // test fixture + + using Spam = vector; + + const Spam SPAM{"spam" + ,"sausage" + ,"spam" + ,"spam" + ,"bacon" + ,"spam" + ,"tomato" + ,"and" + ,"spam" + }; + + } // (END)fixture + + + + + + + + /*****************************************************************//** + * @test verify a setup for consecutive searches with backtracking. + * + * @see iter-chain-search.hpp + * @see iter-cursor.hpp + * @see [usage example](event-log.hpp) + */ + class IterChainSearch_test : public Test + { + + virtual void + run (Arg) + { + simpleSearch(); + chainedIteration(); + backtracking(); + } + + + /** @test simple basic use case. */ + void + simpleSearch () + { + auto search = chainSearch(SPAM) + .search("bacon") + .search("tomato"); + + CHECK (search); + CHECK (not isnil(search)); + CHECK ("tomato" == *search); + CHECK (isSameObject (*search, SPAM[6])); + + search.clearFilter(); + CHECK ("tomato" == *search); + ++search; + CHECK ("and" == *search); + search.search("spam"); + CHECK ("spam" == *search); + CHECK (isSameObject (*search, SPAM[8])); + + ++search; + CHECK (not search); + CHECK (isnil (search)); + VERIFY_ERROR (ITER_EXHAUST, *search); + } + + + + /** @test verify the + */ + void + chainedIteration () + { + } + + + + /** @test verify the + */ + void + backtracking () + { + } + }; + + LAUNCHER (IterChainSearch_test, "unit common"); + + +}} // namespace lib::test + diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 83bc81ce0..d0ce3ca79 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -31173,6 +31173,8 @@ + + @@ -31197,15 +31199,98 @@ - + - - + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - @@ -31213,6 +31298,16 @@ + + + + + + + + + + From 433543a2c77d78e0a03ebfd301f13d60cb916cd3 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 5 Sep 2018 04:48:11 +0200 Subject: [PATCH 058/178] DOC: some doxygen fixes --- doc/design/architecture/Subsystems.txt | 4 +- src/lib/iter-tree-explorer.hpp | 66 ++++++++++++++++--- src/lib/zombie-check.hpp | 2 +- .../basics/dependency-configuration-test.cpp | 2 +- tests/library/iter-tree-explorer-test.cpp | 4 +- 5 files changed, 64 insertions(+), 14 deletions(-) diff --git a/doc/design/architecture/Subsystems.txt b/doc/design/architecture/Subsystems.txt index f57d2d85b..49e833dd7 100644 --- a/doc/design/architecture/Subsystems.txt +++ b/doc/design/architecture/Subsystems.txt @@ -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. diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index 45ea45e40..d7b296989 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -1254,10 +1254,14 @@ namespace lib { * This allows to separate the mechanics of evaluation and result combination * from the actual processing and thus to define tree structured computations * based on an opaque source data structure not further disclosed. + * @tparam SRC a suitably adapted _source iterator_ or _state core_, wrapped + * into an instance of the iter_explorer::BaseAdapter template * - * \param usage + * \par usage + * TreeExplorer is meant to be used as *Builder* for a processing pipeline. + * For this to work, it is essential to pick the `SRC` baseclass properly. * - to build a TreeExplorer, use the treeExplore() free function, - * which cares to pick up an possibly adapt the given iteration source + * which cares to pick up and possibly adapt the given iteration source * - to add processing layers, invoke the builder operations on TreeExplorer * in a chained fashion, thereby binding functors or lambdas. Capture the * final result with an auto variable. @@ -1323,6 +1327,7 @@ namespace lib { * typed argument. Obviously, argument and result type should also make sense for * the desired evaluation pattern, otherwise you'll get all kinds of nasty * template compilation failures (have fun!) + * @return processing pipeline with attached [Expander](\ref iter_explorer::Expander) decorator */ template auto @@ -1340,6 +1345,7 @@ namespace lib { * necessary to invoke `expandChildren()` (and doing so would have no further effect than * just iterating). Thus, on iteration, each current element will be fed to the _expand functor_ * and the results will be integrated depth first. + * @return processing pipeline with attached [AutoExpander](\ref iter_explorer::AutoExpander) decorator * @warning iteration will be infinite, unless the _expand functor_ provides some built-in * termination condition, returning an empty child sequence at that point. This would * then be the signal for the internal combination mechanism to return to visiting the @@ -1382,6 +1388,7 @@ namespace lib { * functor supports the same definition styles as described for #expand * - it can be pure functional, src -> res * - it can accept the underlying source iterator and exploit side-effects + * @return processing pipeline with attached [Transformer](\ref iter_explorer::Transformer) decorator */ template auto @@ -1398,6 +1405,7 @@ namespace lib { * The previously created source layers will be "pulled" to fast-forward immediately to the * next element confirmed this way by the bound functor. If none of the source elements * is acceptable, the iterator will transition to exhausted state immediately. + * @return processing pipeline with attached [Filter](\ref iter_explorer::Filter) decorator */ template auto @@ -1418,12 +1426,14 @@ namespace lib { * from the current element. Whenever the filter is remoulded, it is immediately re-evaluated, * possibly causing the underlying iterator to be pulled until an element matching the condition * is found. - * @see MutableFilter::andFilter - * @see MutableFilter::andNotFilter - * @see MutableFilter::orFilter - * @see MutableFilter::orNotFilter - * @see MutableFilter::flipFilter - * @see MutableFilter::setNewFilter + * @return processing pipeline with attached [special MutableFilter](\ref iter_explorer::MutableFilter) decorator + * @see \ref IterTreeExplorer_test::verify_FilterChanges() + * @see \ref iter_explorer::MutableFilter::andFilter() + * @see \ref iter_explorer::MutableFilter::andNotFilter() + * @see \ref iter_explorer::MutableFilter::orFilter() + * @see \ref iter_explorer::MutableFilter::orNotFilter() + * @see \ref iter_explorer::MutableFilter::flipFilter() + * @see \ref iter_explorer::MutableFilter::setNewFilter() */ template auto @@ -1448,6 +1458,8 @@ namespace lib { * builder calls, into heap allocated memory and returns an [iterator front-end](\ref IterExploreSource). * Any iteration and manipulation on that front-end is passed through virtual function calls into * the back-end, thereby concealing all details of the processing pipeline. + * @return an front-end handle object, which is an "Lumiera Forward Iterator", + * while holding onto a heap allocated [abstracted data source](\ref lib::IterExplorer). */ IterExploreSource asIterSource() @@ -1473,6 +1485,44 @@ namespace lib { * be sure to understand that invoking any further builder operation on * TreeExplorer will invalidate that variable (by moving it into the * augmented iterator returned from such builder call). + * + * ## usage + * + * This function starts a *Builder* expression. It picks up the given source, + * which can be something "sequence-like" or "iterable", and will automatically + * wrapped and adapted. + * - from a STL container, we retrieve a pair of STL iterators (`begin()`, `end()`) + * - a "Lumiera Forward Iterator" is copied or moved into the wrapper and used as + * data source, pulling results on demand until exhaustion + * - a _State Core_ object is copied or moved into the wrapper and adapted to + * be iterated as "Lumiera Forward Iterator". Any object with suitable extension + * points and behaviour can be used, as explained [here](\ref lib::IterStateWrapper). + * + * The resulting TreeExplorer instance can be directly used as "Lumiera Forward Iterator". + * However, typically you might want to invoke the builder functions to configure further + * processing steps in a processing pipeline... + * - to [filter](\ref TreeExplorer::filter) the results with a predicate (functor) + * - to [transform](\ref TreeExplorer::transform) the yielded results + * - to bind and configure a [child expansion operation](\ref TreeExplorer::expand), which + * can then be triggered by explicitly invoking [.expandChildren()](\ref iter_explorer::Expander::expandChildren) + * on the resulting pipeline; the generated child sequence is "flat mapped" into the results + * (a *Monad* style usage). + * - to [package](\ref TreeExplorer::asIterSource) the pipeline built thus far behind an opaque + * interface and move the implementation into heap memory. + * + * A typical usage might be... + * \code + * auto multiply = [](int v){ return 2*v; }; + * + * auto ii = treeExplore (CountDown{7,4}) + * .transform(multiply); + * + * CHECK (14 == *ii); + * ++ii; + * CHECK (12 == *ii); + * ++ii; + * \endcode + * In this example, a `CountDown` _state core_ is used, as defined in iter-tree-explorer-test.cpp */ template inline auto diff --git a/src/lib/zombie-check.hpp b/src/lib/zombie-check.hpp index 81ffa4fff..5c19079a7 100644 --- a/src/lib/zombie-check.hpp +++ b/src/lib/zombie-check.hpp @@ -20,7 +20,7 @@ */ -/** @file dependable-base.hpp +/** @file zombie-check.hpp ** Detector to set off alarm when (re)using deceased objects. ** When implementing services based on static fields or objects, ** an invocation after static shutdown can not be precluded -- be it by diff --git a/tests/basics/dependency-configuration-test.cpp b/tests/basics/dependency-configuration-test.cpp index a3d450575..5fc6af694 100644 --- a/tests/basics/dependency-configuration-test.cpp +++ b/tests/basics/dependency-configuration-test.cpp @@ -303,7 +303,7 @@ namespace test{ - /** @test instance creation can be preconfigured with a closure + /** @test instance creation can be preconfigured with a closure. * Both Singleton and Test-Mock creation can optionally be performed through a * user provided Lambda or Functor. To demonstrate this, we use a `Veryspecial` local class, * which takes an `int&` as constructor parameter -- and we create the actual instance through diff --git a/tests/library/iter-tree-explorer-test.cpp b/tests/library/iter-tree-explorer-test.cpp index 601cf5f3c..79f8e3d95 100644 --- a/tests/library/iter-tree-explorer-test.cpp +++ b/tests/library/iter-tree-explorer-test.cpp @@ -221,8 +221,8 @@ namespace test{ * to build various functional evaluation pipelines, * based on the \ref TreeExplorer template. * - the adapter to wrap the source, which can either - * [be a "state core"](\ref verify_wrappedState() ), or can - * [be a "Lumiera Forward Iterator"](\ref verify_wrappedIterator() ) + * [be a state core](\ref verify_wrappedState() ), or can + * [be a Lumiera Forward Iterator](\ref verify_wrappedIterator() ) * - the defining use case for TreeExplorer is to build a * [pipeline for depth-first exploration](\ref verify_expandOperation() ) * of a (functional) tree structure. This "tree" is created by invoking From 99e3eeca9f4456341877bc762157f303388af204 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 8 Sep 2018 16:33:21 +0200 Subject: [PATCH 059/178] Library: formatting of simplified types adjusted Need to be more careful when eating trailing spaces after a std::string. Because the full-blown type is a template, sometimes the compiler adds a spurious additional space behind the closing angle bracket, due to the now obsolete "maximum munch rule" of C++98 -- to prevent closing angle brackets to become '>>'. But in other cases, some type adornments or other language identifiers follow the string type, like e.g. 'const'. In those cases, the trailing space must be retained. We solve this by a look-ahead assertion in the regular expression: consume the trailing space _only_ if a non-word character follows (like '>'). --- src/lib/format-obj.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/format-obj.cpp b/src/lib/format-obj.cpp index f2cdc0216..9de89b7d4 100644 --- a/src/lib/format-obj.cpp +++ b/src/lib/format-obj.cpp @@ -205,7 +205,7 @@ apologies for that." "|lumiera::" , regex::ECMAScript | regex::optimize}; - static regex stdString {"(__cxx11::)?basic_string, allocator\\s*>\\s*" + static regex stdString {"(__cxx11::)?basic_string, allocator\\s*>(\\s+\\B)?" , regex::ECMAScript | regex::optimize}; static regex stdAllocator {"(\\w+<(" TYP_EXP ")), allocator<\\2>\\s*" From 4988153e15f2222b1bc0a61d73c19f4dc440e99e Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 8 Sep 2018 16:37:42 +0200 Subject: [PATCH 060/178] Library: prevent implicit bool conversion on iterator-like objects ...it should have been this way all the time. Generic code might otherwise be ill guided to assume a conversion from the Iterator to its value type, while in fact an explicit dereferentiation is necessary --- src/gui/model/expander-revealer.hpp | 1 + src/lib/iter-adapter-ptr-deref.hpp | 12 +++++++-- src/lib/iter-adapter-stl.hpp | 25 ++++++++++++++----- src/lib/iter-adapter.hpp | 30 +++++++++++++++++++---- src/lib/itertools.hpp | 6 ++++- src/lib/sync.hpp | 4 +-- tests/library/iter-tree-explorer-test.cpp | 2 +- 7 files changed, 63 insertions(+), 17 deletions(-) diff --git a/src/gui/model/expander-revealer.hpp b/src/gui/model/expander-revealer.hpp index 662918b2d..8811ea96b 100644 --- a/src/gui/model/expander-revealer.hpp +++ b/src/gui/model/expander-revealer.hpp @@ -107,6 +107,7 @@ namespace model { and bool{changeState_}; } + explicit operator bool() const { REQUIRE (canExpand()); diff --git a/src/lib/iter-adapter-ptr-deref.hpp b/src/lib/iter-adapter-ptr-deref.hpp index ca4a5b958..b038cadc0 100644 --- a/src/lib/iter-adapter-ptr-deref.hpp +++ b/src/lib/iter-adapter-ptr-deref.hpp @@ -141,7 +141,11 @@ namespace lib { return *this; } - operator bool() const { return isValid(); } + explicit + operator bool() const + { + return isValid(); + } @@ -266,7 +270,11 @@ namespace lib { takeAddress(); } - operator bool() const { return isValid(); } + explicit + operator bool() const + { + return isValid(); + } diff --git a/src/lib/iter-adapter-stl.hpp b/src/lib/iter-adapter-stl.hpp index e412973e8..e41e29008 100644 --- a/src/lib/iter-adapter-stl.hpp +++ b/src/lib/iter-adapter-stl.hpp @@ -72,11 +72,15 @@ namespace iter_stl { DistinctIter() : i_(), prev_() { } DistinctIter(IT const& i) : i_(i),prev_() { memorise(); } - pointer operator->() const { return i_; } - reference operator*() const { return *i_;} - bool isValid() const { return i_; } + pointer operator->() const { return i_; } + reference operator*() const { return *i_; } + bool isValid() const { return i_; } - operator bool() const { return i_; } + explicit + operator bool() const + { + return bool{i_}; + } DistinctIter& @@ -472,8 +476,17 @@ namespace iter_stl { IterSnapshot& operator= (IterSnapshot const&) = default; IterSnapshot& operator= (IterSnapshot &&) = default; - operator bool() const { return isValid(); } - size_t size() const { return buffer_.size(); } + explicit + operator bool() const + { + return isValid(); + } + + size_t + size() const + { + return buffer_.size(); + } diff --git a/src/lib/iter-adapter.hpp b/src/lib/iter-adapter.hpp index 8834b166c..1364c36d5 100644 --- a/src/lib/iter-adapter.hpp +++ b/src/lib/iter-adapter.hpp @@ -194,7 +194,11 @@ namespace lib { , pos_() { } - operator bool() const { return isValid(); } + explicit + operator bool() const + { + return isValid(); + } /* === lumiera forward iterator concept === */ @@ -352,7 +356,11 @@ namespace lib { : core_() { } - operator bool() const { return isValid(); } + explicit + operator bool() const + { + return isValid(); + } /* === lumiera forward iterator concept === */ @@ -491,7 +499,11 @@ namespace lib { , e_(oIter.getEnd()) { } - operator bool() const { return isValid(); } + explicit + operator bool() const + { + return isValid(); + } /* === lumiera forward iterator concept === */ @@ -599,7 +611,11 @@ namespace lib { // standard copy operations acceptable - operator bool() const { return isValid(); } + explicit + operator bool() const + { + return isValid(); + } @@ -732,7 +748,11 @@ namespace lib { : i_(srcIter) { } - operator bool() const { return isValid(); } + explicit + operator bool() const + { + return isValid(); + } diff --git a/src/lib/itertools.hpp b/src/lib/itertools.hpp index e95fdd654..d1fb2a616 100644 --- a/src/lib/itertools.hpp +++ b/src/lib/itertools.hpp @@ -205,7 +205,11 @@ namespace lib { hasData(); } - operator bool() const { return isValid(); } + explicit + operator bool() const + { + return isValid(); + } diff --git a/src/lib/sync.hpp b/src/lib/sync.hpp index add2d7ec6..76c7c034b 100644 --- a/src/lib/sync.hpp +++ b/src/lib/sync.hpp @@ -257,7 +257,7 @@ namespace lib { return *this; } - operator bool() { return 0 != tv_sec; } // allows if (timeout_).... + explicit operator bool() { return 0 != tv_sec; } // allows if (timeout_).... }; @@ -368,7 +368,7 @@ namespace lib { } void setTimeout(ulong relative) {timeout_.setOffset(relative);} - bool isTimedWait() {return (timeout_);} + bool isTimedWait() {return bool{timeout_};} }; typedef Mutex NonrecursiveLock_NoWait; diff --git a/tests/library/iter-tree-explorer-test.cpp b/tests/library/iter-tree-explorer-test.cpp index 79f8e3d95..8dc2cdc72 100644 --- a/tests/library/iter-tree-explorer-test.cpp +++ b/tests/library/iter-tree-explorer-test.cpp @@ -1188,7 +1188,7 @@ namespace test{ bool checkPoint() const { - return src; + return bool{src}; } State& From 37b2e2e4adaf9e46c62fede1adb1b3198a3e9253 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 7 Sep 2018 00:54:29 +0200 Subject: [PATCH 061/178] TreeExplorer: prevent ill guided type adaptor choice when adapting a functor, the wrapper automatically decides if this functor was meant to be used in "monadic style" (value -> new monad) or in "manipulate state-core style" (iter& -> iter) Unfortunately, in some cases functions accepting a partially built pipeline will be classified in the wrong way, due to the fact that IterableDecorator has a templated wildcard constructor and thus seems to accept the conversion value_type -> IterableDecorator. This causes the "monadic style" to be chosen erroneously, and leads to a template instantiation failure just at the point when the generated functor will be used. The solution is to explicitly *rule out* the monadic usage style in all those cases, where the *argument* of the functor to bind can in fact be directly constructed / converted from the source iterator or state core. Because, if that is the case - it is superfluous explicitly to dereference the source iterator; the functor can do the same - chances are that the "manipulation style" was intended (as was the case in the concrete example at hand here) --- src/lib/iter-tree-explorer.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index d7b296989..a2e48cf6c 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -502,8 +502,9 @@ namespace lib { /** adapt to a functor, which accepts the value type of the source sequence ("monadic" usage pattern) */ template - struct ArgAccessor>> - { + struct ArgAccessor + ,__not_>>>> // need to exclude the latter, since IterableDecorator + { // often seems to accept IT::value_type (while in fact it doesn't) static auto build() { return [](auto& iter) { return *iter; }; } }; From 6834c26470e00d0d6fcb280a351d694b57168c97 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 5 Sep 2018 06:27:36 +0200 Subject: [PATCH 062/178] ChainSearch: draft a solution how to construct the pipeline builder base type this is a tricky undertaking, since our treeExplore() helper constructs a complex wrapped type, depending on the actual builder expressions used. Solution is to use decltype on the result of a helper function, and let the _DecoratorTraits from TreeExplorer do the necessary type adaptations --- src/lib/iter-chain-search.hpp | 33 +++++++++++++++++------- src/lib/iter-tree-explorer.hpp | 7 +++++ tests/library/iter-chain-search-test.cpp | 17 +++++++++++- wiki/thinkPad.ichthyo.mm | 15 +++++++++++ 4 files changed, 61 insertions(+), 11 deletions(-) diff --git a/src/lib/iter-chain-search.hpp b/src/lib/iter-chain-search.hpp index fd4fc98af..a9eeaf7b1 100644 --- a/src/lib/iter-chain-search.hpp +++ b/src/lib/iter-chain-search.hpp @@ -41,7 +41,7 @@ #include "lib/error.hpp" -//#include "lib/iter-adapter.hpp" +#include "lib/iter-tree-explorer.hpp" //#include //#include @@ -52,19 +52,32 @@ namespace lib { using std::move; + using std::forward; using std::string; namespace iter { + namespace { + + template + auto + buildSearchFilter(SRC&& dataSource) + { + return treeExplore (forward (dataSource)) + .mutableFilter(); + } + } + /** * @internal implementation for.... */ - template - class Cur + template + class _IterChainSetup { public: + using Pipeline = decltype( buildSearchFilter (std::declval()) ); }; @@ -81,10 +94,11 @@ namespace lib { * After the first combination of matches is exhausted, the search will backtrack and try to evaluate * the next combination, leading to a tree of search solutions. */ -// template + template class IterChainSearch + : public iter::_IterChainSetup::Pipeline { -// using _Core = iter::CursorGear; + using _Base = typename iter::_IterChainSetup::Pipeline; // using _Parent = IterStateWrapper; public: @@ -122,7 +136,7 @@ namespace lib { IterChainSearch&& search (string target) { - UNIMPLEMENTED ("configure additional chained search condition"); + TODO ("configure additional chained search condition"); return move(*this); } @@ -163,15 +177,14 @@ namespace lib { * TreeExplorer will invalidate that variable (by moving it into the * augmented iterator returned from such builder call). */ - template + template inline auto - chainSearch (CON&& srcData) + chainSearch (SRC&& srcData) { // using SrcIter = typename _DecoratorTraits::SrcIter; // using Base = iter_explorer::BaseAdapter; - UNIMPLEMENTED ("figure out the Iterator type and build a suitable IterChainSearch instance"); - return IterChainSearch(); + return IterChainSearch(); // return TreeExplorer (std::forward (srcSeq)); } diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index a2e48cf6c..be4590e2c 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -166,6 +166,13 @@ namespace lib { // standard copy operations acceptable }; + template + struct StlRange + : StlRange + { + using StlRange::StlRange; + }; + /** * Decorate a state or logic core to treat it as Lumiera Forward Iterator. diff --git a/tests/library/iter-chain-search-test.cpp b/tests/library/iter-chain-search-test.cpp index 0967a50c9..abb3e79bb 100644 --- a/tests/library/iter-chain-search-test.cpp +++ b/tests/library/iter-chain-search-test.cpp @@ -28,7 +28,8 @@ #include "lib/test/run.hpp" #include "lib/test/test-helper.hpp" -#include "lib/format-util.hpp" +#include "lib/format-util.hpp" /////////////////////////////TODO necessary? +#include "lib/format-cout.hpp" /////////////////////////////TODO necessary? #include "lib/iter-chain-search.hpp" #include "lib/util.hpp" @@ -69,6 +70,12 @@ namespace test{ +///////////////////////////////////////////////////TODO WIP +#define SHOW_TYPE(_TY_) \ + cout << "typeof( " << STRINGIFY(_TY_) << " )= " << lib::meta::typeStr<_TY_>() < guggi; // --> std::__cxx11::basic_string const& +///////////////////////////////////////////////////TODO WIP CHECK (search); CHECK (not isnil(search)); CHECK ("tomato" == *search); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index d0ce3ca79..c067e3c85 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -31247,6 +31247,18 @@ + + + + + + + + + + + + @@ -31282,6 +31294,9 @@ + + + From a52ed91de0bc401f7c286024adf8a6c5b017056a Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 7 Sep 2018 00:45:04 +0200 Subject: [PATCH 063/178] ChainSearch: draft a solution how to construct the Expand functor type ...now matters start to get really nasty, since we have to pick up an infered type from a partially built pipeline and use it to construct the signature for a functor to bind into the more elaborate complete pipeline --- src/lib/iter-chain-search.hpp | 16 +- tests/library/iter-chain-search-test.cpp | 1 + wiki/thinkPad.ichthyo.mm | 245 ++++++++++++++++++++--- 3 files changed, 230 insertions(+), 32 deletions(-) diff --git a/src/lib/iter-chain-search.hpp b/src/lib/iter-chain-search.hpp index a9eeaf7b1..e6a0b6651 100644 --- a/src/lib/iter-chain-search.hpp +++ b/src/lib/iter-chain-search.hpp @@ -61,11 +61,20 @@ namespace lib { template auto - buildSearchFilter(SRC&& dataSource) + buildSearchFilter (SRC&& dataSource) { return treeExplore (forward (dataSource)) .mutableFilter(); } + + template + auto + buildExplorer (SRC&& dataSource, FUN&& expandFunctor) + { + return buildSearchFilter (forward (dataSource)) + .expand (forward (expandFunctor)) + .expandAll(); + } } /** @@ -74,10 +83,12 @@ namespace lib { template class _IterChainSetup { + using Filter = decltype( buildSearchFilter (std::declval()) ); + using StepFunctor = typename iter_explorer::_BoundFunctor::Functor; public: - using Pipeline = decltype( buildSearchFilter (std::declval()) ); + using Pipeline = decltype( buildExplorer (std::declval(), std::declval()) ); }; @@ -102,6 +113,7 @@ namespace lib { // using _Parent = IterStateWrapper; public: + using DebugPipeline = _Base; // IterChainSearch() =default; // IterChainSearch (IterChainSearch&&) =default; // IterChainSearch (IterChainSearch const&) =default; diff --git a/tests/library/iter-chain-search-test.cpp b/tests/library/iter-chain-search-test.cpp index abb3e79bb..2a2e7b708 100644 --- a/tests/library/iter-chain-search-test.cpp +++ b/tests/library/iter-chain-search-test.cpp @@ -110,6 +110,7 @@ namespace test{ ///////////////////////////////////////////////////TODO WIP using Searcher = decltype(search); SHOW_TYPE (Searcher); + SHOW_TYPE (Searcher::DebugPipeline); SHOW_TYPE (Searcher::value_type); SHOW_TYPE (Searcher::pointer); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index c067e3c85..a83f43e26 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -25107,8 +25107,7 @@ match("a").after("b") scheitert, weil sich die Suche am ersten "a" festbeißt.

- - +
@@ -30761,8 +30760,7 @@ ...von der entsprechenden Methode im Transformer

- - +
@@ -30778,8 +30776,7 @@ und im Unit-Test nur für die Kombination Transformer + Explorer abgedeckt....

- - +
@@ -30804,8 +30801,7 @@ - - + @@ -30829,8 +30825,7 @@ hinter der sich eine verkettete Suche mit Backtracking verbirgt

- -
+ @@ -30855,8 +30850,7 @@ sofern längerfristig Itertools durch TreeExplorer abgelöst werden könnte

- - +
@@ -30900,8 +30894,7 @@ Diese wird dann per Value gecaptured, was die gewünschte Kopie bewirkt.

- - +
@@ -30925,8 +30918,7 @@ (und auch dafür ist die Syntax grausam)

- -
+
@@ -30940,8 +30932,7 @@ ...es soll bloß einfach funktionieren!!!!!!!!!!!

- - +
@@ -30972,8 +30963,7 @@ die Itertools komplett durch den TreeExplorer ablösen zu können

- - +
@@ -31046,8 +31036,7 @@ - - +
@@ -31145,8 +31134,7 @@ ...so daß es nur noch wenige Zugangs-Punkte zum unterliegenden Iterator gibt

- - +
@@ -31165,8 +31153,7 @@ und außerdem haben wir nun ein direktes API für die Laufrichtung

- - +
@@ -31193,8 +31180,7 @@ Invariante: pullMatch()

- - + @@ -31235,8 +31221,207 @@
- - + + + + + + + + + + + + + + +

+ wir müssen hier eine Festlegung treffen +

+ +
+ + + + + +

+ ...weil die Ergebnisse der einzelnen Schritt-Funktoren, +

+

+ jeweils in den Auswertungs-Stack gepackt werden sollen. +

+

+ +

+

+ Daher müssen sie untereinander kompatibel sein. +

+ +
+ +
+ + + + + + +

+ im typischen Fall kopiert man den Basis-Iterator, +

+

+ und manipuliert dann einige Einstellungen auf diesem. +

+

+ +

+

+ Wir könnten diese Kopie-Semantik sogar erzwingen, +

+

+ indem wir als Argument-Typ des Schritt-Funktors eine const& vorgeben +

+ +
+ +
+ + + + + + + + + + + + +

+ ...den ich mache, um den Adapter in jedem Einzelfall zu bekommen +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +

+ ....und fällt dann beim Instantiieren des Template auf die Schnautze +

+ +
+
+ + + + +
+ + + + + + + + +

+ sofern eine Initialisierung ausidem Source-Iterator möglich ist +

+ + +
+
+ + +
+ + + + + + +

+ und sonst ist das nicht wirklich "kniffelig" +

+ + +
+ + + + + +

+ ...bloß sind die zig-fach geschachtelten Template-Typen, +

+

+ die dann die Intantiierungs-Call-Hierarchie bevölkern, +

+

+ nahezu unlesbar.... +

+

+ +

+

+ +

+

+ AAber ... wenn es erst mal duch den Compiler ist, +

+

+ dann sollte der Optimizer diese gesamten x-fachen Wrapper nahezu restlos entfernen +

+ + +
+ +
+
+ + + + + + + + +

+ Konsequenz: jede Argument-Funktion wird nochmal gewrappt +

+ + +
+ +
+ + + +
From 9b0937bc63afe57809df931ba8a0c875527485f2 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 7 Sep 2018 01:56:41 +0200 Subject: [PATCH 064/178] ChainSearch: move into namespace iter ...seems to be a not-so-commonly used helper --- src/lib/iter-chain-search.hpp | 47 ++++++++++++------------ tests/library/iter-chain-search-test.cpp | 4 +- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/lib/iter-chain-search.hpp b/src/lib/iter-chain-search.hpp index e6a0b6651..353f73c86 100644 --- a/src/lib/iter-chain-search.hpp +++ b/src/lib/iter-chain-search.hpp @@ -50,31 +50,30 @@ namespace lib { +namespace iter { using std::move; using std::forward; using std::string; - namespace iter { + + namespace { // implementation helpers... - namespace { - - template - auto - buildSearchFilter (SRC&& dataSource) - { - return treeExplore (forward (dataSource)) - .mutableFilter(); - } - - template - auto - buildExplorer (SRC&& dataSource, FUN&& expandFunctor) - { - return buildSearchFilter (forward (dataSource)) - .expand (forward (expandFunctor)) - .expandAll(); - } + template + auto + buildSearchFilter (SRC&& dataSource) + { + return treeExplore (forward (dataSource)) + .mutableFilter(); + } + + template + auto + buildExplorer (SRC&& dataSource, FUN&& expandFunctor) + { + return buildSearchFilter (forward (dataSource)) + .expand (forward (expandFunctor)) + .expandAll(); } /** @@ -91,8 +90,8 @@ namespace lib { using Pipeline = decltype( buildExplorer (std::declval(), std::declval()) ); }; - - } // namespace iter + }//(End)implementation helpers + @@ -107,9 +106,9 @@ namespace lib { */ template class IterChainSearch - : public iter::_IterChainSetup::Pipeline + : public _IterChainSetup::Pipeline { - using _Base = typename iter::_IterChainSetup::Pipeline; + using _Base = typename _IterChainSetup::Pipeline; // using _Parent = IterStateWrapper; public: @@ -202,5 +201,5 @@ namespace lib { -} // namespace lib +}} // namespace lib::iter #endif /*SRC_LIB_ITER_CHAIN_SEARCH_H*/ diff --git a/tests/library/iter-chain-search-test.cpp b/tests/library/iter-chain-search-test.cpp index 2a2e7b708..eb6f82f11 100644 --- a/tests/library/iter-chain-search-test.cpp +++ b/tests/library/iter-chain-search-test.cpp @@ -39,6 +39,7 @@ namespace lib { +namespace iter{ namespace test{ using ::Test; @@ -157,5 +158,4 @@ namespace test{ LAUNCHER (IterChainSearch_test, "unit common"); -}} // namespace lib::test - +}}} // namespace lib::iter::test From d398177a714d098e364335c1d4759077a6e84cd4 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 7 Sep 2018 03:02:41 +0200 Subject: [PATCH 065/178] ChainSearch: now actually build the processing pipeline in the ctor ..this resolves the most challenging part of the construction work; we use the static helper functions to infer a type and construct a suitable processing pipeline and we invoke the same helper to initialise the base class in the ctor. Incidentally... we can now drop all the placeholder stubs, since we now inherit the full iterator and child explorer API. The test now starts actually to work... we get spam and sausage! TODO: now actually fill in the expand functor such as to pick the concrete filter step in the chain from a sequence of preconfigured filter bindings --- src/lib/iter-chain-search.hpp | 74 +++++++++--------------- tests/library/iter-chain-search-test.cpp | 3 + wiki/thinkPad.ichthyo.mm | 18 +++--- 3 files changed, 39 insertions(+), 56 deletions(-) diff --git a/src/lib/iter-chain-search.hpp b/src/lib/iter-chain-search.hpp index 353f73c86..e40b8e3a8 100644 --- a/src/lib/iter-chain-search.hpp +++ b/src/lib/iter-chain-search.hpp @@ -77,16 +77,21 @@ namespace iter { } /** - * @internal implementation for.... + * @internal helper to rebind on inferred types. + * @remark we use the TreeExplorer framework to assemble the processing pipeline + * from suitable building blocks configured with some lambdas. However, we + * also want to _inherit_ from this filter pipeline, so to expose the typical + * iterator operations without much ado. Thus we use some (static) helper function + * templates, instantiate them with the actual source data iterator and pick up + * the inferred type. */ template - class _IterChainSetup + struct _IterChainSetup { using Filter = decltype( buildSearchFilter (std::declval()) ); using StepFunctor = typename iter_explorer::_BoundFunctor::Functor; - public: using Pipeline = decltype( buildExplorer (std::declval(), std::declval()) ); }; @@ -108,40 +113,25 @@ namespace iter { class IterChainSearch : public _IterChainSetup::Pipeline { - using _Base = typename _IterChainSetup::Pipeline; -// using _Parent = IterStateWrapper; + using _Trait = _IterChainSetup; + using _Base = typename _Trait::Pipeline; + + using Filter = typename _Trait::Filter; + using Step = typename _Trait::StepFunctor; public: using DebugPipeline = _Base; -// IterChainSearch() =default; -// IterChainSearch (IterChainSearch&&) =default; -// IterChainSearch (IterChainSearch const&) =default; -// IterChainSearch& operator= (IterChainSearch&&) =default; -// IterChainSearch& operator= (IterChainSearch const&) =default; + using _Base::_Base; -// template -// explicit -// IterChainSearch (CON& container) -//// : _Parent(_Core(container.begin(), container.end())) -// { } -// -// IterChainSearch (IT&& begin, IT&& end) -//// : _Parent(_Core(std::forward(begin), std::forward(end))) -// { } + template + explicit + IterChainSearch (SEQ&& srcData) + : _Base{move (buildExplorer (forward (srcData) + ,Step{[this](Filter const& curr){ return configureFilterChain(curr); }}))} + { } - operator bool() const - { - UNIMPLEMENTED ("solution test"); - } - - bool - empty() const - { - return not(*this); - } - /** */ IterChainSearch&& @@ -158,21 +148,13 @@ namespace iter { return move(*this); } - - /////////////////////////TODO dummy placeholder code. Replace by real iterator access - string& - operator*() const + private: + Filter + configureFilterChain (Filter const& currentFilterState) { - static string boo{"bääääh"}; - return boo; + uint depth = this->depth(); + return Filter{}; /////TODO empty filter means recursion end } - - IterChainSearch& - operator++() - { - return *this; - } - /////////////////////////TODO dummy placeholder code. Replace by real iterator access }; @@ -192,11 +174,7 @@ namespace iter { inline auto chainSearch (SRC&& srcData) { -// using SrcIter = typename _DecoratorTraits::SrcIter; -// using Base = iter_explorer::BaseAdapter; - - return IterChainSearch(); -// return TreeExplorer (std::forward (srcSeq)); + return IterChainSearch{forward (srcData)}; } diff --git a/tests/library/iter-chain-search-test.cpp b/tests/library/iter-chain-search-test.cpp index eb6f82f11..d7ff544a3 100644 --- a/tests/library/iter-chain-search-test.cpp +++ b/tests/library/iter-chain-search-test.cpp @@ -116,6 +116,9 @@ namespace test{ SHOW_TYPE (Searcher::pointer); // TypeDebugger guggi; // --> std::__cxx11::basic_string const& + cout << "search[0]=" <<*search<
- + + @@ -31288,7 +31289,7 @@
- + @@ -31429,19 +31430,20 @@ - - - - + + + + + - - + + From 2b72175e04dd254fae45a0811b9e84b557932562 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 7 Sep 2018 15:23:46 +0200 Subject: [PATCH 066/178] ChainSearch: Storage for the filter chain --- src/lib/iter-chain-search.hpp | 28 +++++++++++++++++--------- wiki/thinkPad.ichthyo.mm | 37 +++++++++++++++++------------------ 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/src/lib/iter-chain-search.hpp b/src/lib/iter-chain-search.hpp index e40b8e3a8..1c2e66c11 100644 --- a/src/lib/iter-chain-search.hpp +++ b/src/lib/iter-chain-search.hpp @@ -46,6 +46,7 @@ //#include //#include #include +#include #include @@ -57,7 +58,7 @@ namespace iter { using std::string; - namespace { // implementation helpers... + namespace { // type construction helpers... template auto @@ -95,7 +96,7 @@ namespace iter { using Pipeline = decltype( buildExplorer (std::declval(), std::declval()) ); }; - }//(End)implementation helpers + }//(End)type construction helpers @@ -103,9 +104,9 @@ namespace iter { /** * Iterator based linear search mechanism, with the ability to perform consecutive search with backtracking. - * The IterChainSearch can be configured with a sequence of search goals (filter conditions), and will - * apply these in succession on the underlying iterator. It will search for the first hit of the first - * condition, and then continue to search _from there_ matching on the second condition, and so on. + * The IterChainSearch can be configured with a sequence of search goals (filter conditions), and will apply + * these in succession on the underlying iterator. It will search _by linear search_ for the first hit of the + * first condition, and then continue to search _from there_ matching on the second condition, and so on. * After the first combination of matches is exhausted, the search will backtrack and try to evaluate * the next combination, leading to a tree of search solutions. */ @@ -119,11 +120,15 @@ namespace iter { using Filter = typename _Trait::Filter; using Step = typename _Trait::StepFunctor; + std::vector stepChain_; + public: - using DebugPipeline = _Base; - - using _Base::_Base; - + /** Build a chain-search mechanism based on the given source data sequence. + * @remark iterators will be copied or moved as appropriate, while from a STL compliant + * container just a pair of (`begin()`, `end()`) iterators is retrieved; the latter + * is also the reason why a rvalue reference to STL container is rejected, since the + * container needs to reside elsewhere; only the iterator is wrapped here. + */ template explicit IterChainSearch (SEQ&& srcData) @@ -131,6 +136,9 @@ namespace iter { ,Step{[this](Filter const& curr){ return configureFilterChain(curr); }}))} { } + // inherited default ctor and standard copy operations + using _Base::_Base; + /** */ @@ -169,6 +177,8 @@ namespace iter { * be sure to understand that invoking any further builder operation on * TreeExplorer will invalidate that variable (by moving it into the * augmented iterator returned from such builder call). + * @param srcData either a »Lumiera Forward Iterator«, a _reference_ to a STL + * container, or a [»State Core«](\ref lib::IterStateWrapper) object. */ template inline auto diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index e7dd2ac54..d4a638b21 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -31215,15 +31215,15 @@ - - + + - - - - + + + + @@ -31403,7 +31403,8 @@ - + + @@ -31415,23 +31416,21 @@ Konsequenz: jede Argument-Funktion wird nochmal gewrappt

- -
+
-
- - + + - - + + @@ -31447,13 +31446,13 @@ - - - + + + - + - + From d923138d1c65eb0f880419311f9901d0711a2797 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 7 Sep 2018 16:17:35 +0200 Subject: [PATCH 067/178] ChainSearch: configure the core of the chained search mechanism ...based on a monadic tree expansion: we define a single step, which takes the current filter configuration and builds the next filter configuration, based on a stored chain of configuration functions The actual exhausting depth-first results just by the greedy application pattern, and uses the stack embedded in the "Explorer" layer of TreeExplorer --- src/lib/iter-chain-search.hpp | 58 ++++++++++++++++++++---- tests/library/iter-chain-search-test.cpp | 3 -- wiki/thinkPad.ichthyo.mm | 36 +++++++-------- 3 files changed, 68 insertions(+), 29 deletions(-) diff --git a/src/lib/iter-chain-search.hpp b/src/lib/iter-chain-search.hpp index 1c2e66c11..01e957cd7 100644 --- a/src/lib/iter-chain-search.hpp +++ b/src/lib/iter-chain-search.hpp @@ -42,6 +42,7 @@ #include "lib/error.hpp" #include "lib/iter-tree-explorer.hpp" +#include "lib/meta/util.hpp" //#include //#include @@ -57,6 +58,8 @@ namespace iter { using std::forward; using std::string; + using lib::meta::disable_if; + namespace { // type construction helpers... @@ -91,7 +94,7 @@ namespace iter { { using Filter = decltype( buildSearchFilter (std::declval()) ); - using StepFunctor = typename iter_explorer::_BoundFunctor::Functor; + using StepFunctor = typename iter_explorer::_BoundFunctor::Functor; using Pipeline = decltype( buildExplorer (std::declval(), std::declval()) ); }; @@ -117,6 +120,7 @@ namespace iter { using _Trait = _IterChainSetup; using _Base = typename _Trait::Pipeline; + using Value = typename _Base::value_type; using Filter = typename _Trait::Filter; using Step = typename _Trait::StepFunctor; @@ -141,18 +145,53 @@ namespace iter { - /** */ - IterChainSearch&& - search (string target) + /** configure additional chained search condition. + * @param a functor `Filter const& -> filter`, which takes a current filter configuration, + * returning a copy from this configuration, possibly configured differently. + * @note the given functor, lambda or function reference will be wrapped and adapted + * to conform to the required function signature. When using a generic lambda, + * the argument type `Filter const&` is assumed + * @remarks the additional chained search condition given here will be applied _after_ + * matching all other conditions already in the filter chain. Each such condition + * is used to _filter_ the underlying source iterator, i.e. pull it until finding + * and element to match the condition. Basically these conditions are _not_ used in + * conjunction, but rather one after another. But since each such step in the chain + * is defined by a functor, which gets the previous filter configuration as argument, + * it is _possible_ to build a step which _extends_ or sharpens the preceding condition. + */ + template + disable_if, + IterChainSearch&& > + search (FUN&& configureSearchStep) { - TODO ("configure additional chained search condition"); + stepChain_.emplace_back (Step{forward (configureSearchStep)}); return move(*this); } + /** attach additional direct search for a given value. + * After successfully searching for all the conditions currently in the filter chain, + * the underlying iterator will finally be pulled until matching the given target value. + */ + IterChainSearch&& + search (Value target) + { + search ([=](Filter filter) // note: filter taken by value + { + filter.setNewFilter ([target](Value const& currVal) { return currVal == target; }); + return filter; // return copy of the original state with changed filter + }); + return move(*this); + } + + /** drop all search condition frames. + * @remark the filter chain becomes empty, + * passing through the unaltered + * source sequence + */ IterChainSearch&& clearFilter() { - UNIMPLEMENTED ("drop all search condition frames"); + stepChain_.clear(); return move(*this); } @@ -160,8 +199,11 @@ namespace iter { Filter configureFilterChain (Filter const& currentFilterState) { - uint depth = this->depth(); - return Filter{}; /////TODO empty filter means recursion end + uint depth = currentFilterState.depth(); + if (depth < stepChain_.size()) + return stepChain_[depth](currentFilterState); // augmented copy + else + return Filter{}; // empty filter indicates recursion end } }; diff --git a/tests/library/iter-chain-search-test.cpp b/tests/library/iter-chain-search-test.cpp index d7ff544a3..89f193a11 100644 --- a/tests/library/iter-chain-search-test.cpp +++ b/tests/library/iter-chain-search-test.cpp @@ -111,14 +111,11 @@ namespace test{ ///////////////////////////////////////////////////TODO WIP using Searcher = decltype(search); SHOW_TYPE (Searcher); - SHOW_TYPE (Searcher::DebugPipeline); SHOW_TYPE (Searcher::value_type); SHOW_TYPE (Searcher::pointer); // TypeDebugger guggi; // --> std::__cxx11::basic_string const& cout << "search[0]=" <<*search< - - - - - - + + + + + + - - + + @@ -31215,9 +31215,9 @@ - + - + @@ -31456,14 +31456,14 @@ - - - + + + - + - - + + @@ -31484,8 +31484,8 @@ - - + + From c0b8b63df037e6f68f42eae5809446fb2ef3166f Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 7 Sep 2018 20:44:25 +0200 Subject: [PATCH 068/178] ChainSearch: ensure to pass current state without spurious copy It is essential that we pass the current state of the filter into the expand functor, where it needs to be copied (once!) to create a child state, which can then be augmented. This augmented state is then pushed onto a stack, to enable backtracking. Due to the flexible adapters and the wrapping into the TreeExplorer builder, we ended up performing several spurious copies on the current state --- src/lib/iter-chain-search.hpp | 21 +++++--- src/lib/iter-tree-explorer.hpp | 10 ++++ wiki/thinkPad.ichthyo.mm | 93 ++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 6 deletions(-) diff --git a/src/lib/iter-chain-search.hpp b/src/lib/iter-chain-search.hpp index 01e957cd7..6c661abc5 100644 --- a/src/lib/iter-chain-search.hpp +++ b/src/lib/iter-chain-search.hpp @@ -92,11 +92,19 @@ namespace iter { template struct _IterChainSetup { - using Filter = decltype( buildSearchFilter (std::declval()) ); - - using StepFunctor = typename iter_explorer::_BoundFunctor::Functor; + using Filter = decltype( buildSearchFilter(std::declval()).asIterator() ); + using StepFunctor = std::function; + using StepWrapper = typename iter_explorer::_BoundFunctor::Functor; + // ^^^^^^^^^^^^^ used as argument on generic lambda using Pipeline = decltype( buildExplorer (std::declval(), std::declval()) ); + + static Pipeline + configurePipeline (SRC&& dataSource, StepFunctor step) + { + return buildExplorer(forward (dataSource), move (step)); + } + }; }//(End)type construction helpers @@ -122,7 +130,7 @@ namespace iter { using Value = typename _Base::value_type; using Filter = typename _Trait::Filter; - using Step = typename _Trait::StepFunctor; + using Step = typename _Trait::StepWrapper; std::vector stepChain_; @@ -136,8 +144,8 @@ namespace iter { template explicit IterChainSearch (SEQ&& srcData) - : _Base{move (buildExplorer (forward (srcData) - ,Step{[this](Filter const& curr){ return configureFilterChain(curr); }}))} + : _Base{_Trait::configurePipeline (forward (srcData) + ,[this](Filter const& curr){ return configureFilterChain(curr); })} { } // inherited default ctor and standard copy operations @@ -165,6 +173,7 @@ namespace iter { search (FUN&& configureSearchStep) { stepChain_.emplace_back (Step{forward (configureSearchStep)}); + this->iterNext(); // establish invariant: expand to leaf and forward to first match return move(*this); } diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index be4590e2c..a7f5c5686 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -1474,6 +1474,16 @@ namespace lib { { return IterExploreSource {move(*this)}; } + + + /** _terminal builder_ to strip the TreeExplorer and expose the built Pipeline. + * @return a »Lumiera Forward iterator« incorporating the complete pipeline logic. + */ + SRC + asIterator() + { + return SRC {move(*this)}; + } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 92ac0c48a..4b7f561ef 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -31215,6 +31215,99 @@ + + + + + + + + + + + + + + + + + + + + + + +

+ ...d.h. die einzelnen Steps in der Pipeline direkt wrappen. +

+

+ Dann ist außen herum keine Anpassung der Argumente mehr notwendig, +

+

+ und man kann die Expand-Funktion direkt als std::function-Objekt durchgeben +

+ + +
+ +
+
+ + + + + + +

+ ...um den Expand-Funktor zu zwingen, eine Kopie zu machen; +

+

+ es sollte die Filter-Konfiguration auf der Kopie manipuliert werden, +

+

+ während das Original auf dem Auswertungs-Stack liegen bleibt, +

+

+ für späteres Backtracking... +

+ + +
+
+ + + + + + + + +

+ ...damit man stets weitere Builder-Funktionen auf der Pipeline aufrufen kann +

+ + +
+
+ + + + + + +

+ weil wir diesen manipulieren +

+ + +
+
+ + + + +
+
From 34b78fc47e4e25841ce04f8f9626fb28bb788640 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 7 Sep 2018 21:12:30 +0200 Subject: [PATCH 069/178] ChainSearch: identify some possible problems - as implemented now, we will finally backtrack into the unfiltered base iterator - it is not possible to implement a clearFilter() operation --- src/lib/iter-chain-search.hpp | 3 +- tests/library/iter-chain-search-test.cpp | 6 --- wiki/thinkPad.ichthyo.mm | 54 ++++++++++++++++++++---- 3 files changed, 48 insertions(+), 15 deletions(-) diff --git a/src/lib/iter-chain-search.hpp b/src/lib/iter-chain-search.hpp index 6c661abc5..30d0779d5 100644 --- a/src/lib/iter-chain-search.hpp +++ b/src/lib/iter-chain-search.hpp @@ -200,6 +200,7 @@ namespace iter { IterChainSearch&& clearFilter() { + //////////////////////////////////////////////////////TODO logically broken. We need also to get rid of the current expansions, while retaining the current position stepChain_.clear(); return move(*this); } @@ -208,7 +209,7 @@ namespace iter { Filter configureFilterChain (Filter const& currentFilterState) { - uint depth = currentFilterState.depth(); + uint depth = this->depth(); if (depth < stepChain_.size()) return stepChain_[depth](currentFilterState); // augmented copy else diff --git a/tests/library/iter-chain-search-test.cpp b/tests/library/iter-chain-search-test.cpp index 89f193a11..98ede36de 100644 --- a/tests/library/iter-chain-search-test.cpp +++ b/tests/library/iter-chain-search-test.cpp @@ -109,12 +109,6 @@ namespace test{ .search("tomato"); ///////////////////////////////////////////////////TODO WIP - using Searcher = decltype(search); - SHOW_TYPE (Searcher); - SHOW_TYPE (Searcher::value_type); - SHOW_TYPE (Searcher::pointer); - -// TypeDebugger guggi; // --> std::__cxx11::basic_string const& cout << "search[0]=" <<*search< - - - - + + + + - - + + - - + + @@ -31525,6 +31525,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ wir sind irgendwo im Baum, nicht auf dem Basis-Layer. +

+

+ Und der Basis-Layer steht irgendwo, nicht an der aktuellen Position. +

+ + +
+ +
+
From e3ca8548a41e96cc49b40e6700f6e114f77758e9 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 8 Sep 2018 14:41:51 +0200 Subject: [PATCH 070/178] TreeExplorer: allow for a disabled filter ...considered as one of the building blocks to resolve Problems in the Design of ChainSearch --- src/lib/iter-tree-explorer.hpp | 21 +- tests/library/iter-tree-explorer-test.cpp | 25 +- wiki/thinkPad.ichthyo.mm | 798 ++++++++++++++-------- 3 files changed, 556 insertions(+), 288 deletions(-) diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index a7f5c5686..def63b5a2 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -445,6 +445,8 @@ namespace lib { namespace iter_explorer { // Implementation of Iterator decorating layers... + constexpr auto ACCEPT_ALL = [](auto){return true;}; + /** * @internal technical details of binding a functor into the TreeExplorer. * Notably, this happens when adapting an _"expansion functor"_ to allow expanding a given element @@ -927,12 +929,19 @@ namespace lib { return unConst(*this); } + bool + isDisabled() const + { + return not predicate_.boundFunction; + } + /** @note establishes the invariant: * whatever the source yields as current element, * has already been approved by our predicate */ void pullFilter () { + if (isDisabled()) return; while (srcIter() and not predicate_(srcIter())) ++srcIter(); } @@ -1082,6 +1091,13 @@ namespace lib { }); } + /** discard filter predicates and disable any filtering */ + void + disableFilter() + { + _Base::predicate_.boundFunction = nullptr; + } + private: /** @internal boilerplate to remould the filter predicate in-place @@ -1113,6 +1129,9 @@ namespace lib { WrappedPredicate& firstClause = _Base::predicate_; // pick up the existing filter predicate ChainPredicate chainClause{forward (additionalClause)}; // wrap the extension predicate in a similar way + if (_Base::isDisabled()) + firstClause.boundFunction = ACCEPT_ALL; + _Base::predicate_ = WrappedPredicate{buildCombinedClause (firstClause, chainClause)}; _Base::pullFilter(); // pull to re-establish the Invariant } @@ -1457,7 +1476,7 @@ namespace lib { auto mutableFilter() { - return mutableFilter ([](auto){ return true; }); + return mutableFilter (iter_explorer::ACCEPT_ALL); } diff --git a/tests/library/iter-tree-explorer-test.cpp b/tests/library/iter-tree-explorer-test.cpp index 8dc2cdc72..588be76c4 100644 --- a/tests/library/iter-tree-explorer-test.cpp +++ b/tests/library/iter-tree-explorer-test.cpp @@ -853,7 +853,7 @@ namespace test{ verify_FilterChanges() { auto seq = treeExplore(CountDown{20}) - .mutableFilter([](uint){ return true; }); + .mutableFilter(); auto takeEve = [](uint i){ return i%2 == 0; }; auto takeTrd = [](uint i){ return i%3 == 0; }; @@ -908,6 +908,29 @@ namespace test{ CHECK (0 == seq.p); // ...which he manipulated, so that core == 0 CHECK (isnil (seq)); // .....and thus iteration end is detected VERIFY_ERROR (ITER_EXHAUST, *seq ); + + + // verify enabling and disabling... + auto seq2 = treeExplore(CountDown{10}) + .mutableFilter(takeTrd); + + CHECK (9 == *seq2); + seq2.disableFilter(); + CHECK (9 == *seq2); + ++seq2; + CHECK (8 == *seq2); + seq2.andNotFilter (takeEve); + CHECK (7 == *seq2); + ++seq2; + CHECK (5 == *seq2); + seq2.disableFilter(); + CHECK (5 == *seq2); + ++seq2; + CHECK (4 == *seq2); + ++seq2; + CHECK (3 == *seq2); + seq2.flipFilter(); // everything rejected + CHECK (isnil (seq2)); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 5876d4158..fc46297ea 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -1,6 +1,6 @@ - + @@ -30746,6 +30746,8 @@ + + @@ -30783,6 +30785,400 @@ + + + + + + + + + + +

+ sofern längerfristig Itertools durch TreeExplorer abgelöst werden könnte +

+ +
+
+ + + + + + + + + + + + + + +

+ ...und das hängt an einem hauchdünnen Faden, +

+

+ und ist subtil bis zum geht nicht mehr.... +

+

+ +

+

+ Wenn man das Lambda einfach per [=] schreibt, und das Feld this->predicate_ verwendet, +

+

+ dann wird this gecaptured (und das ist effektiv per Referenz). Wenn ich dann den +

+

+ konstruierten Funktor an this->predicate_ zuweise, haben wir eine Endlos-Rekursion. +

+

+ +

+

+ Lösung: man muß im lokalen Frame eine Referenz auf this->predicate definieren und binden. +

+

+ Diese wird dann per Value gecaptured, was die gewünschte Kopie bewirkt. +

+ +
+
+ + + + + + +

+ ...muß den Chain-Funktor aus dem Template-Argument erzeugen, +

+

+ und ihn dann in die per-Value-Closure des erzeugten neuen Lambda binden (=Kopie). +

+

+ +

+

+ Erst ab C++17 kann man Lambda-Captures pre move machen +

+

+ (und auch dafür ist die Syntax grausam) +

+ +
+
+
+ + + + + + + +

+ ...es soll bloß einfach funktionieren!!!!!!!!!!! +

+ +
+ +
+ + + + + + + + + +

+ keine gute Idee. +

+

+ Dann verwenden wir im einen Funktor-Framework eine Filter-Komponente +

+

+ aus dem anderen Framework, obwohl es direkt hier auch eine Filter-Komponente gäbe. +

+

+ +

+

+ Außerdem habe ich immer noch die Hoffnung, irgendwann mal +

+

+ die Itertools komplett durch den TreeExplorer ablösen zu können +

+ +
+ +
+
+ + + + + + + + + + + + +

+ Name: mutableFilter() +

+ +
+ +
+
+ + + + + + + + + + + + + + + + +

+ ...sieht gut aus. +

+

+ +

+

+ Folgendes habe ich gesehen +

+
    +
  • + zu Beginn zeigt das eingebettete Funktor-Objekt auf eine Position auf dem Stack +
  • +
  • + beim Aufrufen der andFilter()-Funktion werden diverse Funktoren kopiert,
    wobei nacheinander die (zu erwartenden) Argumente als Quelle auftauchen
    +
  • +
  • + danach hat sich der Funktor geändert: er zeigt nun auf eine Position auf dem Heap
    +
  • +
  • + der bisherige Funktor wurde mit der Closure des zusammengesetzten Funktors kollabiert (hat gleiche Addresse)
    +
  • +
  • + der Chain-Funktor hat eine Closure bekommen, die ebenfalls Heap-alloziert ist.
    das deutet darauf hin, daß das capturen per copy funktioniert hat
    +
  • +
  • + Beim Aufruf steppen wir nacheinander erst in den kombinierten Funktor,
    und von dort wie erwartet in die beiden Lambdas. +
  • +
  • +
    +
  • +
+ +
+ +
+
+
+
+ + + + + + + + + + + + + +

+ ...für das dort hineingereichte Funktor-Objekt wird der Argument-Accessor ausgewählt (Metaprogrammierung). +

+

+ Er ist dann im Typ des Wrappers == _Traits::Functor codiert. +

+

+ +

+

+ Wir können zwar den im Wrapper enthaltenen Funktor neu zuweisen (in gewissen Grenzen), +

+

+ aber er wird stets den zu Beginn gewählten Argument-Accessor nehmen. +

+

+ Typischerweise wird dieser ja sogar beim Aufruf des getemplateteten Funtions-Operators geInlined +

+ +
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Problem: std::function in FilterPredicate +

+ +
+ + + + + + +

+ entweder Val -> bool oder Iter -> bool +

+ +
+ +
+
+
+
+
+
+ + + + + + + + + + + +

+ ...für das dort hineingereichte Funktor-Objekt wird der Argument-Accessor ausgewählt (Metaprogrammierung). +

+

+ Er ist dann im Typ des Wrappers == _Traits::Functor codiert. +

+

+ +

+

+ Wir können zwar den im Wrapper enthaltenen Funktor neu zuweisen (in gewissen Grenzen), +

+

+ aber er wird stets den zu Beginn gewählten Argument-Accessor nehmen. +

+

+ Typischerweise wird dieser ja sogar beim Aufruf des getemplateteten Funtions-Operators geInlined +

+ + +
+ +
+ + + + + + + + + + +

+ der Aufruf vom äußeren in den inneren Wrapper ist 1:1 und sollte vom Compiler wegoptimiert werden +

+ +
+
+ + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + +

+ ...ich verbrenne Stunden beim Debuggen von neuem Code +

+ + +
+ +
+ + + + + + + + + + + + + @@ -30837,290 +31233,9 @@ - + + - - - - - - - -

- sofern längerfristig Itertools durch TreeExplorer abgelöst werden könnte -

- -
-
- - - - - - - - - - - - - - -

- ...und das hängt an einem hauchdünnen Faden, -

-

- und ist subtil bis zum geht nicht mehr.... -

-

- -

-

- Wenn man das Lambda einfach per [=] schreibt, und das Feld this->predicate_ verwendet, -

-

- dann wird this gecaptured (und das ist effektiv per Referenz). Wenn ich dann den -

-

- konstruierten Funktor an this->predicate_ zuweise, haben wir eine Endlos-Rekursion. -

-

- -

-

- Lösung: man muß im lokalen Frame eine Referenz auf this->predicate definieren und binden. -

-

- Diese wird dann per Value gecaptured, was die gewünschte Kopie bewirkt. -

- -
-
- - - - - - -

- ...muß den Chain-Funktor aus dem Template-Argument erzeugen, -

-

- und ihn dann in die per-Value-Closure des erzeugten neuen Lambda binden (=Kopie). -

-

- -

-

- Erst ab C++17 kann man Lambda-Captures pre move machen -

-

- (und auch dafür ist die Syntax grausam) -

- -
-
-
- - - - - - - -

- ...es soll bloß einfach funktionieren!!!!!!!!!!! -

- -
- -
- - - - - - - - - -

- keine gute Idee. -

-

- Dann verwenden wir im einen Funktor-Framework eine Filter-Komponente -

-

- aus dem anderen Framework, obwohl es direkt hier auch eine Filter-Komponente gäbe. -

-

- -

-

- Außerdem habe ich immer noch die Hoffnung, irgendwann mal -

-

- die Itertools komplett durch den TreeExplorer ablösen zu können -

- -
- -
-
- - - - - - - - - - - - -

- Name: mutableFilter() -

- -
- -
-
- - - - - - - - - - - - - - - - -

- ...sieht gut aus. -

-

- -

-

- Folgendes habe ich gesehen -

-
    -
  • - zu Beginn zeigt das eingebettete Funktor-Objekt auf eine Position auf dem Stack -
  • -
  • - beim Aufrufen der andFilter()-Funktion werden diverse Funktoren kopiert,
    wobei nacheinander die (zu erwartenden) Argumente als Quelle auftauchen
    -
  • -
  • - danach hat sich der Funktor geändert: er zeigt nun auf eine Position auf dem Heap
    -
  • -
  • - der bisherige Funktor wurde mit der Closure des zusammengesetzten Funktors kollabiert (hat gleiche Addresse)
    -
  • -
  • - der Chain-Funktor hat eine Closure bekommen, die ebenfalls Heap-alloziert ist.
    das deutet darauf hin, daß das capturen per copy funktioniert hat
    -
  • -
  • - Beim Aufruf steppen wir nacheinander erst in den kombinierten Funktor,
    und von dort wie erwartet in die beiden Lambdas. -
  • -
  • -
    -
  • -
- -
- -
-
-
-
- - - - - - - - - - - - -

- ...für das dort hineingereichte Funktor-Objekt wird der Argument-Accessor ausgewählt (Metaprogrammierung). -

-

- Er ist dann im Typ des Wrappers == _Traits::Functor codiert. -

-

- -

-

- Wir können zwar den im Wrapper enthaltenen Funktor neu zuweisen (in gewissen Grenzen), -

-

- aber er wird stets den zu Beginn gewählten Argument-Accessor nehmen. -

-

- Typischerweise wird dieser ja sogar beim Aufruf des getemplateteten Funtions-Operators geInlined -

- -
-
- - - - - - - - - - -

- der Aufruf vom äußeren in den inneren Wrapper ist 1:1 und sollte vom Compiler wegoptimiert werden -

- -
-
- - - - - - - - - - - - - - - - - - -
-
-
- - - - - - - - -
@@ -31162,6 +31277,31 @@ + + + + + + + + +

+ die Search-Engine bläht die Debug-Infos gewaltig auf +

+ + +
+ + + + + + +
+ + + +
@@ -31525,7 +31665,8 @@ - + + @@ -31533,6 +31674,9 @@ + + + @@ -31545,7 +31689,7 @@ - + @@ -31560,6 +31704,7 @@ + @@ -31577,6 +31722,87 @@ + + + + + + + + + + + +

+ ...denn +

+
    +
  • + es ist praktisch, zunächst "leer" zu konstruieren +
  • +
  • + es ist verständlicher, wenn alle Bedingungen symmetrisch angegeben werden +
  • +
  • + es ist natürlich, daß ein "leerer" Filter alles durchläßt +
  • +
+ +
+
+ + + + + + +

+ Filter leer == alles durchlassen +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
From 5d4f1015aa0fca66f8d8a0f398aabe9cfd380ad2 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 8 Sep 2018 23:32:32 +0200 Subject: [PATCH 071/178] TreeExplorer: consider refactoring of the functor adaptatiion mechanism ...and in preparation start with some renamings... The overall goal is to simplify the type signatures and thereby to make the generates pipelines more assignment compatible. The debugging experience form the last days indicated that the current design is not maintainable on the long run. Both the template instantiation chains and the call stacks are way to complicated and hard to understand and diagnose --- src/lib/iter-chain-search.hpp | 2 +- src/lib/iter-tree-explorer.hpp | 26 +++---- wiki/thinkPad.ichthyo.mm | 131 +++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 14 deletions(-) diff --git a/src/lib/iter-chain-search.hpp b/src/lib/iter-chain-search.hpp index 30d0779d5..4065cccd2 100644 --- a/src/lib/iter-chain-search.hpp +++ b/src/lib/iter-chain-search.hpp @@ -95,7 +95,7 @@ namespace iter { using Filter = decltype( buildSearchFilter(std::declval()).asIterator() ); using StepFunctor = std::function; - using StepWrapper = typename iter_explorer::_BoundFunctor::Functor; + using StepWrapper = typename iter_explorer::_FunTraits::Functor; // ^^^^^^^^^^^^^ used as argument on generic lambda using Pipeline = decltype( buildExplorer (std::declval(), std::declval()) ); diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index def63b5a2..46debeb6d 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -473,7 +473,7 @@ namespace lib { * @tparam SRC the source iterator type to apply when attempting to use a generic lambda as functor */ template - struct _BoundFunctor + struct _FunTraits { /** handle all regular "function-like" entities */ template @@ -500,7 +500,7 @@ namespace lib { /** adapt to a functor, which accesses the source iterator or embedded "state core" */ template - struct ArgAccessor + struct ArgAdapter { using FunArgType = remove_reference_t; static_assert (std::is_convertible::value, @@ -511,7 +511,7 @@ namespace lib { /** adapt to a functor, which accepts the value type of the source sequence ("monadic" usage pattern) */ template - struct ArgAccessor + struct ArgAdapter ,__not_>>>> // need to exclude the latter, since IterableDecorator { // often seems to accept IT::value_type (while in fact it doesn't) static auto build() { return [](auto& iter) { return *iter; }; } @@ -519,7 +519,7 @@ namespace lib { /** adapt to a functor collaborating with an IterSource based iterator pipeline */ template - struct ArgAccessor, typename IT::Source> + struct ArgAdapter, typename IT::Source> , is_base_of, remove_reference_t> > >> { @@ -538,7 +538,7 @@ namespace lib { Res operator() (ARG& arg) const { - auto accessArg = ArgAccessor::build(); + auto accessArg = ArgAdapter::build(); return boundFunction (accessArg (arg)); } @@ -593,7 +593,7 @@ namespace lib { * the source iterator wrapped by this decorator. * @remark since we allow a lot of leeway regarding the actual form and definition of the * _expansion functor_, there is a lot of minute technical details, mostly confined - * within the _BoundFunctor traits. + * within the _FunTraits traits. * @tparam SRC the wrapped source iterator, typically a TreeExplorer or nested decorator. * @tparam FUN the concrete type of the functor passed. Will be dissected to find the signature */ @@ -602,7 +602,7 @@ namespace lib { : public SRC { static_assert(can_IterForEach::value, "Lumiera Iterator required as source"); - using _Traits = _BoundFunctor; + using _Traits = _FunTraits; using ExpandFunctor = typename _Traits::Functor; using ResIter = typename _DecoratorTraits::SrcIter; @@ -782,7 +782,7 @@ namespace lib { : public SRC { static_assert(can_IterForEach::value, "Lumiera Iterator required as source"); - using _Traits = _BoundFunctor; + using _Traits = _FunTraits; using Res = typename _Traits::Res; using TransformFunctor = typename _Traits::Functor; @@ -869,7 +869,7 @@ namespace lib { : public SRC { static_assert(can_IterForEach::value, "Lumiera Iterator required as source"); - using _Traits = _BoundFunctor; + using _Traits = _FunTraits; using Res = typename _Traits::Res; static_assert(std::is_constructible::value, "Functor must be a predicate"); @@ -967,7 +967,7 @@ namespace lib { * be mixed and combined. To allow for this flexibility, we need to drive the _base class_ * with the most general form of a predicate functor, corresponding to `bool`. * Hereby we exploit the fact that the _wrapped filter,_ i.e. the Functor type constructed - * in the _BoundFunctor traits, boils down to that most generic form, adapting the arguments + * in the _FunTraits traits, boils down to that most generic form, adapting the arguments * automatically. Thus the initial functor, the one passed to the ctor of this class, is * effectively wrapped twice, so it can be combined later on with any other form and * shape of functor. And since everything is inline, the compiler will remove any @@ -975,9 +975,9 @@ namespace lib { */ template class MutableFilter - : public Filter::Functor> + : public Filter::Functor> { - using _Traits = _BoundFunctor; + using _Traits = _FunTraits; using FilterPredicate = typename _Traits::Functor; using _Base = Filter; @@ -1119,7 +1119,7 @@ namespace lib { void remouldFilter (COND&& additionalClause, COMB buildCombinedClause) { - using _ChainTraits = _BoundFunctor; + using _ChainTraits = _FunTraits; using Res = typename _ChainTraits::Res; static_assert(std::is_constructible::value, "Chained Functor must be a predicate"); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index fc46297ea..d66335796 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -25129,6 +25129,56 @@ + + + + + + +

+ ...ist mir nicht völlig klar, warum das bei einigen Includes auftritt, +

+

+ und bei anderen nicht. Beispiel +

+

+ +

+

+ In file included from src/lib/diff/test-mutation-target.hpp:55:0, +

+

+                  from tests/library/diff/diff-complex-application-test.cpp:35: +

+

+ src/lib/test/event-log.hpp:96:9: warning: 'lib::test::EventMatch' has a field 'lib::test::EventMatch::solution_' whose type uses the anonymous namespace [-Wsubobject-linkage] +

+

+    class EventMatch +

+

+          ^~~~~~~~~~ +

+

+ In file included from src/lib/diff/test-mutation-target.hpp:55:0, +

+

+                  from tests/library/diff/tree-mutator-binding-test.cpp:32: +

+

+ src/lib/test/event-log.hpp:96:9: warning: 'lib::test::EventMatch' has a field 'lib::test::EventMatch::solution_' whose type uses the anonymous namespace [-Wsubobject-linkage] +

+

+    class EventMatch +

+

+          ^~~~~~~~~~ +

+ + +
+ +
@@ -31176,6 +31226,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Konsequenz: brauche Template Funktions-Operator +

+ + +
+
+
+
+
+
+ + + + + + + + + + + + +

+ der Funktor für den Expander wird explizit als Sonderfall  aufgefaßt +

+ + +
+ + + + + +

+ das eigentliche Problem mit der bestehenden Lösung ist, +

+

+ daß ich ausgerechnet mit diesem Sonderfall angefangen habe. +

+

+ Daher ist jetzt das gesamte Design "anders herum" +

+ + +
+
+ +
From a175306f50a7189a38c79acdbd3f13270591955d Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 9 Sep 2018 01:11:58 +0200 Subject: [PATCH 072/178] TreeExplorer: allow to drive the function traits solely by the function signature ...which still needs to be the *concrete* signature of the funcition to pass, but we'll attempt to loosen that requirement in the next refactoring steps --- src/lib/iter-tree-explorer.hpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index 46debeb6d..7270a1683 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -469,10 +469,13 @@ namespace lib { * keep the argument-accepting front-end still generic (templated `operator()`). This * special adapter supports the case when the _expansion functor_ yields a child sequence * type different but compatible to the original source sequence embedded in TreeExplorer. - * @tparam FUN something _"function-like"_ passed as functor to be bound - * @tparam SRC the source iterator type to apply when attempting to use a generic lambda as functor + * @tparam FUN either the signature, or something _"function-like"_ passed as functor to be bound + * @tparam SRC (optional) but need to specify the source iterator type to apply when passing + * a generic lambda or template as FUN. Such a generic functor will be _instantiated_ + * passing the type `SRC&` as argument. This instantiation may fail (and abort compilation), + * but when it succeeds, we can infer the result type `Res` from the generic lambda */ - template + template struct _FunTraits { /** handle all regular "function-like" entities */ @@ -492,7 +495,7 @@ namespace lib { }; - using Sig = typename FunDetector::Sig; + using Sig = typename FunDetector::Sig; using Arg = typename _Fun::Args::List::Head; // assuming function with a single argument using Res = typename _Fun::Ret; From a91025bfe043edf95464aafc1d7433e1d00f89be Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 9 Sep 2018 02:09:38 +0200 Subject: [PATCH 073/178] TreeExplorer: perpare a builder for a suitably adapted, wrapped functor. This does not touch the existing code-path, but the idea is to use the _FunTraits directly from within the constructor of the respective processing layer, and to confine the knowledge of the actual FUN functor type to within that limited context. Only the generic signature of the resulting std::function need to be encoded into the type of the processing component, which should help to simplify the type signatures --- src/lib/iter-tree-explorer.hpp | 17 +++++++++-- wiki/thinkPad.ichthyo.mm | 53 +++++++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index 7270a1683..2bf94e392 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -475,7 +475,7 @@ namespace lib { * passing the type `SRC&` as argument. This instantiation may fail (and abort compilation), * but when it succeeds, we can infer the result type `Res` from the generic lambda */ - template + template struct _FunTraits { /** handle all regular "function-like" entities */ @@ -495,7 +495,7 @@ namespace lib { }; - using Sig = typename FunDetector::Sig; + using Sig = typename FunDetector::Sig; using Arg = typename _Fun::Args::List::Head; // assuming function with a single argument using Res = typename _Fun::Ret; @@ -510,6 +510,8 @@ namespace lib { "the bound functor must accept the source iterator or state core as parameter"); static auto build() { return [](ARG& arg) -> ARG& { return arg; }; } + + static auto wrap (FUN&& rawFunctor) { return forward (rawFunctor); } }; /** adapt to a functor, which accepts the value type of the source sequence ("monadic" usage pattern) */ @@ -518,6 +520,8 @@ namespace lib { ,__not_>>>> // need to exclude the latter, since IterableDecorator { // often seems to accept IT::value_type (while in fact it doesn't) static auto build() { return [](auto& iter) { return *iter; }; } + + static auto wrap (function rawFun) { return [rawFun](IT& srcIter) { return rawFun(*srcIter); }; } }; /** adapt to a functor collaborating with an IterSource based iterator pipeline */ @@ -529,6 +533,8 @@ namespace lib { using Source = typename IT::Source; static auto build() { return [](auto& iter) -> Source& { return iter.source(); }; } + + static auto wrap (function rawFun) { return [rawFun](IT& iter) { return rawFun(*iter.source()); }; } }; @@ -546,6 +552,13 @@ namespace lib { return boundFunction (accessArg (arg)); } }; + + template + static auto + adaptFunctor (FUN&& rawFunctor) + { + return function {ArgAdapter::wrap (forward (rawFunctor))}; + } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index d66335796..a892bba72 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -31269,7 +31269,7 @@
- + @@ -31308,6 +31308,57 @@ + + + + + + + + + + + + + + + + + + +

+ ...wo der volle Typ des Funktors FUN bekannt ist +

+ + +
+
+ + + + + + + + +

+ ...und in dem besonders wichtigen Fall, +

+

+ in dem der Funktor direkt den SRC-Iterator akzeptiert, +

+

+ wird er ohne Weiteres durchgereicht. +

+

+ In dem Fall dann keine doppelte Verpackung mehr! +

+ + +
+
+
+
From 1e657acbffae269cd22ac2f3a9fe45fb824639bb Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 9 Sep 2018 03:23:17 +0200 Subject: [PATCH 074/178] TreeExplorer: refactor Transformer to employ the improved wrapping style ...step by step switch over to the new usage pattern. Transformer should be the blueprint for all other functor usages. The reworked solutions behaves as expected; we see two functor invocations; the outer functor, which does the argument adaptation, is allocated in heap memory --- src/lib/iter-tree-explorer.hpp | 21 ++++++++------- src/lib/wrapper.hpp | 2 ++ tests/library/iter-tree-explorer-test.cpp | 1 + wiki/thinkPad.ichthyo.mm | 33 ++++++++++++++++++++--- 4 files changed, 43 insertions(+), 14 deletions(-) diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index 2bf94e392..b3a18bbe0 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -793,32 +793,31 @@ namespace lib { * is adapted in a similar way as the "expand functor", so to detect and convert the * expected input on invocation. */ - template + template class Transformer : public SRC { static_assert(can_IterForEach::value, "Lumiera Iterator required as source"); - using _Traits = _FunTraits; - using Res = typename _Traits::Res; - using TransformFunctor = typename _Traits::Functor; - using TransformedItem = wrapper::ItemWrapper; + using TransformFunctor = function; + using TransformedItem = wrapper::ItemWrapper; TransformFunctor trafo_; TransformedItem treated_; public: - using value_type = typename meta::TypeBinding::value_type; - using reference = typename meta::TypeBinding::reference; - using pointer = typename meta::TypeBinding::pointer; + using value_type = typename meta::TypeBinding::value_type; + using reference = typename meta::TypeBinding::reference; + using pointer = typename meta::TypeBinding::pointer; Transformer() =default; // inherited default copy operations + template Transformer (SRC&& dataSrc, FUN&& transformFunctor) : SRC{move (dataSrc)} - , trafo_{forward (transformFunctor)} + , trafo_{_FunTraits::template adaptFunctor (forward (transformFunctor))} { } @@ -1437,7 +1436,9 @@ namespace lib { auto transform (FUN&& transformFunctor) { - using ResCore = iter_explorer::Transformer; + using Product = typename iter_explorer::_FunTraits::Res; + + using ResCore = iter_explorer::Transformer; using ResIter = typename _DecoratorTraits::SrcIter; return TreeExplorer (ResCore {move(*this), forward(transformFunctor)}); diff --git a/src/lib/wrapper.hpp b/src/lib/wrapper.hpp index aa66db2fe..363274fb6 100644 --- a/src/lib/wrapper.hpp +++ b/src/lib/wrapper.hpp @@ -185,11 +185,13 @@ namespace wrapper { explicit ItemWrapper(TY const& o) + : created_(false) { build (o); } explicit ItemWrapper(TY && ro) + : created_(false) { build (std::move(ro)); } diff --git a/tests/library/iter-tree-explorer-test.cpp b/tests/library/iter-tree-explorer-test.cpp index 588be76c4..914349475 100644 --- a/tests/library/iter-tree-explorer-test.cpp +++ b/tests/library/iter-tree-explorer-test.cpp @@ -517,6 +517,7 @@ namespace test{ .transform(multiply) ; + CHECK (14 == *ii); CHECK (14 == *ii); ++ii; CHECK (12 == *ii); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index a892bba72..bab370530 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -31200,8 +31200,8 @@
- - + + @@ -31318,8 +31318,8 @@ - - + + @@ -31358,6 +31358,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + From 36d79be8b2325472ed29ca4362cdd669f2faec6a Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 9 Sep 2018 15:27:25 +0200 Subject: [PATCH 075/178] TreeExplorer: refactor Filter in a similar way --- src/lib/iter-tree-explorer.hpp | 85 ++++++++++++----------- tests/library/iter-tree-explorer-test.cpp | 38 +++++----- wiki/thinkPad.ichthyo.mm | 18 +++-- 3 files changed, 77 insertions(+), 64 deletions(-) diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index b3a18bbe0..15b6f0b11 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -562,6 +562,24 @@ namespace lib { }; + template + inline void + static_assert_isPredicate() + { + using Res = typename _FunTraits::Res; + static_assert(std::is_constructible::value, "Functor must be a predicate"); + } + + template + inline void + static_assert_isSourceResultCompatible() + { + using ResIter = typename _DecoratorTraits::Res>::SrcIter; + static_assert (std::is_convertible::value, + "the iterator from the expansion must yield compatible values"); + } + + /** * @internal Base of pipe processing decorator chain. @@ -879,17 +897,14 @@ namespace lib { * The filter predicate and thus the source iterator is evaluated _eagerly_, to establish the * *invariant* of this class: _if a "current element" exists, it has already been approved._ */ - template + template class Filter : public SRC { static_assert(can_IterForEach::value, "Lumiera Iterator required as source"); - using _Traits = _FunTraits; - using Res = typename _Traits::Res; - static_assert(std::is_constructible::value, "Functor must be a predicate"); protected: - using FilterPredicate = typename _Traits::Functor; + using FilterPredicate = function; FilterPredicate predicate_; @@ -898,9 +913,10 @@ namespace lib { Filter() =default; // inherited default copy operations + template Filter (SRC&& dataSrc, FUN&& filterFun) : SRC{move (dataSrc)} - , predicate_{forward (filterFun)} + , predicate_{_FunTraits::template adaptFunctor (forward (filterFun))} { pullFilter(); // initially pull to establish the invariant } @@ -947,7 +963,7 @@ namespace lib { bool isDisabled() const { - return not predicate_.boundFunction; + return not bool{predicate_}; } /** @note establishes the invariant: @@ -979,34 +995,22 @@ namespace lib { * @remarks filter predicates can be specified in a wide variety of forms, and will be adapted * automatically. This flexibility also holds for any of the additional clauses provided * for remoulding the filter. Especially this means that functors of different kinds can - * be mixed and combined. To allow for this flexibility, we need to drive the _base class_ - * with the most general form of a predicate functor, corresponding to `bool`. - * Hereby we exploit the fact that the _wrapped filter,_ i.e. the Functor type constructed - * in the _FunTraits traits, boils down to that most generic form, adapting the arguments - * automatically. Thus the initial functor, the one passed to the ctor of this class, is - * effectively wrapped twice, so it can be combined later on with any other form and - * shape of functor. And since everything is inline, the compiler will remove any - * overhead resulting from this redundant wrapping. + * be mixed and combined. */ - template + template class MutableFilter - : public Filter::Functor> + : public Filter { - using _Traits = _FunTraits; - using FilterPredicate = typename _Traits::Functor; - using _Base = Filter; - + using _Filter = Filter; static_assert(can_IterForEach::value, "Lumiera Iterator required as source"); - using Res = typename _Traits::Res; - static_assert(std::is_constructible::value, "Functor must be a predicate"); - public: MutableFilter() =default; // inherited default copy operations + template MutableFilter (SRC&& dataSrc, FUN&& filterFun) - : _Base{move (dataSrc), FilterPredicate{forward (filterFun)}} + : _Filter{move (dataSrc), forward (filterFun)} { } @@ -1110,7 +1114,7 @@ namespace lib { void disableFilter() { - _Base::predicate_.boundFunction = nullptr; + _Filter::predicate_ = nullptr; } @@ -1134,21 +1138,18 @@ namespace lib { void remouldFilter (COND&& additionalClause, COMB buildCombinedClause) { - using _ChainTraits = _FunTraits; - using Res = typename _ChainTraits::Res; - static_assert(std::is_constructible::value, "Chained Functor must be a predicate"); + static_assert_isPredicate(); - using WrappedPredicate = typename _Base::FilterPredicate; - using ChainPredicate = typename _ChainTraits::Functor; + using WrappedPredicate = typename _Filter::FilterPredicate; - WrappedPredicate& firstClause = _Base::predicate_; // pick up the existing filter predicate - ChainPredicate chainClause{forward (additionalClause)}; // wrap the extension predicate in a similar way - - if (_Base::isDisabled()) - firstClause.boundFunction = ACCEPT_ALL; + WrappedPredicate& firstClause = _Filter::predicate_; // pick up the existing filter predicate + WrappedPredicate chainClause{_FunTraits::template adaptFunctor (forward (additionalClause))}; + // wrap the extension predicate in a similar way + if (_Filter::isDisabled()) + _Filter::predicate_ = ACCEPT_ALL; - _Base::predicate_ = WrappedPredicate{buildCombinedClause (firstClause, chainClause)}; - _Base::pullFilter(); // pull to re-establish the Invariant + _Filter::predicate_ = buildCombinedClause (firstClause, chainClause); + _Filter::pullFilter(); // pull to re-establish the Invariant } }; @@ -1455,7 +1456,9 @@ namespace lib { auto filter (FUN&& filterPredicate) { - using ResCore = iter_explorer::Filter; + iter_explorer::static_assert_isPredicate(); + + using ResCore = iter_explorer::Filter; using ResIter = typename _DecoratorTraits::SrcIter; return TreeExplorer (ResCore {move(*this), forward(filterPredicate)}); @@ -1483,7 +1486,9 @@ namespace lib { auto mutableFilter (FUN&& filterPredicate) { - using ResCore = iter_explorer::MutableFilter; + iter_explorer::static_assert_isPredicate(); + + using ResCore = iter_explorer::MutableFilter; using ResIter = typename _DecoratorTraits::SrcIter; return TreeExplorer (ResCore {move(*this), forward(filterPredicate)}); diff --git a/tests/library/iter-tree-explorer-test.cpp b/tests/library/iter-tree-explorer-test.cpp index 914349475..08c3f799b 100644 --- a/tests/library/iter-tree-explorer-test.cpp +++ b/tests/library/iter-tree-explorer-test.cpp @@ -912,26 +912,26 @@ namespace test{ // verify enabling and disabling... - auto seq2 = treeExplore(CountDown{10}) - .mutableFilter(takeTrd); + seq = treeExplore(CountDown{10}) + .mutableFilter(takeTrd); - CHECK (9 == *seq2); - seq2.disableFilter(); - CHECK (9 == *seq2); - ++seq2; - CHECK (8 == *seq2); - seq2.andNotFilter (takeEve); - CHECK (7 == *seq2); - ++seq2; - CHECK (5 == *seq2); - seq2.disableFilter(); - CHECK (5 == *seq2); - ++seq2; - CHECK (4 == *seq2); - ++seq2; - CHECK (3 == *seq2); - seq2.flipFilter(); // everything rejected - CHECK (isnil (seq2)); + CHECK (9 == *seq); + seq.disableFilter(); + CHECK (9 == *seq); + ++seq; + CHECK (8 == *seq); + seq.andNotFilter (takeEve); + CHECK (7 == *seq); + ++seq; + CHECK (5 == *seq); + seq.disableFilter(); + CHECK (5 == *seq); + ++seq; + CHECK (4 == *seq); + ++seq; + CHECK (3 == *seq); + seq.flipFilter(); // everything rejected + CHECK (isnil (seq)); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index bab370530..b63eb3126 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -31126,6 +31126,10 @@ + + + +
@@ -31239,7 +31243,8 @@
- + + @@ -31266,6 +31271,9 @@ + + + @@ -31370,11 +31378,11 @@
- - + + - - + + From 90677404565306b891f8efd95002ac7c0dd74d17 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 9 Sep 2018 19:36:36 +0200 Subject: [PATCH 076/178] TreeExplorer: refactor Expander to fit in with the reworked design 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 --- src/lib/iter-tree-explorer.hpp | 44 ++++---- wiki/thinkPad.ichthyo.mm | 195 +++++++++++++++++++++++++-------- 2 files changed, 172 insertions(+), 67 deletions(-) diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index 15b6f0b11..671184b90 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -511,7 +511,7 @@ namespace lib { static auto build() { return [](ARG& arg) -> ARG& { return arg; }; } - static auto wrap (FUN&& rawFunctor) { return forward (rawFunctor); } + static decltype(auto) wrap (FUN&& rawFunctor) { return forward (rawFunctor); } }; /** adapt to a functor, which accepts the value type of the source sequence ("monadic" usage pattern) */ @@ -521,7 +521,7 @@ namespace lib { { // often seems to accept IT::value_type (while in fact it doesn't) static auto build() { return [](auto& iter) { return *iter; }; } - static auto wrap (function rawFun) { return [rawFun](IT& srcIter) { return rawFun(*srcIter); }; } + static auto wrap (function rawFun) { return [rawFun](IT& srcIter) -> Res { return rawFun(*srcIter); }; } }; /** adapt to a functor collaborating with an IterSource based iterator pipeline */ @@ -534,7 +534,7 @@ namespace lib { static auto build() { return [](auto& iter) -> Source& { return iter.source(); }; } - static auto wrap (function rawFun) { return [rawFun](IT& iter) { return rawFun(*iter.source()); }; } + static auto wrap (function rawFun) { return [rawFun](IT& iter) -> Res { return rawFun(iter.source()); }; } }; @@ -570,15 +570,6 @@ namespace lib { static_assert(std::is_constructible::value, "Functor must be a predicate"); } - template - inline void - static_assert_isSourceResultCompatible() - { - using ResIter = typename _DecoratorTraits::Res>::SrcIter; - static_assert (std::is_convertible::value, - "the iterator from the expansion must yield compatible values"); - } - /** @@ -631,28 +622,33 @@ namespace lib { * @tparam SRC the wrapped source iterator, typically a TreeExplorer or nested decorator. * @tparam FUN the concrete type of the functor passed. Will be dissected to find the signature */ - template + template class Expander : public SRC { static_assert(can_IterForEach::value, "Lumiera Iterator required as source"); - using _Traits = _FunTraits; - using ExpandFunctor = typename _Traits::Functor; - using ResIter = typename _DecoratorTraits::SrcIter; - static_assert (std::is_convertible::value, + using ResIter = typename _DecoratorTraits::SrcIter; + static_assert (std::is_convertible(), "the iterator from the expansion must yield compatible values"); - ExpandFunctor expandChildren_; + using RootExpandFunctor = function; + using ChldExpandFunctor = function; + + RootExpandFunctor expandRoot_; + ChldExpandFunctor expandChild_; + IterStack expansions_; public: Expander() =default; // inherited default copy operations + template Expander (SRC&& parentExplorer, FUN&& expandFunctor) : SRC{move (parentExplorer)} // NOTE: slicing move to strip TreeExplorer (Builder) - , expandChildren_{forward (expandFunctor)} + , expandRoot_ {_FunTraits ::template adaptFunctor (forward (expandFunctor))} // adapt to accept SRC& + , expandChild_{_FunTraits::template adaptFunctor (forward (expandFunctor))} // adapt to accept RES& , expansions_{} { } @@ -663,8 +659,8 @@ namespace lib { { REQUIRE (this->checkPoint(), "attempt to expand an empty explorer"); - ResIter expanded{ hasChildren()? expandChildren_(*expansions_) - : expandChildren_(*this)}; + ResIter expanded{ hasChildren()? expandChild_(*expansions_) + : expandRoot_(*this)}; incrementCurrent(); // consume current head element (but don't clean-up) if (not isnil(expanded)) expansions_.push (move(expanded)); @@ -834,7 +830,7 @@ namespace lib { template Transformer (SRC&& dataSrc, FUN&& transformFunctor) - : SRC{move (dataSrc)} + : SRC{move (dataSrc)} // NOTE: slicing move to strip TreeExplorer (Builder) , trafo_{_FunTraits::template adaptFunctor (forward (transformFunctor))} { } @@ -1376,7 +1372,9 @@ namespace lib { auto expand (FUN&& expandFunctor) { - using ResCore = iter_explorer::Expander; + using ExpandedChildren = typename iter_explorer::_FunTraits::Res; + + using ResCore = iter_explorer::Expander; using ResIter = typename _DecoratorTraits::SrcIter; return TreeExplorer (ResCore {move(*this), forward(expandFunctor)}); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index b63eb3126..2aff97a52 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -25175,8 +25175,7 @@          ^~~~~~~~~~

- - +
@@ -31095,8 +31094,8 @@ - - + + @@ -31136,9 +31135,9 @@ - + - + @@ -31164,11 +31163,10 @@ Typischerweise wird dieser ja sogar beim Aufruf des getemplateteten Funtions-Operators geInlined

- -
+
- + @@ -31203,6 +31201,15 @@ + + + + + + + + + @@ -31216,8 +31223,7 @@ ...ich verbrenne Stunden beim Debuggen von neuem Code

- - +
@@ -31267,8 +31273,7 @@ Konsequenz: brauche Template Funktions-Operator

- - +
@@ -31293,8 +31298,7 @@ der Funktor für den Expander wird explizit als Sonderfall  aufgefaßt

- - + @@ -31310,13 +31314,13 @@ Daher ist jetzt das gesamte Design "anders herum"

- -
+
- + + @@ -31338,8 +31342,7 @@ ...wo der volle Typ des Funktors FUN bekannt ist

- - +
@@ -31362,12 +31365,11 @@ In dem Fall dann keine doppelte Verpackung mehr!

- - +
- - + + @@ -31384,8 +31386,122 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ da sich die Iteratoren wirklich unterscheiden können dürfen +

+ + +
+
+
+ + + + + + + + + + + + + + + + + + + +

+ ...wobei der konkrete Overhead noch nicht wirklich klar ist; +

+

+ hängt davon ab, wie geschickt der Optimizer ist, und was man konkret als Funktoren angibt. +

+

+ +

+

+ Vermutlich haben wir hier +

+
    +
  • + zwingend eine Indirektion durch einen Funktions-Pointer (weil eine Type Erasure stattfindet) +
  • +
  • + möglicherweise eine zusätzliche Heap-Allocation (es sei denn, der Optimizer ist wirklich clever) +
  • +
+ + +
+ +
+ + + + + + +

+ und das ist das Argument, das sticht +

+ + +
+ +
+ + + + + + +

+ ...will sagen, wenn schon eine neue Lösung, dann von A bis Z +

+ + +
+ +
+
+ + +
@@ -31504,8 +31620,7 @@ die Search-Engine bläht die Debug-Infos gewaltig auf

- - + @@ -31602,8 +31717,7 @@ und man kann die Expand-Funktion direkt als std::function-Objekt durchgeben

- - +
@@ -31626,8 +31740,7 @@ für späteres Backtracking...

- - +
@@ -31641,8 +31754,7 @@ ...damit man stets weitere Builder-Funktionen auf der Pipeline aufrufen kann

- - +
@@ -31654,8 +31766,7 @@ weil wir diesen manipulieren

- -
+
@@ -31801,8 +31912,7 @@ sofern eine Initialisierung ausidem Source-Iterator möglich ist

- - +
@@ -31817,8 +31927,7 @@ und sonst ist das nicht wirklich "kniffelig"

- - + @@ -31846,8 +31955,7 @@ dann sollte der Optimizer diese gesamten x-fachen Wrapper nahezu restlos entfernen

- -
+
@@ -31917,8 +32025,7 @@ Und der Basis-Layer steht irgendwo, nicht an der aktuellen Position.

- - +
From 7cdd680e788df5395dbd24320cd7d4e8ecb62fdc Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 9 Sep 2018 20:44:32 +0200 Subject: [PATCH 077/178] TreeExplorer: clean-up after refactoring 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) --- src/lib/iter-chain-search.hpp | 6 +-- src/lib/iter-tree-explorer.hpp | 54 ++++++++++---------- tests/library/iter-tree-explorer-test.cpp | 3 -- wiki/thinkPad.ichthyo.mm | 62 +++++++++++++---------- 4 files changed, 62 insertions(+), 63 deletions(-) diff --git a/src/lib/iter-chain-search.hpp b/src/lib/iter-chain-search.hpp index 4065cccd2..e839d37cf 100644 --- a/src/lib/iter-chain-search.hpp +++ b/src/lib/iter-chain-search.hpp @@ -93,10 +93,8 @@ namespace iter { struct _IterChainSetup { using Filter = decltype( buildSearchFilter(std::declval()).asIterator() ); - using StepFunctor = std::function; - using StepWrapper = typename iter_explorer::_FunTraits::Functor; - // ^^^^^^^^^^^^^ used as argument on generic lambda + using Pipeline = decltype( buildExplorer (std::declval(), std::declval()) ); static Pipeline @@ -130,7 +128,7 @@ namespace iter { using Value = typename _Base::value_type; using Filter = typename _Trait::Filter; - using Step = typename _Trait::StepWrapper; + using Step = typename _Trait::StepFunctor; std::vector stepChain_; diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index 671184b90..bd3007278 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -465,10 +465,10 @@ namespace lib { * accepts the iterator or state core itself (the "opaque state manipulation" usage pattern). * - we generate a suitable argument accessor function and build the function composition * of this accessor and the provided _expansion functor_. - * - the resulting, combined functor is stored into a std::function, but wired in a way to - * keep the argument-accepting front-end still generic (templated `operator()`). This - * special adapter supports the case when the _expansion functor_ yields a child sequence - * type different but compatible to the original source sequence embedded in TreeExplorer. + * - the resulting, combined functor is stored into a std::function, thereby abstracting + * from the actual adapting mechanism. This allows to combine different kinds of functors + * within the same processing step; and in addition, it allows the processing step to + * remain agnostic with respect to the adaptation and concrete type of the functor/lambda. * @tparam FUN either the signature, or something _"function-like"_ passed as functor to be bound * @tparam SRC (optional) but need to specify the source iterator type to apply when passing * a generic lambda or template as FUN. Such a generic functor will be _instantiated_ @@ -509,9 +509,11 @@ namespace lib { static_assert (std::is_convertible::value, "the bound functor must accept the source iterator or state core as parameter"); - static auto build() { return [](ARG& arg) -> ARG& { return arg; }; } - - static decltype(auto) wrap (FUN&& rawFunctor) { return forward (rawFunctor); } + static decltype(auto) + wrap (FUN&& rawFunctor) ///< actually pass-through the raw functor unaltered + { + return forward (rawFunctor); + } }; /** adapt to a functor, which accepts the value type of the source sequence ("monadic" usage pattern) */ @@ -519,9 +521,11 @@ namespace lib { struct ArgAdapter ,__not_>>>> // need to exclude the latter, since IterableDecorator { // often seems to accept IT::value_type (while in fact it doesn't) - static auto build() { return [](auto& iter) { return *iter; }; } - - static auto wrap (function rawFun) { return [rawFun](IT& srcIter) -> Res { return rawFun(*srcIter); }; } + static auto + wrap (function rawFun) ///< adapt by dereferencing the source iterator + { + return [rawFun](IT& srcIter) -> Res { return rawFun(*srcIter); }; + } }; /** adapt to a functor collaborating with an IterSource based iterator pipeline */ @@ -532,27 +536,16 @@ namespace lib { { using Source = typename IT::Source; - static auto build() { return [](auto& iter) -> Source& { return iter.source(); }; } - - static auto wrap (function rawFun) { return [rawFun](IT& iter) -> Res { return rawFun(iter.source()); }; } - }; - - - /** holder for the suitably adapted _expansion functor_ */ - struct Functor - { - function boundFunction; - - template - Res - operator() (ARG& arg) const + static auto + wrap (function rawFun) ///< extract the (abstracted) IterSource { - auto accessArg = ArgAdapter::build(); - - return boundFunction (accessArg (arg)); + return [rawFun](IT& iter) -> Res { return rawFun(iter.source()); }; } }; + + + /** builder to create a nested/wrapping functor, suitably adapting the arguments */ template static auto adaptFunctor (FUN&& rawFunctor) @@ -618,7 +611,10 @@ namespace lib { * the source iterator wrapped by this decorator. * @remark since we allow a lot of leeway regarding the actual form and definition of the * _expansion functor_, there is a lot of minute technical details, mostly confined - * within the _FunTraits traits. + * within the _FunTraits traits. For the same reason, we need to prepare two different + * bindings of the passed raw functor, one to work on the source sequence, and the other + * one to work on the result sequence of a recursive child expansions; these two sequences + * need not be implemented in the same way, which simplifies the definition of algorithms. * @tparam SRC the wrapped source iterator, typically a TreeExplorer or nested decorator. * @tparam FUN the concrete type of the functor passed. Will be dissected to find the signature */ @@ -653,6 +649,7 @@ namespace lib { { } + /** core operation: expand current head element */ void expandChildren() @@ -1189,6 +1186,7 @@ namespace lib { using Parent = WrappedLumieraIter; using Val = typename SRC::value_type; ///////////////////////////////////TICKET #1125 : get rid of Val + ~PackagedTreeExplorerSource() { } public: using Parent::Parent; diff --git a/tests/library/iter-tree-explorer-test.cpp b/tests/library/iter-tree-explorer-test.cpp index 08c3f799b..418a33f2f 100644 --- a/tests/library/iter-tree-explorer-test.cpp +++ b/tests/library/iter-tree-explorer-test.cpp @@ -428,8 +428,6 @@ namespace test{ .expand([](CountDown core){ return NumberSequence{ core.yield() - 1}; }) // expand-functor: StateCore -> Iter ); -#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1118 : GDB Segfault on loading the inferior - /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1118 : Generated code works just fine and passes Test though verify_treeExpandingIterator( treeExplore(CountDown{5}) .expand([](auto & it){ return CountDown{ *it - 1}; }) // generic Lambda: Iter& -> StateCore @@ -439,7 +437,6 @@ namespace test{ treeExplore(CountDown{5}) .expand([](auto it){ return decltype(it){ *it - 1}; }) // generic Lambda: Iter -> Iter ); -#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1118 : GDB Segfault on loading the inferior } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 2aff97a52..3ad762420 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -30795,8 +30795,8 @@
- - + + @@ -30834,8 +30834,8 @@ - - + + @@ -31040,8 +31040,8 @@ - - + + @@ -31079,9 +31079,9 @@ - - - + + + @@ -31091,10 +31091,10 @@ - - + + - + @@ -31133,10 +31133,10 @@ - - - - + + + + @@ -31211,8 +31211,8 @@ - - + + @@ -31230,7 +31230,8 @@ - + + @@ -31282,7 +31283,8 @@ - + + @@ -31319,8 +31321,9 @@ - + + @@ -31386,7 +31389,7 @@ - + @@ -31504,8 +31507,11 @@ - - + + + + + @@ -31564,8 +31570,8 @@ - - + + @@ -32095,8 +32101,8 @@ - - + + From 5b84e0e823843ecaf8795077717ccb7815007ad0 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 9 Sep 2018 22:12:55 +0200 Subject: [PATCH 078/178] TreeExplorer: remove spurous function template argument In all practical use cases, this argument gets the same type as the SRC template argument of _FunTraits --- src/lib/iter-tree-explorer.hpp | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index bd3007278..650e161f1 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -546,11 +546,10 @@ namespace lib { /** builder to create a nested/wrapping functor, suitably adapting the arguments */ - template static auto adaptFunctor (FUN&& rawFunctor) { - return function {ArgAdapter::wrap (forward (rawFunctor))}; + return function {ArgAdapter::wrap (forward (rawFunctor))}; } }; @@ -642,9 +641,9 @@ namespace lib { template Expander (SRC&& parentExplorer, FUN&& expandFunctor) - : SRC{move (parentExplorer)} // NOTE: slicing move to strip TreeExplorer (Builder) - , expandRoot_ {_FunTraits ::template adaptFunctor (forward (expandFunctor))} // adapt to accept SRC& - , expandChild_{_FunTraits::template adaptFunctor (forward (expandFunctor))} // adapt to accept RES& + : SRC{move (parentExplorer)} // NOTE: slicing move to strip TreeExplorer (Builder) + , expandRoot_ {_FunTraits ::adaptFunctor (forward (expandFunctor))} // adapt to accept SRC& + , expandChild_{_FunTraits::adaptFunctor (forward (expandFunctor))} // adapt to accept RES& , expansions_{} { } @@ -828,7 +827,7 @@ namespace lib { template Transformer (SRC&& dataSrc, FUN&& transformFunctor) : SRC{move (dataSrc)} // NOTE: slicing move to strip TreeExplorer (Builder) - , trafo_{_FunTraits::template adaptFunctor (forward (transformFunctor))} + , trafo_{_FunTraits::adaptFunctor (forward (transformFunctor))} { } @@ -909,7 +908,7 @@ namespace lib { template Filter (SRC&& dataSrc, FUN&& filterFun) : SRC{move (dataSrc)} - , predicate_{_FunTraits::template adaptFunctor (forward (filterFun))} + , predicate_{_FunTraits::adaptFunctor (forward (filterFun))} { pullFilter(); // initially pull to establish the invariant } @@ -1133,16 +1132,13 @@ namespace lib { { static_assert_isPredicate(); - using WrappedPredicate = typename _Filter::FilterPredicate; - - WrappedPredicate& firstClause = _Filter::predicate_; // pick up the existing filter predicate - WrappedPredicate chainClause{_FunTraits::template adaptFunctor (forward (additionalClause))}; - // wrap the extension predicate in a similar way if (_Filter::isDisabled()) _Filter::predicate_ = ACCEPT_ALL; - - _Filter::predicate_ = buildCombinedClause (firstClause, chainClause); - _Filter::pullFilter(); // pull to re-establish the Invariant + + _Filter::predicate_ = buildCombinedClause (_Filter::predicate_ // pick up the existing filter predicate + ,_FunTraits::adaptFunctor (forward (additionalClause)) + ); // wrap the extension predicate in a similar way + _Filter::pullFilter(); // then pull to re-establish the Invariant } }; From 0eec4d3b5f5d57c5e8057fae1c8fe9dac9935cfe Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 9 Sep 2018 23:44:32 +0200 Subject: [PATCH 079/178] ChainSearch: now use the improved TreeExplorer capabilities to address the shortcoming ...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) --- src/lib/iter-chain-search.hpp | 40 +++++++++++++++++------- wiki/thinkPad.ichthyo.mm | 59 +++++++++++++++++++++++------------ 2 files changed, 68 insertions(+), 31 deletions(-) diff --git a/src/lib/iter-chain-search.hpp b/src/lib/iter-chain-search.hpp index e839d37cf..beeb69469 100644 --- a/src/lib/iter-chain-search.hpp +++ b/src/lib/iter-chain-search.hpp @@ -21,13 +21,17 @@ */ -/** @file iter-cursor.hpp - ** An iterator with the ability to switch direction. - ** This wrapper relies on the ability of typical STL container iterators - ** to work in both directions, similar to std::reverse_iterator. - ** Yet it is a single, self-contained element and in compliance to the - ** ["Lumiera Forward Iterator"](iter-adapter.hpp) concept. But it has - ** the additional ability to [switch the working direction](\ref IterCursor::switchDir). +/** @file iter-chain-search.hpp + ** Evaluation mechanism to apply a sequence of conditions onto a linear search. + ** This search algorithm is implemented on top of a tree expanding (monadic) filter pipeline, + ** to allow for backtracking. The intention is not to combine the individual conditions, but + ** rather to apply them one by one. After finding a match for the first condition, we'll search + ** for the next condition _starting at the position of the previous match_. In the most general + ** case, this immediate progression down the search chain might be too greedy; it could be that + ** we don't find a match for the next condition, but if we backtrack and first search further + ** on the previous condition, continuing with the search from that further position might + ** then lead to a match. Basically we have to try all combinations of all possible local + ** matches, to find a solution to satisfy the whole chain of conditions. ** ** @see IterCursor_test ** @see iter-adapter.hpp @@ -130,8 +134,12 @@ namespace iter { using Filter = typename _Trait::Filter; using Step = typename _Trait::StepFunctor; - std::vector stepChain_; + /** @internal access embedded filter sub-Pipeline */ + Filter& filter() { return *this; } + /** Storage for a sequence of filter configuration functors */ + std::vector stepChain_; + public: /** Build a chain-search mechanism based on the given source data sequence. * @remark iterators will be copied or moved as appropriate, while from a STL compliant @@ -144,7 +152,10 @@ namespace iter { IterChainSearch (SEQ&& srcData) : _Base{_Trait::configurePipeline (forward (srcData) ,[this](Filter const& curr){ return configureFilterChain(curr); })} - { } + , stepChain_{} + { // mark initial pristine state + _Base::disableFilter(); + } // inherited default ctor and standard copy operations using _Base::_Base; @@ -170,8 +181,14 @@ namespace iter { IterChainSearch&& > search (FUN&& configureSearchStep) { - stepChain_.emplace_back (Step{forward (configureSearchStep)}); - this->iterNext(); // establish invariant: expand to leaf and forward to first match + Step nextStep{forward (configureSearchStep)}; + + if (_Base::isDisabled()) + this-> filter() = move (nextStep (*this)); // immediately apply first step + else // + stepChain_.emplace_back (move (nextStep)); // append all further steps into the chain... + // then establish invariant: + this->iterNext(); // expand to leaf and forward to first match return move(*this); } @@ -200,6 +217,7 @@ namespace iter { { //////////////////////////////////////////////////////TODO logically broken. We need also to get rid of the current expansions, while retaining the current position stepChain_.clear(); + _Base::disableFilter(); return move(*this); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 3ad762420..886e792a8 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -30834,8 +30834,8 @@ - - + + @@ -31135,7 +31135,7 @@ - + @@ -31201,7 +31201,7 @@ - + @@ -31211,7 +31211,7 @@ - + @@ -31327,13 +31327,13 @@ - + - + @@ -31373,7 +31373,7 @@ - + @@ -31507,7 +31507,7 @@ - + @@ -31570,8 +31570,8 @@ - - + + @@ -32093,26 +32093,45 @@ - + - - - + + + - + - + - + + + + + + + + + + + + + + + + + - - + + + + + From 2ca3e95e9e6595b50f1fcdd3449c7b97292f13fb Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Tue, 11 Sep 2018 04:04:32 +0200 Subject: [PATCH 080/178] ChainSearch: allow for overconstrained search ...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 --- src/lib/iter-chain-search.hpp | 19 +++++++++++-------- src/lib/iter-tree-explorer.hpp | 3 +++ wiki/thinkPad.ichthyo.mm | 17 ++++++++++------- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/lib/iter-chain-search.hpp b/src/lib/iter-chain-search.hpp index beeb69469..c662fb926 100644 --- a/src/lib/iter-chain-search.hpp +++ b/src/lib/iter-chain-search.hpp @@ -181,14 +181,17 @@ namespace iter { IterChainSearch&& > search (FUN&& configureSearchStep) { - Step nextStep{forward (configureSearchStep)}; - - if (_Base::isDisabled()) - this-> filter() = move (nextStep (*this)); // immediately apply first step - else // - stepChain_.emplace_back (move (nextStep)); // append all further steps into the chain... - // then establish invariant: - this->iterNext(); // expand to leaf and forward to first match + if (not this->empty()) + { + Step nextStep{forward (configureSearchStep)}; + + if (_Base::isDisabled()) + this-> filter() = move (nextStep (*this)); // immediately apply first step + else // + stepChain_.emplace_back (move (nextStep)); // append all further steps into the chain... + // then establish invariant: + this->iterNext(); // expand to leaf and forward to first match + } return move(*this); } diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index 650e161f1..8e73566a4 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -187,6 +187,7 @@ namespace lib { COR & _core() { return static_cast (*this); } COR const& _core() const { return static_cast (*this); } + protected: void __throw_if_empty() const { @@ -754,6 +755,7 @@ namespace lib { void iterNext() { + SRC::__throw_if_empty(); SRC::expandChildren(); } }; @@ -786,6 +788,7 @@ namespace lib { { if (shallExpand_) { + SRC::__throw_if_empty(); SRC::expandChildren(); shallExpand_ = false; } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 886e792a8..7a982f05f 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -32106,8 +32106,6 @@ - - @@ -32120,17 +32118,22 @@ + + - - + + + + + + + + - - - From 5b92f5cf749a109a9821b2def4096abbd95557ea Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Tue, 11 Sep 2018 04:25:08 +0200 Subject: [PATCH 081/178] ChainSearch: fix broken logic when configuring base layer 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 --- src/lib/iter-chain-search.hpp | 11 ++++++----- wiki/thinkPad.ichthyo.mm | 22 +++++++++++----------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/lib/iter-chain-search.hpp b/src/lib/iter-chain-search.hpp index c662fb926..1d7c19596 100644 --- a/src/lib/iter-chain-search.hpp +++ b/src/lib/iter-chain-search.hpp @@ -186,11 +186,12 @@ namespace iter { Step nextStep{forward (configureSearchStep)}; if (_Base::isDisabled()) - this-> filter() = move (nextStep (*this)); // immediately apply first step - else // - stepChain_.emplace_back (move (nextStep)); // append all further steps into the chain... - // then establish invariant: - this->iterNext(); // expand to leaf and forward to first match + this-> filter() = move (nextStep (*this)); // apply first step immediately + else + { + stepChain_.emplace_back (move (nextStep)); // append all further steps into the chain... + this->iterNext(); // then establish invariant: + } // expand to leaf and forward to first match } return move(*this); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 7a982f05f..1b0738591 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -31994,9 +31994,9 @@ - + - + @@ -32093,9 +32093,9 @@ - + - + @@ -32112,19 +32112,19 @@ - + - - + + - - + + - + - + From df7a9492b7aab8b786200c54fdbf30b1b53f5640 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 12 Sep 2018 03:52:07 +0200 Subject: [PATCH 082/178] TreeExplorer: helper function so support ChainSearch::clearFilter() 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 --- src/lib/iter-stack.hpp | 6 +++ src/lib/iter-tree-explorer.hpp | 13 ++++++ tests/library/iter-tree-explorer-test.cpp | 48 +++++++++++++++++++++++ wiki/thinkPad.ichthyo.mm | 25 ++++++++---- 4 files changed, 84 insertions(+), 8 deletions(-) diff --git a/src/lib/iter-stack.hpp b/src/lib/iter-stack.hpp index 39220f291..0e33ed15c 100644 --- a/src/lib/iter-stack.hpp +++ b/src/lib/iter-stack.hpp @@ -155,6 +155,12 @@ namespace lib { return topElement; } + void + clear() + { + this->stateCore().clear(); + } + /* == diagnostics == */ diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index 8e73566a4..519310889 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -674,6 +674,19 @@ namespace lib { return expansions_.size(); } + /** lock into the current child sequence. + * This special feature turns the current child sequence into the new root, + * thereby discarding everything else in the expansions stack, including the + * original root sequence. + */ + void + rootCurrent() + { + if (not hasChildren()) return; + static_cast (*this) = move (*expansions_); + expansions_.clear(); + } + public: /* === Iteration control API for IterableDecorator === */ diff --git a/tests/library/iter-tree-explorer-test.cpp b/tests/library/iter-tree-explorer-test.cpp index 418a33f2f..84f53459b 100644 --- a/tests/library/iter-tree-explorer-test.cpp +++ b/tests/library/iter-tree-explorer-test.cpp @@ -272,6 +272,7 @@ namespace test{ verify_wrappedIterator(); verify_expandOperation(); + verify_expand_rootCurrent(); verify_transformOperation(); verify_combinedExpandTransform(); verify_scheduledExpansion(); @@ -486,6 +487,53 @@ namespace test{ } + /** @test special feature of the Expander to lock into current child sequence. + * This feature was added to support a specific use-case in the IterChainSearch component. + * After expanding several levels deep into a tree, it allows to turn the _current child sequence_ + * into a new root sequence and discard the whole rest of the tree, including the original root sequence. + * It is implemented by moving the current child sequence down into the root sequence. We demonstrate + * this behaviour with the simple standard setup from #verify_expandOperation() + */ + void + verify_expand_rootCurrent() + { + auto tree = treeExplore(CountDown{25}) + .expand([](uint j){ return CountDown{j-1}; }); + + CHECK (materialise(tree) == "25-24-23-22-21-20-19-18-17-16-15-14-13-12-11-10-9-8-7-6-5-4-3-2-1"); + + CHECK (0 == tree.depth()); + CHECK (25 == *tree); + ++tree; + ++tree; + ++tree; + ++tree; + CHECK (21 == *tree); + tree.expandChildren(); + CHECK (1 == tree.depth()); + ++tree; + ++tree; + ++tree; + ++tree; + ++tree; + CHECK (15 == *tree); + tree.expandChildren(); + ++tree; + ++tree; + CHECK (2 == tree.depth()); + CHECK (materialise(tree) == "12-11-10-9-8-7-6-5-4-3-2-1-" // this is the level-2 child sequence + "14-13-12-11-10-9-8-7-6-5-4-3-2-1-" // ...returning to the rest of the level-1 sequence + "20-19-18-17-16-15-14-13-12-11-10-9-8-7-6-5-4-3-2-1"); // ...followed by the rest of the original root sequence + CHECK (12 == *tree); + + tree.rootCurrent(); + CHECK (12 == *tree); + CHECK (materialise(tree) == "12-11-10-9-8-7-6-5-4-3-2-1"); // note: level-2 continues unaltered, but level-1 and the original root are gone. + CHECK (0 == tree.depth()); + } + + + /** @test pipe each result through a transformation function. * The _transforming iterator_ is added as a decorator, wrapping the original iterator, * TreeEplorer or state core. As you'd expect, the given functor is required to accept diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 1b0738591..4ae9bf45f 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -31132,6 +31132,18 @@ + + + + + + + + + + + + @@ -32141,14 +32153,11 @@ - - - - - - - - + + + + + From 10f21f77f829b754e946cd4a1708ec900f4a95ec Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 12 Sep 2018 04:17:49 +0200 Subject: [PATCH 083/178] ChainSearch: resolve the problems and get basic functionality to work 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. --- src/lib/iter-chain-search.hpp | 6 ++-- tests/library/iter-chain-search-test.cpp | 15 +++++++-- wiki/thinkPad.ichthyo.mm | 39 +++++++++++++----------- 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/src/lib/iter-chain-search.hpp b/src/lib/iter-chain-search.hpp index 1d7c19596..b1eec1949 100644 --- a/src/lib/iter-chain-search.hpp +++ b/src/lib/iter-chain-search.hpp @@ -213,14 +213,14 @@ namespace iter { /** drop all search condition frames. * @remark the filter chain becomes empty, - * passing through the unaltered - * source sequence + * passing through the reset of the + * source sequence unaltered */ IterChainSearch&& clearFilter() { - //////////////////////////////////////////////////////TODO logically broken. We need also to get rid of the current expansions, while retaining the current position stepChain_.clear(); + _Base::rootCurrent(); _Base::disableFilter(); return move(*this); } diff --git a/tests/library/iter-chain-search-test.cpp b/tests/library/iter-chain-search-test.cpp index 98ede36de..e1688776f 100644 --- a/tests/library/iter-chain-search-test.cpp +++ b/tests/library/iter-chain-search-test.cpp @@ -132,7 +132,12 @@ namespace test{ - /** @test verify the + /** @test verify consecutive application of several functors on the underlying filter. + * In the general case, each step in the chain is a function working on a copy of the + * current filter state. Since each such step configures its own copy of the complete + * pipeline, it may reconfigure this filter pipeline in arbitrary ways. After exhausting + * the last layer, the evaluation returns to the previous layer, but immediately re-applies + * the configuration step on the then next element. */ void chainedIteration () @@ -141,7 +146,13 @@ namespace test{ - /** @test verify the + /** @test verify a complex search with backtracking. + * This becomes relevant when a given search condition can be "too greedy" for the + * complete chain to succeed. Most notably this is the case when the search is fundamentally + * reconfigured in some steps, e.g. by switching the search orientation. To demonstrate this, + * we use a "gear switching" iterator, which allows us to reverse the direction and to search + * backwards from the current position. We configure the second condition in the chain such + * that it can not succeed when starting from the first match on the first condition */ void backtracking () diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 4ae9bf45f..ea5dc9102 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -31132,7 +31132,7 @@ - + @@ -31686,8 +31686,8 @@ - - + + @@ -32001,12 +32001,12 @@ - - - + + + - - + + @@ -32019,8 +32019,8 @@ - - + + @@ -32030,7 +32030,7 @@ - + @@ -32045,7 +32045,10 @@ - + + + + @@ -32105,7 +32108,7 @@ - + @@ -32140,15 +32143,15 @@ - + - - + + @@ -32200,8 +32203,8 @@ - - + + From 29d2c151b3e6137d93faca2d8fff53c73d05b689 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 13 Sep 2018 15:48:51 +0200 Subject: [PATCH 084/178] ChainSearch: add builder function just to replace the filter 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.... :-) --- src/lib/iter-chain-search.hpp | 33 +++++++++++----- tests/library/iter-chain-search-test.cpp | 17 ++++++-- wiki/thinkPad.ichthyo.mm | 50 ++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 12 deletions(-) diff --git a/src/lib/iter-chain-search.hpp b/src/lib/iter-chain-search.hpp index b1eec1949..3de00ddea 100644 --- a/src/lib/iter-chain-search.hpp +++ b/src/lib/iter-chain-search.hpp @@ -177,9 +177,8 @@ namespace iter { * it is _possible_ to build a step which _extends_ or sharpens the preceding condition. */ template - disable_if, - IterChainSearch&& > - search (FUN&& configureSearchStep) + IterChainSearch&& + addStep (FUN&& configureSearchStep) { if (not this->empty()) { @@ -196,18 +195,34 @@ namespace iter { return move(*this); } + /** attach additional search with the given filter predicate. + * After successfully searching for all the conditions currently in the filter chain, + * the embedded iterator will finally be pulled until matching the given target value. + * @remarks adds a new layer on the stack of search conditions with a _copy_ of the + * previously used iterator, and installs the given filter predicate therein. + */ + template + disable_if, + IterChainSearch&& > + search (FUN&& filterPredicate) + { + addStep ([predicate{forward (filterPredicate)}] + (Filter filter) // note: filter taken by value + { + filter.setNewFilter (predicate); + return filter; // return copy of the original state with changed filter + }); + return move(*this); + } + /** attach additional direct search for a given value. * After successfully searching for all the conditions currently in the filter chain, - * the underlying iterator will finally be pulled until matching the given target value. + * the embedded iterator will finally be pulled until matching the given target value. */ IterChainSearch&& search (Value target) { - search ([=](Filter filter) // note: filter taken by value - { - filter.setNewFilter ([target](Value const& currVal) { return currVal == target; }); - return filter; // return copy of the original state with changed filter - }); + search ([target](Value const& currVal) { return currVal == target; }); return move(*this); } diff --git a/tests/library/iter-chain-search-test.cpp b/tests/library/iter-chain-search-test.cpp index e1688776f..a62b165b6 100644 --- a/tests/library/iter-chain-search-test.cpp +++ b/tests/library/iter-chain-search-test.cpp @@ -45,6 +45,7 @@ namespace test{ using ::Test; using util::join; using util::isnil; + using util::startsWith; using util::isSameObject; using std::vector; using std::string; @@ -76,6 +77,14 @@ namespace test{ cout << "typeof( " << STRINGIFY(_TY_) << " )= " << lib::meta::typeStr<_TY_>() < + + + + + + +

+ ...d.h. direkt das Prädikat, und nicht eine Funktion, die den Filter konfiguriert +

+ + +
+ + + + + + + + + + + + + + + + + + + + + + +

+ ...habe ich noch gar nicht gemerkt, daß das geht, +

+

+ und manchen komischen Workaround implementiert. +

+ + +
+
+ + + +
+
+
From 8aae789b82237f5df3ec1c4eba7278740b2bf51e Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 13 Sep 2018 17:50:53 +0200 Subject: [PATCH 085/178] ChainSearch: test case to scrutinise chained filter reconfiguration ...and TADAA ... there we get an insidious bug: we capture *this by reference into the expansion functor, and then we move *this away, out from the builder into the target.... --- tests/library/iter-chain-search-test.cpp | 12 ++++++++++++ wiki/thinkPad.ichthyo.mm | 16 ++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/tests/library/iter-chain-search-test.cpp b/tests/library/iter-chain-search-test.cpp index a62b165b6..f8cca854a 100644 --- a/tests/library/iter-chain-search-test.cpp +++ b/tests/library/iter-chain-search-test.cpp @@ -152,6 +152,18 @@ namespace test{ .search([](string const& str){ return startsWith (str, "s"); }); ///////////////////////////////////////////////////TODO WIP cout << materialise (search) < + + + + + + + + + + + + @@ -32163,7 +32175,7 @@ - + @@ -32175,7 +32187,7 @@ - + From 09d923db068491f33d63864e473aadd3cd7c9ee6 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 14 Sep 2018 23:55:42 +0200 Subject: [PATCH 086/178] TreeExplorer: add the ability to inject a custom defined layer 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). --- src/lib/iter-tree-explorer.hpp | 26 +++++++++++++++++++++++++- wiki/thinkPad.ichthyo.mm | 30 ++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index 519310889..47d9a9685 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -46,7 +46,7 @@ ** calculations with exception handling but also simple data structures like lists or trees). The key point with any ** monad is the ability to _bind a function_ into the monad; this function will work on the _contained base values_ ** and produce a modified new monad instance. In the simple case of a list, "binding" a function basically means - ** to _map the function onto_ the elements in the list. + ** to _map the function onto_ the elements in the list. (actually it means the `flatMap` operation) ** ** ## Rationale ** The primary benefit of using the monad pattern is to separate the transforming operation completely from @@ -1510,6 +1510,30 @@ namespace lib { } + + /** builder function to attach a _custom extension layer._ + * Any template in compliance with the general construction scheme can be injected through the template parameter. + * - it must take a first template parameter SRC and inherit from this source iterator + * - towards layers on top, it must behave like a _state core,_ either by redefining the state core API functions, + * as defined by \ref IterStateWrapper, or by inheriting them from a lower layer. + * - it is bound to play well with the other layers; especially it needs to be aware of `expandChildren()` calls, + * which for the consumer side behave like `iterNext()` calls. If a layer needs to do something special for + * `iterNext()`, it needs to perform a similar action for `expandChildren()`. + * - it must be behave like a default-constructible, copyable value object + * @return augmented TreeExplorer, incorporating and adpting the injected layer + */ + template class LAY> + auto + processingLayer() + { + using ResCore = LAY; + using ResIter = typename _DecoratorTraits::SrcIter; + + return TreeExplorer (ResCore {move(*this)}); + } + + + /** _terminal builder_ to package the processing pipeline as IterSource. * Invoking this function moves the whole iterator compound, as assembled by the preceding * builder calls, into heap allocated memory and returns an [iterator front-end](\ref IterExploreSource). diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index dddd2f1ce..4f61fb890 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -31144,6 +31144,36 @@ + + + + + + + + + +

+ ...ist einfach und offensichtlich; +

+

+ ohnehin laufen schon ziemlich alle konkreten Layer-Builder nach einem Schema-F +

+ + +
+ +
+ + + + + + + + + +
From 767156e912639761ea7de8eb1893667e450c0516 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 16 Sep 2018 01:04:00 +0200 Subject: [PATCH 087/178] TreeExplorer: unit test coverage for injected custom layer --- tests/library/iter-tree-explorer-test.cpp | 45 +++++++++++++++++++++++ wiki/thinkPad.ichthyo.mm | 26 ++++++++----- 2 files changed, 61 insertions(+), 10 deletions(-) diff --git a/tests/library/iter-tree-explorer-test.cpp b/tests/library/iter-tree-explorer-test.cpp index 84f53459b..cb8fc8470 100644 --- a/tests/library/iter-tree-explorer-test.cpp +++ b/tests/library/iter-tree-explorer-test.cpp @@ -275,6 +275,7 @@ namespace test{ verify_expand_rootCurrent(); verify_transformOperation(); verify_combinedExpandTransform(); + verify_customProcessingLayer(); verify_scheduledExpansion(); verify_FilterIterator(); verify_FilterChanges(); @@ -720,6 +721,50 @@ namespace test{ } + /** + * demo of a custom processing layer + * interacting directly with the iteration mechanism. + * @note we can assume `SRC` is itself a Lumiera Iterator + */ + template + struct MagicTestRubbish + : public SRC + { + using SRC::SRC; + + void + iterNext() + { + ++(*this); + if (*this) + ++(*this); + } + }; + + /** @test extension point to inject a client-defined custom processing layer + * This special builder function allows to install a template, which needs to wrap + * a source iterator and expose a _state core like_ interface. We demonstrate this + * extension mechanism here by defining a processing layer which skips each other element. + */ + void + verify_customProcessingLayer() + { + CHECK (materialise( + treeExplore(CountDown{7}) + .processingLayer() + ) + == "7-5-3-1"); + + CHECK (materialise( + treeExplore(CountDown{7}) + .transform([](uint v){ return 2*v; }) + .processingLayer() + .filter([](int v){ return v % 3; }) + ) + == "14-10-2"); + } + + /** @test child expansion can be scheduled to happen on next iteration. * As such, _"child expansion"_ happens right away, thereby consuming a node diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 4f61fb890..f1510183b 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -31144,10 +31144,10 @@ - + - - + + @@ -31162,16 +31162,22 @@ - + - - + + - - + + + + + + + + + + - - From 05e6e7f316348644cf2923065b644414b2dbc798 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 15 Sep 2018 03:07:33 +0200 Subject: [PATCH 088/178] ChainSearch: remould construction to get the logic correct 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. --- src/lib/iter-chain-search.hpp | 64 ++++----- src/lib/iter-tree-explorer.hpp | 22 +++- tests/library/iter-chain-search-test.cpp | 1 - wiki/thinkPad.ichthyo.mm | 160 ++++++++++++++++++++++- 4 files changed, 202 insertions(+), 45 deletions(-) diff --git a/src/lib/iter-chain-search.hpp b/src/lib/iter-chain-search.hpp index 3de00ddea..0c58798ce 100644 --- a/src/lib/iter-chain-search.hpp +++ b/src/lib/iter-chain-search.hpp @@ -75,13 +75,12 @@ namespace iter { .mutableFilter(); } - template + template auto - buildExplorer (SRC&& dataSource, FUN&& expandFunctor) + buildExplorer (SRC&& dataSource) { return buildSearchFilter (forward (dataSource)) - .expand (forward (expandFunctor)) - .expandAll(); + .expand ([](auto it){ return it; }); // child iterator starts as copy of current level iterator } /** @@ -97,16 +96,9 @@ namespace iter { struct _IterChainSetup { using Filter = decltype( buildSearchFilter(std::declval()).asIterator() ); - using StepFunctor = std::function; - - using Pipeline = decltype( buildExplorer (std::declval(), std::declval()) ); - - static Pipeline - configurePipeline (SRC&& dataSource, StepFunctor step) - { - return buildExplorer(forward (dataSource), move (step)); - } + using Pipeline = decltype( buildExplorer (std::declval()) ); + using StepFunctor = std::function; }; }//(End)type construction helpers @@ -134,9 +126,6 @@ namespace iter { using Filter = typename _Trait::Filter; using Step = typename _Trait::StepFunctor; - /** @internal access embedded filter sub-Pipeline */ - Filter& filter() { return *this; } - /** Storage for a sequence of filter configuration functors */ std::vector stepChain_; @@ -150,9 +139,7 @@ namespace iter { template explicit IterChainSearch (SEQ&& srcData) - : _Base{_Trait::configurePipeline (forward (srcData) - ,[this](Filter const& curr){ return configureFilterChain(curr); })} - , stepChain_{} + : _Base{buildExplorer (forward (srcData))} { // mark initial pristine state _Base::disableFilter(); } @@ -161,13 +148,28 @@ namespace iter { using _Base::_Base; + /* === adapted iteration control API === */ + void + iterNext() + { + _Base::__throw_if_empty(); + while (_Base::depth() < stepChain_.size() // Backtracking loop: attempt to establish all conditions + and _Base::checkPoint()) // possibly trying further combinations until success: + { + _Base::expandChildren(); // create copy of current filter embedded into child level + stepChain_[_Base::depth()] (_Base::accessCurrentChildIter()); // invoke step functor to reconfigure this filter... + _Base::dropExhaustedChildren(); // which thereby might become empty + } + } + + /** configure additional chained search condition. - * @param a functor `Filter const& -> filter`, which takes a current filter configuration, - * returning a copy from this configuration, possibly configured differently. + * @param a manipulation functor `void(Filter&)`, which works on the current filter + * to possibly change its configuration. * @note the given functor, lambda or function reference will be wrapped and adapted * to conform to the required function signature. When using a generic lambda, - * the argument type `Filter const&` is assumed + * the argument type `Filter&` is assumed * @remarks the additional chained search condition given here will be applied _after_ * matching all other conditions already in the filter chain. Each such condition * is used to _filter_ the underlying source iterator, i.e. pull it until finding @@ -185,7 +187,7 @@ namespace iter { Step nextStep{forward (configureSearchStep)}; if (_Base::isDisabled()) - this-> filter() = move (nextStep (*this)); // apply first step immediately + nextStep (*this); // apply first step immediately else { stepChain_.emplace_back (move (nextStep)); // append all further steps into the chain... @@ -207,10 +209,9 @@ namespace iter { search (FUN&& filterPredicate) { addStep ([predicate{forward (filterPredicate)}] - (Filter filter) // note: filter taken by value - { + (Filter& filter) + { // manipulte current filter configuration filter.setNewFilter (predicate); - return filter; // return copy of the original state with changed filter }); return move(*this); } @@ -239,17 +240,6 @@ namespace iter { _Base::disableFilter(); return move(*this); } - - private: - Filter - configureFilterChain (Filter const& currentFilterState) - { - uint depth = this->depth(); - if (depth < stepChain_.size()) - return stepChain_[depth](currentFilterState); // augmented copy - else - return Filter{}; // empty filter indicates recursion end - } }; diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index 47d9a9685..ebba4bbc4 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -722,12 +722,6 @@ namespace lib { or *expansions_; } - bool - hasChildren() const - { - return 0 < depth(); - } - void incrementCurrent() { @@ -737,12 +731,28 @@ namespace lib { ++(*this); } + + protected: + /** @internal accessor for downstream layers to allow close collaboration */ + ResIter& + accessCurrentChildIter() + { + REQUIRE (hasChildren()); + return *expansions_; + } + void dropExhaustedChildren() { while (not invariant()) ++expansions_; } + + bool + hasChildren() const + { + return 0 < depth(); + } }; diff --git a/tests/library/iter-chain-search-test.cpp b/tests/library/iter-chain-search-test.cpp index f8cca854a..bfd03e752 100644 --- a/tests/library/iter-chain-search-test.cpp +++ b/tests/library/iter-chain-search-test.cpp @@ -160,7 +160,6 @@ namespace test{ string currVal = *filter; filter.setNewFilter ([=](string const& val){ return val != currVal; }); - return filter; }); ///////////////////////////////////////////////////TODO WIP cout << materialise (search) < - + + + + + + + + + + + + + + + + + + + + + + +

+ ...nach der »reinen Lehre« +

+ +
+ + + + + + + + + + + + + + + + + + + + +

+ ...insofern wir nämlich zwingend auf den jeweilign Kind-Iterator zugreifen müssen. +

+

+ Nicht nur auf den aktuellen Wert (=dereferenzierter Iterator, d.h. die Funktion yield()) +

+ + +
+ + +
+
@@ -32262,6 +32321,105 @@
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ weil ich dann auf dem IterChainSearch unmittelbar die builder-Funktionen definieren kann +

+ + +
+
+
+
+ + + + + + +

+ konfiguriert danach direkt den Filter +

+ + +
+ + + + + + + + + + +

+ ...weil der Basis-Iterator (also der Template-Parameter SRC) +

+

+ explizit und absichtlich einen anderen Typ haben könnte, als der expandierte Kind-Iterator. +

+

+ Das ist ein wesentliches Feature dieses Expander-Designs, würde aber leider eine +

+

+ komplett generische Accessor-Funktion unmöglich machen (das Template würde in +

+

+ einem Solchen Fall insgesamt vom Compiler zurückgewiesen) +

+ + +
+ +
+
+
+
+
+
From 38a1aad897a2eed750f2ee7eb9431b78393e1c7d Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 15 Sep 2018 04:19:08 +0200 Subject: [PATCH 089/178] ChainSearch: bugfixes on reworked construction ...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? --- src/lib/iter-chain-search.hpp | 5 +- tests/library/iter-chain-search-test.cpp | 2 +- wiki/thinkPad.ichthyo.mm | 61 +++++++++++++++++++----- 3 files changed, 53 insertions(+), 15 deletions(-) diff --git a/src/lib/iter-chain-search.hpp b/src/lib/iter-chain-search.hpp index 0c58798ce..6332fa2b2 100644 --- a/src/lib/iter-chain-search.hpp +++ b/src/lib/iter-chain-search.hpp @@ -153,11 +153,12 @@ namespace iter { iterNext() { _Base::__throw_if_empty(); - while (_Base::depth() < stepChain_.size() // Backtracking loop: attempt to establish all conditions + uint depth; + while (stepChain_.size() > (depth=_Base::depth()) // Backtracking loop: attempt to establish all conditions and _Base::checkPoint()) // possibly trying further combinations until success: { _Base::expandChildren(); // create copy of current filter embedded into child level - stepChain_[_Base::depth()] (_Base::accessCurrentChildIter()); // invoke step functor to reconfigure this filter... + stepChain_[depth] (_Base::accessCurrentChildIter()); // invoke step functor to reconfigure this filter... _Base::dropExhaustedChildren(); // which thereby might become empty } } diff --git a/tests/library/iter-chain-search-test.cpp b/tests/library/iter-chain-search-test.cpp index bfd03e752..8d30fed75 100644 --- a/tests/library/iter-chain-search-test.cpp +++ b/tests/library/iter-chain-search-test.cpp @@ -155,7 +155,7 @@ namespace test{ ///////////////////////////////////////////////////TODO WIP CHECK (search); - search.addStep([](auto filter) + search.addStep([](auto& filter) { string currVal = *filter; filter.setNewFilter ([=](string const& val){ diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index e5b65fe5a..3e5a22242 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -32333,22 +32333,22 @@ - - + + - - - - + + + + - - + + @@ -32372,8 +32372,45 @@
+ + + + + + +

+ denn: TreeExplorer == IterableDecorator< Pipeline > +

+ + +
+ + + + + + + +

+ der oberste Layer, also hier IterChainSearch +

+

+ ist selbst ein StateCore. Also brauche ich noch einen Dekocator +

+

+ Wenn der fehlt, wird der nächste darunterliegende Decorator gepullt, +

+

+ und der wickellt direkt den Expander ein +

+ + +
+
- + +
+ @@ -32385,9 +32422,9 @@ - - - + + + From 646a2e42cf9da30d6ee8ada57c3b548a30b5ba93 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 15 Sep 2018 16:41:11 +0200 Subject: [PATCH 090/178] ChainSearch: need to overload also the iterator function ...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 --- src/lib/iter-chain-search.hpp | 68 +++++++++++++++++++++++++++++------ wiki/thinkPad.ichthyo.mm | 53 ++++++++++++++++++++------- 2 files changed, 99 insertions(+), 22 deletions(-) diff --git a/src/lib/iter-chain-search.hpp b/src/lib/iter-chain-search.hpp index 6332fa2b2..4c7e96145 100644 --- a/src/lib/iter-chain-search.hpp +++ b/src/lib/iter-chain-search.hpp @@ -33,6 +33,38 @@ ** then lead to a match. Basically we have to try all combinations of all possible local ** matches, to find a solution to satisfy the whole chain of conditions. ** + ** ## Design + ** + ** The IterChainSearch component is built as a processing pipeline, based on the + ** [Tree-Explorer framework](\ref iter-tree-explorer.hpp). This yields several benefits, yet also + ** imposes some drawbacks. Without much effort, we get a extremely flexible and configurable + ** solution, with acceptable / moderate performance. The result automatically adapts to a wide + ** array of data sources; it is possible (and even intended) to attach it on top of an already + ** existing on-demand data processing pipeline. The "source" can itself be a "state core" and + ** we may install suitable filter predicates to possibly collaborate with the internals of + ** such a state core. Yet we may also confine ourselves to pure functions and value processing. + ** + ** The obvious downside of such an approach is its complexity in terms of code to understand. + ** The collaboration between several layers in a filter pipeline can be intricate, leading to + ** chains of layered similar functions calling each other. Most of these abstractions and + ** decorations will be removed by the compiler and optimiser, typically leading to long + ** compilation times and rather large size of the generated code (which in debugging mode + ** with full type information can even become excessively huge). + ** + ** Some caveats + ** - the resulting pipeline is copyable, and it is typically moved out from a builder + ** function into the target location. So beware of _lambda captures by reference._ + ** When you capture _anything_ within the pipeline itself, like some `this` pointer, + ** you'll end up with a dangling reference for sure. However, it is just fine to + ** capture extended facilities within the scope _enclosing_ the pipeline, as long + ** as this scope remains intact during the whole usage time of the pipeline. + ** - the resulting entity, an instantiation of the IterChainSearch template, is + ** not a generic TreeExplorer (builder). Be careful when using any builder + ** functions still accessible. The only functions meant to be used in + ** builder style are the IterChainSearch::search() variations. + ** - if you copy, the embedded state is copied alongside, but not + ** any external state referred by it. + ** ** @see IterCursor_test ** @see iter-adapter.hpp ** @see [usage example](event-log.hpp) @@ -48,8 +80,6 @@ #include "lib/iter-tree-explorer.hpp" #include "lib/meta/util.hpp" -//#include -//#include #include #include #include @@ -128,6 +158,13 @@ namespace iter { /** Storage for a sequence of filter configuration functors */ std::vector stepChain_; + + + bool + needsExpansion() const + { + return _Base::depth() < stepChain_.size(); + } public: /** Build a chain-search mechanism based on the given source data sequence. @@ -153,14 +190,25 @@ namespace iter { iterNext() { _Base::__throw_if_empty(); - uint depth; - while (stepChain_.size() > (depth=_Base::depth()) // Backtracking loop: attempt to establish all conditions - and _Base::checkPoint()) // possibly trying further combinations until success: - { - _Base::expandChildren(); // create copy of current filter embedded into child level - stepChain_[depth] (_Base::accessCurrentChildIter()); // invoke step functor to reconfigure this filter... - _Base::dropExhaustedChildren(); // which thereby might become empty - } + if (not needsExpansion()) + _Base::iterNext(); + else + do + { + uint depth =_Base::depth(); + _Base::expandChildren(); // create copy of current filter embedded into child level + stepChain_[depth] (_Base::accessCurrentChildIter()); // invoke step functor to reconfigure this filter... + _Base::dropExhaustedChildren(); // which thereby might become empty + } + while (needsExpansion() // Backtracking loop: attempt to establish all conditions + and _Base::checkPoint()); // possibly trying further combinations until success: + } + + IterChainSearch& + operator++() + { + iterNext(); + return *this; } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 3e5a22242..f06848cc2 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -31832,8 +31832,8 @@ - - + + @@ -32336,8 +32336,8 @@ - - + + @@ -32345,13 +32345,13 @@ - - + + - - + + @@ -32372,7 +32372,7 @@
- + @@ -32384,8 +32384,8 @@ - - + + @@ -32406,7 +32406,13 @@ - + + + + + + + @@ -32456,6 +32462,29 @@
+ + + + + + + + + + +

+ sausage-bacon-tomato-and-sausage-spam-spam-bacon-spam-tomato-and-spam-spam-bacon-tomato-and-spam-bacon-tomato-and-spam-tomato-and-spam +

+ + +
+ +
+
+ + + +
From 84c30fe802953251778bfbc53f291811ec8e7fba Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 15 Sep 2018 17:19:13 +0200 Subject: [PATCH 091/178] ChainSearch: need to gear up immediately after backtracking ...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! --- src/lib/iter-chain-search.hpp | 18 ++++++++---------- wiki/thinkPad.ichthyo.mm | 25 +++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/lib/iter-chain-search.hpp b/src/lib/iter-chain-search.hpp index 4c7e96145..6b3a13666 100644 --- a/src/lib/iter-chain-search.hpp +++ b/src/lib/iter-chain-search.hpp @@ -192,16 +192,14 @@ namespace iter { _Base::__throw_if_empty(); if (not needsExpansion()) _Base::iterNext(); - else - do - { - uint depth =_Base::depth(); - _Base::expandChildren(); // create copy of current filter embedded into child level - stepChain_[depth] (_Base::accessCurrentChildIter()); // invoke step functor to reconfigure this filter... - _Base::dropExhaustedChildren(); // which thereby might become empty - } - while (needsExpansion() // Backtracking loop: attempt to establish all conditions - and _Base::checkPoint()); // possibly trying further combinations until success: + while (needsExpansion() // Backtracking loop: attempt to establish all conditions + and _Base::checkPoint()) // possibly trying further combinations until success: + { + uint depth =_Base::depth(); + _Base::expandChildren(); // create copy of current filter embedded into child level + stepChain_[depth] (_Base::accessCurrentChildIter()); // invoke step functor to reconfigure this filter... + _Base::dropExhaustedChildren(); // which thereby might become empty + } } IterChainSearch& diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index f06848cc2..0d4bdb3ab 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -32466,7 +32466,7 @@ - + @@ -32478,7 +32478,28 @@ - + + + + + + + + + + + + + + + +

+ sausage-bacon-tomato-and-spam-spam-bacon-spam-tomato-and-spam-bacon-tomato-and-bacon-tomato-and-tomato-and +

+ + +
+
From 84399aa407992a9c98c333eca95b1313deb38fce Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 15 Sep 2018 22:40:58 +0200 Subject: [PATCH 092/178] ChainSearch: verify proper interplay of two dynamic search conditions --- tests/library/iter-chain-search-test.cpp | 50 ++++++++++++++---------- wiki/thinkPad.ichthyo.mm | 10 ++--- 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/tests/library/iter-chain-search-test.cpp b/tests/library/iter-chain-search-test.cpp index 8d30fed75..a42cb8d5a 100644 --- a/tests/library/iter-chain-search-test.cpp +++ b/tests/library/iter-chain-search-test.cpp @@ -68,7 +68,15 @@ namespace test{ ,"spam" }; - } // (END)fixture + + /** Diagnostic helper: join all the elements from a _copy_ of the iterator */ + template + inline string + materialise (II&& ii) + { + return util::join (std::forward (ii), "-"); + } + }// (END)fixture @@ -77,14 +85,6 @@ namespace test{ cout << "typeof( " << STRINGIFY(_TY_) << " )= " << lib::meta::typeStr<_TY_>() < - + @@ -32345,12 +32345,12 @@ - + - + @@ -32464,8 +32464,8 @@ - - + + From 1683439b320d67eb2ac6707aaaa1c6ba18fc9ecc Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 16 Sep 2018 00:26:46 +0200 Subject: [PATCH 093/178] ChainSearch: backtracking verified -- finished --- src/lib/iter-chain-search.hpp | 41 ++++----- src/lib/iter-tree-explorer.hpp | 2 +- tests/library/iter-chain-search-test.cpp | 49 ++++++++--- wiki/thinkPad.ichthyo.mm | 104 ++++++++++++----------- 4 files changed, 117 insertions(+), 79 deletions(-) diff --git a/src/lib/iter-chain-search.hpp b/src/lib/iter-chain-search.hpp index 6b3a13666..d8913318c 100644 --- a/src/lib/iter-chain-search.hpp +++ b/src/lib/iter-chain-search.hpp @@ -24,7 +24,7 @@ /** @file iter-chain-search.hpp ** Evaluation mechanism to apply a sequence of conditions onto a linear search. ** This search algorithm is implemented on top of a tree expanding (monadic) filter pipeline, - ** to allow for backtracking. The intention is not to combine the individual conditions, but + ** to allow for backtracking. The intention is not just to combine the individual conditions, but ** rather to apply them one by one. After finding a match for the first condition, we'll search ** for the next condition _starting at the position of the previous match_. In the most general ** case, this immediate progression down the search chain might be too greedy; it could be that @@ -36,10 +36,10 @@ ** ## Design ** ** The IterChainSearch component is built as a processing pipeline, based on the - ** [Tree-Explorer framework](\ref iter-tree-explorer.hpp). This yields several benefits, yet also - ** imposes some drawbacks. Without much effort, we get a extremely flexible and configurable - ** solution, with acceptable / moderate performance. The result automatically adapts to a wide - ** array of data sources; it is possible (and even intended) to attach it on top of an already + ** [Tree-Explorer framework](\ref iter-tree-explorer.hpp). This yields several benefits, but + ** imposes some drawbacks. Without much effort, we get an extremely flexible and configurable + ** solution, with acceptable / moderate performance. The result automatically adapts to a huge + ** selection of data sources; it is possible (and is even intended) to attach it on top of an ** existing on-demand data processing pipeline. The "source" can itself be a "state core" and ** we may install suitable filter predicates to possibly collaborate with the internals of ** such a state core. Yet we may also confine ourselves to pure functions and value processing. @@ -47,9 +47,9 @@ ** The obvious downside of such an approach is its complexity in terms of code to understand. ** The collaboration between several layers in a filter pipeline can be intricate, leading to ** chains of layered similar functions calling each other. Most of these abstractions and - ** decorations will be removed by the compiler and optimiser, typically leading to long + ** decorations will be resolved by the compiler and optimiser, typically leading to long ** compilation times and rather large size of the generated code (which in debugging mode - ** with full type information can even become excessively huge). + ** with full type information can even become excessively large). ** ** Some caveats ** - the resulting pipeline is copyable, and it is typically moved out from a builder @@ -63,10 +63,11 @@ ** functions still accessible. The only functions meant to be used in ** builder style are the IterChainSearch::search() variations. ** - if you copy, the embedded state is copied alongside, but not - ** any external state referred by it. + ** any further external state referred by it. ** + ** @see IterChainSearch_test ** @see IterCursor_test - ** @see iter-adapter.hpp + ** @see iter-tree-explorer.hpp ** @see [usage example](event-log.hpp) ** */ @@ -110,7 +111,7 @@ namespace iter { buildExplorer (SRC&& dataSource) { return buildSearchFilter (forward (dataSource)) - .expand ([](auto it){ return it; }); // child iterator starts as copy of current level iterator + .expand ([](auto it){ return it; }); // child iterator starts as copy of current level iterator } /** @@ -128,6 +129,7 @@ namespace iter { using Filter = decltype( buildSearchFilter(std::declval()).asIterator() ); using Pipeline = decltype( buildExplorer (std::declval()) ); + /** each step in the chain is a functor to reconfigure the underlying filter */ using StepFunctor = std::function; }; @@ -137,13 +139,13 @@ namespace iter { - /** + /*********************************************************************************************************//** * Iterator based linear search mechanism, with the ability to perform consecutive search with backtracking. * The IterChainSearch can be configured with a sequence of search goals (filter conditions), and will apply * these in succession on the underlying iterator. It will search _by linear search_ for the first hit of the * first condition, and then continue to search _from there_ matching on the second condition, and so on. * After the first combination of matches is exhausted, the search will backtrack and try to evaluate - * the next combination, leading to a tree of search solutions. + * the next combination, leading to a tree of on-demand search solutions. */ template class IterChainSearch @@ -177,7 +179,7 @@ namespace iter { explicit IterChainSearch (SEQ&& srcData) : _Base{buildExplorer (forward (srcData))} - { // mark initial pristine state + { // mark initial pristine state _Base::disableFilter(); } @@ -216,7 +218,7 @@ namespace iter { * to possibly change its configuration. * @note the given functor, lambda or function reference will be wrapped and adapted * to conform to the required function signature. When using a generic lambda, - * the argument type `Filter&` is assumed + * the argument type `Filter&` is used for instantiation * @remarks the additional chained search condition given here will be applied _after_ * matching all other conditions already in the filter chain. Each such condition * is used to _filter_ the underlying source iterator, i.e. pull it until finding @@ -235,7 +237,7 @@ namespace iter { if (_Base::isDisabled()) nextStep (*this); // apply first step immediately - else + else // (implicitly enables the base filter) { stepChain_.emplace_back (move (nextStep)); // append all further steps into the chain... this->iterNext(); // then establish invariant: @@ -295,14 +297,15 @@ namespace iter { /* ==== convenient builder free function ==== */ /** setup a chain search configuration by suitably wrapping the given container. - * @return a TreeEplorer, which is an Iterator to yield all the source elements, - * but may also be used to build an processing pipeline. + * @return an IterChainSearch instantiation, which is an Iterator to yield all the + * source elements, but can be outfitted with a sequence of filter conditions, + * to be applied to the underlying source one by one. + * @param srcData either a »Lumiera Forward Iterator«, a _reference_ to a STL + * container, or a [»State Core«](\ref lib::IterStateWrapper) object. * @warning if you capture the result of this call by an auto variable, * be sure to understand that invoking any further builder operation on * TreeExplorer will invalidate that variable (by moving it into the * augmented iterator returned from such builder call). - * @param srcData either a »Lumiera Forward Iterator«, a _reference_ to a STL - * container, or a [»State Core«](\ref lib::IterStateWrapper) object. */ template inline auto diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index ebba4bbc4..fe896534e 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -719,7 +719,7 @@ namespace lib { invariant() const { return not hasChildren() - or *expansions_; + or expansions_->isValid(); } void diff --git a/tests/library/iter-chain-search-test.cpp b/tests/library/iter-chain-search-test.cpp index a42cb8d5a..e76aa14b8 100644 --- a/tests/library/iter-chain-search-test.cpp +++ b/tests/library/iter-chain-search-test.cpp @@ -28,9 +28,9 @@ #include "lib/test/run.hpp" #include "lib/test/test-helper.hpp" -#include "lib/format-util.hpp" /////////////////////////////TODO necessary? -#include "lib/format-cout.hpp" /////////////////////////////TODO necessary? +#include "lib/format-util.hpp" #include "lib/iter-chain-search.hpp" +#include "lib/iter-cursor.hpp" #include "lib/util.hpp" #include @@ -80,18 +80,22 @@ namespace test{ -///////////////////////////////////////////////////TODO WIP -#define SHOW_TYPE(_TY_) \ - cout << "typeof( " << STRINGIFY(_TY_) << " )= " << lib::meta::typeStr<_TY_>() <; + + auto search = chainSearch(Cursor{SPAM.begin(), SPAM.end()}) + .search("spam") + .addStep([](auto& filter) + { + filter.switchBackwards(); // switch search direction without moving the cursor + filter.flipFilter(); // flip from match on "spam" to match on non-spam + }) + .addStep([](auto& filter) + { // filter is still configured to search non-spam backwards + ++filter; // just "advance" this filter by one step (backward) + }); + + CHECK (materialise (search) // Layer-0: 1-3 spam fail altogether, too greedy. Try 4rd spam.... + // Layer-1: turn back, toggle to non-spam, find bacon + == "sausage-" // Layer-2: non-spam and one step further backwards yields sausage + // + // BACKTRACK to Layer-0: pick 5th (and last) spam... + // Layer-1: turn back, toggle to non-spam, find "and" + "tomato-bacon-sausage-" // Layer-2: non-spam and one step back yields tomato, next bacon, next sausage. + // BACKTRACK to Layer-1: take previous one, which is tomato + "bacon-sausage-" // Layer-2: non-spam and one step further back yields bacon, then next sausage. + // BACKTRACK to Layer-1: take previous non-spam, which is bacon + "sausage" // Layer-2: non-spam and one step further back yields sausage. + ""); // BACKTRACK to Layer-1: exhausted, BACKTRACK to Layer-0: exhausted. Done. } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 799e1047a..0dbe24d12 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -30764,7 +30764,11 @@ - + + + + + @@ -31259,7 +31263,8 @@ - + + @@ -31709,9 +31714,9 @@ - + - + @@ -31720,8 +31725,8 @@ - - + + @@ -31904,7 +31909,7 @@ - + @@ -32108,7 +32113,7 @@ - + @@ -32172,7 +32177,34 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -32213,8 +32245,8 @@ - - + + @@ -32321,9 +32353,9 @@ - + - + @@ -32462,9 +32494,9 @@ - - - + + + @@ -32502,41 +32534,15 @@ - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - + + @@ -32549,11 +32555,11 @@ - - + + - - + + From 0c7996fe90b94d2522d2391c4473418d77acb0b5 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 16 Sep 2018 03:02:22 +0200 Subject: [PATCH 094/178] EventLog: drop-in the new IterChainSerach engine - 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")) --- src/lib/test/event-log.hpp | 19 ++-- wiki/thinkPad.ichthyo.mm | 188 ++++++++++++++++++++++++++++++------- 2 files changed, 167 insertions(+), 40 deletions(-) diff --git a/src/lib/test/event-log.hpp b/src/lib/test/event-log.hpp index c4e9d9686..5e13b2019 100644 --- a/src/lib/test/event-log.hpp +++ b/src/lib/test/event-log.hpp @@ -45,7 +45,7 @@ #include "lib/error.hpp" #include "lib/idi/entry-id.hpp" -#include "lib/iter-tree-explorer.hpp" +#include "lib/iter-chain-search.hpp" #include "lib/iter-cursor.hpp" #include "lib/format-util.hpp" #include "lib/format-cout.hpp" @@ -78,8 +78,7 @@ namespace test{ buildSearchFilter(Log const& srcSeq) { using Cursor = lib::iter::CursorGear; - return treeExplore (Cursor{srcSeq.begin(), srcSeq.end()}) - .mutableFilter(); + return iter::chainSearch (Cursor{srcSeq.begin(), srcSeq.end()}); } } @@ -327,15 +326,23 @@ namespace test{ void attachNextSerchStep (COND&& filter, Direction direction) { - solution_.reverse (BACKWARD == direction); - solution_.setNewFilter (forward (filter)); + solution_.addStep ([predicate{forward (filter)}, direction] + (auto& filter) + { + filter.reverse (BACKWARD == direction); + filter.setNewFilter (predicate); + }); } template void refineSerach (COND&& additionalFilter) { - solution_.andFilter (forward (additionalFilter)); + solution_.addStep ([predicate{forward (additionalFilter)}] + (auto& filter) + { + filter.andFilter (predicate); + }); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 0dbe24d12..ef1167ee5 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -31164,8 +31164,7 @@ ohnehin laufen schon ziemlich alle konkreten Layer-Builder nach einem Schema-F

- - +
@@ -31478,8 +31477,7 @@ da sich die Iteratoren wirklich unterscheiden können dürfen

- - +
@@ -31522,8 +31520,7 @@ - - + @@ -31536,8 +31533,7 @@ und das ist das Argument, das sticht

- - +
@@ -31550,8 +31546,7 @@ ...will sagen, wenn schon eine neue Lösung, dann von A bis Z

- - +
@@ -31667,8 +31662,126 @@ + + + + + + + + + + +

+ ...denn durch das Backtracking +

+

+ würde man nun ziemlich undurchsichtige Misch-Zustände bekommen. +

+

+ Und: jeder direkt gesetzte Filter könnte die Invariante im Expander verletzen +

+

+ (weil er einen Kind-Iterator leer machen könnte, ohne daß dieser gePOPpt wird) +

+ + +
+
+
+ + + + + + + + + + + + + + + + +

+ weil wir uns bisher bei allen vorausgegangenen Bedingungen +

+

+ auf den ersten Match "festgebissen" haben, und nur über den Iterator mit der +

+

+ zuletzt gesetzen Bedingung weiter iteriert haben. +

+

+ +

+

+ +

+

+ Künftig gibt es nach jedem Fail ein Backtracking. +

+ + +
+ + + + + + + +

+ ...d.h es findet zwar ein Backtracking statt, aber wenn alle konjunktiven Klauseln gesetzt sind, +

+

+ sollte sich erneut ein FAIL ergeben +

+ + +
+
+ + +
+
+ + + + + + + + +

+ est-event-log-test.cpp:228:  verify_callLogging: (log.ensureNot("fun").after("fun").after("fun2")) +

+ + +
+ + + + + + + +

+ Verdacht: Negation +

+ + +
+
+
+ + +
+ @@ -31680,15 +31793,32 @@

- + - + + + + + + + + + + + + + + + + + + + + - - @@ -31900,8 +32030,7 @@ Nicht nur auf den aktuellen Wert (=dereferenzierter Iterator, d.h. die Funktion yield())

- - +
@@ -32312,8 +32441,7 @@ ...d.h. direkt das Prädikat, und nicht eine Funktion, die den Filter konfiguriert

- - + @@ -32343,8 +32471,7 @@ und manchen komischen Workaround implementiert.

- - +
@@ -32400,8 +32527,7 @@ weil ich dann auf dem IterChainSearch unmittelbar die builder-Funktionen definieren kann

- - +
@@ -32414,8 +32540,7 @@ denn: TreeExplorer == IterableDecorator< Pipeline >

- - + @@ -32436,8 +32561,7 @@ und der wickellt direkt den Expander ein

- -
+
@@ -32458,8 +32582,7 @@ konfiguriert danach direkt den Filter

- - + @@ -32486,8 +32609,7 @@ einem Solchen Fall insgesamt vom Compiler zurückgewiesen)

- - +
@@ -32508,8 +32630,7 @@ sausage-bacon-tomato-and-sausage-spam-spam-bacon-spam-tomato-and-spam-spam-bacon-tomato-and-spam-bacon-tomato-and-spam-tomato-and-spam

- - + @@ -32529,8 +32650,7 @@ sausage-bacon-tomato-and-spam-spam-bacon-spam-tomato-and-spam-bacon-tomato-and-bacon-tomato-and-tomato-and

- - +
From 991b8ace821f37f65cab39723badba89e62409ef Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 19 Sep 2018 00:21:09 +0200 Subject: [PATCH 095/178] EventLog: rectify the quirky logic for before / after chains 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 --- src/lib/test/event-log.hpp | 92 +++++++++++++++++++++++++++++++------ wiki/thinkPad.ichthyo.mm | 94 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+), 15 deletions(-) diff --git a/src/lib/test/event-log.hpp b/src/lib/test/event-log.hpp index 5e13b2019..0e3f36c46 100644 --- a/src/lib/test/event-log.hpp +++ b/src/lib/test/event-log.hpp @@ -319,19 +319,23 @@ namespace test{ /* === configure the underlying search engine === */ enum Direction { - FORWARD, BACKWARD + FORWARD, BACKWARD, CURRENT }; template void attachNextSerchStep (COND&& filter, Direction direction) { - solution_.addStep ([predicate{forward (filter)}, direction] - (auto& filter) - { - filter.reverse (BACKWARD == direction); - filter.setNewFilter (predicate); - }); + if (CURRENT == direction) + solution_.search (forward (filter)); + else + solution_.addStep ([predicate{forward (filter)}, direction] + (auto& filter) + { + filter.reverse (BACKWARD == direction); + ++filter; + filter.setNewFilter (predicate); + }); } template @@ -363,6 +367,60 @@ namespace test{ } + /** + * basic search function: continue linear lookup over the elements of the + * EventLog to find a match (substring match) of the given text. The search begins + * at the current position and proceeds in the currently configured direction. + * Initially the search starts at the first record and proceeds forward. + */ + EventMatch& + locate (string match) + { + attachNextSerchStep (find(match), CURRENT); + evaluateQuery ("match(\""+match+"\")"); + return *this; + } + + /** basic search like locate() but with the given regular expression */ + EventMatch& + locateMatch (string regExp) + { + attachNextSerchStep (findRegExp(regExp), CURRENT); + evaluateQuery ("find-RegExp(\""+regExp+"\")"); + return *this; + } + + /** basic search for a matching "event" + * @param match perform a substring match against the arguments of the event + * @see beforeEvent() for a description of possible "events" + */ + EventMatch& + locateEvent (string match) + { + attachNextSerchStep (findEvent(match), CURRENT); + evaluateQuery ("match-event(\""+match+"\")"); + return *this; + } + + EventMatch& + locateEvent (string classifier, string match) + { + attachNextSerchStep (findEvent(classifier,match), CURRENT); + evaluateQuery ("match-event(ID=\""+classifier+"\", \""+match+"\")"); + return *this; + } + + /** basic search for some specific function invocation + * @param match perform a substring match against the name of the function invoked + */ + EventMatch& + locateCall (string match) + { + attachNextSerchStep (findCall(match), CURRENT); + evaluateQuery ("match-call(\""+match+"\")"); + return *this; + } + /** * find a match (substring match) of the given text * in an EventLog entry after the current position @@ -860,14 +918,15 @@ namespace test{ * The resulting matcher object will qualify on any log entry * containing the given string. By adding subsequent further * query expressions on the returned [matcher object](\ref EventMatch), - * the query can be refined. Refining a query might induce backtracking. - * The final result can be retrieved by `bool` conversion + * the query can be refined. Moreover it is possible to chain up further + * search queries, which will be executing starting from the position of the + * previous match. The final result can be retrieved by `bool` conversion */ EventMatch verify (string match) const { EventMatch matcher(*log_); - matcher.before (match); // "the start of the log is before the match" + matcher.locate (match); // new matcher starts linear search from first log element return matcher; } @@ -881,7 +940,7 @@ namespace test{ verifyMatch (string regExp) const { EventMatch matcher(*log_); - matcher.beforeMatch (regExp); + matcher.locateMatch (regExp); return matcher; } @@ -896,15 +955,18 @@ namespace test{ verifyEvent (string match) const { EventMatch matcher(*log_); - matcher.beforeEvent (match); + matcher.locateEvent (match); return matcher; } + /** start a query to match for an specific kind of element + * @param classifier select kind of event by match on type or ID + */ EventMatch verifyEvent (string classifier, string match) const { EventMatch matcher(*log_); - matcher.beforeEvent (classifier, match); + matcher.locateEvent (classifier, match); return matcher; } @@ -922,7 +984,7 @@ namespace test{ verifyCall (string match) const { EventMatch matcher(*log_); - matcher.beforeCall (match); + matcher.locateCall (match); return matcher; } @@ -940,7 +1002,7 @@ namespace test{ { EventMatch matcher(*log_); matcher.look_for_match_ = false; // flip logic; fail if match succeeds - matcher.before (match); + matcher.locate (match); return matcher; } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index ef1167ee5..77dd75431 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -31775,8 +31775,64 @@
+ + + + + + + + +

+ fun after fun matcht auf den immer gleichen Record +

+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -31820,6 +31876,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 3994f805b071ab42bb13dea39460e85482847911 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 19 Sep 2018 01:24:26 +0200 Subject: [PATCH 096/178] EventLog: further bugfix to get the augmented sequencing logic correct. 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. --- src/lib/test/event-log.hpp | 3 +- wiki/thinkPad.ichthyo.mm | 58 ++++++++++++++++++++++++++++++++++---- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/lib/test/event-log.hpp b/src/lib/test/event-log.hpp index 0e3f36c46..013823ab5 100644 --- a/src/lib/test/event-log.hpp +++ b/src/lib/test/event-log.hpp @@ -333,7 +333,8 @@ namespace test{ (auto& filter) { filter.reverse (BACKWARD == direction); - ++filter; + filter.disableFilter(); // deactivate any filtering temporarily + ++filter; // move one step in the required direction filter.setNewFilter (predicate); }); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 77dd75431..6f4704ff0 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -31762,7 +31762,7 @@ - + @@ -31774,6 +31774,7 @@ + @@ -31792,7 +31793,8 @@ - + + @@ -31808,13 +31810,59 @@ + + + + + + + + + + + + + - + + + + + + + + +

+ und zwar mit abgeschaltetem Filter +

+ + +
+ + + + + +

+ für diesen einen Schritt +

+

+ muß die Filter-Funktion vorübergehend deaktiviert werden, +

+

+ damit wir exakt das nächste / vorhergehende Element bekommen +

+ + +
+ +
- + + @@ -31828,13 +31876,13 @@ -
+
From 03a1d5819834b933db45668df5d770a456ca0a23 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 19 Sep 2018 02:29:02 +0200 Subject: [PATCH 097/178] EventLog: verify and complete the TestEventLog_test can now cover all the cases as initially intended, including backtracking --- src/lib/test/event-log.hpp | 6 +++ tests/00support.tests | 6 +++ tests/library/test/test-event-log-test.cpp | 16 +++++--- wiki/thinkPad.ichthyo.mm | 43 +++++++++++++++++----- 4 files changed, 55 insertions(+), 16 deletions(-) diff --git a/src/lib/test/event-log.hpp b/src/lib/test/event-log.hpp index 013823ab5..90897c7f0 100644 --- a/src/lib/test/event-log.hpp +++ b/src/lib/test/event-log.hpp @@ -32,6 +32,12 @@ ** ** Failure of match prints a detailed trace message to `STDERR`, in order ** to deliver a precise indication what part of the condition failed. + ** @note this sequence prints the matches succeeding _at the point_ where each + ** condition is added to the chain. Adding more conditions, especially when + ** combined with changed search direction, might lead to backtracking, which + ** happens silently within the search engine, without printing any further + ** diagnostics. This means: the sequence of matches you see in this diagnostic + ** output is not necessarily the last match patch, which lead to the final failure ** ** @see TestEventLog_test ** @see [usage example](\ref AbstractTangible_test) diff --git a/tests/00support.tests b/tests/00support.tests index 6d11e9424..68857d8d1 100644 --- a/tests/00support.tests +++ b/tests/00support.tests @@ -34,6 +34,12 @@ err-lit: ..before match("beans") @ Rec(EventLogHeader| this = baked beans ) err-lit: ..before match("spam") @ Rec(event|{spam}) err-lit: ..after HEAD baked beans err-lit: __Log_condition_violated__ +err-lit: FAILED to match("spam") +err-lit: ..before match("spam") @ Rec(event|{spam}) +err-lit: ..before match("ham") @ Rec(event|{ham}) +err-lit: ..before match("beans") @ Rec(EventLogHeader| this = baked beans ) +err-lit: ..after HEAD baked beans +err-lit: __Log_condition_violated__ err-lit: FOUND at least match("ham") @ Rec(event|{ham}) err-lit: ..after match("eggs") @ Rec(EventLogHeader| this = eggs ) err-lit: ..before match("spam") @ Rec(event|{spam}) diff --git a/tests/library/test/test-event-log-test.cpp b/tests/library/test/test-event-log-test.cpp index c6ad29d90..f8293bbec 100644 --- a/tests/library/test/test-event-log-test.cpp +++ b/tests/library/test/test-event-log-test.cpp @@ -110,8 +110,14 @@ namespace test{ CHECK (not log.verify("spam").after("beans").after("ham")); log.event("beans"); - CHECK (log.verify("beans").after("spam")); - } + CHECK (log.verify("beans").after("spam")); // Note: Backtracking! The first match on beans fails, + // only the match on second beans succeeds. + + // consecutive matches always move by at least one step + CHECK ( log.verify("beans").after("ham").after("spam") .after("baked")); + CHECK (not log.verify("beans").after("ham").after("spam").after("spam").after("baked")); + CHECK ( log.verify("beans").after("ham").after("spam").locate("spam").locate("spam").after("baked")); + } // ^^^^^^ locate re-applies at current pos without moving void @@ -342,14 +348,12 @@ namespace test{ "with shallots and aubergines garnished with truffle pate, brandy and with a fried egg on top and spam"); CHECK (log.verify("spam").before("(spam|").before("egg on top and spam")); + CHECK (log.verify("and spam").after("(spam|").after("spam!").before("bacon")); CHECK (log.ensureNot("and spam").after("(spam|").after("spam!").after("bacon")); - // note: each consecutive match starts with the same element, on which the previous one succeeded - CHECK (log.verify("spam").before("spam").before("spam").before("spam").before("spam").before("bacon")); - // RegExp on full String representation CHECK (log.verifyMatch("spam.+spam")); - CHECK (log.verifyMatch("spam.+spam").beforeMatch("spam(?!spam)")); + CHECK (log.verifyMatch("spam.+spam").beforeMatch("spam(?!.+spam)")); CHECK (log.verifyEvent("fatal","spam").afterMatch("(spam.*){15}")); // Cover all arguments with sequence of regular expressions diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 6f4704ff0..f63628e37 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -31695,6 +31695,23 @@ + + + + + + +

+ ...um "Hängenbleiben" auf dem gleichen Element auszuschließen. +

+

+ Vorsicht: um sauber genau einen Schritt machen zu können, müssen wir explizit vorübergehend den Filter abschalten +

+ + +
+ +
@@ -31749,7 +31766,7 @@ - + @@ -31762,6 +31779,7 @@ + @@ -31793,8 +31811,9 @@ - + + @@ -31810,8 +31829,8 @@ - - + + @@ -31819,17 +31838,21 @@ - + + + + + - - - + + + - + @@ -31862,7 +31885,7 @@ - + From 121d78e13b536b65960ba2f215b3bd4e4738c085 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 19 Sep 2018 03:27:48 +0200 Subject: [PATCH 098/178] EventLog: now able to write the condition to verify doRevealYourself (#1162) this initially (on 1.9.18) triggered this extended digression; The initial naive implementation (without backtracking) did not allow to express such a simple thing like "function XXX" not invoked (again) after "function XXX" --- tests/gui/abstract-tangible-test.cpp | 7 ++-- wiki/thinkPad.ichthyo.mm | 48 ++++++++++++++++++---------- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/tests/gui/abstract-tangible-test.cpp b/tests/gui/abstract-tangible-test.cpp index 29a42bfe0..2aa65abd2 100644 --- a/tests/gui/abstract-tangible-test.cpp +++ b/tests/gui/abstract-tangible-test.cpp @@ -535,9 +535,12 @@ namespace test { // Note the fine point: the target element /was/ already expanded // and thus there is no second "expanded" event, nor is there a // second state mark emitted into the UI-Bus... - CHECK (mock.ensureNot("expand") -// .afterCall("revealYourself") + CHECK (mock.ensureNot("expanded") + .afterCall("revealYourself") .afterEvent("expanded")); + CHECK (nexusLog.ensureNot("note") + .afterCall("mark").arg(targetID, stateMark) + .after("handling state-mark")); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index f63628e37..93511c487 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -2035,11 +2035,14 @@ - - - - - + + + + + + + + @@ -25089,7 +25092,7 @@ - + @@ -25129,7 +25132,7 @@ - + @@ -25175,7 +25178,8 @@          ^~~~~~~~~~

-
+ +
@@ -31657,12 +31661,13 @@
- + - + - + + @@ -31764,8 +31769,8 @@ - - + + @@ -31842,12 +31847,21 @@ - - + + - - + + + + + + + + + + + From 9e96effcf163944ef3d59f8d03a1632ecd833fe5 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 20 Sep 2018 01:42:31 +0200 Subject: [PATCH 099/178] EventLog: prepare for dedicated translation unit 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. --- src/lib/test/event-log.hpp | 31 ++++++++++++++++++++----------- wiki/thinkPad.ichthyo.mm | 19 ++++++++++--------- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/lib/test/event-log.hpp b/src/lib/test/event-log.hpp index 90897c7f0..19bfe0165 100644 --- a/src/lib/test/event-log.hpp +++ b/src/lib/test/event-log.hpp @@ -535,24 +535,35 @@ namespace test{ return *this; } + void + refineSerach_matchArguments (ArgSeq&& argSeq) + { + string argList(util::join(argSeq)); + refineSerach (matchArguments(move(argSeq))); + evaluateQuery ("match-arguments("+argList+")"); + } + /** refine filter to additionally require specific arguments * @remarks the refined filter works on each record qualified by the * query expression established thus far; it additionally * looks into the arguments (children list) of the log entry. - * @warning match is processed by comparision of string representation. + * @warning match is processed by comparison of string representation. */ template EventMatch& arg (ARGS const& ...args) { - ArgSeq argSeq(stringify (args...)); - string argList(util::join(argSeq)); - - refineSerach (matchArguments(move(argSeq))); - evaluateQuery ("match-arguments("+argList+")"); + refineSerach_matchArguments (stringify (args...)); return *this; } + void + refineSerach_matchArgsRegExp (RExSeq&& regExpSeq, string rendered_regExps) + { + refineSerach (matchArgsRegExp (move (regExpSeq))); + evaluateQuery ("match-args-RegExp("+rendered_regExps+")"); + } + /** refine filter to additionally cover all arguments * with a series of regular expressions. * @note For this match to succeed, any arguments of the log entry @@ -567,8 +578,8 @@ namespace test{ EventMatch& argMatch (ARGS const& ...regExps) { - refineSerach (matchArgsRegExp (stringify (regExps...))); - evaluateQuery ("match-args-RegExp("+util::join(stringify(regExps...))+")"); + refineSerach_matchArgsRegExp (stringify (regExps...), + util::join(stringify(regExps...))); return *this; } @@ -630,9 +641,7 @@ namespace test{ on (const X *const targetObj) { string targetID = idi::instanceTypeID (targetObj); - refineSerach (matchAttribute("this",targetID)); - evaluateQuery ("match-this(\""+targetID+"\")"); - return *this; + return this->on(targetID); } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 93511c487..ac9bb7bd4 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -31666,7 +31666,7 @@ - + @@ -31769,7 +31769,7 @@ - + @@ -31920,8 +31920,8 @@ - - + + @@ -31953,10 +31953,10 @@ - + - + @@ -31966,7 +31966,8 @@ - + + @@ -31975,8 +31976,8 @@ - - + + From 1df5bf5c5ecc728679cb37c807d0dc6c1937457c Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 20 Sep 2018 02:19:10 +0200 Subject: [PATCH 100/178] EventLog: split into header and dedicated translation unit (WIP) --- src/lib/test/event-log.cpp | 1038 ++++++++++++++++++++++++++++++++++++ src/lib/test/event-log.hpp | 32 +- wiki/thinkPad.ichthyo.mm | 4 +- 3 files changed, 1071 insertions(+), 3 deletions(-) create mode 100644 src/lib/test/event-log.cpp diff --git a/src/lib/test/event-log.cpp b/src/lib/test/event-log.cpp new file mode 100644 index 000000000..1cac0408e --- /dev/null +++ b/src/lib/test/event-log.cpp @@ -0,0 +1,1038 @@ +/* + EventLog - test facility to verify the occurrence of expected events + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + 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 event-log.cpp + ** Implementation details of event logging and verification for unit tests. + ** The EventLog is a helper for writing tests or for special rigging within + ** the test or debugging or mock variant of some common services. It allows + ** to log some events to memory and then to build match expressions to verify + ** those captured information. + ** + ** The machinery for matching on the occurrence of some events can be quite + ** intricate at implementation level; we use an implementation based on a + ** filtering iterator pipeline. This kind of code is template heavy and + ** can cause a significant increase of generated code size -- especially + ** on debug builds. For that reason, we prefer to call through regular + ** (non-inline) functions and package all the actual implementation + ** code into a dedicated translation unit. + ** + ** @see TestEventLog_test + ** @see IterChainSearch_test + ** @see iter-tree-explorer.hpp + ** + */ + + + +#include "lib/error.hpp" +#include "lib/test/event-log.hpp" +#include "lib/idi/entry-id.hpp" +#include "lib/iter-chain-search.hpp" +#include "lib/iter-cursor.hpp" +#include "lib/format-util.hpp" +#include "lib/format-cout.hpp" +#include "lib/diff/record.hpp" +#include "lib/symbol.hpp" +#include "lib/util.hpp" + +#include +#include +#include +#include + + + +namespace lib { +namespace test{ + namespace error = lumiera::error; + + using util::stringify; + using util::contains; + using util::isnil; + using lib::Symbol; + using std::string; + + namespace { + using Entry = lib::diff::Record; + using Log = std::vector; + + auto + buildSearchFilter(Log const& srcSeq) + { + using Cursor = lib::iter::CursorGear; + return iter::chainSearch (Cursor{srcSeq.begin(), srcSeq.end()}); + } + } + + + /** + * @internal ongoing evaluation and match within an [EventLog]. + * @remarks an EventMatch object is returned when building query expression + * to verify the occurrence of some events within the EventLog. + * This "matcher" object implements the query logic with backtracking. + * The query expressions are added as filter functors to a filtering + * iterator; when all of the log's contents are filtered away, + * the evaluation counts as failed. + */ + class EventMatch + { + using Filter = decltype( buildSearchFilter (std::declval()) ); + + using ArgSeq = lib::diff::RecordSetup::Storage; + using RExSeq = std::vector; + + + /** match predicate evaluator */ + Filter solution_; + + /** record last match for diagnostics */ + string lastMatch_; + + /** support for positive and negative queries. */ + bool look_for_match_; + + /** record when the underlying query has failed */ + string violation_; + + /** core of the evaluation machinery: + * apply a filter predicate and then pull + * through the log to find a acceptable entry + */ + bool + foundSolution() + { + return not isnil (solution_); + } + + /** this is actually called after each refinement of + * the filter and matching conditions. The effect is to search + * for an (intermediary) solution right away and to mark failure + * as soon as some condition can not be satisfied. Rationale is to + * indicate the point where a chained match fails + * @see ::operator bool() for extracting the result + * @param matchSpec diagnostics description of the predicate just being added + * @param rel indication of the searching relation / direction + */ + void + evaluateQuery (string matchSpec, Literal rel = "after") + { + if (look_for_match_ and not isnil (violation_)) return; + // already failed, no further check necessary + + if (foundSolution()) // NOTE this pulls the filter + { + lastMatch_ = matchSpec+" @ "+string(*solution_) + + (isnil(lastMatch_)? "" + : "\n.."+rel+" "+lastMatch_); + if (not look_for_match_) + violation_ = "FOUND at least "+lastMatch_; + } + else + { + if (look_for_match_) + violation_ = "FAILED to "+matchSpec + + "\n.."+rel + + " "+lastMatch_; + else + violation_ = ""; + } + } + + + /** @internal for creating EventLog matchers */ + EventMatch(Log const& srcSeq) + : solution_{buildSearchFilter (srcSeq)} + , lastMatch_{"HEAD "+ solution_->get("this")} + , look_for_match_{true} + , violation_{} + { } + + friend class EventLog; + + + + /* == elementary matchers == */ + + auto + find (string match) + { + return [=](Entry const& entry) + { + return contains (string(entry), match); + }; + } + + auto + findRegExp (string regExpDef) + { + std::regex regExp(regExpDef); + return [=](Entry const& entry) + { + return std::regex_search(string(entry), regExp); + }; + } + + auto + findEvent (string match) + { + return [=](Entry const& entry) + { + return ( entry.getType() == "event" + or entry.getType() == "error" + or entry.getType() == "create" + or entry.getType() == "destroy" + or entry.getType() == "logJoin" + ) + and !isnil(entry.scope()) + and contains (*entry.scope(), match); + }; + } + + auto + findEvent (string classifier, string match) + { + return [=](Entry const& entry) + { + return ( entry.getType() == classifier + or (entry.hasAttribute("ID") and contains (entry.get("ID"), classifier)) + ) + and !isnil(entry.scope()) + and contains (*entry.scope(), match); + }; + } + + auto + findCall (string match) + { + return [=](Entry const& entry) + { + return entry.getType() == "call" + and contains (entry.get("fun"), match); + }; + } + + + /** this filter functor is for refinement of an existing filter + * @param argSeq perform a substring match consecutively + * for each of the log entry's arguments + * @note the match also fails, when the given log entry + * has more or less arguments, than the number of + * given match expressions in `argSeq` + * @see ExtensibleFilterIter::andFilter() + */ + auto + matchArguments (ArgSeq&& argSeq) + { + return [=](Entry const& entry) + { + auto scope = entry.scope(); + for (auto const& match : argSeq) + if (isnil (scope) or not contains(*scope, match)) + return false; + else + ++scope; + + return isnil(scope); // must be exhausted by now + }; // otherwise the sizes do not match... + } + + + /** refinement filter, to cover all arguments by regular expression(s) + * @param regExpSeq several regular expressions, which, when applied + * consecutively until exhaustion, must cover and verify _all_ + * arguments of the log entry. + * @remarks to explain, we "consume" arguments with a regExp from the list, + * and when this one doesn't match anymore, we try the next one. + * When we'ver tried all regular expressions, we must have also + * consumed all arguments, otherwise we fail. + */ + auto + matchArgsRegExp (RExSeq&& regExpSeq) + { + return [=](Entry const& entry) + { + auto scope = entry.scope(); + for (auto const& rex : regExpSeq) + { + if (isnil (scope)) return false; + while (scope and std::regex_search(*scope, rex)) + ++scope; + } + + return isnil(scope); // must be exhausted by now + }; // otherwise we didn't get full coverage... + } + + + /** refinement filter to match on the given typeID */ + auto + matchType (string typeID) + { + return [=](Entry const& entry) + { + return contains (entry.getType(), typeID); + }; + } + + + /** refinement filter to ensure a specific attribute is present on the log entry */ + auto + ensureAttribute (string key) + { + return [=](Entry const& entry) + { + return entry.hasAttribute(key); + }; + } + + + /** refinement filter to ensure a specific attribute is present on the log entry */ + auto + matchAttribute (string key, string valueMatch) + { + return [=](Entry const& entry) + { + return entry.hasAttribute(key) + and contains (entry.get(key), valueMatch); + }; + } + + + /* === configure the underlying search engine === */ + + enum Direction { + FORWARD, BACKWARD, CURRENT + }; + + template + void + attachNextSerchStep (COND&& filter, Direction direction) + { + if (CURRENT == direction) + solution_.search (forward (filter)); + else + solution_.addStep ([predicate{forward (filter)}, direction] + (auto& filter) + { + filter.reverse (BACKWARD == direction); + filter.disableFilter(); // deactivate any filtering temporarily + ++filter; // move one step in the required direction + filter.setNewFilter (predicate); + }); + } + + template + void + refineSerach (COND&& additionalFilter) + { + solution_.addStep ([predicate{forward (additionalFilter)}] + (auto& filter) + { + filter.andFilter (predicate); + }); + } + + + + public: + /** final evaluation of the match query, + * usually triggered from the unit test `CHECK()`. + * @note failure cause is printed to STDERR. + */ + operator bool() const + { + if (!isnil (violation_)) + { + cerr << "__Log_condition_violated__\n"+violation_ <<"\n"; + return false; + } + return true; + } + + + /** + * basic search function: continue linear lookup over the elements of the + * EventLog to find a match (substring match) of the given text. The search begins + * at the current position and proceeds in the currently configured direction. + * Initially the search starts at the first record and proceeds forward. + */ + EventMatch& + locate (string match) + { + attachNextSerchStep (find(match), CURRENT); + evaluateQuery ("match(\""+match+"\")"); + return *this; + } + + /** basic search like locate() but with the given regular expression */ + EventMatch& + locateMatch (string regExp) + { + attachNextSerchStep (findRegExp(regExp), CURRENT); + evaluateQuery ("find-RegExp(\""+regExp+"\")"); + return *this; + } + + /** basic search for a matching "event" + * @param match perform a substring match against the arguments of the event + * @see beforeEvent() for a description of possible "events" + */ + EventMatch& + locateEvent (string match) + { + attachNextSerchStep (findEvent(match), CURRENT); + evaluateQuery ("match-event(\""+match+"\")"); + return *this; + } + + EventMatch& + locateEvent (string classifier, string match) + { + attachNextSerchStep (findEvent(classifier,match), CURRENT); + evaluateQuery ("match-event(ID=\""+classifier+"\", \""+match+"\")"); + return *this; + } + + /** basic search for some specific function invocation + * @param match perform a substring match against the name of the function invoked + */ + EventMatch& + locateCall (string match) + { + attachNextSerchStep (findCall(match), CURRENT); + evaluateQuery ("match-call(\""+match+"\")"); + return *this; + } + + /** + * find a match (substring match) of the given text + * in an EventLog entry after the current position + * @remarks the name of this junctor might seem counter intuitive; + * it was chosen due to expected DSL usage: `log.verify("α").before("β")`. + * Operationally this means first to find a Record matching the substring "α" + * and then to forward from this point until hitting a record to match "β". + */ + EventMatch& + before (string match) + { + attachNextSerchStep (find(match), FORWARD); + evaluateQuery ("match(\""+match+"\")"); + return *this; + } + + /** find a match with the given regular expression */ + EventMatch& + beforeMatch (string regExp) + { + attachNextSerchStep (findRegExp(regExp), FORWARD); + evaluateQuery ("find-RegExp(\""+regExp+"\")"); + return *this; + } + + /** find a match for an "event" _after_ the current point of reference + * @remarks the term "event" designates several types of entries, which + * typically capture something happening within the observed entity. + * Especially, the following [record types](\ref lib::Record::getType()) + * qualify as event: + * - `event` + * - `error` + * - `create` + * - `destroy` + * - `logJoin` + * @param match perform a substring match against the arguments of the event + * @see ::findEvent + */ + EventMatch& + beforeEvent (string match) + { + attachNextSerchStep (findEvent(match), FORWARD); + evaluateQuery ("match-event(\""+match+"\")"); + return *this; + } + + EventMatch& + beforeEvent (string classifier, string match) + { + attachNextSerchStep (findEvent(classifier,match), FORWARD); + evaluateQuery ("match-event(ID=\""+classifier+"\", \""+match+"\")"); + return *this; + } + + /** find a match for some function invocation _after_ the current point of reference + * @param match perform a substring match against the name of the function invoked + * @see ::findCall + */ + EventMatch& + beforeCall (string match) + { + attachNextSerchStep (findCall(match), FORWARD); + evaluateQuery ("match-call(\""+match+"\")"); + return *this; + } + + EventMatch& + after (string match) + { + attachNextSerchStep (find(match), BACKWARD); + evaluateQuery ("match(\""+match+"\")", "before"); + return *this; + } + + EventMatch& + afterMatch (string regExp) + { + attachNextSerchStep (findRegExp(regExp), BACKWARD); + evaluateQuery ("find-RegExp(\""+regExp+"\")", "before"); + return *this; + } + + EventMatch& + afterEvent (string match) + { + attachNextSerchStep (findEvent(match), BACKWARD); + evaluateQuery ("match-event(\""+match+"\")", "before"); + return *this; + } + + EventMatch& + afterEvent (string classifier, string match) + { + attachNextSerchStep (findEvent(classifier,match), BACKWARD); + evaluateQuery ("match-event(ID=\""+classifier+"\", \""+match+"\")", "before"); + return *this; + } + + /** find a function invocation backwards, before the current point of reference */ + EventMatch& + afterCall (string match) + { + attachNextSerchStep (findCall(match), BACKWARD); + evaluateQuery ("match-call(\""+match+"\")", "before"); + return *this; + } + + void + refineSerach_matchArguments (ArgSeq&& argSeq) + { + string argList(util::join(argSeq)); + refineSerach (matchArguments(move(argSeq))); + evaluateQuery ("match-arguments("+argList+")"); + } + + /** refine filter to additionally require specific arguments + * @remarks the refined filter works on each record qualified by the + * query expression established thus far; it additionally + * looks into the arguments (children list) of the log entry. + * @warning match is processed by comparison of string representation. + */ + template + EventMatch& + arg (ARGS const& ...args) + { + refineSerach_matchArguments (stringify (args...)); + return *this; + } + + void + refineSerach_matchArgsRegExp (RExSeq&& regExpSeq, string rendered_regExps) + { + refineSerach (matchArgsRegExp (move (regExpSeq))); + evaluateQuery ("match-args-RegExp("+rendered_regExps+")"); + } + + /** refine filter to additionally cover all arguments + * with a series of regular expressions. + * @note For this match to succeed, any arguments of the log entry + * must be covered by applying the given regular expressions + * consecutively. Each regular expression is applied to further + * arguments, as long as it matches. It is possible to have + * just one RegExp to "rule them all", but as soon as there + * is one argument, that can not be covered by the next + * RegExp, the whole match counts as failed. + */ + template + EventMatch& + argMatch (ARGS const& ...regExps) + { + refineSerach_matchArgsRegExp (stringify (regExps...), + util::join(stringify(regExps...))); + return *this; + } + + /** refine filter to additionally require a matching log entry type */ + EventMatch& + type (string typeID) + { + refineSerach (matchType(typeID)); + evaluateQuery ("match-type("+typeID+")"); + return *this; + } + + /** refine filter to additionally require the presence an attribute */ + EventMatch& + key (string key) + { + refineSerach (ensureAttribute(key)); + evaluateQuery ("ensure-attribute("+key+")"); + return *this; + } + + /** refine filter to additionally match on a specific attribute */ + EventMatch& + attrib (string key, string valueMatch) + { + refineSerach (matchAttribute(key,valueMatch)); + evaluateQuery ("match-attribute("+key+"=\""+valueMatch+"\")"); + return *this; + } + + /** refine filter to additionally match on the ID attribute */ + EventMatch& + id (string classifier) + { + refineSerach (matchAttribute("ID",classifier)); + evaluateQuery ("match-ID(\""+classifier+"\")"); + return *this; + } + + /** refine filter to additionally match the `'this'` attribute */ + EventMatch& + on (string targetID) + { + refineSerach (matchAttribute("this",targetID)); + evaluateQuery ("match-this(\""+targetID+"\")"); + return *this; + } + + EventMatch& + on (const char* targetID) + { + refineSerach (matchAttribute("this",targetID)); + evaluateQuery ("match-this(\""+string(targetID)+"\")"); + return *this; + } + + template + EventMatch& + on (const X *const targetObj) + { + string targetID = idi::instanceTypeID (targetObj); + return this->on(targetID); + } + }; + + + + + + /****************************************************************//** + * Helper to log and verify the occurrence of events. + * The EventLog object is a front-end handle, logging flexible + * [information records](\ref lib::Record) into a possibly shared (vector) + * buffer in heap storage. An extended query DSL allows to write + * assertions to cover the occurrence of events in unit tests. + * @see TestEventLog_test + */ + class EventLog + { + + std::shared_ptr log_; + + + void + log (std::initializer_list const& ili) + { + log_->emplace_back(ili); + } + + template + void + log (Symbol typeID, ATTR&& attribs, ARGS&& args) + { + log_->emplace_back(typeID, std::forward(attribs) + , std::forward(args)); + } + + string + getID() const + { + return log_->front().get("this"); + } + + + public: + explicit + EventLog (string logID) + : log_(new Log) + { + log({"type=EventLogHeader", "this="+logID}); + } + + explicit + EventLog (const char* logID) + : EventLog(string(logID)) + { } + + template + explicit + EventLog (const X *const obj) + : EventLog(idi::instanceTypeID (obj)) + { } + + + // standard copy operations acceptable + + + /** Merge this log into another log, forming a combined log + * @param otherLog target to integrate this log's contents into. + * @return reference to the merged new log instance + * @remarks EventLog uses a heap based, sharable log storage, + * where the EventLog object is just a front-end (shared ptr). + * The `joinInto` operation both integrates this logs contents + * into the other log, and then disconnects from the old storage + * and connects to the storage of the combined log. + * @warning beware of clone copies. Since copying EventLog is always a + * shallow copy, all copied handles actually refer to the same + * log storage. If you invoke `joinInto` in such a situation, + * only the current EventLog front-end handle will be rewritten + * to point to the combined log, while any other clone will + * continue to point to the original log storage. + * @see TestEventLog_test::verify_logJoining() + */ + EventLog& + joinInto (EventLog& otherLog) + { + Log& target = *otherLog.log_; + target.reserve (target.size() + log_->size() + 1); + target.emplace_back (log_->front()); + auto p = log_->begin(); + while (++p != log_->end()) // move our log's content into the other log + target.emplace_back(std::move(*p)); + this->log_->resize(1); + this->log({"type=joined", otherLog.getID()}); // leave a tag to indicate + otherLog.log({"type=logJoin", this->getID()}); // where the `joinInto` took place, + this->log_ = otherLog.log_; // connect this to the other storage + return *this; + } + + + /** purge log contents while retaining just the original Header-ID */ + EventLog& + clear() + { + string originalLogID = this->getID(); + return this->clear (originalLogID); + } + + /** purge log contents and also reset Header-ID + * @note actually we're starting a new log + * and let the previous one go away. + * @warning while this also unties any joined logs, + * other log front-ends might still hold onto + * the existing, combined log. Just we are + * detached and writing to a pristine log. + */ + EventLog& + clear (string alteredLogID) + { + log_.reset (new Log); + log({"type=EventLogHeader", "this="+alteredLogID}); + return *this; + } + + EventLog& + clear (const char* alteredLogID) + { + return clear (string{alteredLogID}); + } + + template + EventLog& + clear (const X *const obj) + { + return clear (idi::instanceTypeID (obj)); + } + + + + /* ==== Logging API ==== */ + + using ArgSeq = lib::diff::RecordSetup::Storage; + + EventLog& + event (string text) + { + log ("event", ArgSeq{}, ArgSeq{text}); // we use this ctor variant to ensure + return *this; // that text is not misinterpreted as attribute, + } // which might happen when text contains a '=' + + /** log some event, with additional ID or classifier + * @param classifier info to be saved into the `ID` attribute + * @param text actual payload info, to be logged as argument + */ + EventLog& + event (string classifier, string text) + { + log ("event", ArgSeq{"ID="+classifier}, ArgSeq{text}); + return *this; + } + + /** Log occurrence of a function call with no arguments. + * @param target the object or scope on which the function is invoked + * @param function name of the function being invoked + */ + EventLog& + call (string target, string function) + { + return call(target, function, ArgSeq()); + } + + /** Log a function call with a sequence of stringified arguments */ + EventLog& + call (string target, string function, ArgSeq&& args) + { + log ("call", ArgSeq{"fun="+function, "this="+target}, std::forward(args)); + return *this; + } + + EventLog& + call (const char* target, const char* function, ArgSeq&& args) + { + return call (string(target), string(function), std::forward(args)); + } + + /** Log a function call with arbitrary arguments */ + template + EventLog& + call (string target, string function, ARGS const& ...args) + { + return call (target, function, stringify(args...)); + } + + /** Log a function call on given object ("`this`")... */ + template + EventLog& + call (const X *const targetObj, string function, ARGS const& ...args) + { + return call (idi::instanceTypeID (targetObj), function, args...); + } + + template + EventLog& + call (const char* target, string function, ARGS const& ...args) + { + return call (string(target), function, args...); + } + + template + EventLog& + note (ELMS const& ...initialiser) + { + log_->emplace_back(stringify (initialiser...)); + return *this; + } + + + /** Log a warning entry */ + EventLog& + warn (string text) + { + log({"type=warn", text}); + return *this; + } + + + /** Log an error note */ + EventLog& + error (string text) + { + log({"type=error", text}); + return *this; + } + + + /** Log a fatal failure */ + EventLog& + fatal (string text) + { + log({"type=fatal", text}); + return *this; + } + + + /** Log the creation of an object. + * Such an entry can be [matched as event](\ref ::verifyEvent) */ + EventLog& + create (string text) + { + log({"type=create", text}); + return *this; + } + + + /** Log the destruction of an object. + * Can be matched as event. */ + EventLog& + destroy (string text) + { + log({"type=destroy", text}); + return *this; + } + + + + /* ==== Iteration ==== */ + + bool + empty() const + { + return 1 >= log_->size(); // do not count the log header + } + + + using Iter = lib::RangeIter; + + typedef Iter const_iterator; + typedef const Entry value_type; + + const_iterator begin() const { return Iter(log_->begin(), log_->end()); } + const_iterator end() const { return Iter(); } + + friend const_iterator begin (EventLog const& log) { return log.begin(); } + friend const_iterator end (EventLog const& log) { return log.end(); } + + + + + /* ==== Query/Verification API ==== */ + + /** start a query to match for some substring. + * The resulting matcher object will qualify on any log entry + * containing the given string. By adding subsequent further + * query expressions on the returned [matcher object](\ref EventMatch), + * the query can be refined. Moreover it is possible to chain up further + * search queries, which will be executing starting from the position of the + * previous match. The final result can be retrieved by `bool` conversion + */ + EventMatch + verify (string match) const + { + EventMatch matcher(*log_); + matcher.locate (match); // new matcher starts linear search from first log element + return matcher; + } + + /** start a query to match with a regular expression + * @param regExp definition + * @remarks the expression will work against the full + * `string` representation of the log entries. + * Meaning, it can also match type and attributes + */ + EventMatch + verifyMatch (string regExp) const + { + EventMatch matcher(*log_); + matcher.locateMatch (regExp); + return matcher; + } + + /** start a query to match for some event. + * @remarks only a subset of all log entries is treated as "event", + * any other log entry will not be considered for this query. + * Besides the regular [events](\ref ::event()), also errors, + * creation and destruction of objects count as "event". + * @param match text to (substring)match against the argument logged as event + */ + EventMatch + verifyEvent (string match) const + { + EventMatch matcher(*log_); + matcher.locateEvent (match); + return matcher; + } + + /** start a query to match for an specific kind of element + * @param classifier select kind of event by match on type or ID + */ + EventMatch + verifyEvent (string classifier, string match) const + { + EventMatch matcher(*log_); + matcher.locateEvent (classifier, match); + return matcher; + } + + template + EventMatch + verifyEvent (string classifier, X const& something) const + { + return verifyEvent (classifier, util::toString (something)); + } + + /** start a query to match especially a function call + * @param match text to match against the function name + */ + EventMatch + verifyCall (string match) const + { + EventMatch matcher(*log_); + matcher.locateCall (match); + return matcher; + } + + /** start a query to ensure the given expression does _not_ match. + * @remarks the query expression is built similar to the other queries, + * but the logic of evaluation is flipped: whenever we find any match + * the overall result (when evaluating to `bool`) will be `false`. + * @warning this is not an proper exhaustive negation, since the implementation + * does not proper backtracking with a stack of choices. This becomes + * evident, when you combine `ensureNot()` with a switch in search + * direction, like e.g. using `afterXXX` at some point in the chain. + */ + EventMatch + ensureNot (string match) const + { + EventMatch matcher(*log_); + matcher.look_for_match_ = false; // flip logic; fail if match succeeds + matcher.locate (match); + return matcher; + } + + + /** equality comparison is based on the actual log contents */ + friend bool + operator== (EventLog const& l1, EventLog const& l2) + { + return l1.log_ == l2.log_ + or (l1.log_ and l2.log_ + and *l1.log_ == *l2.log_); + } + friend bool + operator!= (EventLog const& l1, EventLog const& l2) + { + return not (l1 == l2); + } + }; + + + +}} // namespace lib::test diff --git a/src/lib/test/event-log.hpp b/src/lib/test/event-log.hpp index 19bfe0165..d00e46c65 100644 --- a/src/lib/test/event-log.hpp +++ b/src/lib/test/event-log.hpp @@ -27,9 +27,39 @@ ** the [EventLog] allows to record invocations and similar events. It is ** implemented as a "PImpl" to allow sharing of logs, which helps to trace ** events from transient UI elements and from destructor code. The front-end - ** used for access offers a query facility, so the test code may express some + ** used for access offers a query DSL, so the test code may express some ** expected patterns of incidence and verify match or non-match. ** + ** # Usage + ** + ** The EventLog just captures invocations in memory, as sequence of + ** [string records](\ref lib::diff::Record). The _logging API_ offers some + ** dedicated functions to record invocations, events, the creation and destruction + ** of objects, warnings and error messages. Data passed to those logging functions + ** is rendered into string, using Lumiera's generic string conversion helpers. + ** + ** To _verify and match_ on log contents, start an evaluation as a builder (DSL) + ** expression, starting from one of the #verify functions, or starting with #ensureNot, + ** to probe that a given set of matches does not occur in the log. These entrance + ** functions set up the initial search condition, and further search conditions + ** can be chained up by invoking suitable matching functions on the generated + ** EventMatch builder. Most notably, it is possible to search for some further + ** match condition _before_ or _after_ the position where the preceding condition + ** produced a match -- allowing to specify sequences and patterns of events to + ** expect in the log + ** \code{.cpp} + ** EventLog log("baked beans"); + ** log.event("spam"); + ** log.event("ham"); + ** + ** CHECK (log.verify("ham") + ** .after("beans") + ** .before("spam") + ** .before("ham")); + ** \endcode + ** In general, this kind of search with a switch of the search direction in the middle + ** of a match requires some backtracking to try possible matches until exhaustion. + ** ** Failure of match prints a detailed trace message to `STDERR`, in order ** to deliver a precise indication what part of the condition failed. ** @note this sequence prints the matches succeeding _at the point_ where each diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index ac9bb7bd4..8bd0f1099 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -31978,8 +31978,8 @@ - - + + From 258a807e974a0b55c12a4605733fbf4a95f8d508 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 20 Sep 2018 04:11:00 +0200 Subject: [PATCH 101/178] EventLog: reorder code to accommodate the split 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. --- src/lib/test/event-log.cpp | 1700 ++++++++++++++++-------------------- src/lib/test/event-log.hpp | 698 ++------------- wiki/thinkPad.ichthyo.mm | 76 +- 3 files changed, 852 insertions(+), 1622 deletions(-) diff --git a/src/lib/test/event-log.cpp b/src/lib/test/event-log.cpp index 1cac0408e..d359f34fb 100644 --- a/src/lib/test/event-log.cpp +++ b/src/lib/test/event-log.cpp @@ -33,7 +33,7 @@ ** can cause a significant increase of generated code size -- especially ** on debug builds. For that reason, we prefer to call through regular ** (non-inline) functions and package all the actual implementation - ** code into a dedicated translation unit. + ** code into this dedicated translation unit. ** ** @see TestEventLog_test ** @see IterChainSearch_test @@ -45,19 +45,6 @@ #include "lib/error.hpp" #include "lib/test/event-log.hpp" -#include "lib/idi/entry-id.hpp" -#include "lib/iter-chain-search.hpp" -#include "lib/iter-cursor.hpp" -#include "lib/format-util.hpp" -#include "lib/format-cout.hpp" -#include "lib/diff/record.hpp" -#include "lib/symbol.hpp" -#include "lib/util.hpp" - -#include -#include -#include -#include @@ -71,967 +58,746 @@ namespace test{ using lib::Symbol; using std::string; - namespace { - using Entry = lib::diff::Record; - using Log = std::vector; + + namespace { /* == elementary matchers == */ - auto - buildSearchFilter(Log const& srcSeq) + using Filter = decltype( buildSearchFilter (std::declval()) ); + + using ArgSeq = lib::diff::RecordSetup::Storage; + using RExSeq = std::vector; + + + inline auto + find (string match) { - using Cursor = lib::iter::CursorGear; - return iter::chainSearch (Cursor{srcSeq.begin(), srcSeq.end()}); + return [=](Entry const& entry) + { + return contains (string(entry), match); + }; } + + inline auto + findRegExp (string regExpDef) + { + std::regex regExp(regExpDef); + return [=](Entry const& entry) + { + return std::regex_search(string(entry), regExp); + }; + } + + inline auto + findEvent (string match) + { + return [=](Entry const& entry) + { + return ( entry.getType() == "event" + or entry.getType() == "error" + or entry.getType() == "create" + or entry.getType() == "destroy" + or entry.getType() == "logJoin" + ) + and !isnil(entry.scope()) + and contains (*entry.scope(), match); + }; + } + + inline auto + findEvent (string classifier, string match) + { + return [=](Entry const& entry) + { + return ( entry.getType() == classifier + or (entry.hasAttribute("ID") and contains (entry.get("ID"), classifier)) + ) + and !isnil(entry.scope()) + and contains (*entry.scope(), match); + }; + } + + inline auto + findCall (string match) + { + return [=](Entry const& entry) + { + return entry.getType() == "call" + and contains (entry.get("fun"), match); + }; + } + + + /** this filter functor is for refinement of an existing filter + * @param argSeq perform a substring match consecutively + * for each of the log entry's arguments + * @note the match also fails, when the given log entry + * has more or less arguments, than the number of + * given match expressions in `argSeq` + * @see ExtensibleFilterIter::andFilter() + */ + inline auto + matchArguments (ArgSeq&& argSeq) + { + return [=](Entry const& entry) + { + auto scope = entry.scope(); + for (auto const& match : argSeq) + if (isnil (scope) or not contains(*scope, match)) + return false; + else + ++scope; + + return isnil(scope); // must be exhausted by now + }; // otherwise the sizes do not match... + } + + + /** refinement filter, to cover all arguments by regular expression(s) + * @param regExpSeq several regular expressions, which, when applied + * consecutively until exhaustion, must cover and verify _all_ + * arguments of the log entry. + * @remarks to explain, we "consume" arguments with a regExp from the list, + * and when this one doesn't match anymore, we try the next one. + * When we'ver tried all regular expressions, we must have also + * consumed all arguments, otherwise we fail. + */ + inline auto + matchArgsRegExp (RExSeq&& regExpSeq) + { + return [=](Entry const& entry) + { + auto scope = entry.scope(); + for (auto const& rex : regExpSeq) + { + if (isnil (scope)) return false; + while (scope and std::regex_search(*scope, rex)) + ++scope; + } + + return isnil(scope); // must be exhausted by now + }; // otherwise we didn't get full coverage... + } + + + /** refinement filter to match on the given typeID */ + inline auto + matchType (string typeID) + { + return [=](Entry const& entry) + { + return contains (entry.getType(), typeID); + }; + } + + + /** refinement filter to ensure a specific attribute is present on the log entry */ + inline auto + ensureAttribute (string key) + { + return [=](Entry const& entry) + { + return entry.hasAttribute(key); + }; + } + + + /** refinement filter to ensure a specific attribute is present on the log entry */ + inline auto + matchAttribute (string key, string valueMatch) + { + return [=](Entry const& entry) + { + return entry.hasAttribute(key) + and contains (entry.get(key), valueMatch); + }; + } + + + + /* === configure the underlying search engine === */ + + enum Direction { + FORWARD, BACKWARD, CURRENT + }; + + template + inline void + attachNextSerchStep (Filter& solution, COND&& filter, Direction direction) + { + if (CURRENT == direction) + solution.search (forward (filter)); + else + solution.addStep ([predicate{forward (filter)}, direction] + (auto& filter) + { + filter.reverse (BACKWARD == direction); + filter.disableFilter(); // deactivate any filtering temporarily + ++filter; // move one step in the required direction + filter.setNewFilter (predicate); + }); + } + + template + inline void + refineSerach (Filter& solution, COND&& additionalFilter) + { + solution.addStep ([predicate{forward (additionalFilter)}] + (auto& filter) + { + filter.andFilter (predicate); + }); + } + + }//(End) Elementary Matchers and handling of the search engine + + + + + + EventMatch::EventMatch (Log const& srcSeq) + : solution_{buildSearchFilter (srcSeq)} + , lastMatch_{"HEAD "+ solution_->get("this")} + , look_for_match_{true} + , violation_{} + { } + + + + /** core of the evaluation machinery: + * apply a filter predicate and then pull + * through the log to find a acceptable entry + */ + bool + EventMatch::foundSolution() + { + return not isnil (solution_); + } + + + /** this is actually called after each refinement of + * the filter and matching conditions. The effect is to search + * for an (intermediary) solution right away and to mark failure + * as soon as some condition can not be satisfied. Rationale is to + * indicate the point where a chained match fails + * @see ::operator bool() for extracting the result + * @param matchSpec diagnostics description of the predicate just being added + * @param rel indication of the searching relation / direction + */ + void + EventMatch::evaluateQuery (string matchSpec, Literal rel) + { + if (look_for_match_ and not isnil (violation_)) return; + // already failed, no further check necessary + + if (foundSolution()) // NOTE this pulls the filter + { + lastMatch_ = matchSpec+" @ "+string(*solution_) + + (isnil(lastMatch_)? "" + : "\n.."+rel+" "+lastMatch_); + if (not look_for_match_) + violation_ = "FOUND at least "+lastMatch_; + } + else + { + if (look_for_match_) + violation_ = "FAILED to "+matchSpec + + "\n.."+rel + + " "+lastMatch_; + else + violation_ = ""; + } + } + + + + /** + * basic search function: continue linear lookup over the elements of the + * EventLog to find a match (substring match) of the given text. The search begins + * at the current position and proceeds in the currently configured direction. + * Initially the search starts at the first record and proceeds forward. + */ + EventMatch& + EventMatch::locate (string match) + { + attachNextSerchStep (solution_, find(match), CURRENT); + evaluateQuery ("match(\""+match+"\")"); + return *this; + } + + /** + * basic search like locate() but with the given regular expression + */ + EventMatch& + EventMatch::locateMatch (string regExp) + { + attachNextSerchStep (solution_, findRegExp(regExp), CURRENT); + evaluateQuery ("find-RegExp(\""+regExp+"\")"); + return *this; + } + + /** + * basic search for a matching "event" + * @param match perform a substring match against the arguments of the event + * @see beforeEvent() for a description of possible "events" + */ + EventMatch& + EventMatch::locateEvent (string match) + { + attachNextSerchStep (solution_, findEvent(match), CURRENT); + evaluateQuery ("match-event(\""+match+"\")"); + return *this; + } + + EventMatch& + EventMatch::locateEvent (string classifier, string match) + { + attachNextSerchStep (solution_, findEvent(classifier,match), CURRENT); + evaluateQuery ("match-event(ID=\""+classifier+"\", \""+match+"\")"); + return *this; + } + + /** + * basic search for some specific function invocation + * @param match perform a substring match against the name of the function invoked + */ + EventMatch& + EventMatch::locateCall (string match) + { + attachNextSerchStep (solution_, findCall(match), CURRENT); + evaluateQuery ("match-call(\""+match+"\")"); + return *this; + } + + + + + /** + * find a match (substring match) of the given text + * in an EventLog entry after the current position + * @remarks the name of this junctor might seem counter intuitive; + * it was chosen due to expected DSL usage: `log.verify("α").before("β")`. + * Operationally this means first to find a Record matching the substring "α" + * and then to forward from this point until hitting a record to match "β". + */ + EventMatch& + EventMatch::before (string match) + { + attachNextSerchStep (solution_, find(match), FORWARD); + evaluateQuery ("match(\""+match+"\")"); + return *this; + } + + /** + * find a match with the given regular expression + */ + EventMatch& + EventMatch::beforeMatch (string regExp) + { + attachNextSerchStep (solution_, findRegExp(regExp), FORWARD); + evaluateQuery ("find-RegExp(\""+regExp+"\")"); + return *this; + } + + /** + * find a match for an "event" _after_ the current point of reference + * @remarks the term "event" designates several types of entries, which + * typically capture something happening within the observed entity. + * Especially, the following [record types](\ref lib::Record::getType()) + * qualify as event: + * - `event` + * - `error` + * - `create` + * - `destroy` + * - `logJoin` + * @param match perform a substring match against the arguments of the event + * @see ::findEvent + */ + EventMatch& + EventMatch::beforeEvent (string match) + { + attachNextSerchStep (solution_, findEvent(match), FORWARD); + evaluateQuery ("match-event(\""+match+"\")"); + return *this; + } + + EventMatch& + EventMatch::beforeEvent (string classifier, string match) + { + attachNextSerchStep (solution_, findEvent(classifier,match), FORWARD); + evaluateQuery ("match-event(ID=\""+classifier+"\", \""+match+"\")"); + return *this; + } + + /** + * find a match for some function invocation _after_ the current point of reference + * @param match perform a substring match against the name of the function invoked + * @see ::findCall + */ + EventMatch& + EventMatch::beforeCall (string match) + { + attachNextSerchStep (solution_, findCall(match), FORWARD); + evaluateQuery ("match-call(\""+match+"\")"); + return *this; + } + + + + + /** + * find a match (substring match) of the given text + * in an EventLog entry _before_ the current position, + * switching to backwards search direction + */ + EventMatch& + EventMatch::after (string match) + { + attachNextSerchStep (solution_, find(match), BACKWARD); + evaluateQuery ("match(\""+match+"\")", "before"); + return *this; + } + + EventMatch& + EventMatch::afterMatch (string regExp) + { + attachNextSerchStep (solution_, findRegExp(regExp), BACKWARD); + evaluateQuery ("find-RegExp(\""+regExp+"\")", "before"); + return *this; + } + + EventMatch& + EventMatch::afterEvent (string match) + { + attachNextSerchStep (solution_, findEvent(match), BACKWARD); + evaluateQuery ("match-event(\""+match+"\")", "before"); + return *this; + } + + EventMatch& + EventMatch::afterEvent (string classifier, string match) + { + attachNextSerchStep (solution_, findEvent(classifier,match), BACKWARD); + evaluateQuery ("match-event(ID=\""+classifier+"\", \""+match+"\")", "before"); + return *this; + } + + /** + * find a function invocation backwards, before the current point of reference + */ + EventMatch& + EventMatch::afterCall (string match) + { + attachNextSerchStep (solution_, findCall(match), BACKWARD); + evaluateQuery ("match-call(\""+match+"\")", "before"); + return *this; + } + + + + /** + * @internal refine filter condition additionally to match the call arguments. + */ + void + EventMatch::refineSerach_matchArguments (ArgSeq&& argSeq) + { + string argList(util::join(argSeq)); + refineSerach (solution_, matchArguments(move(argSeq))); + evaluateQuery ("match-arguments("+argList+")"); + } + + /** + * @internal refine filter condition additionally to cover call arguments by + * a sequence of regular expressions. Each regExp is used consecutively, + * until it fails to match; at that point, the next regExp is tried. + * When reaching the end of the argument sequence, we also expect + * to have used up all available regular expressions. + */ + void + EventMatch::refineSerach_matchArgsRegExp (RExSeq&& regExpSeq, string rendered_regExps) + { + refineSerach (solution_, matchArgsRegExp (move (regExpSeq))); + evaluateQuery ("match-args-RegExp("+rendered_regExps+")"); + } + + + + /** + * refine filter to additionally require a matching log entry type + */ + EventMatch& + EventMatch::type (string typeID) + { + refineSerach (solution_, matchType(typeID)); + evaluateQuery ("match-type("+typeID+")"); + return *this; + } + + /** + * refine filter to additionally require the presence an attribute + */ + EventMatch& + EventMatch::key (string key) + { + refineSerach (solution_, ensureAttribute(key)); + evaluateQuery ("ensure-attribute("+key+")"); + return *this; + } + + /** + * refine filter to additionally match on a specific attribute + */ + EventMatch& + EventMatch::attrib (string key, string valueMatch) + { + refineSerach (solution_, matchAttribute(key,valueMatch)); + evaluateQuery ("match-attribute("+key+"=\""+valueMatch+"\")"); + return *this; + } + + /** + * refine filter to additionally match on the ID attribute + */ + EventMatch& + EventMatch::id (string classifier) + { + refineSerach (solution_, matchAttribute("ID",classifier)); + evaluateQuery ("match-ID(\""+classifier+"\")"); + return *this; + } + + /** + * refine filter to additionally match the `'this'` attribute + */ + EventMatch& + EventMatch::on (string targetID) + { + refineSerach (solution_, matchAttribute("this",targetID)); + evaluateQuery ("match-this(\""+targetID+"\")"); + return *this; + } + + EventMatch& + EventMatch::on (const char* targetID) + { + refineSerach (solution_, matchAttribute("this",targetID)); + evaluateQuery ("match-this(\""+string(targetID)+"\")"); + return *this; + } + + + + + + /* ==== EventLog implementation ==== */ + + EventLog::EventLog (string logID) + : log_(new Log) + { + log({"type=EventLogHeader", "this="+logID}); + } + + + /** + * @remarks EventLog uses a heap based, sharable log storage, + * where the EventLog object is just a front-end (shared ptr). + * The `joinInto` operation both integrates this logs contents + * into the other log, and then disconnects from the old storage + * and connects to the storage of the combined log. + * @warning beware of clone copies. Since copying EventLog is always a + * shallow copy, all copied handles actually refer to the same + * log storage. If you invoke `joinInto` in such a situation, + * only the current EventLog front-end handle will be rewritten + * to point to the combined log, while any other clone will + * continue to point to the original log storage. + */ + EventLog& + EventLog::joinInto (EventLog& otherLog) + { + Log& target = *otherLog.log_; + target.reserve (target.size() + log_->size() + 1); + target.emplace_back (log_->front()); + auto p = log_->begin(); + while (++p != log_->end()) // move our log's content into the other log + target.emplace_back (std::move(*p)); + this->log_->resize(1); + this->log({"type=joined", otherLog.getID()}); // leave a tag to indicate + otherLog.log({"type=logJoin", this->getID()}); // where the `joinInto` took place, + this->log_ = otherLog.log_; // connect this to the other storage + return *this; + } + + + + EventLog& + EventLog::clear() + { + string originalLogID = this->getID(); + return this->clear (originalLogID); + } + + /** purge log contents and also reset Header-ID + * @note actually we're starting a new log + * and let the previous one go away. + * @warning while this also unties any joined logs, + * other log front-ends might still hold onto + * the existing, combined log. Just we are + * detached and writing to a pristine log. + */ + EventLog& + EventLog::clear (string alteredLogID) + { + log_.reset (new Log); + log({"type=EventLogHeader", "this="+alteredLogID}); + return *this; + } + + EventLog& + EventLog::clear (const char* alteredLogID) + { + return clear (string{alteredLogID}); + } + + + + /* ==== Logging API ==== */ + + EventLog& + EventLog::event (string text) + { + log ("event", ArgSeq{}, ArgSeq{text}); // we use this ctor variant to ensure + return *this; // that text is not misinterpreted as attribute, + } // which might happen when text contains a '=' + + EventLog& + EventLog::event (string classifier, string text) + { + log ("event", ArgSeq{"ID="+classifier}, ArgSeq{text}); + return *this; + } + + EventLog& + EventLog::call (string target, string function) + { + return call(target, function, ArgSeq()); + } + + EventLog& + EventLog::call (string target, string function, ArgSeq&& args) + { + log ("call", ArgSeq{"fun="+function, "this="+target}, std::forward(args)); + return *this; + } + + EventLog& + EventLog::call (const char* target, const char* function, ArgSeq&& args) + { + return call (string(target), string(function), std::forward(args)); + } + + + EventLog& + EventLog::warn (string text) + { + log({"type=warn", text}); + return *this; + } + + EventLog& + EventLog::error (string text) + { + log({"type=error", text}); + return *this; + } + + EventLog& + EventLog::fatal (string text) + { + log({"type=fatal", text}); + return *this; + } + + + + EventLog& + EventLog::create (string text) + { + log({"type=create", text}); + return *this; + } + + EventLog& + EventLog::destroy (string text) + { + log({"type=destroy", text}); + return *this; + } + + + + + /* ==== Query/Verification API ==== */ + + EventMatch + EventLog::verify (string match) const + { + EventMatch matcher(*log_); + matcher.locate (match); // new matcher starts linear search from first log element + return matcher; + } + + + EventMatch + EventLog::verifyMatch (string regExp) const + { + EventMatch matcher(*log_); + matcher.locateMatch (regExp); + return matcher; + } + + + EventMatch + EventLog::verifyEvent (string match) const + { + EventMatch matcher(*log_); + matcher.locateEvent (match); + return matcher; + } + + + EventMatch + EventLog::verifyEvent (string classifier, string match) const + { + EventMatch matcher(*log_); + matcher.locateEvent (classifier, match); + return matcher; + } + + + EventMatch + EventLog::verifyCall (string match) const + { + EventMatch matcher(*log_); + matcher.locateCall (match); + return matcher; } /** - * @internal ongoing evaluation and match within an [EventLog]. - * @remarks an EventMatch object is returned when building query expression - * to verify the occurrence of some events within the EventLog. - * This "matcher" object implements the query logic with backtracking. - * The query expressions are added as filter functors to a filtering - * iterator; when all of the log's contents are filtered away, - * the evaluation counts as failed. + * @remarks the query expression is built similar to the other queries, + * but the logic of evaluation is flipped: whenever we find any match + * the overall result (when evaluating to `bool`) will be `false`. + * @warning this is not an proper exhaustive negation, since the implementation + * does not proper backtracking with a stack of choices. This becomes + * evident, when you combine `ensureNot()` with a switch in search + * direction, like e.g. using `afterXXX` at some point in the chain. */ - class EventMatch - { - using Filter = decltype( buildSearchFilter (std::declval()) ); - - using ArgSeq = lib::diff::RecordSetup::Storage; - using RExSeq = std::vector; - - - /** match predicate evaluator */ - Filter solution_; - - /** record last match for diagnostics */ - string lastMatch_; - - /** support for positive and negative queries. */ - bool look_for_match_; - - /** record when the underlying query has failed */ - string violation_; - - /** core of the evaluation machinery: - * apply a filter predicate and then pull - * through the log to find a acceptable entry - */ - bool - foundSolution() - { - return not isnil (solution_); - } - - /** this is actually called after each refinement of - * the filter and matching conditions. The effect is to search - * for an (intermediary) solution right away and to mark failure - * as soon as some condition can not be satisfied. Rationale is to - * indicate the point where a chained match fails - * @see ::operator bool() for extracting the result - * @param matchSpec diagnostics description of the predicate just being added - * @param rel indication of the searching relation / direction - */ - void - evaluateQuery (string matchSpec, Literal rel = "after") - { - if (look_for_match_ and not isnil (violation_)) return; - // already failed, no further check necessary - - if (foundSolution()) // NOTE this pulls the filter - { - lastMatch_ = matchSpec+" @ "+string(*solution_) - + (isnil(lastMatch_)? "" - : "\n.."+rel+" "+lastMatch_); - if (not look_for_match_) - violation_ = "FOUND at least "+lastMatch_; - } - else - { - if (look_for_match_) - violation_ = "FAILED to "+matchSpec - + "\n.."+rel - + " "+lastMatch_; - else - violation_ = ""; - } - } - - - /** @internal for creating EventLog matchers */ - EventMatch(Log const& srcSeq) - : solution_{buildSearchFilter (srcSeq)} - , lastMatch_{"HEAD "+ solution_->get("this")} - , look_for_match_{true} - , violation_{} - { } - - friend class EventLog; - - - - /* == elementary matchers == */ - - auto - find (string match) - { - return [=](Entry const& entry) - { - return contains (string(entry), match); - }; - } - - auto - findRegExp (string regExpDef) - { - std::regex regExp(regExpDef); - return [=](Entry const& entry) - { - return std::regex_search(string(entry), regExp); - }; - } - - auto - findEvent (string match) - { - return [=](Entry const& entry) - { - return ( entry.getType() == "event" - or entry.getType() == "error" - or entry.getType() == "create" - or entry.getType() == "destroy" - or entry.getType() == "logJoin" - ) - and !isnil(entry.scope()) - and contains (*entry.scope(), match); - }; - } - - auto - findEvent (string classifier, string match) - { - return [=](Entry const& entry) - { - return ( entry.getType() == classifier - or (entry.hasAttribute("ID") and contains (entry.get("ID"), classifier)) - ) - and !isnil(entry.scope()) - and contains (*entry.scope(), match); - }; - } - - auto - findCall (string match) - { - return [=](Entry const& entry) - { - return entry.getType() == "call" - and contains (entry.get("fun"), match); - }; - } - - - /** this filter functor is for refinement of an existing filter - * @param argSeq perform a substring match consecutively - * for each of the log entry's arguments - * @note the match also fails, when the given log entry - * has more or less arguments, than the number of - * given match expressions in `argSeq` - * @see ExtensibleFilterIter::andFilter() - */ - auto - matchArguments (ArgSeq&& argSeq) - { - return [=](Entry const& entry) - { - auto scope = entry.scope(); - for (auto const& match : argSeq) - if (isnil (scope) or not contains(*scope, match)) - return false; - else - ++scope; - - return isnil(scope); // must be exhausted by now - }; // otherwise the sizes do not match... - } - - - /** refinement filter, to cover all arguments by regular expression(s) - * @param regExpSeq several regular expressions, which, when applied - * consecutively until exhaustion, must cover and verify _all_ - * arguments of the log entry. - * @remarks to explain, we "consume" arguments with a regExp from the list, - * and when this one doesn't match anymore, we try the next one. - * When we'ver tried all regular expressions, we must have also - * consumed all arguments, otherwise we fail. - */ - auto - matchArgsRegExp (RExSeq&& regExpSeq) - { - return [=](Entry const& entry) - { - auto scope = entry.scope(); - for (auto const& rex : regExpSeq) - { - if (isnil (scope)) return false; - while (scope and std::regex_search(*scope, rex)) - ++scope; - } - - return isnil(scope); // must be exhausted by now - }; // otherwise we didn't get full coverage... - } - - - /** refinement filter to match on the given typeID */ - auto - matchType (string typeID) - { - return [=](Entry const& entry) - { - return contains (entry.getType(), typeID); - }; - } - - - /** refinement filter to ensure a specific attribute is present on the log entry */ - auto - ensureAttribute (string key) - { - return [=](Entry const& entry) - { - return entry.hasAttribute(key); - }; - } - - - /** refinement filter to ensure a specific attribute is present on the log entry */ - auto - matchAttribute (string key, string valueMatch) - { - return [=](Entry const& entry) - { - return entry.hasAttribute(key) - and contains (entry.get(key), valueMatch); - }; - } - - - /* === configure the underlying search engine === */ - - enum Direction { - FORWARD, BACKWARD, CURRENT - }; - - template - void - attachNextSerchStep (COND&& filter, Direction direction) - { - if (CURRENT == direction) - solution_.search (forward (filter)); - else - solution_.addStep ([predicate{forward (filter)}, direction] - (auto& filter) - { - filter.reverse (BACKWARD == direction); - filter.disableFilter(); // deactivate any filtering temporarily - ++filter; // move one step in the required direction - filter.setNewFilter (predicate); - }); - } - - template - void - refineSerach (COND&& additionalFilter) - { - solution_.addStep ([predicate{forward (additionalFilter)}] - (auto& filter) - { - filter.andFilter (predicate); - }); - } - - - - public: - /** final evaluation of the match query, - * usually triggered from the unit test `CHECK()`. - * @note failure cause is printed to STDERR. - */ - operator bool() const - { - if (!isnil (violation_)) - { - cerr << "__Log_condition_violated__\n"+violation_ <<"\n"; - return false; - } - return true; - } - - - /** - * basic search function: continue linear lookup over the elements of the - * EventLog to find a match (substring match) of the given text. The search begins - * at the current position and proceeds in the currently configured direction. - * Initially the search starts at the first record and proceeds forward. - */ - EventMatch& - locate (string match) - { - attachNextSerchStep (find(match), CURRENT); - evaluateQuery ("match(\""+match+"\")"); - return *this; - } - - /** basic search like locate() but with the given regular expression */ - EventMatch& - locateMatch (string regExp) - { - attachNextSerchStep (findRegExp(regExp), CURRENT); - evaluateQuery ("find-RegExp(\""+regExp+"\")"); - return *this; - } - - /** basic search for a matching "event" - * @param match perform a substring match against the arguments of the event - * @see beforeEvent() for a description of possible "events" - */ - EventMatch& - locateEvent (string match) - { - attachNextSerchStep (findEvent(match), CURRENT); - evaluateQuery ("match-event(\""+match+"\")"); - return *this; - } - - EventMatch& - locateEvent (string classifier, string match) - { - attachNextSerchStep (findEvent(classifier,match), CURRENT); - evaluateQuery ("match-event(ID=\""+classifier+"\", \""+match+"\")"); - return *this; - } - - /** basic search for some specific function invocation - * @param match perform a substring match against the name of the function invoked - */ - EventMatch& - locateCall (string match) - { - attachNextSerchStep (findCall(match), CURRENT); - evaluateQuery ("match-call(\""+match+"\")"); - return *this; - } - - /** - * find a match (substring match) of the given text - * in an EventLog entry after the current position - * @remarks the name of this junctor might seem counter intuitive; - * it was chosen due to expected DSL usage: `log.verify("α").before("β")`. - * Operationally this means first to find a Record matching the substring "α" - * and then to forward from this point until hitting a record to match "β". - */ - EventMatch& - before (string match) - { - attachNextSerchStep (find(match), FORWARD); - evaluateQuery ("match(\""+match+"\")"); - return *this; - } - - /** find a match with the given regular expression */ - EventMatch& - beforeMatch (string regExp) - { - attachNextSerchStep (findRegExp(regExp), FORWARD); - evaluateQuery ("find-RegExp(\""+regExp+"\")"); - return *this; - } - - /** find a match for an "event" _after_ the current point of reference - * @remarks the term "event" designates several types of entries, which - * typically capture something happening within the observed entity. - * Especially, the following [record types](\ref lib::Record::getType()) - * qualify as event: - * - `event` - * - `error` - * - `create` - * - `destroy` - * - `logJoin` - * @param match perform a substring match against the arguments of the event - * @see ::findEvent - */ - EventMatch& - beforeEvent (string match) - { - attachNextSerchStep (findEvent(match), FORWARD); - evaluateQuery ("match-event(\""+match+"\")"); - return *this; - } - - EventMatch& - beforeEvent (string classifier, string match) - { - attachNextSerchStep (findEvent(classifier,match), FORWARD); - evaluateQuery ("match-event(ID=\""+classifier+"\", \""+match+"\")"); - return *this; - } - - /** find a match for some function invocation _after_ the current point of reference - * @param match perform a substring match against the name of the function invoked - * @see ::findCall - */ - EventMatch& - beforeCall (string match) - { - attachNextSerchStep (findCall(match), FORWARD); - evaluateQuery ("match-call(\""+match+"\")"); - return *this; - } - - EventMatch& - after (string match) - { - attachNextSerchStep (find(match), BACKWARD); - evaluateQuery ("match(\""+match+"\")", "before"); - return *this; - } - - EventMatch& - afterMatch (string regExp) - { - attachNextSerchStep (findRegExp(regExp), BACKWARD); - evaluateQuery ("find-RegExp(\""+regExp+"\")", "before"); - return *this; - } - - EventMatch& - afterEvent (string match) - { - attachNextSerchStep (findEvent(match), BACKWARD); - evaluateQuery ("match-event(\""+match+"\")", "before"); - return *this; - } - - EventMatch& - afterEvent (string classifier, string match) - { - attachNextSerchStep (findEvent(classifier,match), BACKWARD); - evaluateQuery ("match-event(ID=\""+classifier+"\", \""+match+"\")", "before"); - return *this; - } - - /** find a function invocation backwards, before the current point of reference */ - EventMatch& - afterCall (string match) - { - attachNextSerchStep (findCall(match), BACKWARD); - evaluateQuery ("match-call(\""+match+"\")", "before"); - return *this; - } - - void - refineSerach_matchArguments (ArgSeq&& argSeq) - { - string argList(util::join(argSeq)); - refineSerach (matchArguments(move(argSeq))); - evaluateQuery ("match-arguments("+argList+")"); - } - - /** refine filter to additionally require specific arguments - * @remarks the refined filter works on each record qualified by the - * query expression established thus far; it additionally - * looks into the arguments (children list) of the log entry. - * @warning match is processed by comparison of string representation. - */ - template - EventMatch& - arg (ARGS const& ...args) - { - refineSerach_matchArguments (stringify (args...)); - return *this; - } - - void - refineSerach_matchArgsRegExp (RExSeq&& regExpSeq, string rendered_regExps) - { - refineSerach (matchArgsRegExp (move (regExpSeq))); - evaluateQuery ("match-args-RegExp("+rendered_regExps+")"); - } - - /** refine filter to additionally cover all arguments - * with a series of regular expressions. - * @note For this match to succeed, any arguments of the log entry - * must be covered by applying the given regular expressions - * consecutively. Each regular expression is applied to further - * arguments, as long as it matches. It is possible to have - * just one RegExp to "rule them all", but as soon as there - * is one argument, that can not be covered by the next - * RegExp, the whole match counts as failed. - */ - template - EventMatch& - argMatch (ARGS const& ...regExps) - { - refineSerach_matchArgsRegExp (stringify (regExps...), - util::join(stringify(regExps...))); - return *this; - } - - /** refine filter to additionally require a matching log entry type */ - EventMatch& - type (string typeID) - { - refineSerach (matchType(typeID)); - evaluateQuery ("match-type("+typeID+")"); - return *this; - } - - /** refine filter to additionally require the presence an attribute */ - EventMatch& - key (string key) - { - refineSerach (ensureAttribute(key)); - evaluateQuery ("ensure-attribute("+key+")"); - return *this; - } - - /** refine filter to additionally match on a specific attribute */ - EventMatch& - attrib (string key, string valueMatch) - { - refineSerach (matchAttribute(key,valueMatch)); - evaluateQuery ("match-attribute("+key+"=\""+valueMatch+"\")"); - return *this; - } - - /** refine filter to additionally match on the ID attribute */ - EventMatch& - id (string classifier) - { - refineSerach (matchAttribute("ID",classifier)); - evaluateQuery ("match-ID(\""+classifier+"\")"); - return *this; - } - - /** refine filter to additionally match the `'this'` attribute */ - EventMatch& - on (string targetID) - { - refineSerach (matchAttribute("this",targetID)); - evaluateQuery ("match-this(\""+targetID+"\")"); - return *this; - } - - EventMatch& - on (const char* targetID) - { - refineSerach (matchAttribute("this",targetID)); - evaluateQuery ("match-this(\""+string(targetID)+"\")"); - return *this; - } - - template - EventMatch& - on (const X *const targetObj) - { - string targetID = idi::instanceTypeID (targetObj); - return this->on(targetID); - } - }; - - - - - - /****************************************************************//** - * Helper to log and verify the occurrence of events. - * The EventLog object is a front-end handle, logging flexible - * [information records](\ref lib::Record) into a possibly shared (vector) - * buffer in heap storage. An extended query DSL allows to write - * assertions to cover the occurrence of events in unit tests. - * @see TestEventLog_test - */ - class EventLog - { - - std::shared_ptr log_; - - - void - log (std::initializer_list const& ili) - { - log_->emplace_back(ili); - } - - template - void - log (Symbol typeID, ATTR&& attribs, ARGS&& args) - { - log_->emplace_back(typeID, std::forward(attribs) - , std::forward(args)); - } - - string - getID() const - { - return log_->front().get("this"); - } - - - public: - explicit - EventLog (string logID) - : log_(new Log) - { - log({"type=EventLogHeader", "this="+logID}); - } - - explicit - EventLog (const char* logID) - : EventLog(string(logID)) - { } - - template - explicit - EventLog (const X *const obj) - : EventLog(idi::instanceTypeID (obj)) - { } - - - // standard copy operations acceptable - - - /** Merge this log into another log, forming a combined log - * @param otherLog target to integrate this log's contents into. - * @return reference to the merged new log instance - * @remarks EventLog uses a heap based, sharable log storage, - * where the EventLog object is just a front-end (shared ptr). - * The `joinInto` operation both integrates this logs contents - * into the other log, and then disconnects from the old storage - * and connects to the storage of the combined log. - * @warning beware of clone copies. Since copying EventLog is always a - * shallow copy, all copied handles actually refer to the same - * log storage. If you invoke `joinInto` in such a situation, - * only the current EventLog front-end handle will be rewritten - * to point to the combined log, while any other clone will - * continue to point to the original log storage. - * @see TestEventLog_test::verify_logJoining() - */ - EventLog& - joinInto (EventLog& otherLog) - { - Log& target = *otherLog.log_; - target.reserve (target.size() + log_->size() + 1); - target.emplace_back (log_->front()); - auto p = log_->begin(); - while (++p != log_->end()) // move our log's content into the other log - target.emplace_back(std::move(*p)); - this->log_->resize(1); - this->log({"type=joined", otherLog.getID()}); // leave a tag to indicate - otherLog.log({"type=logJoin", this->getID()}); // where the `joinInto` took place, - this->log_ = otherLog.log_; // connect this to the other storage - return *this; - } - - - /** purge log contents while retaining just the original Header-ID */ - EventLog& - clear() - { - string originalLogID = this->getID(); - return this->clear (originalLogID); - } - - /** purge log contents and also reset Header-ID - * @note actually we're starting a new log - * and let the previous one go away. - * @warning while this also unties any joined logs, - * other log front-ends might still hold onto - * the existing, combined log. Just we are - * detached and writing to a pristine log. - */ - EventLog& - clear (string alteredLogID) - { - log_.reset (new Log); - log({"type=EventLogHeader", "this="+alteredLogID}); - return *this; - } - - EventLog& - clear (const char* alteredLogID) - { - return clear (string{alteredLogID}); - } - - template - EventLog& - clear (const X *const obj) - { - return clear (idi::instanceTypeID (obj)); - } - - - - /* ==== Logging API ==== */ - - using ArgSeq = lib::diff::RecordSetup::Storage; - - EventLog& - event (string text) - { - log ("event", ArgSeq{}, ArgSeq{text}); // we use this ctor variant to ensure - return *this; // that text is not misinterpreted as attribute, - } // which might happen when text contains a '=' - - /** log some event, with additional ID or classifier - * @param classifier info to be saved into the `ID` attribute - * @param text actual payload info, to be logged as argument - */ - EventLog& - event (string classifier, string text) - { - log ("event", ArgSeq{"ID="+classifier}, ArgSeq{text}); - return *this; - } - - /** Log occurrence of a function call with no arguments. - * @param target the object or scope on which the function is invoked - * @param function name of the function being invoked - */ - EventLog& - call (string target, string function) - { - return call(target, function, ArgSeq()); - } - - /** Log a function call with a sequence of stringified arguments */ - EventLog& - call (string target, string function, ArgSeq&& args) - { - log ("call", ArgSeq{"fun="+function, "this="+target}, std::forward(args)); - return *this; - } - - EventLog& - call (const char* target, const char* function, ArgSeq&& args) - { - return call (string(target), string(function), std::forward(args)); - } - - /** Log a function call with arbitrary arguments */ - template - EventLog& - call (string target, string function, ARGS const& ...args) - { - return call (target, function, stringify(args...)); - } - - /** Log a function call on given object ("`this`")... */ - template - EventLog& - call (const X *const targetObj, string function, ARGS const& ...args) - { - return call (idi::instanceTypeID (targetObj), function, args...); - } - - template - EventLog& - call (const char* target, string function, ARGS const& ...args) - { - return call (string(target), function, args...); - } - - template - EventLog& - note (ELMS const& ...initialiser) - { - log_->emplace_back(stringify (initialiser...)); - return *this; - } - - - /** Log a warning entry */ - EventLog& - warn (string text) - { - log({"type=warn", text}); - return *this; - } - - - /** Log an error note */ - EventLog& - error (string text) - { - log({"type=error", text}); - return *this; - } - - - /** Log a fatal failure */ - EventLog& - fatal (string text) - { - log({"type=fatal", text}); - return *this; - } - - - /** Log the creation of an object. - * Such an entry can be [matched as event](\ref ::verifyEvent) */ - EventLog& - create (string text) - { - log({"type=create", text}); - return *this; - } - - - /** Log the destruction of an object. - * Can be matched as event. */ - EventLog& - destroy (string text) - { - log({"type=destroy", text}); - return *this; - } - - - - /* ==== Iteration ==== */ - - bool - empty() const - { - return 1 >= log_->size(); // do not count the log header - } - - - using Iter = lib::RangeIter; - - typedef Iter const_iterator; - typedef const Entry value_type; - - const_iterator begin() const { return Iter(log_->begin(), log_->end()); } - const_iterator end() const { return Iter(); } - - friend const_iterator begin (EventLog const& log) { return log.begin(); } - friend const_iterator end (EventLog const& log) { return log.end(); } - - - - - /* ==== Query/Verification API ==== */ - - /** start a query to match for some substring. - * The resulting matcher object will qualify on any log entry - * containing the given string. By adding subsequent further - * query expressions on the returned [matcher object](\ref EventMatch), - * the query can be refined. Moreover it is possible to chain up further - * search queries, which will be executing starting from the position of the - * previous match. The final result can be retrieved by `bool` conversion - */ - EventMatch - verify (string match) const - { - EventMatch matcher(*log_); - matcher.locate (match); // new matcher starts linear search from first log element - return matcher; - } - - /** start a query to match with a regular expression - * @param regExp definition - * @remarks the expression will work against the full - * `string` representation of the log entries. - * Meaning, it can also match type and attributes - */ - EventMatch - verifyMatch (string regExp) const - { - EventMatch matcher(*log_); - matcher.locateMatch (regExp); - return matcher; - } - - /** start a query to match for some event. - * @remarks only a subset of all log entries is treated as "event", - * any other log entry will not be considered for this query. - * Besides the regular [events](\ref ::event()), also errors, - * creation and destruction of objects count as "event". - * @param match text to (substring)match against the argument logged as event - */ - EventMatch - verifyEvent (string match) const - { - EventMatch matcher(*log_); - matcher.locateEvent (match); - return matcher; - } - - /** start a query to match for an specific kind of element - * @param classifier select kind of event by match on type or ID - */ - EventMatch - verifyEvent (string classifier, string match) const - { - EventMatch matcher(*log_); - matcher.locateEvent (classifier, match); - return matcher; - } - - template - EventMatch - verifyEvent (string classifier, X const& something) const - { - return verifyEvent (classifier, util::toString (something)); - } - - /** start a query to match especially a function call - * @param match text to match against the function name - */ - EventMatch - verifyCall (string match) const - { - EventMatch matcher(*log_); - matcher.locateCall (match); - return matcher; - } - - /** start a query to ensure the given expression does _not_ match. - * @remarks the query expression is built similar to the other queries, - * but the logic of evaluation is flipped: whenever we find any match - * the overall result (when evaluating to `bool`) will be `false`. - * @warning this is not an proper exhaustive negation, since the implementation - * does not proper backtracking with a stack of choices. This becomes - * evident, when you combine `ensureNot()` with a switch in search - * direction, like e.g. using `afterXXX` at some point in the chain. - */ - EventMatch - ensureNot (string match) const - { - EventMatch matcher(*log_); - matcher.look_for_match_ = false; // flip logic; fail if match succeeds - matcher.locate (match); - return matcher; - } - - - /** equality comparison is based on the actual log contents */ - friend bool - operator== (EventLog const& l1, EventLog const& l2) - { - return l1.log_ == l2.log_ - or (l1.log_ and l2.log_ - and *l1.log_ == *l2.log_); - } - friend bool - operator!= (EventLog const& l1, EventLog const& l2) - { - return not (l1 == l2); - } - }; + EventMatch + EventLog::ensureNot (string match) const + { + EventMatch matcher(*log_); + matcher.look_for_match_ = false; // flip logic; fail if match succeeds + matcher.locate (match); + return matcher; + } diff --git a/src/lib/test/event-log.hpp b/src/lib/test/event-log.hpp index d00e46c65..c88042b71 100644 --- a/src/lib/test/event-log.hpp +++ b/src/lib/test/event-log.hpp @@ -80,12 +80,12 @@ #include "lib/error.hpp" -#include "lib/idi/entry-id.hpp" -#include "lib/iter-chain-search.hpp" #include "lib/iter-cursor.hpp" +#include "lib/iter-chain-search.hpp" #include "lib/format-util.hpp" #include "lib/format-cout.hpp" #include "lib/diff/record.hpp" +#include "lib/idi/genfunc.hpp" #include "lib/symbol.hpp" #include "lib/util.hpp" @@ -98,13 +98,10 @@ namespace lib { namespace test{ - namespace error = lumiera::error; - using util::stringify; - using util::contains; - using util::isnil; using lib::Symbol; using std::string; + using util::stringify; namespace { using Entry = lib::diff::Record; @@ -148,246 +145,14 @@ namespace test{ /** record when the underlying query has failed */ string violation_; - /** core of the evaluation machinery: - * apply a filter predicate and then pull - * through the log to find a acceptable entry - */ - bool - foundSolution() - { - return not isnil (solution_); - } - - /** this is actually called after each refinement of - * the filter and matching conditions. The effect is to search - * for an (intermediary) solution right away and to mark failure - * as soon as some condition can not be satisfied. Rationale is to - * indicate the point where a chained match fails - * @see ::operator bool() for extracting the result - * @param matchSpec diagnostics description of the predicate just being added - * @param rel indication of the searching relation / direction - */ - void - evaluateQuery (string matchSpec, Literal rel = "after") - { - if (look_for_match_ and not isnil (violation_)) return; - // already failed, no further check necessary - - if (foundSolution()) // NOTE this pulls the filter - { - lastMatch_ = matchSpec+" @ "+string(*solution_) - + (isnil(lastMatch_)? "" - : "\n.."+rel+" "+lastMatch_); - if (not look_for_match_) - violation_ = "FOUND at least "+lastMatch_; - } - else - { - if (look_for_match_) - violation_ = "FAILED to "+matchSpec - + "\n.."+rel - + " "+lastMatch_; - else - violation_ = ""; - } - } /** @internal for creating EventLog matchers */ - EventMatch(Log const& srcSeq) - : solution_{buildSearchFilter (srcSeq)} - , lastMatch_{"HEAD "+ solution_->get("this")} - , look_for_match_{true} - , violation_{} - { } + EventMatch(Log const& srcSeq); friend class EventLog; - - /* == elementary matchers == */ - - auto - find (string match) - { - return [=](Entry const& entry) - { - return contains (string(entry), match); - }; - } - - auto - findRegExp (string regExpDef) - { - std::regex regExp(regExpDef); - return [=](Entry const& entry) - { - return std::regex_search(string(entry), regExp); - }; - } - - auto - findEvent (string match) - { - return [=](Entry const& entry) - { - return ( entry.getType() == "event" - or entry.getType() == "error" - or entry.getType() == "create" - or entry.getType() == "destroy" - or entry.getType() == "logJoin" - ) - and !isnil(entry.scope()) - and contains (*entry.scope(), match); - }; - } - - auto - findEvent (string classifier, string match) - { - return [=](Entry const& entry) - { - return ( entry.getType() == classifier - or (entry.hasAttribute("ID") and contains (entry.get("ID"), classifier)) - ) - and !isnil(entry.scope()) - and contains (*entry.scope(), match); - }; - } - - auto - findCall (string match) - { - return [=](Entry const& entry) - { - return entry.getType() == "call" - and contains (entry.get("fun"), match); - }; - } - - - /** this filter functor is for refinement of an existing filter - * @param argSeq perform a substring match consecutively - * for each of the log entry's arguments - * @note the match also fails, when the given log entry - * has more or less arguments, than the number of - * given match expressions in `argSeq` - * @see ExtensibleFilterIter::andFilter() - */ - auto - matchArguments (ArgSeq&& argSeq) - { - return [=](Entry const& entry) - { - auto scope = entry.scope(); - for (auto const& match : argSeq) - if (isnil (scope) or not contains(*scope, match)) - return false; - else - ++scope; - - return isnil(scope); // must be exhausted by now - }; // otherwise the sizes do not match... - } - - - /** refinement filter, to cover all arguments by regular expression(s) - * @param regExpSeq several regular expressions, which, when applied - * consecutively until exhaustion, must cover and verify _all_ - * arguments of the log entry. - * @remarks to explain, we "consume" arguments with a regExp from the list, - * and when this one doesn't match anymore, we try the next one. - * When we'ver tried all regular expressions, we must have also - * consumed all arguments, otherwise we fail. - */ - auto - matchArgsRegExp (RExSeq&& regExpSeq) - { - return [=](Entry const& entry) - { - auto scope = entry.scope(); - for (auto const& rex : regExpSeq) - { - if (isnil (scope)) return false; - while (scope and std::regex_search(*scope, rex)) - ++scope; - } - - return isnil(scope); // must be exhausted by now - }; // otherwise we didn't get full coverage... - } - - - /** refinement filter to match on the given typeID */ - auto - matchType (string typeID) - { - return [=](Entry const& entry) - { - return contains (entry.getType(), typeID); - }; - } - - - /** refinement filter to ensure a specific attribute is present on the log entry */ - auto - ensureAttribute (string key) - { - return [=](Entry const& entry) - { - return entry.hasAttribute(key); - }; - } - - - /** refinement filter to ensure a specific attribute is present on the log entry */ - auto - matchAttribute (string key, string valueMatch) - { - return [=](Entry const& entry) - { - return entry.hasAttribute(key) - and contains (entry.get(key), valueMatch); - }; - } - - - /* === configure the underlying search engine === */ - - enum Direction { - FORWARD, BACKWARD, CURRENT - }; - - template - void - attachNextSerchStep (COND&& filter, Direction direction) - { - if (CURRENT == direction) - solution_.search (forward (filter)); - else - solution_.addStep ([predicate{forward (filter)}, direction] - (auto& filter) - { - filter.reverse (BACKWARD == direction); - filter.disableFilter(); // deactivate any filtering temporarily - ++filter; // move one step in the required direction - filter.setNewFilter (predicate); - }); - } - - template - void - refineSerach (COND&& additionalFilter) - { - solution_.addStep ([predicate{forward (additionalFilter)}] - (auto& filter) - { - filter.andFilter (predicate); - }); - } - - - public: /** final evaluation of the match query, * usually triggered from the unit test `CHECK()`. @@ -395,7 +160,7 @@ namespace test{ */ operator bool() const { - if (!isnil (violation_)) + if (not util::isnil (violation_)) { cerr << "__Log_condition_violated__\n"+violation_ <<"\n"; return false; @@ -404,174 +169,31 @@ namespace test{ } - /** - * basic search function: continue linear lookup over the elements of the - * EventLog to find a match (substring match) of the given text. The search begins - * at the current position and proceeds in the currently configured direction. - * Initially the search starts at the first record and proceeds forward. - */ - EventMatch& - locate (string match) - { - attachNextSerchStep (find(match), CURRENT); - evaluateQuery ("match(\""+match+"\")"); - return *this; - } + /* query builders to continue search at current position */ - /** basic search like locate() but with the given regular expression */ - EventMatch& - locateMatch (string regExp) - { - attachNextSerchStep (findRegExp(regExp), CURRENT); - evaluateQuery ("find-RegExp(\""+regExp+"\")"); - return *this; - } + EventMatch& locate (string match); + EventMatch& locateMatch (string regExp); + EventMatch& locateEvent (string match); + EventMatch& locateEvent (string classifier, string match); + EventMatch& locateCall (string match); - /** basic search for a matching "event" - * @param match perform a substring match against the arguments of the event - * @see beforeEvent() for a description of possible "events" - */ - EventMatch& - locateEvent (string match) - { - attachNextSerchStep (findEvent(match), CURRENT); - evaluateQuery ("match-event(\""+match+"\")"); - return *this; - } - EventMatch& - locateEvent (string classifier, string match) - { - attachNextSerchStep (findEvent(classifier,match), CURRENT); - evaluateQuery ("match-event(ID=\""+classifier+"\", \""+match+"\")"); - return *this; - } + /* query builders to find a match stepping forwards */ - /** basic search for some specific function invocation - * @param match perform a substring match against the name of the function invoked - */ - EventMatch& - locateCall (string match) - { - attachNextSerchStep (findCall(match), CURRENT); - evaluateQuery ("match-call(\""+match+"\")"); - return *this; - } + EventMatch& before (string match); + EventMatch& beforeMatch (string regExp); + EventMatch& beforeEvent (string match); + EventMatch& beforeEvent (string classifier, string match); + EventMatch& beforeCall (string match); - /** - * find a match (substring match) of the given text - * in an EventLog entry after the current position - * @remarks the name of this junctor might seem counter intuitive; - * it was chosen due to expected DSL usage: `log.verify("α").before("β")`. - * Operationally this means first to find a Record matching the substring "α" - * and then to forward from this point until hitting a record to match "β". - */ - EventMatch& - before (string match) - { - attachNextSerchStep (find(match), FORWARD); - evaluateQuery ("match(\""+match+"\")"); - return *this; - } - /** find a match with the given regular expression */ - EventMatch& - beforeMatch (string regExp) - { - attachNextSerchStep (findRegExp(regExp), FORWARD); - evaluateQuery ("find-RegExp(\""+regExp+"\")"); - return *this; - } + /* query builders to find a match stepping backwards */ - /** find a match for an "event" _after_ the current point of reference - * @remarks the term "event" designates several types of entries, which - * typically capture something happening within the observed entity. - * Especially, the following [record types](\ref lib::Record::getType()) - * qualify as event: - * - `event` - * - `error` - * - `create` - * - `destroy` - * - `logJoin` - * @param match perform a substring match against the arguments of the event - * @see ::findEvent - */ - EventMatch& - beforeEvent (string match) - { - attachNextSerchStep (findEvent(match), FORWARD); - evaluateQuery ("match-event(\""+match+"\")"); - return *this; - } - - EventMatch& - beforeEvent (string classifier, string match) - { - attachNextSerchStep (findEvent(classifier,match), FORWARD); - evaluateQuery ("match-event(ID=\""+classifier+"\", \""+match+"\")"); - return *this; - } - - /** find a match for some function invocation _after_ the current point of reference - * @param match perform a substring match against the name of the function invoked - * @see ::findCall - */ - EventMatch& - beforeCall (string match) - { - attachNextSerchStep (findCall(match), FORWARD); - evaluateQuery ("match-call(\""+match+"\")"); - return *this; - } - - EventMatch& - after (string match) - { - attachNextSerchStep (find(match), BACKWARD); - evaluateQuery ("match(\""+match+"\")", "before"); - return *this; - } - - EventMatch& - afterMatch (string regExp) - { - attachNextSerchStep (findRegExp(regExp), BACKWARD); - evaluateQuery ("find-RegExp(\""+regExp+"\")", "before"); - return *this; - } - - EventMatch& - afterEvent (string match) - { - attachNextSerchStep (findEvent(match), BACKWARD); - evaluateQuery ("match-event(\""+match+"\")", "before"); - return *this; - } - - EventMatch& - afterEvent (string classifier, string match) - { - attachNextSerchStep (findEvent(classifier,match), BACKWARD); - evaluateQuery ("match-event(ID=\""+classifier+"\", \""+match+"\")", "before"); - return *this; - } - - /** find a function invocation backwards, before the current point of reference */ - EventMatch& - afterCall (string match) - { - attachNextSerchStep (findCall(match), BACKWARD); - evaluateQuery ("match-call(\""+match+"\")", "before"); - return *this; - } - - void - refineSerach_matchArguments (ArgSeq&& argSeq) - { - string argList(util::join(argSeq)); - refineSerach (matchArguments(move(argSeq))); - evaluateQuery ("match-arguments("+argList+")"); - } + EventMatch& after (string match); + EventMatch& afterMatch (string regExp); + EventMatch& afterEvent (string match); + EventMatch& afterEvent (string classifier, string match); + EventMatch& afterCall (string match); /** refine filter to additionally require specific arguments * @remarks the refined filter works on each record qualified by the @@ -587,12 +209,6 @@ namespace test{ return *this; } - void - refineSerach_matchArgsRegExp (RExSeq&& regExpSeq, string rendered_regExps) - { - refineSerach (matchArgsRegExp (move (regExpSeq))); - evaluateQuery ("match-args-RegExp("+rendered_regExps+")"); - } /** refine filter to additionally cover all arguments * with a series of regular expressions. @@ -613,72 +229,39 @@ namespace test{ return *this; } - /** refine filter to additionally require a matching log entry type */ - EventMatch& - type (string typeID) - { - refineSerach (matchType(typeID)); - evaluateQuery ("match-type("+typeID+")"); - return *this; - } - /** refine filter to additionally require the presence an attribute */ - EventMatch& - key (string key) - { - refineSerach (ensureAttribute(key)); - evaluateQuery ("ensure-attribute("+key+")"); - return *this; - } - /** refine filter to additionally match on a specific attribute */ - EventMatch& - attrib (string key, string valueMatch) - { - refineSerach (matchAttribute(key,valueMatch)); - evaluateQuery ("match-attribute("+key+"=\""+valueMatch+"\")"); - return *this; - } + /* query builders to augment and refine the currently defined search condition*/ - /** refine filter to additionally match on the ID attribute */ - EventMatch& - id (string classifier) - { - refineSerach (matchAttribute("ID",classifier)); - evaluateQuery ("match-ID(\""+classifier+"\")"); - return *this; - } - - /** refine filter to additionally match the `'this'` attribute */ - EventMatch& - on (string targetID) - { - refineSerach (matchAttribute("this",targetID)); - evaluateQuery ("match-this(\""+targetID+"\")"); - return *this; - } - - EventMatch& - on (const char* targetID) - { - refineSerach (matchAttribute("this",targetID)); - evaluateQuery ("match-this(\""+string(targetID)+"\")"); - return *this; - } + EventMatch& type (string typeID); + EventMatch& key (string key); + EventMatch& attrib (string key, string valueMatch); + EventMatch& id (string classifier); + EventMatch& on (string targetID); + EventMatch& on (const char* targetID); template - EventMatch& - on (const X *const targetObj) + EventMatch& on (const X *const targetObj) { string targetID = idi::instanceTypeID (targetObj); return this->on(targetID); } + + private: + bool foundSolution(); + void evaluateQuery (string matchSpec, Literal rel = "after"); + + void refineSerach_matchArguments (ArgSeq&& argSeq); + void refineSerach_matchArgsRegExp (RExSeq&& regExpSeq, string rendered_regExps); }; + + + /****************************************************************//** * Helper to log and verify the occurrence of events. * The EventLog object is a front-end handle, logging flexible @@ -716,11 +299,7 @@ namespace test{ public: explicit - EventLog (string logID) - : log_(new Log) - { - log({"type=EventLogHeader", "this="+logID}); - } + EventLog (string logID); explicit EventLog (const char* logID) @@ -740,65 +319,15 @@ namespace test{ /** Merge this log into another log, forming a combined log * @param otherLog target to integrate this log's contents into. * @return reference to the merged new log instance - * @remarks EventLog uses a heap based, sharable log storage, - * where the EventLog object is just a front-end (shared ptr). - * The `joinInto` operation both integrates this logs contents - * into the other log, and then disconnects from the old storage - * and connects to the storage of the combined log. - * @warning beware of clone copies. Since copying EventLog is always a - * shallow copy, all copied handles actually refer to the same - * log storage. If you invoke `joinInto` in such a situation, - * only the current EventLog front-end handle will be rewritten - * to point to the combined log, while any other clone will - * continue to point to the original log storage. * @see TestEventLog_test::verify_logJoining() */ - EventLog& - joinInto (EventLog& otherLog) - { - Log& target = *otherLog.log_; - target.reserve (target.size() + log_->size() + 1); - target.emplace_back (log_->front()); - auto p = log_->begin(); - while (++p != log_->end()) // move our log's content into the other log - target.emplace_back(std::move(*p)); - this->log_->resize(1); - this->log({"type=joined", otherLog.getID()}); // leave a tag to indicate - otherLog.log({"type=logJoin", this->getID()}); // where the `joinInto` took place, - this->log_ = otherLog.log_; // connect this to the other storage - return *this; - } + EventLog& joinInto (EventLog& otherLog); /** purge log contents while retaining just the original Header-ID */ - EventLog& - clear() - { - string originalLogID = this->getID(); - return this->clear (originalLogID); - } - - /** purge log contents and also reset Header-ID - * @note actually we're starting a new log - * and let the previous one go away. - * @warning while this also unties any joined logs, - * other log front-ends might still hold onto - * the existing, combined log. Just we are - * detached and writing to a pristine log. - */ - EventLog& - clear (string alteredLogID) - { - log_.reset (new Log); - log({"type=EventLogHeader", "this="+alteredLogID}); - return *this; - } - - EventLog& - clear (const char* alteredLogID) - { - return clear (string{alteredLogID}); - } + EventLog& clear(); + EventLog& clear (string alteredLogID); + EventLog& clear (const char* alteredLogID); template EventLog& @@ -813,47 +342,25 @@ namespace test{ using ArgSeq = lib::diff::RecordSetup::Storage; - EventLog& - event (string text) - { - log ("event", ArgSeq{}, ArgSeq{text}); // we use this ctor variant to ensure - return *this; // that text is not misinterpreted as attribute, - } // which might happen when text contains a '=' + /** log some text as event */ + EventLog& event (string text); /** log some event, with additional ID or classifier * @param classifier info to be saved into the `ID` attribute * @param text actual payload info, to be logged as argument */ - EventLog& - event (string classifier, string text) - { - log ("event", ArgSeq{"ID="+classifier}, ArgSeq{text}); - return *this; - } + EventLog& event (string classifier, string text); /** Log occurrence of a function call with no arguments. * @param target the object or scope on which the function is invoked * @param function name of the function being invoked */ - EventLog& - call (string target, string function) - { - return call(target, function, ArgSeq()); - } + EventLog& call (string target, string function); /** Log a function call with a sequence of stringified arguments */ - EventLog& - call (string target, string function, ArgSeq&& args) - { - log ("call", ArgSeq{"fun="+function, "this="+target}, std::forward(args)); - return *this; - } + EventLog& call (string target, string function, ArgSeq&& args); - EventLog& - call (const char* target, const char* function, ArgSeq&& args) - { - return call (string(target), string(function), std::forward(args)); - } + EventLog& call (const char* target, const char* function, ArgSeq&& args); /** Log a function call with arbitrary arguments */ template @@ -882,56 +389,29 @@ namespace test{ EventLog& note (ELMS const& ...initialiser) { - log_->emplace_back(stringify (initialiser...)); + log_->emplace_back (stringify (initialiser...)); return *this; } /** Log a warning entry */ - EventLog& - warn (string text) - { - log({"type=warn", text}); - return *this; - } - + EventLog& warn (string text); /** Log an error note */ - EventLog& - error (string text) - { - log({"type=error", text}); - return *this; - } - + EventLog& error (string text); /** Log a fatal failure */ - EventLog& - fatal (string text) - { - log({"type=fatal", text}); - return *this; - } - + EventLog& fatal (string text); /** Log the creation of an object. * Such an entry can be [matched as event](\ref ::verifyEvent) */ - EventLog& - create (string text) - { - log({"type=create", text}); - return *this; - } + EventLog& create (string text); /** Log the destruction of an object. * Can be matched as event. */ - EventLog& - destroy (string text) - { - log({"type=destroy", text}); - return *this; - } + EventLog& destroy (string text); + @@ -968,13 +448,7 @@ namespace test{ * search queries, which will be executing starting from the position of the * previous match. The final result can be retrieved by `bool` conversion */ - EventMatch - verify (string match) const - { - EventMatch matcher(*log_); - matcher.locate (match); // new matcher starts linear search from first log element - return matcher; - } + EventMatch verify (string match) const; /** start a query to match with a regular expression * @param regExp definition @@ -982,13 +456,7 @@ namespace test{ * `string` representation of the log entries. * Meaning, it can also match type and attributes */ - EventMatch - verifyMatch (string regExp) const - { - EventMatch matcher(*log_); - matcher.locateMatch (regExp); - return matcher; - } + EventMatch verifyMatch (string regExp) const; /** start a query to match for some event. * @remarks only a subset of all log entries is treated as "event", @@ -997,24 +465,12 @@ namespace test{ * creation and destruction of objects count as "event". * @param match text to (substring)match against the argument logged as event */ - EventMatch - verifyEvent (string match) const - { - EventMatch matcher(*log_); - matcher.locateEvent (match); - return matcher; - } + EventMatch verifyEvent (string match) const; /** start a query to match for an specific kind of element * @param classifier select kind of event by match on type or ID */ - EventMatch - verifyEvent (string classifier, string match) const - { - EventMatch matcher(*log_); - matcher.locateEvent (classifier, match); - return matcher; - } + EventMatch verifyEvent (string classifier, string match) const; template EventMatch @@ -1026,31 +482,13 @@ namespace test{ /** start a query to match especially a function call * @param match text to match against the function name */ - EventMatch - verifyCall (string match) const - { - EventMatch matcher(*log_); - matcher.locateCall (match); - return matcher; - } + EventMatch verifyCall (string match) const; - /** start a query to ensure the given expression does _not_ match. - * @remarks the query expression is built similar to the other queries, - * but the logic of evaluation is flipped: whenever we find any match - * the overall result (when evaluating to `bool`) will be `false`. - * @warning this is not an proper exhaustive negation, since the implementation - * does not proper backtracking with a stack of choices. This becomes - * evident, when you combine `ensureNot()` with a switch in search - * direction, like e.g. using `afterXXX` at some point in the chain. - */ + /** start a query to ensure the given expression does _not_ match. */ EventMatch - ensureNot (string match) const - { - EventMatch matcher(*log_); - matcher.look_for_match_ = false; // flip logic; fail if match succeeds - matcher.locate (match); - return matcher; - } + ensureNot (string match) const; + + /** equality comparison is based on the actual log contents */ diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 8bd0f1099..0e5afa175 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -25091,9 +25091,9 @@ - + - + @@ -25128,9 +25128,9 @@ - - - + + + @@ -30775,7 +30775,7 @@ - + @@ -31611,9 +31611,9 @@

- - - + + + @@ -31920,8 +31920,8 @@ - - + + @@ -31960,6 +31960,21 @@ + + + + + + + + + + + + + + + @@ -31976,28 +31991,39 @@ - - + + - - + + - - + + - - - - + + + + - - + + + + + + +

+ geht gar nicht anders, denn diese sind templates +

+ + +
+
- - + +
From 8fa3eb2517a69c7a357f5a44e17e422f7efa5a2b Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 21 Sep 2018 05:17:54 +0200 Subject: [PATCH 102/178] NotificationDisplay: integrate with the new Revealer-Functor (closes #1162) 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 --- src/gui/ctrl/notification-hub.hpp | 4 +-- src/gui/model/expander-revealer.hpp | 5 ++-- src/gui/panel/infobox-panel.cpp | 3 +++ src/gui/widget/error-log-display.hpp | 7 +---- wiki/thinkPad.ichthyo.mm | 38 ++++++++++++++++++++++------ 5 files changed, 39 insertions(+), 18 deletions(-) diff --git a/src/gui/ctrl/notification-hub.hpp b/src/gui/ctrl/notification-hub.hpp index 638e6bc90..96f64aa49 100644 --- a/src/gui/ctrl/notification-hub.hpp +++ b/src/gui/ctrl/notification-hub.hpp @@ -152,7 +152,7 @@ namespace ctrl { doFlash() override { if (not widget_) return; - widget_->revealYourself(); + widget_->reveal(); widget_->triggerFlash(); } @@ -173,7 +173,7 @@ namespace ctrl { getWidget().expand (yes); } ); - installRevealer([&](){ getWidget().revealYourself(); }); // implementation implies also expand + installRevealer([&](){ getWidget().reveal(); }); // implementation implies also expand } ~NotificationHub() { }; diff --git a/src/gui/model/expander-revealer.hpp b/src/gui/model/expander-revealer.hpp index 8811ea96b..bdf91bba0 100644 --- a/src/gui/model/expander-revealer.hpp +++ b/src/gui/model/expander-revealer.hpp @@ -36,10 +36,10 @@ ** ** The base class of all [tangible UI elements](\ref Tangible) provides a default implementation ** for these generic interaction mechanism: It offers slots to connect UI signals against, and - ** it understands the _`mark`_ messages "`expand`" and "`revealYourself`". These are implemented + ** it understands the \em `mark` messages `"expand"` and `"revealYourself"`. 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 - ** _`note`_ message on the UI-Bus, so to make those state changes persistent. However, in order + ** \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" @@ -53,6 +53,7 @@ ** 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) ** */ diff --git a/src/gui/panel/infobox-panel.cpp b/src/gui/panel/infobox-panel.cpp index 2c8d86996..c17910b73 100644 --- a/src/gui/panel/infobox-panel.cpp +++ b/src/gui/panel/infobox-panel.cpp @@ -98,6 +98,9 @@ namespace panel{ theLog_->expand = model::Expander{[&]() { 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(); diff --git a/src/gui/widget/error-log-display.hpp b/src/gui/widget/error-log-display.hpp index 376ceea2f..7e4d59a7e 100644 --- a/src/gui/widget/error-log-display.hpp +++ b/src/gui/widget/error-log-display.hpp @@ -98,6 +98,7 @@ namespace widget { } model::Expander expand; + model::Revealer reveal; void @@ -153,12 +154,6 @@ namespace widget { UNIMPLEMENTED ("paint and set callback-timeout to return to normal state"); } - void - revealYourself() - { - UNIMPLEMENTED ("store a link to parent. Ask parent to reveal us. Switch to expanded display"); - } - private:/* ===== Internals ===== */ diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 0e5afa175..b77d5ef85 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -2045,8 +2045,23 @@
- + + + + + + + + + + + + + + + + @@ -2152,7 +2167,7 @@ - + @@ -2387,8 +2402,8 @@ - - + + @@ -2414,14 +2429,17 @@ - + + + + - + @@ -2457,7 +2475,7 @@ - + @@ -2471,6 +2489,7 @@

+
@@ -18835,6 +18854,9 @@
+ + +
@@ -37962,7 +37984,7 @@ - + From 5a7a5a57206902f809e5d52cac2787eeb727e6bf Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 21 Sep 2018 13:46:42 +0200 Subject: [PATCH 103/178] DOC: fix syntax of some doxygen links seemingly we really need the \ref in the link target expression --- src/common/guifacade.cpp | 2 +- src/common/query/query-resolver.hpp | 2 +- src/gui/ctrl/bus-term.hpp | 2 +- src/gui/ctrl/core-service.hpp | 8 ++++---- src/gui/ctrl/nexus.hpp | 4 ++-- src/gui/interact/view-locator.cpp | 4 ++-- src/gui/interact/view-spec-dsl.hpp | 2 +- src/gui/model/tangible.cpp | 2 +- src/gui/model/tangible.hpp | 8 ++++---- src/gui/notification-service.cpp | 2 +- src/gui/notification-service.hpp | 2 +- src/gui/timeline/header-pane-widget.hpp | 2 +- src/gui/timeline/layout-manager.hpp | 2 +- src/gui/timeline/timeline-controller.hpp | 2 +- src/gui/timeline/timeline-widget.hpp | 2 +- src/gui/timeline/track-body.hpp | 2 +- src/gui/ui-bus.cpp | 10 +++++----- src/gui/ui-bus.hpp | 4 ++-- src/include/display-facade.h | 2 +- src/include/session-command-facade.h | 4 ++-- src/lib/diff/list-diff.hpp | 2 +- src/lib/diff/mutation-message.hpp | 2 +- src/lib/format-cout.hpp | 2 +- src/lib/format-obj.cpp | 2 +- src/lib/format-obj.hpp | 2 +- src/lib/format-util.hpp | 2 +- src/lib/iter-chain-search.hpp | 2 +- src/lib/iter-cursor.hpp | 6 +++--- src/lib/meta/trait.hpp | 4 ++-- src/lib/meta/util.hpp | 2 +- src/proc/control/command-instance-manager.hpp | 2 +- src/proc/control/command-setup.cpp | 4 ++-- src/proc/control/command-setup.hpp | 2 +- src/proc/control/command.hpp | 4 ++-- src/proc/control/media-impl-lib.hpp | 2 +- src/proc/engine/dispatcher.hpp | 2 +- tests/gui/abstract-tangible-test.cpp | 2 +- tests/gui/bus-term-test.cpp | 4 ++-- tests/gui/model/element-access-test.cpp | 2 +- tests/gui/test/mock-elm.hpp | 2 +- tests/gui/test/test-nexus.cpp | 10 +++++----- tests/gui/test/test-nexus.hpp | 4 ++-- tests/library/iter-chain-search-test.cpp | 2 +- tests/library/iter-cursor-test.cpp | 2 +- 44 files changed, 69 insertions(+), 69 deletions(-) diff --git a/src/common/guifacade.cpp b/src/common/guifacade.cpp index bde729f1e..25779272a 100644 --- a/src/common/guifacade.cpp +++ b/src/common/guifacade.cpp @@ -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. diff --git a/src/common/query/query-resolver.hpp b/src/common/query/query-resolver.hpp index 52ea1f231..0a6f2759d 100644 --- a/src/common/query/query-resolver.hpp +++ b/src/common/query/query-resolver.hpp @@ -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 diff --git a/src/gui/ctrl/bus-term.hpp b/src/gui/ctrl/bus-term.hpp index b2deb406d..d4b6f918c 100644 --- a/src/gui/ctrl/bus-term.hpp +++ b/src/gui/ctrl/bus-term.hpp @@ -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. diff --git a/src/gui/ctrl/core-service.hpp b/src/gui/ctrl/core-service.hpp index 61cb7775a..8c23fc7b7 100644 --- a/src/gui/ctrl/core-service.hpp +++ b/src/gui/ctrl/core-service.hpp @@ -47,8 +47,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 +65,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). ** diff --git a/src/gui/ctrl/nexus.hpp b/src/gui/ctrl/nexus.hpp index eab41a8ea..205c88dd2 100644 --- a/src/gui/ctrl/nexus.hpp +++ b/src/gui/ctrl/nexus.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. diff --git a/src/gui/interact/view-locator.cpp b/src/gui/interact/view-locator.cpp index ecfb2e848..350864201 100644 --- a/src/gui/interact/view-locator.cpp +++ b/src/gui/interact/view-locator.cpp @@ -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 diff --git a/src/gui/interact/view-spec-dsl.hpp b/src/gui/interact/view-spec-dsl.hpp index 120be8581..1a0dd6b2e 100644 --- a/src/gui/interact/view-spec-dsl.hpp +++ b/src/gui/interact/view-spec-dsl.hpp @@ -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 diff --git a/src/gui/model/tangible.cpp b/src/gui/model/tangible.cpp index dc6e95d39..e81daf3dd 100644 --- a/src/gui/model/tangible.cpp +++ b/src/gui/model/tangible.cpp @@ -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) ** */ diff --git a/src/gui/model/tangible.hpp b/src/gui/model/tangible.hpp index 170d14e62..fb07452a4 100644 --- a/src/gui/model/tangible.hpp +++ b/src/gui/model/tangible.hpp @@ -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.
Any such element is connected to the [UI-Bus](ui-bus.hpp). + ** within the Lumiera UI.
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 ** @@ -154,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 diff --git a/src/gui/notification-service.cpp b/src/gui/notification-service.cpp index f3cdc5032..2862a1fb2 100644 --- a/src/gui/notification-service.cpp +++ b/src/gui/notification-service.cpp @@ -273,7 +273,7 @@ namespace gui { /** - * 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. diff --git a/src/gui/notification-service.hpp b/src/gui/notification-service.hpp index 93dd82666..20ea60c9f 100644 --- a/src/gui/notification-service.hpp +++ b/src/gui/notification-service.hpp @@ -68,7 +68,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. */ diff --git a/src/gui/timeline/header-pane-widget.hpp b/src/gui/timeline/header-pane-widget.hpp index fdc44d0a0..ef4bf8a29 100644 --- a/src/gui/timeline/header-pane-widget.hpp +++ b/src/gui/timeline/header-pane-widget.hpp @@ -33,7 +33,7 @@ ** 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). ** ** @todo WIP-WIP-WIP as of 12/2016 ** diff --git a/src/gui/timeline/layout-manager.hpp b/src/gui/timeline/layout-manager.hpp index e73d52384..fb13c20f3 100644 --- a/src/gui/timeline/layout-manager.hpp +++ b/src/gui/timeline/layout-manager.hpp @@ -57,7 +57,7 @@ ** ** 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 + ** record, which is passed to the [Element's allocate function](\ref Element::allocate). The element ** in turn has the liability to walks 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 diff --git a/src/gui/timeline/timeline-controller.hpp b/src/gui/timeline/timeline-controller.hpp index 75cb0f854..23b78b782 100644 --- a/src/gui/timeline/timeline-controller.hpp +++ b/src/gui/timeline/timeline-controller.hpp @@ -77,7 +77,7 @@ namespace timeline { /** * 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, diff --git a/src/gui/timeline/timeline-widget.hpp b/src/gui/timeline/timeline-widget.hpp index 1128cf871..f968b3849 100644 --- a/src/gui/timeline/timeline-widget.hpp +++ b/src/gui/timeline/timeline-widget.hpp @@ -38,7 +38,7 @@ ** 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 + ** 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 diff --git a/src/gui/timeline/track-body.hpp b/src/gui/timeline/track-body.hpp index 5e271115f..df8479122 100644 --- a/src/gui/timeline/track-body.hpp +++ b/src/gui/timeline/track-body.hpp @@ -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 diff --git a/src/gui/ui-bus.cpp b/src/gui/ui-bus.cpp index 999dcedce..d6c3233f7 100644 --- a/src/gui/ui-bus.cpp +++ b/src/gui/ui-bus.cpp @@ -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. diff --git a/src/gui/ui-bus.hpp b/src/gui/ui-bus.hpp index 6455b9cfb..d6788528d 100644 --- a/src/gui/ui-bus.hpp +++ b/src/gui/ui-bus.hpp @@ -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 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. diff --git a/src/include/display-facade.h b/src/include/display-facade.h index 270dc6172..82e9e846a 100644 --- a/src/include/display-facade.h +++ b/src/include/display-facade.h @@ -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 ** diff --git a/src/include/session-command-facade.h b/src/include/session-command-facade.h index 5c1205e8d..ce49d9f28 100644 --- a/src/include/session-command-facade.h +++ b/src/include/session-command-facade.h @@ -75,7 +75,7 @@ namespace control { * * The service exposed through this facade offers dedicated support for * the _standard command cycle_, as is typically performed from the UI. - * Such a usage cycle starts with ["opening"](#cycle) a local anonymous + * Such a usage cycle starts with ["opening"](\ref #cycle) a local anonymous * clone copy from the global command definition, which is then used * in further calls to be outfitted with actual arguments and finally * to be handed over to the dispatcher for execution. @@ -87,7 +87,7 @@ namespace control { * use the embedded #facade factory, which yields a proxy to route any * calls through the lumieraorg_SessionCommand interface * @throws lumiera::error::State when interface is not opened - * @see [Command system](command.hpp) + * @see [Command system](\ref command.hpp) * @see SessionCommandFunction_test */ class SessionCommand diff --git a/src/lib/diff/list-diff.hpp b/src/lib/diff/list-diff.hpp index 45e151e1b..3b90d61cc 100644 --- a/src/lib/diff/list-diff.hpp +++ b/src/lib/diff/list-diff.hpp @@ -23,7 +23,7 @@ /** @file list-diff.hpp ** A token language to represent changes in a list of elements. - ** In combination with the [DiffLanguage framework](diff-langue.hpp), this building + ** In combination with the [DiffLanguage framework](\ref diff-langue.hpp), this building ** block defines the set of operations to express changes in a given list of elements. ** By implementing the lib::diff::ListDiffInterpreter interface (visitor), a concrete ** usage can receive such a diff description and e.g. apply it to a target data structure. diff --git a/src/lib/diff/mutation-message.hpp b/src/lib/diff/mutation-message.hpp index 416bec91b..ff146cdac 100644 --- a/src/lib/diff/mutation-message.hpp +++ b/src/lib/diff/mutation-message.hpp @@ -256,7 +256,7 @@ namespace diff{ * becomes the new generator and the old generator object is released, * since the assignment of the new backend typically removes the last * reference in the smart-ptr managing the generation backend. This - * process can be repeated and then the [diagnostics](operator string()) + * process can be repeated and then the [diagnostics](\ref operator string()) * will show the remainder of the sequence _left at that point._ */ inline MutationMessage& diff --git a/src/lib/format-cout.hpp b/src/lib/format-cout.hpp index 10840e1a1..3b7b65683 100644 --- a/src/lib/format-cout.hpp +++ b/src/lib/format-cout.hpp @@ -45,7 +45,7 @@ ** @see FormatCOUT_test ** @see FormatHelper_test ** @see [generic string conversion helper](\ref util::toString() ) - ** @see [frontend for boost::format, printf-style](format-string.hpp) + ** @see [frontend for boost::format, printf-style](\ref format-string.hpp) ** */ diff --git a/src/lib/format-obj.cpp b/src/lib/format-obj.cpp index 9de89b7d4..b7eb0d26a 100644 --- a/src/lib/format-obj.cpp +++ b/src/lib/format-obj.cpp @@ -31,7 +31,7 @@ ** investigate object contents and show types and addresses. They ** are referred from our [lightweight string converter](\ref ** util::StringConv), but also from the util::toString() function - ** and more common [formatting utils](format-util.hpp). + ** and more common [formatting utils](\ref format-util.hpp). ** ** @see FormatHelper_test ** @see FormatString_test diff --git a/src/lib/format-obj.hpp b/src/lib/format-obj.hpp index 562269e11..a4f7c8a87 100644 --- a/src/lib/format-obj.hpp +++ b/src/lib/format-obj.hpp @@ -38,7 +38,7 @@ ** the way we did it for `boost::format`? This would reduce inclusion cost... ** ** @see FormatHelper_test - ** @see [frontend for boost::format, printf-style](format-string.hpp) + ** @see [frontend for boost::format, printf-style](\ref format-string.hpp) ** */ diff --git a/src/lib/format-util.hpp b/src/lib/format-util.hpp index eebf22638..3d926e9f5 100644 --- a/src/lib/format-util.hpp +++ b/src/lib/format-util.hpp @@ -30,7 +30,7 @@ ** through our [generic string conversion](\ref util::toString) ** ** @see FormatHelper_test - ** @see [frontend for boost::format, printf-style](format-string.hpp) + ** @see [frontend for boost::format, printf-style](\ref format-string.hpp) ** */ diff --git a/src/lib/iter-chain-search.hpp b/src/lib/iter-chain-search.hpp index d8913318c..084a24851 100644 --- a/src/lib/iter-chain-search.hpp +++ b/src/lib/iter-chain-search.hpp @@ -68,7 +68,7 @@ ** @see IterChainSearch_test ** @see IterCursor_test ** @see iter-tree-explorer.hpp - ** @see [usage example](event-log.hpp) + ** @see [usage example](\ref event-log.hpp) ** */ diff --git a/src/lib/iter-cursor.hpp b/src/lib/iter-cursor.hpp index 259057956..013ed975e 100644 --- a/src/lib/iter-cursor.hpp +++ b/src/lib/iter-cursor.hpp @@ -26,12 +26,12 @@ ** This wrapper relies on the ability of typical STL container iterators ** to work in both directions, similar to std::reverse_iterator. ** Yet it is a single, self-contained element and in compliance to the - ** ["Lumiera Forward Iterator"](iter-adapter.hpp) concept. But it has + ** ["Lumiera Forward Iterator"](\ref iter-adapter.hpp) concept. But it has ** the additional ability to [switch the working direction](\ref IterCursor::switchDir). ** ** @see IterCursor_test ** @see iter-adapter.hpp - ** @see [usage example](event-log.hpp) + ** @see [usage example](\ref event-log.hpp) ** */ @@ -156,7 +156,7 @@ namespace lib { * It can be built on top of any bidirectional STL iterator or similar entity, * which has an `--` operator. Initially, IterCursor will operate in forward * direction; irrespective of the current direction, it always fulfils the - * ["Lumiera Forward Iterator"](iter-adapter.hpp) concept, i.e. it can be + * ["Lumiera Forward Iterator"](\ref iter-adapter.hpp) concept, i.e. it can be * iterated until exhaustion, in which case it will evaluate to bool(false). * @note IterCursor instances can be equality compared, also taking the * current direction into account. As a special case, all diff --git a/src/lib/meta/trait.hpp b/src/lib/meta/trait.hpp index d82364655..71b2d249b 100644 --- a/src/lib/meta/trait.hpp +++ b/src/lib/meta/trait.hpp @@ -39,10 +39,10 @@ ** the ability for string conversions ** ** \par ability to iterate - ** these traits [can be used](util-foreach.hpp) to build the notion of a + ** these traits [can be used](\ref util-foreach.hpp) to build the notion of a ** generic container -- basically anything that can be enumerated. ** Within Lumiera, we frequently use our own concept of "iterability", - ** known as ["Lumiera Forward Iterator"](iter-adapter.hpp). These + ** known as ["Lumiera Forward Iterator"](\ref iter-adapter.hpp). These ** helpers here allow to unify this concept with the "Range" ** concept from the standard library (`begin()` and `end()`) ** diff --git a/src/lib/meta/util.hpp b/src/lib/meta/util.hpp index 5df0dad53..6a742c56a 100644 --- a/src/lib/meta/util.hpp +++ b/src/lib/meta/util.hpp @@ -334,7 +334,7 @@ namespace util { * representation for pretty much every language object._ This minimal solution * is defined here, to allow for built-in diagnostics for custom types without * the danger of creating much header inclusion and code size bloat. A more - * elaborate, [extended solution](lib::toString), including _lexical conversions + * elaborate, [extended solution](\ref lib::toString), including _lexical conversions * for numbers,_ is defined in format-obj.hpp * @note any exceptions during string conversion are caught and silently ignored; * the returned string indicates "↯" in this case. diff --git a/src/proc/control/command-instance-manager.hpp b/src/proc/control/command-instance-manager.hpp index 1134d9d1b..7a7f3c478 100644 --- a/src/proc/control/command-instance-manager.hpp +++ b/src/proc/control/command-instance-manager.hpp @@ -88,7 +88,7 @@ namespace control { * #getInstance. It represents an _anonymous instance_ kept alive solely by the CommandInstanceManager * (i.e. there is no registration of a command under that instanceID in the global CommandRegistry). * When done with the parametrisation, by calling #dispatch, this anonymous instance will be handed - * over to the [Dispatcher](CommandDispatch) (installed on construction). Typically, this will in fact + * over to the [Dispatcher](\ref CommandDispatch) (installed on construction). Typically, this will in fact * be the proc::control::ProcDispatcher, which runs in a dedicated thread ("session loop thread") and * maintains a queue of commands to be dispatched towards the current session. Since Command is a smart * handle, the enqueued instance will stay alive until execution and then go out of scope. But, after diff --git a/src/proc/control/command-setup.cpp b/src/proc/control/command-setup.cpp index e592325e1..690eec058 100644 --- a/src/proc/control/command-setup.cpp +++ b/src/proc/control/command-setup.cpp @@ -295,8 +295,8 @@ namespace control { * opened instance, which will then be bound and dispatched likewise. * @param instanceID global commandID or previously opened local instanceID * @param argSeq command argument tuple packaged as Record, which - * is the standard format [sent](BusTerm::act(GenNode)) for - * command execution via [UI-bus](ui-bus.hpp) + * is the standard format [sent](\ref BusTerm::act(GenNode)) for + * command execution via [UI-bus](\ref ui-bus.hpp) */ void CommandInstanceManager::bindAndDispatch (Symbol instanceID, Rec const& argSeq) diff --git a/src/proc/control/command-setup.hpp b/src/proc/control/command-setup.hpp index fedcf7871..251d665d9 100644 --- a/src/proc/control/command-setup.hpp +++ b/src/proc/control/command-setup.hpp @@ -24,7 +24,7 @@ /** @file command-setup.hpp ** Provision for setup of concrete commands for use by the UI. ** A *Proc-Layer command* is a functor, which can be parametrised with actual arguments. - ** It needs to be [defined](command-def.hpp) beforehand, which means to establish an unique + ** It needs to be [defined](\ref command-def.hpp) beforehand, which means to establish an unique ** name and to supply three functions, one for the actual command operation, one to capture ** state and one to _undo_ the effect of the command invocation. CommandSetup allows to create ** series of such definitions with minimal effort. Since any access and mutation from the UI into diff --git a/src/proc/control/command.hpp b/src/proc/control/command.hpp index 1a670785a..24b78c54e 100644 --- a/src/proc/control/command.hpp +++ b/src/proc/control/command.hpp @@ -37,8 +37,8 @@ ** stored definition backend. ** ** # Command definition, argument types and UNDO operation - ** For a command to be usable at all, a concrete [command definition](command-def.hpp) need to be supplied - ** somewhere in the code base. Typically this is done through static [command-setup bindings](command-setup.hpp). + ** For a command to be usable at all, a concrete [command definition](\ref command-def.hpp) need to be supplied + ** somewhere in the code base. Typically this is done through static [command-setup bindings](\ref command-setup.hpp). ** Such a command definition links three functions with the name-ID of the command ** - the actual command operation ** - a function to capture state diff --git a/src/proc/control/media-impl-lib.hpp b/src/proc/control/media-impl-lib.hpp index 08496ca30..d151d4954 100644 --- a/src/proc/control/media-impl-lib.hpp +++ b/src/proc/control/media-impl-lib.hpp @@ -24,7 +24,7 @@ /** @file media-impl-lib.hpp ** Abstraction to represent (metadata) of an external library to handle media data ** Lumiera delegates most actual media data processing to well established external - ** libraries. While configuring the render process, a [stream type](streamtype.hpp) + ** libraries. While configuring the render process, a [stream type](\ref streamtype.hpp) ** framework is used to gain uniform access to heterogeneous media and processing ** facilities. At some point, we need to integrate the individual capabilities of ** the referenced libraries, though. This will be done with adapter implementations, diff --git a/src/proc/engine/dispatcher.hpp b/src/proc/engine/dispatcher.hpp index 2b61ce5af..440037837 100644 --- a/src/proc/engine/dispatcher.hpp +++ b/src/proc/engine/dispatcher.hpp @@ -23,7 +23,7 @@ /** @file dispatcher.hpp ** Service abstraction within the render engine for generating render jobs. - ** On interface level, the render engine uses the notion of a [calculation stream](CalcStream) + ** On interface level, the render engine uses the notion of a [calculation stream](\ref CalcStream) ** to represent an ongoing rendering process. Within the _implementation_ of such a process, ** the Dispatcher is responsible for transforming the generic setup of such a calculation stream ** into a sequence of concrete jobs, anchored at some distinct point in time. diff --git a/tests/gui/abstract-tangible-test.cpp b/tests/gui/abstract-tangible-test.cpp index 2aa65abd2..087c4b61e 100644 --- a/tests/gui/abstract-tangible-test.cpp +++ b/tests/gui/abstract-tangible-test.cpp @@ -197,7 +197,7 @@ namespace test { * the occurrence of expected events, invocations and responses. * * ### connectivity - * Any mock element will automatically connect against the [Test-Nexus](test/test-nexus.hpp), + * Any mock element will automatically connect against the [Test-Nexus](\ref test/test-nexus.hpp), * so to be suitably rigged for unit testing. This means, there is no _live connection_ * to the session, but any command- or other messages will be captured and can be * retrieved or verified from the test code. Since lifecycle and robustness in diff --git a/tests/gui/bus-term-test.cpp b/tests/gui/bus-term-test.cpp index a4a0153f5..1b5b91e28 100644 --- a/tests/gui/bus-term-test.cpp +++ b/tests/gui/bus-term-test.cpp @@ -112,7 +112,7 @@ namespace test { /**************************************************************************//** * @test cover the standard node element (terminal element) within the UI-Bus, * with the help of an attached mock UI element. Contrary to the related - * [ui-element test](AbstractTangible_test), here we focus on the bus side + * [ui-element test](\ref AbstractTangible_test), here we focus on the bus side * of the standard interactions. * * This test enacts the fundamental generic communication patterns @@ -150,7 +150,7 @@ namespace test { /** @test build a new BusTerm and verify connectivity. * Every [tangible UI-element](\ref Tangible) bears an embedded BusTerm * member. Since the latter _requires another, up-link BusTerm_ on construction, - * connection to the [UI-Bus](ui-bus.hpp) is structurally ensured. Moreover, + * connection to the [UI-Bus](\ref ui-bus.hpp) is structurally ensured. Moreover, * when hooking up a new UI-element, the initialisation of the embedded BusTerm * will cause a down-link connection to be installed into the central routing * table within the \ref Nexus, the hub of the UI-Bus. Routing and addressing diff --git a/tests/gui/model/element-access-test.cpp b/tests/gui/model/element-access-test.cpp index c87ad38a4..281aa98c5 100644 --- a/tests/gui/model/element-access-test.cpp +++ b/tests/gui/model/element-access-test.cpp @@ -79,7 +79,7 @@ namespace test { * rather than on the real UI topology. * @see GenNodeLocationQuery * - * @see [Mock testing support](test-element-access.hpp) + * @see [Mock testing support](\ref test-element-access.hpp) * @see id-scheme.hpp * @see ViewLocator * @see ViewSpecDSL_test diff --git a/tests/gui/test/mock-elm.hpp b/tests/gui/test/mock-elm.hpp index 132fe7ddc..82baec4b2 100644 --- a/tests/gui/test/mock-elm.hpp +++ b/tests/gui/test/mock-elm.hpp @@ -101,7 +101,7 @@ namespace test{ * on the [Tangible] interface, which we mock here for unit testing. * This special implementation is instrumented to [log](\ref lib::test::EventLog) * any invocation and any messages sent or received through the UI Backbone, - * which is formed by the [UI-Bus](ui-bus.hpp). + * which is formed by the [UI-Bus](\ref ui-bus.hpp). * * @todo some usage details * @see abstract-tangible-test.cpp diff --git a/tests/gui/test/test-nexus.cpp b/tests/gui/test/test-nexus.cpp index 5deb85cda..597cc3cd8 100644 --- a/tests/gui/test/test-nexus.cpp +++ b/tests/gui/test/test-nexus.cpp @@ -26,12 +26,12 @@ ** This compilation unit provides the actual setup for running a faked ** user interface from unit tests. Test code is assumed to access those ** features through the [front-end](\ref gui::test::TestNexus), while the - ** actual implementation instances are placed [as singletons](depend.hpp) + ** actual implementation instances are placed [as singletons](\ref depend.hpp) ** - ** This test setup will mostly treat messages similar to the [real UI-Bus hub](nexus.hpp), + ** This test setup will mostly treat messages similar to the [real UI-Bus hub](\ref nexus.hpp), ** with additional [logging](\ref event-log.hpp). Since the TestNexus runs as singleton, - ** there is a single shared "nexus-log", which can be [accessed](test::Nexus::getLog) or - ** even [cleared](test::Nexus::startNewLog) through the [static front-end](test::Nexus). + ** there is a single shared "nexus-log", which can be [accessed](\ref test::Nexus::getLog) or + ** even [cleared](\ref test::Nexus::startNewLog) through the [static front-end](\ref test::Nexus). ** But there is no connection to any [core services](\ref core-service.hpp), so neither ** commands nor state marks will be processed in any way. In case the unit tests need to ** integrate with or verify these handling operations, we provide the ability to install @@ -420,7 +420,7 @@ namespace test{ } /** - * similar to the [custom command handler](Nexus::setCommandHandler) + * similar to the [custom command handler](\ref Nexus::setCommandHandler) * this hook allows to install a closure to intercept any * "state mark" messages passed over the test-UI-Bus */ diff --git a/tests/gui/test/test-nexus.hpp b/tests/gui/test/test-nexus.hpp index 73639ac6f..3cb9c8a15 100644 --- a/tests/gui/test/test-nexus.hpp +++ b/tests/gui/test/test-nexus.hpp @@ -23,7 +23,7 @@ /** @file test/test-nexus.hpp ** A fake UI backbone for investigations and unit testing. - ** Any relevant element within the Lumiera GTK UI is connected to the [UI-Bus](ui-bus.hpp) + ** Any relevant element within the Lumiera GTK UI is connected to the [UI-Bus](\ref ui-bus.hpp) ** So for testing and investigation we need a white room setup to provide an instrumented ** backbone to run any test probes against. The test::Nexus allows to [hook up](\ref testUI()) ** a generic interface element, to participate in a simulated interface interaction. @@ -64,7 +64,7 @@ namespace test{ /** * Mock UI backbone for unit testing. - * In the absence of a real UI, this simulated [UI-Bus](ui-bus.hpp) + * In the absence of a real UI, this simulated [UI-Bus](\ref ui-bus.hpp) * can be used to wire a [test probe](\ref MockElm) and address it in unit testing. * * @note behind the scenes, this is a singleton. Use the provided diff --git a/tests/library/iter-chain-search-test.cpp b/tests/library/iter-chain-search-test.cpp index e76aa14b8..714af1695 100644 --- a/tests/library/iter-chain-search-test.cpp +++ b/tests/library/iter-chain-search-test.cpp @@ -99,7 +99,7 @@ namespace test{ * * @see iter-chain-search.hpp * @see iter-cursor.hpp - * @see [usage example](event-log.hpp) + * @see [usage example](\ref event-log.hpp) */ class IterChainSearch_test : public Test { diff --git a/tests/library/iter-cursor-test.cpp b/tests/library/iter-cursor-test.cpp index ef3b19195..b70f514d9 100644 --- a/tests/library/iter-cursor-test.cpp +++ b/tests/library/iter-cursor-test.cpp @@ -78,7 +78,7 @@ namespace test{ * * @see iter-cursor.hpp * @see iter-adapter.hpp - * @see [usage example](event-log.hpp) + * @see [usage example](\ref event-log.hpp) */ class IterCursor_test : public Test { From 0be0f77c160dc545b6c68c3beb1b04d2a15e8d88 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 21 Sep 2018 14:38:38 +0200 Subject: [PATCH 104/178] NotificationDisplay: wire up simple message display --- src/gui/panel/infobox-panel.cpp | 2 +- src/gui/widget/error-log-display.hpp | 39 ++++++------ wiki/thinkPad.ichthyo.mm | 88 +++++++++++++++++++++++----- 3 files changed, 94 insertions(+), 35 deletions(-) diff --git a/src/gui/panel/infobox-panel.cpp b/src/gui/panel/infobox-panel.cpp index c17910b73..215689e97 100644 --- a/src/gui/panel/infobox-panel.cpp +++ b/src/gui/panel/infobox-panel.cpp @@ -117,7 +117,7 @@ namespace panel{ static uint bangNo{0}; static _Fmt msgTemplate{"Bang #%d\n"}; - getLog().showMsg(NOTE_WARN, msgTemplate % ++bangNo); + getLog().addMsg (msgTemplate % ++bangNo); } diff --git a/src/gui/widget/error-log-display.hpp b/src/gui/widget/error-log-display.hpp index 7e4d59a7e..01ae3ad54 100644 --- a/src/gui/widget/error-log-display.hpp +++ b/src/gui/widget/error-log-display.hpp @@ -101,33 +101,18 @@ namespace widget { model::Revealer reveal; - 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; - } - } - void clearAll() { UNIMPLEMENTED ("empty buffer and discard all error bookmarks"); } + /** just add normal information message to buffer, + * without special markup and without expanding the widget */ void addMsg (string text) { - UNIMPLEMENTED ("add normal information message to buffer"); + showMsg (NOTE_INFO, text); } void @@ -158,6 +143,24 @@ namespace widget { private:/* ===== Internals ===== */ + 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; + } + } + + Gtk::TextView textLog_; /** add message entry to the (ever growing) text buffer. diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index b77d5ef85..e87c25dd7 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -320,8 +320,8 @@ - - + + @@ -329,7 +329,7 @@ - + @@ -415,7 +415,7 @@ - + @@ -530,8 +530,8 @@ - - + + @@ -930,7 +930,8 @@ - + + @@ -1329,7 +1330,7 @@ - + @@ -1343,6 +1344,7 @@

+
@@ -1960,8 +1962,8 @@ - - + + @@ -1972,6 +1974,12 @@ + + + + + + @@ -2071,7 +2079,8 @@
- + + @@ -2147,7 +2156,7 @@ - + @@ -2473,8 +2482,8 @@ - - + + @@ -2498,8 +2507,8 @@ - - + + @@ -2543,6 +2552,12 @@ + + + + + + @@ -2593,6 +2608,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 5b14e83ebf726c7d6be1d607c6c837c5417f89da Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 23 Sep 2018 16:20:24 +0200 Subject: [PATCH 105/178] NotificationDisplay: investigate options to organise error display markup ...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 --- doc/technical/gui/guiTheme.txt | 2 + src/gui/widget/error-log-display.hpp | 32 +++- wiki/thinkPad.ichthyo.mm | 262 ++++++++++++++++++++++++++- 3 files changed, 281 insertions(+), 15 deletions(-) diff --git a/doc/technical/gui/guiTheme.txt b/doc/technical/gui/guiTheme.txt index 92fb9b3da..f28327e57 100644 --- a/doc/technical/gui/guiTheme.txt +++ b/doc/technical/gui/guiTheme.txt @@ -29,6 +29,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 diff --git a/src/gui/widget/error-log-display.hpp b/src/gui/widget/error-log-display.hpp index 01ae3ad54..16db9d413 100644 --- a/src/gui/widget/error-log-display.hpp +++ b/src/gui/widget/error-log-display.hpp @@ -63,13 +63,15 @@ //#include "lib/util.hpp" //#include -//#include +#include namespace gui { namespace widget { + using std::vector; + /** * @todo WIP-WIP as of 9/2017 @@ -79,11 +81,18 @@ namespace widget { class ErrorLogDisplay : public Gtk::ScrolledWindow { + + using Mark = Glib::RefPtr; + + vector errorMarks_; + Gtk::TextView textLog_; + public: ~ErrorLogDisplay() { }; ErrorLogDisplay() : Gtk::ScrolledWindow() + , errorMarks_{} , textLog_{} { set_size_request (200, 80); // must be > 50 for the scrollbar to work properly @@ -115,10 +124,17 @@ namespace widget { showMsg (NOTE_INFO, text); } + /** 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) { - UNIMPLEMENTED ("add error with special tag and bookmark"); + showMsg (NOTE_ERROR, text); } void @@ -161,13 +177,15 @@ namespace widget { } - 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; + * @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. + * Actually, there is always one predefined text mark [called "insert"][insert-mark], + * which corresponds to the text cursor. Thus it suffices to navigate to text end, + * insert and scroll into view. + * + * [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 */ void addEntry (string const& text) diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index e87c25dd7..0af493d80 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -1718,7 +1718,7 @@ - + @@ -1740,7 +1740,8 @@ - + + @@ -1776,7 +1777,7 @@ - + @@ -1967,11 +1968,107 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ YAGNI +

+ + +
+ +
+
+ + + + + + + + + + + @@ -3921,7 +4018,75 @@
- + + + + + + + +

+ Definition: komplexe Widget-Strukturen, +

+

+ welche nur einmal in dieser speziellen Konfiguration exisiteren, +

+

+ und dabei eine zentrale Rolle im UI spielen. +

+

+ +

+

+ Beispiel: +

+
    +
  • + Timeline +
  • +
  • + Property-Grid +
  • +
+ + +
+ + + + + + + + + +

+ Definition: speziell konfigurierte und verdrahtete Teil-Komponenten, +

+

+ welche wiederholt an verschiedensten Stellen im UI eingesetzt werden, +

+

+ und sich dort jeweils konsistent und uniform verhalten. +

+

+ Beispiel: +

+
    +
  • + Timecode-Widget +
  • +
  • + Placement-Widget +
  • +
+ + +
+ + + + @@ -3993,6 +4158,21 @@ + + + + + + + + + + + + + + + @@ -36779,12 +36959,14 @@ + + - + @@ -36822,6 +37004,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -37591,7 +37836,7 @@ - + @@ -37605,6 +37850,7 @@

+
@@ -37987,7 +38233,7 @@
- + From fa55ff63d591153a95a76ee0438f72cb37dc22e9 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 24 Sep 2018 03:30:42 +0200 Subject: [PATCH 106/178] NotificationDisplay: investigate ways to define the markup tag(s) ...with the option to expand this approach later to use a central StyleManager (#1169) --- src/gui/widget/error-log-display.hpp | 65 +++++++++++++++++++--------- wiki/thinkPad.ichthyo.mm | 16 +++++-- 2 files changed, 57 insertions(+), 24 deletions(-) diff --git a/src/gui/widget/error-log-display.hpp b/src/gui/widget/error-log-display.hpp index 16db9d413..d65127c44 100644 --- a/src/gui/widget/error-log-display.hpp +++ b/src/gui/widget/error-log-display.hpp @@ -59,7 +59,7 @@ #include "gui/gtk-base.hpp" #include "gui/model/expander-revealer.hpp" #include "include/gui-notification-facade.h" - +#include "lib/symbol.hpp" //#include "lib/util.hpp" //#include @@ -70,8 +70,30 @@ namespace gui { namespace widget { + using lib::Literal; using std::vector; + namespace { + + const Literal TAG_ERROR{"ERROR"}; /////////////////////////////////////////////////////////////TICKET #1168 : find a way to manage style of custom extended UI elements + + + using Tag = Glib::RefPtr; + + /** + * @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 tagTable) + { + Tag errorTag = Gtk::TextBuffer::Tag::create(); + errorTag->property_background() = "yellow"; ////////////////////////////////////////////TICKET #1168 : should be retrieved from a central location + tagTable->add (errorTag); + } + } + /** * @todo WIP-WIP as of 9/2017 @@ -104,6 +126,8 @@ namespace widget { set_policy (Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS); this->add (textLog_); textLog_.set_editable (false); + + populateStandardTextTags (textLog_.get_buffer()->get_tag_table()); } model::Expander expand; @@ -159,24 +183,6 @@ namespace widget { private:/* ===== Internals ===== */ - 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; - } - } - - /** 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. @@ -187,7 +193,7 @@ namespace widget { * [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 */ - void + auto addEntry (string const& text) { auto buff = textLog_.get_buffer(); @@ -195,6 +201,25 @@ namespace widget { buff->move_mark (cursor, buff->end()); buff->insert (buff->end(), text); textLog_.scroll_to (cursor); + return cursor; + } + + void + showMsg (NotifyLevel severity, string const& text) + { + //////////////////////////////////////////////////TICKET #1102 : add formatting according to the error level + switch (severity) { + case NOTE_ERROR: + errorMarks_.emplace_back( + addEntry ("ERROR: "+text)); + break; + case NOTE_WARN: + addEntry ("WARN: "+text); + break; + default: + addEntry (text); + break; + } } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 0af493d80..e986eb983 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -1985,17 +1985,24 @@ + + + + - + + + + - - + + @@ -2102,7 +2109,8 @@ - + + From 245a07bc16da1f33aae226bf6fb945e91fd28e55 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 24 Sep 2018 04:17:04 +0200 Subject: [PATCH 107/178] NotificationDisplay: integrate for warning and error display Surprise, surprise.... it really works our "bang" messages are fantastic, yellow and bold now --- src/gui/panel/infobox-panel.cpp | 2 +- src/gui/widget/error-log-display.hpp | 26 ++++++++--- wiki/thinkPad.ichthyo.mm | 69 ++++++++++++++++++---------- 3 files changed, 65 insertions(+), 32 deletions(-) diff --git a/src/gui/panel/infobox-panel.cpp b/src/gui/panel/infobox-panel.cpp index 215689e97..3d9668ace 100644 --- a/src/gui/panel/infobox-panel.cpp +++ b/src/gui/panel/infobox-panel.cpp @@ -117,7 +117,7 @@ namespace panel{ static uint bangNo{0}; static _Fmt msgTemplate{"Bang #%d\n"}; - getLog().addMsg (msgTemplate % ++bangNo); + getLog().addError (msgTemplate % ++bangNo); } diff --git a/src/gui/widget/error-log-display.hpp b/src/gui/widget/error-log-display.hpp index d65127c44..51c83665f 100644 --- a/src/gui/widget/error-log-display.hpp +++ b/src/gui/widget/error-log-display.hpp @@ -49,6 +49,9 @@ ** ** @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 /////////////////////////////////////////////////////////////TICKET #1099 : build a prototype/dummy for UI <-> Proc integration ** */ @@ -76,6 +79,7 @@ namespace widget { namespace { const Literal TAG_ERROR{"ERROR"}; /////////////////////////////////////////////////////////////TICKET #1168 : find a way to manage style of custom extended UI elements + const Literal TAG_WARN{"WARN"}; using Tag = Glib::RefPtr; @@ -88,9 +92,14 @@ namespace widget { inline void populateStandardTextTags (Glib::RefPtr tagTable) { - Tag errorTag = Gtk::TextBuffer::Tag::create(); - errorTag->property_background() = "yellow"; ////////////////////////////////////////////TICKET #1168 : should be retrieved from a central location + 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); } } @@ -194,12 +203,15 @@ namespace widget { * [insert-mark]: https://developer.gnome.org/gtkmm/3.22/classGtk_1_1TextMark.html#details */ auto - addEntry (string const& text) + addEntry (string const& text, Literal markupTagName =nullptr) { auto buff = textLog_.get_buffer(); auto cursor = buff->get_insert(); buff->move_mark (cursor, buff->end()); - buff->insert (buff->end(), text); + if (markupTagName) + buff->insert_with_tag(buff->end(), text, cuString{markupTagName}); + else + buff->insert (buff->end(), text); textLog_.scroll_to (cursor); return cursor; } @@ -211,10 +223,12 @@ namespace widget { switch (severity) { case NOTE_ERROR: errorMarks_.emplace_back( - addEntry ("ERROR: "+text)); + addEntry ("ERROR: "+text, TAG_ERROR)); + if (not expand.isExpanded()) + expand (true); break; case NOTE_WARN: - addEntry ("WARN: "+text); + addEntry ("WARN: "+text, TAG_WARN); break; default: addEntry (text); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index e986eb983..a0f6e7d0b 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -1740,10 +1740,27 @@ - + + + + + + + + +

+ brauchen wir diese zwischengeschaltete Methode showMsg()? +

+

+ wäre es nicht sinnvoller, sie direkt auf das API zu heben? +

+ + +
+
@@ -1966,13 +1983,13 @@ - - + + - - + + @@ -1983,8 +2000,8 @@ - - + + @@ -1998,22 +2015,23 @@ - - + + - - + + - + + - - + + @@ -2031,10 +2049,10 @@ - - - - + + + + @@ -2078,10 +2096,10 @@ - - - - + + + + @@ -2098,7 +2116,8 @@ - + + @@ -2624,8 +2643,8 @@ - - + + From 7f760d77e5f8b9ebb1354eff5f3c152211c690f9 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Tue, 25 Sep 2018 05:31:41 +0200 Subject: [PATCH 108/178] TestControl: prepare a location in the UI for test and diagnostics (#1074) --- src/gui/ctrl/actions.hpp | 6 +- src/gui/dialog/test-control.hpp | 99 +++++++++++++++++++++++++++++++++ src/gui/interact/wizard.cpp | 13 +++++ src/gui/interact/wizard.hpp | 4 ++ wiki/thinkPad.ichthyo.mm | 91 +++++++++++++++++++++++++++++- 5 files changed, 208 insertions(+), 5 deletions(-) create mode 100644 src/gui/dialog/test-control.hpp diff --git a/src/gui/ctrl/actions.hpp b/src/gui/ctrl/actions.hpp index 1cf4d3539..57d99d3ea 100644 --- a/src/gui/ctrl/actions.hpp +++ b/src/gui/ctrl/actions.hpp @@ -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 { + diff --git a/src/gui/dialog/test-control.hpp b/src/gui/dialog/test-control.hpp new file mode 100644 index 000000000..8a80485f1 --- /dev/null +++ b/src/gui/dialog/test-control.hpp @@ -0,0 +1,99 @@ +/* + TEST-CONTROL.hpp - child window to trigger self tests and diagnostics + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + 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 #1074 gradually augment the self-diagnostics controls in the UI + */ + + +#ifndef GUI_DIALOG_TEST_CONTROL_H +#define GUI_DIALOG_TEST_CONTROL_H + + +#include "gui/gtk-base.hpp" +#include "gui/dialog/dialog.hpp" + +namespace gui { +namespace dialog { + + + class TestControl + : public Gtk::Dialog + { + Gtk::Notebook notebook_; + + Gtk::Box page_1_; + Gtk::Button trigger_1_; + + public: + TestControl (Gtk::Window &parent) + : Dialog(_("Test and Diagnostics"), parent, Gtk::DIALOG_DESTROY_WITH_PARENT) + , page_1_{Gtk::ORIENTATION_VERTICAL} + { + using namespace Gtk; + + // Setup the overall dialog layout + set_border_width (BorderPadding); + get_content_area()->pack_start (notebook_); + + //-----Page-1------------------------------ + trigger_1_.set_label ("_doIt"); + trigger_1_.set_use_underline(); + trigger_1_.set_tooltip_markup (_("Ticket #1099:\ntrigger Proc-GUI roundtrip")); + trigger_1_.signal_clicked().connect( + mem_fun(*this, &TestControl::demoGuiRoundtrip)); + + page_1_.pack_start (trigger_1_, PACK_SHRINK); + notebook_.append_page (page_1_, _("#1099")); + + + show_all_children(); + } + + + private: /* ==== individual Test/Diagnostics routines ==== */ + + /** + * Ticket #1099 : perform a dummy round-trip to verify Proc-GUI integration. + * This routine invokes the command 'xxx' 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. + */ + void + demoGuiRoundtrip() + { + UNIMPLEMENTED ("collect command arguments and then send the command message for #1099"); + } + }; + + +}} // namespace gui::dialog +#endif /*GUI_DIALOG_TEST_CONTROL_H*/ diff --git a/src/gui/interact/wizard.cpp b/src/gui/interact/wizard.cpp index 2c7fe3b29..88f5d144d 100644 --- a/src/gui/interact/wizard.cpp +++ b/src/gui/interact/wizard.cpp @@ -115,5 +115,18 @@ namespace interact { } + /** + * Launch a non modal child window to trigger self-test actions. + * This is a tool for developers and diagnostics. The operations exposed here + * allow to trigger some hard wired actions and test routines, performing within + * the regular UI environment and on equal footing with user operated controls. + */ + void + Wizard::launchTestCtrl() + { + UNIMPLEMENTED ("create and attach a non-modal child dialog box page"); + } + + }}// namespace gui::interact diff --git a/src/gui/interact/wizard.hpp b/src/gui/interact/wizard.hpp index 9a5a28e75..d57741d45 100644 --- a/src/gui/interact/wizard.hpp +++ b/src/gui/interact/wizard.hpp @@ -64,6 +64,9 @@ namespace ctrl { class GlobalCtx; class NotificationHub; } +namespace dialog { + class TestControl; +} namespace interact { using std::unique_ptr; @@ -90,6 +93,7 @@ namespace interact { ~Wizard (); void show_HelpAbout(); + void launchTestCtrl(); static ID getErrorLogID() { return proc::asset::meta::theErrorLog_ID; } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index a0f6e7d0b..8db9c8117 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -2712,9 +2712,94 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 0c082361f360df8f9af3fdbf0278e92ddf2adb8f Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Tue, 25 Sep 2018 06:06:54 +0200 Subject: [PATCH 109/178] TestControl: hooked up a simple child-dialog (see #1174) ...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 --- src/gui/dialog/test-control.hpp | 7 +++-- src/gui/interact/wizard.cpp | 7 ++++- src/gui/interact/wizard.hpp | 1 + wiki/thinkPad.ichthyo.mm | 45 ++++++++++++++++++++++++--------- 4 files changed, 45 insertions(+), 15 deletions(-) diff --git a/src/gui/dialog/test-control.hpp b/src/gui/dialog/test-control.hpp index 8a80485f1..c8d242f8c 100644 --- a/src/gui/dialog/test-control.hpp +++ b/src/gui/dialog/test-control.hpp @@ -39,6 +39,7 @@ #include "gui/gtk-base.hpp" #include "gui/dialog/dialog.hpp" +#include "gui/ctrl/bus-term.hpp" namespace gui { namespace dialog { @@ -46,6 +47,7 @@ namespace dialog { class TestControl : public Gtk::Dialog + , ctrl::BusTerm { Gtk::Notebook notebook_; @@ -53,8 +55,9 @@ namespace dialog { Gtk::Button trigger_1_; public: - TestControl (Gtk::Window &parent) + TestControl (ctrl::BusTerm& upLink, Gtk::Window& parent) : Dialog(_("Test and Diagnostics"), parent, Gtk::DIALOG_DESTROY_WITH_PARENT) + , ctrl::BusTerm{lib::idi::EntryID{}, upLink} , page_1_{Gtk::ORIENTATION_VERTICAL} { using namespace Gtk; @@ -74,7 +77,7 @@ namespace dialog { notebook_.append_page (page_1_, _("#1099")); - show_all_children(); + show_all(); } diff --git a/src/gui/interact/wizard.cpp b/src/gui/interact/wizard.cpp index 88f5d144d..ec5a96871 100644 --- a/src/gui/interact/wizard.cpp +++ b/src/gui/interact/wizard.cpp @@ -34,6 +34,7 @@ #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" @@ -124,7 +125,11 @@ namespace interact { void Wizard::launchTestCtrl() { - UNIMPLEMENTED ("create and attach a non-modal child dialog box page"); + if (testControlWindow_) + testControlWindow_->present(); // just (re)show the existing window + else + testControlWindow_ = std::make_unique (globalCtx_.uiBus_.getAccessPoint(), + globalCtx_.windowLoc_.findActiveWindow()); } diff --git a/src/gui/interact/wizard.hpp b/src/gui/interact/wizard.hpp index d57741d45..525ef30b7 100644 --- a/src/gui/interact/wizard.hpp +++ b/src/gui/interact/wizard.hpp @@ -87,6 +87,7 @@ namespace interact { { ctrl::GlobalCtx& globalCtx_; unique_ptr notificationHub_; + unique_ptr testControlWindow_; public: Wizard (ctrl::GlobalCtx&); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 8db9c8117..03cdaa014 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -2712,10 +2712,10 @@ - - - - + + + + @@ -2723,12 +2723,12 @@ - - + + - + @@ -2746,12 +2746,30 @@ - + + + + + + + + + + + + + - - + + + + + + + + @@ -2759,8 +2777,11 @@ - - + + + + + From 74f3ab39326d88a53605b35a7f56b1d1e57e2d8a Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 26 Sep 2018 15:47:39 +0200 Subject: [PATCH 110/178] TestControl: concept draft how to simplify building notebook widgets 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. --- src/gui/dialog/test-control.hpp | 126 ++++++++++++++++++++++++-------- src/gui/interact/wizard.cpp | 4 +- wiki/thinkPad.ichthyo.mm | 70 +++++++++++++++++- 3 files changed, 165 insertions(+), 35 deletions(-) diff --git a/src/gui/dialog/test-control.hpp b/src/gui/dialog/test-control.hpp index c8d242f8c..ad99a4224 100644 --- a/src/gui/dialog/test-control.hpp +++ b/src/gui/dialog/test-control.hpp @@ -40,61 +40,123 @@ #include "gui/gtk-base.hpp" #include "gui/dialog/dialog.hpp" #include "gui/ctrl/bus-term.hpp" +#include "lib/scoped-ptrvect.hpp" +//#include "lib/meta/function.hpp" +#include "lib/nocopy.hpp" + +#include namespace gui { namespace dialog { + using std::forward; + + struct FrameVBox + { + Gtk::Frame frame; + Gtk::Box box; + + FrameVBox (cuString label) + : frame{label} + , box{Gtk::ORIENTATION_VERTICAL} + { + 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); + } + }; + + class Page + : public Gtk::Box + , util::NonCopyable + { + public: + Page() + : Gtk::Box{Gtk::ORIENTATION_VERTICAL} + { } + + virtual ~Page() { } + }; + + class Notebook + : public Gtk::Notebook + , lib::ScopedPtrVect + { + public: + template + Notebook& + buildPage (cuString pageLabel, ARGS&& ...ctorArgs) + { + Gtk::Notebook::append_page (this->manage (new PAG(forward (ctorArgs)...)) + ,pageLabel); + return *this; + } + }; class TestControl : public Gtk::Dialog , ctrl::BusTerm { - Gtk::Notebook notebook_; + using Bus = ctrl::BusTerm&; + Bus busTerm() { return *this; } - Gtk::Box page_1_; - Gtk::Button trigger_1_; + + struct Page1 : Page + { + FrameVBox seg_1_{_("log notification")}, + seg_2_{_("mark via UI-Bus")}; + Gtk::Button trigger_1_; + + Page1(Bus bus) + { + trigger_1_.set_label ("_doIt"); + trigger_1_.set_use_underline(); + trigger_1_.set_tooltip_markup (_("Ticket #1099:\ntrigger Proc-GUI roundtrip")); + trigger_1_.signal_clicked().connect( + mem_fun(*this, &Page1::demoGuiRoundtrip)); + + seg_1_.pack_start (trigger_1_, Gtk::PACK_SHRINK); + pack_start (seg_1_); + pack_start (seg_2_); + } + + /** + * Ticket #1099 : perform a dummy round-trip to verify Proc-GUI integration. + * This routine invokes the command 'xxx' 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. + */ + void + demoGuiRoundtrip() + { + UNIMPLEMENTED ("collect command arguments and then send the command message for #1099"); + } + }; + + + Notebook notebook_; public: TestControl (ctrl::BusTerm& upLink, Gtk::Window& parent) : Dialog(_("Test and Diagnostics"), parent, Gtk::DIALOG_DESTROY_WITH_PARENT) , ctrl::BusTerm{lib::idi::EntryID{}, upLink} - , page_1_{Gtk::ORIENTATION_VERTICAL} { - using namespace Gtk; - // Setup the overall dialog layout set_border_width (BorderPadding); get_content_area()->pack_start (notebook_); - //-----Page-1------------------------------ - trigger_1_.set_label ("_doIt"); - trigger_1_.set_use_underline(); - trigger_1_.set_tooltip_markup (_("Ticket #1099:\ntrigger Proc-GUI roundtrip")); - trigger_1_.signal_clicked().connect( - mem_fun(*this, &TestControl::demoGuiRoundtrip)); - - page_1_.pack_start (trigger_1_, PACK_SHRINK); - notebook_.append_page (page_1_, _("#1099")); - + // construct and wire the pages... + notebook_.buildPage (_("#1099"), busTerm()); show_all(); } - - - private: /* ==== individual Test/Diagnostics routines ==== */ - - /** - * Ticket #1099 : perform a dummy round-trip to verify Proc-GUI integration. - * This routine invokes the command 'xxx' 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. - */ - void - demoGuiRoundtrip() - { - UNIMPLEMENTED ("collect command arguments and then send the command message for #1099"); - } }; diff --git a/src/gui/interact/wizard.cpp b/src/gui/interact/wizard.cpp index ec5a96871..accf9ccac 100644 --- a/src/gui/interact/wizard.cpp +++ b/src/gui/interact/wizard.cpp @@ -118,8 +118,8 @@ namespace interact { /** * Launch a non modal child window to trigger self-test actions. - * This is a tool for developers and diagnostics. The operations exposed here - * allow to trigger some hard wired actions and test routines, performing within + * 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 diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 03cdaa014..c1ca05fbe 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -2771,6 +2771,13 @@ + + + + + + + @@ -2824,7 +2831,9 @@ - + + + @@ -4219,6 +4228,65 @@ + + + + + + +

+ vorbereitete Grundstrukturen für immer wiederkehrende Setups +

+ + +
+ + + + + + + + + + + + + + + + + + + + + + +

+ ...denn das erscheint mir bodenständiger. +

+

+ GUI-Programmierung muß bodenstäding bleiben, +

+

+ man gewinnt hier nichts durch blendende Abstraktionen. +

+

+ +

+

+ Ich hoffe, daß der Hilfscontainer nahezu transparent gemacht werden kann. +

+

+ Und der Rest sollte so vertraut aussehen, daß es selbsterklärend wird. +

+ + +
+
+
+
+
From 77980ef024fd3363237e6934fd5bbf0f0adcc8ae Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 26 Sep 2018 16:55:42 +0200 Subject: [PATCH 111/178] TestControl: the first tangible UI feedback caused via UI-Bus (see #1099) 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 --- src/gui/dialog/test-control.hpp | 87 ++++++++++++++++++++++------ src/gui/widget/error-log-display.hpp | 1 + wiki/thinkPad.ichthyo.mm | 14 ++++- 3 files changed, 81 insertions(+), 21 deletions(-) diff --git a/src/gui/dialog/test-control.hpp b/src/gui/dialog/test-control.hpp index ad99a4224..1e46a12c4 100644 --- a/src/gui/dialog/test-control.hpp +++ b/src/gui/dialog/test-control.hpp @@ -30,6 +30,8 @@ ** perform within the same environment as regular user interactions. ** ** @todo as of 9/2018, this is a first rough draft, relevant for #1099 ////////////////////////////TICKET #1074 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. */ @@ -41,16 +43,35 @@ #include "gui/dialog/dialog.hpp" #include "gui/ctrl/bus-term.hpp" #include "lib/scoped-ptrvect.hpp" -//#include "lib/meta/function.hpp" +#include "lib/diff/gen-node.hpp" #include "lib/nocopy.hpp" #include +#if true /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1099 : WIP-WIP-WIP +namespace proc { + namespace asset { + namespace meta { + class ErrorLog; + + extern lib::idi::EntryID theErrorLog_ID; +} } } +#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1099 : WIP-WIP-WIP namespace gui { namespace dialog { using std::forward; + using lib::diff::GenNode; + + /** + * 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 FrameVBox { Gtk::Frame frame; @@ -72,6 +93,7 @@ namespace dialog { } }; + /** explicitly named shortcut for the typical dialog page content holder */ class Page : public Gtk::Box , util::NonCopyable @@ -80,10 +102,29 @@ namespace dialog { Page() : Gtk::Box{Gtk::ORIENTATION_VERTICAL} { } - - virtual ~Page() { } }; + /** + * 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 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 [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 @@ -99,14 +140,30 @@ namespace dialog { } }; + + /** + * 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 - , ctrl::BusTerm { using Bus = ctrl::BusTerm&; - Bus busTerm() { return *this; } + + Bus uiBus_; + Notebook notebook_; + /** + * Ticket #1099 : perform a dummy round-trip to verify Proc-GUI integration. + * This routine invokes the command 'xxx' 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 { FrameVBox seg_1_{_("log notification")}, @@ -119,41 +176,35 @@ namespace dialog { trigger_1_.set_use_underline(); trigger_1_.set_tooltip_markup (_("Ticket #1099:\ntrigger Proc-GUI roundtrip")); trigger_1_.signal_clicked().connect( - mem_fun(*this, &Page1::demoGuiRoundtrip)); + [&]{ demoGuiRoundtrip(bus); }); seg_1_.pack_start (trigger_1_, Gtk::PACK_SHRINK); pack_start (seg_1_); pack_start (seg_2_); } - /** - * Ticket #1099 : perform a dummy round-trip to verify Proc-GUI integration. - * This routine invokes the command 'xxx' 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. - */ void - demoGuiRoundtrip() + demoGuiRoundtrip (Bus bus) { - UNIMPLEMENTED ("collect command arguments and then send the command message for #1099"); + TODO ("collect command arguments and then send the command message for #1099"); + ID errorLogID = proc::asset::meta::theErrorLog_ID; + bus.mark (errorLogID, GenNode{"Message", "Lalü"}); } }; - Notebook notebook_; public: TestControl (ctrl::BusTerm& upLink, Gtk::Window& parent) : Dialog(_("Test and Diagnostics"), parent, Gtk::DIALOG_DESTROY_WITH_PARENT) - , ctrl::BusTerm{lib::idi::EntryID{}, upLink} + , uiBus_{upLink} { // Setup the overall dialog layout set_border_width (BorderPadding); get_content_area()->pack_start (notebook_); // construct and wire the pages... - notebook_.buildPage (_("#1099"), busTerm()); + notebook_.buildPage (_("#1099"), uiBus_); show_all(); } diff --git a/src/gui/widget/error-log-display.hpp b/src/gui/widget/error-log-display.hpp index 51c83665f..aba28a069 100644 --- a/src/gui/widget/error-log-display.hpp +++ b/src/gui/widget/error-log-display.hpp @@ -212,6 +212,7 @@ namespace widget { buff->insert_with_tag(buff->end(), text, cuString{markupTagName}); else buff->insert (buff->end(), text); + buff->insert (buff->end(), "\n"); textLog_.scroll_to (cursor); return cursor; } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index c1ca05fbe..194005921 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -2771,8 +2771,8 @@ - - + + @@ -2786,6 +2786,13 @@ + + + + + + + @@ -4252,7 +4259,8 @@ - + + From 10b9abd79ba5f744002905681d89781dcdeec285 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 27 Sep 2018 04:15:27 +0200 Subject: [PATCH 112/178] TestControl: build the necessary control widgets - 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... --- src/gui/dialog/test-control.hpp | 79 ++++++++++++++++++++++++++++----- wiki/thinkPad.ichthyo.mm | 38 +++++++++++----- 2 files changed, 94 insertions(+), 23 deletions(-) diff --git a/src/gui/dialog/test-control.hpp b/src/gui/dialog/test-control.hpp index 1e46a12c4..4fbfa3cac 100644 --- a/src/gui/dialog/test-control.hpp +++ b/src/gui/dialog/test-control.hpp @@ -72,14 +72,15 @@ namespace dialog { * as the widget hierarchy is concerned. Both parts are publicly accessible * as members, while providing a shortcut for publishing the box. */ - struct FrameVBox + struct FrameBox { Gtk::Frame frame; Gtk::Box box; - FrameVBox (cuString label) + FrameBox (cuString label, + Gtk::Orientation orientation =Gtk::ORIENTATION_VERTICAL) : frame{label} - , box{Gtk::ORIENTATION_VERTICAL} + , box{orientation} { frame.add (box); } @@ -166,19 +167,75 @@ namespace dialog { */ struct Page1 : Page { - FrameVBox seg_1_{_("log notification")}, - seg_2_{_("mark via UI-Bus")}; - Gtk::Button trigger_1_; + 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 Page1(Bus bus) { - trigger_1_.set_label ("_doIt"); - trigger_1_.set_use_underline(); - trigger_1_.set_tooltip_markup (_("Ticket #1099:\ntrigger Proc-GUI roundtrip")); - trigger_1_.signal_clicked().connect( + content_.set_tooltip_markup (_("Ticket #1099:\n" + "text message content\n" + "when invoking a suitable action,\n" + "it will be passed down and sent back")); + trig_1_.set_use_underline(); + trig_1_.set_label ("_display text"); + trig_1_.set_tooltip_markup (_("Trigger Proc-GUI roundtrip\n" + "Proc invokes GuiNotification::displayInfo")); + trig_1_.signal_clicked().connect( [&]{ demoGuiRoundtrip(bus); }); - seg_1_.pack_start (trigger_1_, Gtk::PACK_SHRINK); + 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_.set_tooltip_markup (_("trigger Proc-command, which in turn\n" + "sends an state mark message, using\n" + "the message action-ID from the combobox")); + actionID_.append ("Flash"); + actionID_.append ("revealYourself"); + actionID_.append ("clearErr"); + actionID_.append ("clearMsg"); + actionID_.append ("expand"); + actionID_.append ("reset"); + actionID_.set_active(1); + actionID_.set_tooltip_markup("select the specific action-ID\n" + "when sending a mark message.\n" + "note: 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_); pack_start (seg_1_); pack_start (seg_2_); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 194005921..cbf8ab468 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -2231,7 +2231,7 @@ - + @@ -2277,7 +2277,7 @@ - + @@ -2712,8 +2712,8 @@ - - + + @@ -2773,20 +2773,22 @@ - + + - - + + - - + + + - + @@ -2803,7 +2805,18 @@ - + + + + + + + + + + + + @@ -2812,7 +2825,8 @@ - + + From 6b941b2c1e138537654d5b7bfe2a2c8970abc039 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 29 Sep 2018 01:26:35 +0200 Subject: [PATCH 113/178] TestControl: retrieve action arguments from the dialog controls ...and this also demonstrates how action code will typically be arranged within such a dialog page --- src/gui/dialog/test-control.hpp | 26 +++++++++++++++++++++----- wiki/thinkPad.ichthyo.mm | 10 ++++++---- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/gui/dialog/test-control.hpp b/src/gui/dialog/test-control.hpp index 4fbfa3cac..2377a07c8 100644 --- a/src/gui/dialog/test-control.hpp +++ b/src/gui/dialog/test-control.hpp @@ -47,6 +47,7 @@ #include "lib/nocopy.hpp" #include +#include /////////TODO #if true /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1099 : WIP-WIP-WIP namespace proc { @@ -60,6 +61,7 @@ namespace proc { namespace gui { namespace dialog { + using std::string; ////////////TODO using std::forward; using lib::diff::GenNode; @@ -177,6 +179,13 @@ namespace dialog { Gtk::Box markParam_; Gtk::ComboBoxText actionID_{true}; // has free-text entry field + string + getLogLevel() + { + return level_info_.get_active()? "NOTE_INFO" : + level_warn_.get_active()? "NOTE_WARN" : "NOTE_ERROR"; + } + Page1(Bus bus) { content_.set_tooltip_markup (_("Ticket #1099:\n" @@ -187,8 +196,6 @@ namespace dialog { trig_1_.set_label ("_display text"); trig_1_.set_tooltip_markup (_("Trigger Proc-GUI roundtrip\n" "Proc invokes GuiNotification::displayInfo")); - trig_1_.signal_clicked().connect( - [&]{ demoGuiRoundtrip(bus); }); level_warn_.join_group(level_info_); level_erro_.join_group(level_info_); @@ -225,7 +232,6 @@ namespace dialog { 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); @@ -238,14 +244,24 @@ namespace dialog { pack_start (content_); pack_start (seg_1_); pack_start (seg_2_); + + // define the action triggers... + trig_1_.signal_clicked().connect( + [&]{ demoGuiRoundtrip(bus, "displayInfo("+getLogLevel()+", \""+content_.get_text()+"\")"); }); + trig_2_.signal_clicked().connect( + [&]{ demoGuiRoundtrip(bus, "markError(\""+content_.get_text()+"\")"); }); + trig_3_.signal_clicked().connect( + [&]{ demoGuiRoundtrip(bus, "markNote(\""+content_.get_text()+"\")"); }); + trig_4_.signal_clicked().connect( + [&]{ demoGuiRoundtrip(bus, "mark("+actionID_.get_entry_text()+", \""+content_.get_text()+"\")"); }); } void - demoGuiRoundtrip (Bus bus) + demoGuiRoundtrip (Bus bus, string placeholder) { TODO ("collect command arguments and then send the command message for #1099"); ID errorLogID = proc::asset::meta::theErrorLog_ID; - bus.mark (errorLogID, GenNode{"Message", "Lalü"}); + bus.mark (errorLogID, GenNode{"Message", placeholder}); } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index cbf8ab468..e42405ae8 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -2788,19 +2788,21 @@ - + - - + + + - + + From e54556f565e1504ea4b85cfabab2debe251c0095 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 29 Sep 2018 13:37:48 +0200 Subject: [PATCH 114/178] DemoGuiRoundtrip: draft mock commands to be invoked this would be the first half of the roundtrip, the call UI -> Proc --- src/proc/cmd.hpp | 5 ++ src/proc/cmd/meta-cmd.cpp | 111 ++++++++++++++++++++++++++++++++++++-- wiki/thinkPad.ichthyo.mm | 34 +++++++----- 3 files changed, 134 insertions(+), 16 deletions(-) diff --git a/src/proc/cmd.hpp b/src/proc/cmd.hpp index e23aec071..c56ac6c2c 100644 --- a/src/proc/cmd.hpp +++ b/src/proc/cmd.hpp @@ -76,6 +76,11 @@ namespace cmd { extern CommandSetup test_meta_activateContentDiff; ////////////////////////////////TICKET #211 prefix test_ bypasses logging and immediately executes for now (2018) extern CommandSetup meta_deactivateContentDiff; + //--Demo-and-Development---- + extern CommandSetup test_meta_displayInfo; + extern CommandSetup test_meta_markError; + extern CommandSetup test_meta_markNote; + extern CommandSetup test_meta_markAction; }} // namespace proc::cmd diff --git a/src/proc/cmd/meta-cmd.cpp b/src/proc/cmd/meta-cmd.cpp index 100eddfe3..ef0cf7ed3 100644 --- a/src/proc/cmd/meta-cmd.cpp +++ b/src/proc/cmd/meta-cmd.cpp @@ -39,16 +39,23 @@ #include "proc/control/command-def.hpp" //#include "proc/mobject/session.hpp" #include "include/gui-notification-facade.h" +#include "gui/interact/wizard.hpp" //////////////////////////////////////////////////////////////TICKET #1099 : include needed temporarily //#include "lib/symbol.hpp" #include "lib/idi/entry-id.hpp" //#include "lib/format-string.hpp" -//#include +#include using lib::hash::LuidH; -//using std::string; +using gui::ID; +using gui::NOTE_INFO; +using gui::NOTE_WARN; +using gui::NOTE_ERROR; +using gui::NotifyLevel; +using gui::GuiNotification; //using util::cStr; //using util::_Fmt; +using std::string; namespace proc { @@ -80,7 +87,7 @@ COMMAND_DEFINITION (test_meta_activateContentDiff) TODO ("send a population diff starting from scratch"); TODO ("activate life-update service within the session"); // Temporary hack to get any meaningful UI <-> Proc communication - gui::GuiNotification::facade().displayInfo(gui::NOTE_INFO, "Request: population-Diff from Session."); + GuiNotification::facade().displayInfo(NOTE_INFO, "Request: population-Diff from Session."); }) .captureUndo ([]() -> bool { @@ -130,6 +137,104 @@ COMMAND_DEFINITION (meta_deactivateContentDiff) + /* ===== Demo and Development ===== */ + + /** DemoGuiRoundtrip: push a notification info message back up into the UI. + * @todo this is a demo mock setup to further development of the Proc-UI integration //////////////////TICKET #1099 : prototypical Proc-GUI communication + */ +COMMAND_DEFINITION (test_meta_displayInfo) + { + def.operation ([](NotifyLevel level, string message) + { + GuiNotification::facade().displayInfo (level, message); + }) + .captureUndo ([](NotifyLevel level, string message) -> bool + { + return true; + }) + .undoOperation ([](NotifyLevel level, string message, bool wasActive) + { + if (wasActive) + { + GuiNotification::facade().displayInfo (NOTE_WARN, "Sorry, can't do that Dave."); + } + }); + }; + + + /** DemoGuiRoundtrip: send a `markError` message back up into the UI. + * @todo this is a demo mock setup to further development of the Proc-UI integration //////////////////TICKET #1099 : prototypical Proc-GUI communication + */ +COMMAND_DEFINITION (test_meta_markError) + { + def.operation ([](string message) + { + ID errorLogID = gui::interact::Wizard::getErrorLogID(); + GuiNotification::facade().markError (errorLogID, message); + }) + .captureUndo ([](string message) -> bool + { + return true; + }) + .undoOperation ([](string message, bool wasActive) + { + if (wasActive) + { + GuiNotification::facade().displayInfo (NOTE_WARN, "Sorry, can't do that Dave."); + } + }); + }; + + + /** DemoGuiRoundtrip: send a `markNote` message back up into the UI. + * @todo this is a demo mock setup to further development of the Proc-UI integration //////////////////TICKET #1099 : prototypical Proc-GUI communication + */ +COMMAND_DEFINITION (test_meta_markNote) + { + def.operation ([](string message) + { + ID errorLogID = gui::interact::Wizard::getErrorLogID(); + GuiNotification::facade().markNote (errorLogID, message); + }) + .captureUndo ([](string message) -> bool + { + return true; + }) + .undoOperation ([](string message, bool wasActive) + { + if (wasActive) + { + GuiNotification::facade().displayInfo (NOTE_WARN, "Sorry, can't do that Dave."); + } + }); + }; + + + /** DemoGuiRoundtrip: send a generic `mark` message with given _action ID_ back up into the UI. + * @todo this is a demo mock setup to further development of the Proc-UI integration //////////////////TICKET #1099 : prototypical Proc-GUI communication + */ +COMMAND_DEFINITION (test_meta_markAction) + { + def.operation ([](string actionID, string message) + { + ID errorLogID = gui::interact::Wizard::getErrorLogID(); + UNIMPLEMENTED ("GuiNotification::facade().mark (errorLogID, actionID, message);"); + }) + .captureUndo ([](string actionID, string message) -> bool + { + return true; + }) + .undoOperation ([](string actionID, string message, bool wasActive) + { + if (wasActive) + { + GuiNotification::facade().displayInfo (NOTE_WARN, "Sorry, can't do that Dave."); + } + }); + }; + + + /** more to come here...*/ diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index e42405ae8..51b725218 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -2804,28 +2804,36 @@ + + + + + + - - - - - - - + + + + + + - - + + + + + - - + + - - + + From abfb897336772f9268ac87071f0646437a90a80d Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 29 Sep 2018 15:23:47 +0200 Subject: [PATCH 115/178] DemoGuiRoundtrip: now invoke the Proc-Layer commands from within the UI dialog --- src/gui/dialog/test-control.hpp | 32 +++++++++++++++----- src/gui/model/tangible.hpp | 28 +++++++++++++----- src/proc/cmd/meta-cmd.cpp | 52 +++++++++++++-------------------- wiki/thinkPad.ichthyo.mm | 20 +++++++++---- 4 files changed, 79 insertions(+), 53 deletions(-) diff --git a/src/gui/dialog/test-control.hpp b/src/gui/dialog/test-control.hpp index 2377a07c8..e9045c5cb 100644 --- a/src/gui/dialog/test-control.hpp +++ b/src/gui/dialog/test-control.hpp @@ -42,6 +42,9 @@ #include "gui/gtk-base.hpp" #include "gui/dialog/dialog.hpp" #include "gui/ctrl/bus-term.hpp" +#include "gui/model/tangible.hpp" +#include "proc/cmd.hpp" +#include "include/gui-notification-facade.h" #include "lib/scoped-ptrvect.hpp" #include "lib/diff/gen-node.hpp" #include "lib/nocopy.hpp" @@ -179,14 +182,27 @@ namespace dialog { Gtk::Box markParam_; Gtk::ComboBoxText actionID_{true}; // has free-text entry field - string + int getLogLevel() { - return level_info_.get_active()? "NOTE_INFO" : - level_warn_.get_active()? "NOTE_WARN" : "NOTE_ERROR"; + return level_info_.get_active()? NOTE_INFO : + level_warn_.get_active()? NOTE_WARN : NOTE_ERROR; } - Page1(Bus bus) + string + getContent() + { + return string{content_.get_text()}; + } + + string + getActionID() + { + return string{actionID_.get_entry_text()}; + } + + + Page1 (Bus bus) { content_.set_tooltip_markup (_("Ticket #1099:\n" "text message content\n" @@ -247,13 +263,13 @@ namespace dialog { // define the action triggers... trig_1_.signal_clicked().connect( - [&]{ demoGuiRoundtrip(bus, "displayInfo("+getLogLevel()+", \""+content_.get_text()+"\")"); }); + [&]{ bus.act (model::commandMessage (proc::cmd::test_meta_displayInfo, getLogLevel(), getContent())); }); trig_2_.signal_clicked().connect( - [&]{ demoGuiRoundtrip(bus, "markError(\""+content_.get_text()+"\")"); }); + [&]{ bus.act (model::commandMessage (proc::cmd::test_meta_markError, getContent())); }); trig_3_.signal_clicked().connect( - [&]{ demoGuiRoundtrip(bus, "markNote(\""+content_.get_text()+"\")"); }); + [&]{ bus.act (model::commandMessage (proc::cmd::test_meta_markNote, getContent())); }); trig_4_.signal_clicked().connect( - [&]{ demoGuiRoundtrip(bus, "mark("+actionID_.get_entry_text()+", \""+content_.get_text()+"\")"); }); + [&]{ bus.act (model::commandMessage (proc::cmd::test_meta_markAction, getActionID(), getContent())); }); } void diff --git a/src/gui/model/tangible.hpp b/src/gui/model/tangible.hpp index fb07452a4..55f8d73ae 100644 --- a/src/gui/model/tangible.hpp +++ b/src/gui/model/tangible.hpp @@ -241,18 +241,32 @@ namespace model { + /** 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 + inline lib::diff::GenNode + commandMessage (Symbol cmdID, ARGS&&... args) + { + using lib::diff::Rec; + using lib::diff::GenNode; + using GenNodeIL = std::initializer_list; + + + return GenNode (string{cmdID}, + Rec(Rec::TYPE_NIL_SYM + ,GenNodeIL{} + ,GenNodeIL {std::forward (args)...})); + } // not typed, no attributes, all arguments as children + + /** convenience shortcut to issue a command with several arguments */ template inline void Tangible::invoke (Symbol cmdID, ARGS&&... args) { - using GenNodeIL = std::initializer_list; - - invoke (cmdID, - Rec(Rec::TYPE_NIL_SYM - ,GenNodeIL{} - ,GenNodeIL {std::forward (args)...})); - } // not typed, no attributes, all arguments as children + uiBus_.act (commandMessage (cmdID, std::forward (args)...)); + } /** diff --git a/src/proc/cmd/meta-cmd.cpp b/src/proc/cmd/meta-cmd.cpp index ef0cf7ed3..2369fda9b 100644 --- a/src/proc/cmd/meta-cmd.cpp +++ b/src/proc/cmd/meta-cmd.cpp @@ -42,7 +42,7 @@ #include "gui/interact/wizard.hpp" //////////////////////////////////////////////////////////////TICKET #1099 : include needed temporarily //#include "lib/symbol.hpp" #include "lib/idi/entry-id.hpp" -//#include "lib/format-string.hpp" +#include "lib/format-string.hpp" //////////////////////////////////////////////////////////////TICKET #1099 : include needed temporarily #include @@ -54,7 +54,7 @@ using gui::NOTE_ERROR; using gui::NotifyLevel; using gui::GuiNotification; //using util::cStr; -//using util::_Fmt; +using util::_Fmt; //////////////////////////////////////////////////////////////TICKET #1099 : include needed temporarily using std::string; @@ -144,20 +144,17 @@ COMMAND_DEFINITION (meta_deactivateContentDiff) */ COMMAND_DEFINITION (test_meta_displayInfo) { - def.operation ([](NotifyLevel level, string message) + def.operation ([](int level, string message) { - GuiNotification::facade().displayInfo (level, message); + GuiNotification::facade().displayInfo (static_cast(level), message); }) - .captureUndo ([](NotifyLevel level, string message) -> bool + .captureUndo ([](int level, string message) -> string { - return true; + return _Fmt{"displayInfo(%d, '%s')"} % level % message; }) - .undoOperation ([](NotifyLevel level, string message, bool wasActive) + .undoOperation ([](int, string, string uiAction) { - if (wasActive) - { - GuiNotification::facade().displayInfo (NOTE_WARN, "Sorry, can't do that Dave."); - } + GuiNotification::facade().displayInfo (NOTE_WARN, "can not UNDO UI-Action: "+uiAction); }); }; @@ -172,16 +169,13 @@ COMMAND_DEFINITION (test_meta_markError) ID errorLogID = gui::interact::Wizard::getErrorLogID(); GuiNotification::facade().markError (errorLogID, message); }) - .captureUndo ([](string message) -> bool + .captureUndo ([](string message) -> string { - return true; + return _Fmt{"GUI::errorLog <- markError('%s')"} % message; }) - .undoOperation ([](string message, bool wasActive) + .undoOperation ([](string, string uiAction) { - if (wasActive) - { - GuiNotification::facade().displayInfo (NOTE_WARN, "Sorry, can't do that Dave."); - } + GuiNotification::facade().displayInfo (NOTE_WARN, "can not UNDO UI-Action: "+uiAction); }); }; @@ -196,16 +190,13 @@ COMMAND_DEFINITION (test_meta_markNote) ID errorLogID = gui::interact::Wizard::getErrorLogID(); GuiNotification::facade().markNote (errorLogID, message); }) - .captureUndo ([](string message) -> bool + .captureUndo ([](string message) -> string { - return true; + return _Fmt{"GUI::errorLog <- markNote('%s')"} % message; }) - .undoOperation ([](string message, bool wasActive) + .undoOperation ([](string, string uiAction) { - if (wasActive) - { - GuiNotification::facade().displayInfo (NOTE_WARN, "Sorry, can't do that Dave."); - } + GuiNotification::facade().displayInfo (NOTE_WARN, "can not UNDO UI-Action: "+uiAction); }); }; @@ -220,16 +211,13 @@ COMMAND_DEFINITION (test_meta_markAction) ID errorLogID = gui::interact::Wizard::getErrorLogID(); UNIMPLEMENTED ("GuiNotification::facade().mark (errorLogID, actionID, message);"); }) - .captureUndo ([](string actionID, string message) -> bool + .captureUndo ([](string actionID, string message) -> string { - return true; + return _Fmt{"GUI::errorLog <- mark(%s, '%s')"} % actionID % message; }) - .undoOperation ([](string actionID, string message, bool wasActive) + .undoOperation ([](string, string, string uiAction) { - if (wasActive) - { - GuiNotification::facade().displayInfo (NOTE_WARN, "Sorry, can't do that Dave."); - } + GuiNotification::facade().displayInfo (NOTE_WARN, "can not UNDO UI-Action: "+uiAction); }); }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 51b725218..880cff9e8 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -2801,15 +2801,23 @@ - + - + - - - - + + + + + + + + + + + + From 5fd3fb3d7b02ae72b5ac3eca5c65284c5ccefb9e Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 29 Sep 2018 17:34:25 +0200 Subject: [PATCH 116/178] DemoGuiRoundtrip: first successful complete roundtrip GUI->Proc->GUI (see #1099) 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. --- src/common/notification-interface-proxy.cpp | 10 +++--- src/gui/notification-service.cpp | 12 +++---- src/include/gui-notification-facade.h | 6 ++-- wiki/thinkPad.ichthyo.mm | 36 +++++++++++++++++++-- 4 files changed, 48 insertions(+), 16 deletions(-) diff --git a/src/common/notification-interface-proxy.cpp b/src/common/notification-interface-proxy.cpp index cb8951562..e6d1160f1 100644 --- a/src/common/notification-interface-proxy.cpp +++ b/src/common/notification-interface-proxy.cpp @@ -62,11 +62,11 @@ 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 mutate (ID uiElement, MutationMessage&& diff) override { _i_.mutate (&uiElement, &diff); } + void triggerGuiShutdown (string const& cause) override { _i_.triggerGuiShutdown (cStr(cause)); } public: diff --git a/src/gui/notification-service.cpp b/src/gui/notification-service.cpp index 2862a1fb2..73251c88d 100644 --- a/src/gui/notification-service.cpp +++ b/src/gui/notification-service.cpp @@ -232,27 +232,27 @@ 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 (*element), text); + _instance().markError (*static_cast (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 (*element), text); + _instance().markNote (*static_cast (element), text); } ) , 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 (*element), move(*reinterpret_cast (diff))); + _instance().mutate (*static_cast (element), move(*reinterpret_cast (diff))); } ) , LUMIERA_INTERFACE_INLINE (triggerGuiShutdown, diff --git a/src/include/gui-notification-facade.h b/src/include/gui-notification-facade.h index f85f00fc1..6babf13f7 100644 --- a/src/include/gui-notification-facade.h +++ b/src/include/gui-notification-facade.h @@ -129,9 +129,9 @@ 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, mutate, (const void*, void*)), LUMIERA_INTERFACE_SLOT (void, triggerGuiShutdown, (const char*)), ); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 880cff9e8..70f4b52e4 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -2835,8 +2835,8 @@ - - + + @@ -2865,11 +2865,43 @@ + + + + + + + + + + + + +

+ ...und zwar an dem Punkt, an dem ich eine DiffMessage in das GUI schicke. +

+

+ DiffMessage repräsentiert einen für den User verdeckten Callback +

+ + +
+
+ + + +
+ + + + + + From f97beaa7743efb42fbadf1ca0cca853c5fa914b0 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 29 Sep 2018 19:04:27 +0200 Subject: [PATCH 117/178] GuiNotification: implementation complete (closes #1047) 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 --- src/common/notification-interface-proxy.cpp | 2 + src/gui/ctrl/core-service.hpp | 7 + src/gui/ctrl/facade.hpp | 4 +- src/gui/ctrl/notification-hub.hpp | 14 +- src/gui/dialog/test-control.hpp | 27 +- src/gui/model/controller.hpp | 1 + src/gui/notification-service.cpp | 68 +- src/gui/notification-service.hpp | 14 +- src/gui/widget/error-log-display.hpp | 38 +- src/include/gui-notification-facade.h | 7 +- src/proc/cmd/meta-cmd.cpp | 6 +- wiki/thinkPad.ichthyo.mm | 984 +++++++++++--------- 12 files changed, 631 insertions(+), 541 deletions(-) diff --git a/src/common/notification-interface-proxy.cpp b/src/common/notification-interface-proxy.cpp index e6d1160f1..d767bdb31 100644 --- a/src/common/notification-interface-proxy.cpp +++ b/src/common/notification-interface-proxy.cpp @@ -42,6 +42,7 @@ namespace gui { namespace lumiera { namespace facade { using gui::ID; + using lib::diff::GenNode; using lib::diff::MutationMessage; @@ -65,6 +66,7 @@ namespace facade { 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)); } diff --git a/src/gui/ctrl/core-service.hpp b/src/gui/ctrl/core-service.hpp index 8c23fc7b7..72b87e16d 100644 --- a/src/gui/ctrl/core-service.hpp +++ b/src/gui/ctrl/core-service.hpp @@ -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 @@ -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 diff --git a/src/gui/ctrl/facade.hpp b/src/gui/ctrl/facade.hpp index 6d6093d2d..ce4eafdf4 100644 --- a/src/gui/ctrl/facade.hpp +++ b/src/gui/ctrl/facade.hpp @@ -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 diff --git a/src/gui/ctrl/notification-hub.hpp b/src/gui/ctrl/notification-hub.hpp index 96f64aa49..c380c29c7 100644 --- a/src/gui/ctrl/notification-hub.hpp +++ b/src/gui/ctrl/notification-hub.hpp @@ -120,7 +120,7 @@ namespace ctrl { virtual bool doMsg (string text) override { - getWidget().addMsg (text); + getWidget().addInfo (text); return false; // logging is no persistent state } @@ -136,7 +136,6 @@ namespace ctrl { doErr (string text) override { getWidget().addError (text); - widget_->expand (true); return false; } @@ -148,6 +147,17 @@ namespace ctrl { 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() == "Warning") + getWidget().addWarn (stateMark.data.get()); + else + // forward to default handler + Controller::doMark (stateMark); + } + virtual void doFlash() override { diff --git a/src/gui/dialog/test-control.hpp b/src/gui/dialog/test-control.hpp index e9045c5cb..4a2796407 100644 --- a/src/gui/dialog/test-control.hpp +++ b/src/gui/dialog/test-control.hpp @@ -50,21 +50,12 @@ #include "lib/nocopy.hpp" #include -#include /////////TODO +#include -#if true /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1099 : WIP-WIP-WIP -namespace proc { - namespace asset { - namespace meta { - class ErrorLog; - - extern lib::idi::EntryID theErrorLog_ID; -} } } -#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1099 : WIP-WIP-WIP namespace gui { namespace dialog { - using std::string; ////////////TODO + using std::string; using std::forward; using lib::diff::GenNode; @@ -93,7 +84,7 @@ namespace dialog { operator Gtk::Widget&() { return frame; } void - pack_start(Gtk::Widget& child, Gtk::PackOptions options = Gtk::PACK_EXPAND_WIDGET, guint padding = 0) + pack_start(Gtk::Widget& child, Gtk::PackOptions options =Gtk::PACK_EXPAND_WIDGET, guint padding =0) { box.pack_start (child, options, padding); } @@ -148,7 +139,7 @@ namespace dialog { /** - * A complex, tabbed-notebook style non-modal dialog window, + * 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) @@ -165,7 +156,7 @@ namespace dialog { /** * Ticket #1099 : perform a dummy round-trip to verify Proc-GUI integration. - * This routine invokes the command 'xxx' down in Proc-Layer, passing the settings + * This routine invokes the command `test_meta_displayInfo` 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. @@ -271,14 +262,6 @@ namespace dialog { trig_4_.signal_clicked().connect( [&]{ bus.act (model::commandMessage (proc::cmd::test_meta_markAction, getActionID(), getContent())); }); } - - void - demoGuiRoundtrip (Bus bus, string placeholder) - { - TODO ("collect command arguments and then send the command message for #1099"); - ID errorLogID = proc::asset::meta::theErrorLog_ID; - bus.mark (errorLogID, GenNode{"Message", placeholder}); - } }; diff --git a/src/gui/model/controller.hpp b/src/gui/model/controller.hpp index 802c47d44..a6df8f52d 100644 --- a/src/gui/model/controller.hpp +++ b/src/gui/model/controller.hpp @@ -100,6 +100,7 @@ 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?? diff --git a/src/gui/notification-service.cpp b/src/gui/notification-service.cpp index 73251c88d..529a60449 100644 --- a/src/gui/notification-service.cpp +++ b/src/gui/notification-service.cpp @@ -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 ** */ @@ -53,6 +60,7 @@ #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" @@ -60,6 +68,7 @@ extern "C" { #include "common/interface-descriptor.h" } +#include #include @@ -68,8 +77,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 { @@ -84,19 +96,29 @@ 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)); - ID errorLogID = interact::Wizard::getErrorLogID(); - ////////////////////////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{"Warning", text}); + break; + default: + throw lumiera::error::Logic (_Fmt{"UI Notification with invalid severity %d encountered. " + "Given message text was '%s'"} % severity % text); + } } void @@ -107,18 +129,24 @@ namespace gui { void - NotificationService::markNote (ID uiElement, string const& text) + NotificationService::markNote (ID uiElement, string const& text) { dispatchMsg (uiElement, GenNode{"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))); }); } @@ -247,6 +275,14 @@ namespace gui { _instance().markNote (*static_cast (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 (element), move(*reinterpret_cast (stateMark))); + } + ) , LUMIERA_INTERFACE_INLINE (mutate, void, (const void* element, void* diff), { @@ -265,9 +301,7 @@ namespace gui { ) ); - - - } // (END) facade implementation details + } //(END) facade implementation details diff --git a/src/gui/notification-service.hpp b/src/gui/notification-service.hpp index 20ea60c9f..b3390fa5c 100644 --- a/src/gui/notification-service.hpp +++ b/src/gui/notification-service.hpp @@ -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 */ @@ -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 diff --git a/src/gui/widget/error-log-display.hpp b/src/gui/widget/error-log-display.hpp index aba28a069..2d8361e46 100644 --- a/src/gui/widget/error-log-display.hpp +++ b/src/gui/widget/error-log-display.hpp @@ -152,9 +152,16 @@ namespace widget { /** just add normal information message to buffer, * without special markup and without expanding the widget */ void - addMsg (string text) + addInfo (string text) { - showMsg (NOTE_INFO, 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. @@ -167,7 +174,10 @@ namespace widget { void addError (string text) { - showMsg (NOTE_ERROR, text); + errorMarks_.emplace_back( + addEntry ("ERROR: "+text, TAG_ERROR)); + if (not expand.isExpanded()) + expand (true); } void @@ -202,7 +212,7 @@ namespace widget { * [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 */ - auto + Mark addEntry (string const& text, Literal markupTagName =nullptr) { auto buff = textLog_.get_buffer(); @@ -216,26 +226,6 @@ namespace widget { textLog_.scroll_to (cursor); return cursor; } - - void - showMsg (NotifyLevel severity, string const& text) - { - //////////////////////////////////////////////////TICKET #1102 : add formatting according to the error level - switch (severity) { - case NOTE_ERROR: - errorMarks_.emplace_back( - addEntry ("ERROR: "+text, TAG_ERROR)); - if (not expand.isExpanded()) - expand (true); - break; - case NOTE_WARN: - addEntry ("WARN: "+text, TAG_WARN); - break; - default: - addEntry (text); - break; - } - } }; diff --git a/src/include/gui-notification-facade.h b/src/include/gui-notification-facade.h index 6babf13f7..2eca7f2bc 100644 --- a/src/include/gui-notification-facade.h +++ b/src/include/gui-notification-facade.h @@ -53,6 +53,7 @@ namespace gui { using std::string; + using lib::diff::GenNode; using lib::diff::MutationMessage; using ID = lib::idi::BareEntryID const&; @@ -69,7 +70,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 +94,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 @@ -131,6 +135,7 @@ LUMIERA_INTERFACE_DECLARE (lumieraorg_GuiNotification, 0, LUMIERA_INTERFACE_SLOT (void, displayInfo, (uint, const char*)), 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*)), ); diff --git a/src/proc/cmd/meta-cmd.cpp b/src/proc/cmd/meta-cmd.cpp index 2369fda9b..6bed40e8c 100644 --- a/src/proc/cmd/meta-cmd.cpp +++ b/src/proc/cmd/meta-cmd.cpp @@ -40,9 +40,10 @@ //#include "proc/mobject/session.hpp" #include "include/gui-notification-facade.h" #include "gui/interact/wizard.hpp" //////////////////////////////////////////////////////////////TICKET #1099 : include needed temporarily -//#include "lib/symbol.hpp" +#include "lib/diff/gen-node.hpp" #include "lib/idi/entry-id.hpp" #include "lib/format-string.hpp" //////////////////////////////////////////////////////////////TICKET #1099 : include needed temporarily +//#include "lib/symbol.hpp" #include @@ -53,6 +54,7 @@ using gui::NOTE_WARN; using gui::NOTE_ERROR; using gui::NotifyLevel; using gui::GuiNotification; +using lib::diff::GenNode; //using util::cStr; using util::_Fmt; //////////////////////////////////////////////////////////////TICKET #1099 : include needed temporarily using std::string; @@ -209,7 +211,7 @@ COMMAND_DEFINITION (test_meta_markAction) def.operation ([](string actionID, string message) { ID errorLogID = gui::interact::Wizard::getErrorLogID(); - UNIMPLEMENTED ("GuiNotification::facade().mark (errorLogID, actionID, message);"); + GuiNotification::facade().mark (errorLogID, GenNode{actionID, message}); }) .captureUndo ([](string actionID, string message) -> string { diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 70f4b52e4..7336c44ce 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -213,9 +213,9 @@ - + - + @@ -329,7 +329,8 @@ - + + @@ -373,6 +374,10 @@ + + + + @@ -417,7 +422,7 @@ - + @@ -515,9 +520,36 @@ + + + + + + + + + + + +

+ zwar sind die Fasaden offen, bevor die Main-Loop läuft, +

+

+ jedoch werden genau durch diese Fasaden alle Aktionen per Dispatch +

+

+ in die GTK-Loop übertragen. Und das kann nur in der Loop selber  passieren. +

+

+ Ein zu früher bzw. zu später Aufruf "fällt einfach hinten runter" +

+ + +
+
@@ -589,8 +621,8 @@
- - + + @@ -859,38 +891,38 @@ - - - + + + - + - - + + - + - + - - + + - - + + - - + + - - + + - + @@ -905,14 +937,14 @@ - - + + - + - + @@ -920,24 +952,24 @@ - - + + - + - + - + - - + + - - + + @@ -950,38 +982,38 @@ - + - - - + + + - + - + - + - + - - + + - + - - + + - + - + - - + + @@ -1002,34 +1034,34 @@ - - + + - - - + + + - + - + - + - + - + - + - + @@ -1081,7 +1113,7 @@ - + @@ -1135,15 +1167,15 @@ - + - + - - + + @@ -1155,7 +1187,7 @@ - + @@ -1167,7 +1199,7 @@ - + @@ -1180,34 +1212,34 @@ - + - + - + - + - + - - - - - + + + + + - + - + @@ -1221,16 +1253,16 @@

- + - - - + + + - - - + + + @@ -1243,9 +1275,9 @@ - - - + + + @@ -1257,7 +1289,7 @@ - + @@ -1275,7 +1307,7 @@ - + @@ -1287,31 +1319,31 @@ - + - + - - - + + + - + - + - + - + - + - + @@ -1330,7 +1362,7 @@ - + @@ -1344,29 +1376,55 @@

- + + +
+ + + + + + +

+ Habe nochmal alle Aufrufe im Einzelnen durchgeprüft +

+
    +
  • + wenn schon das Einfügen des Funktors vor dem Dispatch wirft (out of memory),
    dann bekommt der Aufrufer diese Exception, was typischerweise tödlich ist.
    +
  • +
  • + aber alles, was innerhalb dieses Funktors passiert, wird abgefangen und geloggt.
    +
  • +
+

+ mehr erscheint mir nicht sinnvoll; behandeln kann man solche Fehler ohnehin nicht.

+

+ + +
+
- + - + - + - + - + - + - + @@ -1384,9 +1442,9 @@ - + - + @@ -1419,46 +1477,46 @@ - + - - + + - + - + - + - + - + - + - + - + - + @@ -1466,13 +1524,13 @@ - - - - + + + + - - + + @@ -1485,13 +1543,13 @@ - - - + + + - + @@ -1499,7 +1557,7 @@ - + @@ -1515,6 +1573,9 @@ + + + @@ -1607,11 +1668,11 @@ - + - + @@ -1699,26 +1760,26 @@ - + - + - + - + - + - - + + - + @@ -1728,22 +1789,22 @@ - - + + - + - + - + - + @@ -1756,25 +1817,24 @@ wäre es nicht sinnvoller, sie direkt auf das API zu heben?

- -
+
- + - + - + - + - + - - + + @@ -1787,19 +1847,19 @@ - + - + - + @@ -1810,15 +1870,15 @@ - + - - - + + + - + @@ -1925,150 +1985,150 @@ - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - - + + - + - + - + - + - + - + - + - + - + - - - + + + - + - - - + + + - + - + - + - - + + - + - + - + @@ -2078,119 +2138,118 @@ YAGNI

- -
+
- + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + @@ -2198,19 +2257,19 @@ - + - + - + - + - + @@ -2231,11 +2290,11 @@ - + - + - + @@ -2250,7 +2309,7 @@ - + @@ -2264,43 +2323,43 @@ - + - + - + - + - + - + - + - + - + - + - + - + @@ -2489,12 +2548,12 @@ - + - + - + @@ -2510,7 +2569,7 @@ - + @@ -2521,23 +2580,23 @@

- + - + - + - + - + - + @@ -2558,57 +2617,57 @@ - + - + - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + @@ -2625,27 +2684,27 @@ - + - + - + - + - + - + - + - + @@ -2670,21 +2729,21 @@ - + - + - + - + - + - + @@ -2702,33 +2761,33 @@ - + - + - + - + - + - + - + - + - + - + @@ -2741,131 +2800,138 @@ - + - - - - + - + + + + + + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - - - - - - - - - - - - - - - + - + + - + + + - + - + + + + + + + + + + + + + - - - + + + - - + + - - + + - - + + - - + + - - + + + + + - + @@ -2884,8 +2950,7 @@ DiffMessage repräsentiert einen für den User verdeckten Callback

- - +
@@ -2894,70 +2959,70 @@
- + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - + + + - + - + - + @@ -4262,8 +4327,7 @@ - - + @@ -4294,8 +4358,7 @@ - - + @@ -4309,8 +4372,7 @@ vorbereitete Grundstrukturen für immer wiederkehrende Setups

- - + @@ -4353,8 +4415,7 @@ Und der Rest sollte so vertraut aussehen, daß es selbsterklärend wird.

- - +
@@ -15919,9 +15980,9 @@
- + - + @@ -17741,6 +17802,10 @@ + + + + @@ -17812,8 +17877,8 @@ - - + + @@ -25709,8 +25774,7 @@          ^~~~~~~~~~

- - +
@@ -25953,21 +26017,21 @@
- + - - - + + + - - + + - + - + @@ -32221,8 +32285,7 @@ (weil er einen Kind-Iterator leer machen könnte, ohne daß dieser gePOPpt wird)

- - +
@@ -32244,8 +32307,7 @@ Vorsicht: um sauber genau einen Schritt machen zu können, müssen wir explizit vorübergehend den Filter abschalten

- - +
@@ -32277,8 +32339,7 @@ Künftig gibt es nach jedem Fail ein Backtracking.

- - + @@ -32293,8 +32354,7 @@ sollte sich erneut ein FAIL ergeben

- -
+
@@ -32312,8 +32372,7 @@ est-event-log-test.cpp:228:  verify_callLogging: (log.ensureNot("fun").after("fun").after("fun2"))

- - + @@ -32326,8 +32385,7 @@ Verdacht: Negation

- - +
@@ -32342,8 +32400,7 @@ fun after fun matcht auf den immer gleichen Record

- - +
@@ -32407,8 +32464,7 @@ und zwar mit abgeschaltetem Filter

- - + @@ -32424,8 +32480,7 @@ damit wir exakt das nächste / vorhergehende Element bekommen

- -
+
@@ -32548,8 +32603,7 @@ geht gar nicht anders, denn diese sind templates

- - +
From f9c6a49b9bcd307c1ff23cb7182a10b77f88bc03 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 30 Sep 2018 02:16:33 +0200 Subject: [PATCH 118/178] NotificationDisplay: implement reset/clearAll --- src/gui/widget/error-log-display.hpp | 10 +- src/include/gui-notification-facade.h | 6 +- wiki/renderengine.html | 10 +- wiki/thinkPad.ichthyo.mm | 171 +++++++++++++++----------- 4 files changed, 118 insertions(+), 79 deletions(-) diff --git a/src/gui/widget/error-log-display.hpp b/src/gui/widget/error-log-display.hpp index 2d8361e46..19d2064f4 100644 --- a/src/gui/widget/error-log-display.hpp +++ b/src/gui/widget/error-log-display.hpp @@ -62,6 +62,7 @@ #include "gui/gtk-base.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" @@ -73,6 +74,7 @@ namespace gui { namespace widget { + using util::_Fmt; using lib::Literal; using std::vector; @@ -143,10 +145,16 @@ namespace widget { model::Revealer reveal; + /** empty buffer and discard all error bookmarks */ void clearAll() { - UNIMPLEMENTED ("empty buffer and discard all error bookmarks"); + errorMarks_.clear(); + size_t lineCnt = textLog_.get_buffer()->get_line_count(); + string placeholder; + if (lineCnt > 0) + placeholder = _Fmt{_("───════ %d preceding lines removed ════───\n")} % lineCnt; + textLog_.get_buffer()->set_text (placeholder); // discard existing content } /** just add normal information message to buffer, diff --git a/src/include/gui-notification-facade.h b/src/include/gui-notification-facade.h index 2eca7f2bc..bb4970cfe 100644 --- a/src/include/gui-notification-facade.h +++ b/src/include/gui-notification-facade.h @@ -44,12 +44,16 @@ #ifdef __cplusplus /* ============== C++ Interface ================= */ #include "lib/depend.hpp" -#include "lib/diff/mutation-message.hpp" #include "lib/idi/entry-id.hpp" #include +namespace lib { + namespace diff { + class GenNode; + class MutationMessage; +} } namespace gui { using std::string; diff --git a/wiki/renderengine.html b/wiki/renderengine.html index 9a14cf2a4..655a0034a 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -3358,7 +3358,7 @@ This treatment ensures each nested diff is consumed within a properly typed cont &rarr; this is the purpose of the {{{DataCap}}} within our [[generic node element|GenNode]]
-
+
LayerSeparationInterface provided by the GUI.
 Access point for the lower layers to push information and state changes (asynchronously) to the GUI. Most operations within Lumiera are in fact initiated by the user through the GUI. In the course of such actions, the GUI uses the services of the lower layer and typically receives an immediate synchronous response to indicate the change was noted. Yet often, these operations may cause additional changes to happen asynchronously from the GUI's perspective. For example, an edit operation might trigger a re-build of the low-level model, which then detects an error. Any such consequences and notifications can be "cast" up into the UI, using the {{{NotificationService}}} described here.
 
@@ -3370,7 +3370,7 @@ The GuiNotificationFacade is installed as part of and managed by the ''UI Manage
 A dedicated ''activation state'' is necessary for this reason -- which within the implementation translates into a queuing and dispatching facility to reschedule any calls into the UI event thread ''asynchronously''.
 
 !Addressing of UI elements
-Most calls in this interface require to specify a receiver or target, in the form of an element ID. It is assumed the caller effectively just knows these ~IDs, typically because the same ~IDs are also used as element ~IDs for the corresponding session entities. And, to start with, the whole UI representation of the session was at some point generated by //population diff messages,// which used the same ~IDs to indicate the creation of the corresponding UI representation elements.
+Most calls in this interface require to specify a receiver or target, in the form of an element ID. It is assumed the caller effectively just knows these ~IDs, typically because the same ~IDs are also used as element ~IDs for the corresponding session entities. Even more so, since the whole UI representation of the session was at some point generated by //population diff messages,// which used the same ~IDs to indicate the creation of the corresponding UI representation elements.
 
@@ -9803,7 +9803,7 @@ For now, as of 6/10, we use specialised QueryResolver instances explicitly and d &rarr; QueryRegistration
-
+
Abstraction used in the Backbone of Lumiera's GTK User Interface
 The UI-Bus is a ''Mediator'' -- impersonating the role of the //Model// and the //Controler// in the [[MVC-Pattern|http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller]] in common UI architecture.
 
@@ -9811,10 +9811,10 @@ The ~MVC-Pattern as such is fine, and probably the best we know for construction
 
 !!!rationale
 This way, we separate between immediate local control of UI state and the more global, generic [[interaction control|InteractionControl]] and [[command binding|GuiCommandBinding]].
-And we arrive at a backbone like structure, which can be wired, programmed and reasoned about while abstracting from the local concerns and the state management related to the UI toolkit set in use. Any element //of more than local relevance// -- be it a controller or be it a widget, representing some entity from the model -- will be attached to the UI-Bus by virtue of a ''bus terminal'', establishing a bi-directional connection. This enables any UI element to invoke commands and persist presentation state (by sending //"state mark" mesages//), and in turn this allows the session and engine core to "cast" state updates towards the UI, without precisely knowing where and how to reach the relevant presentation entities.
+And we arrive at a backbone like structure, which can be wired, programmed and reasoned about while abstracting from the local concerns and the state management related to the UI toolkit set in use. Any element //of more than local relevance// -- be it a controller or be it a widget, representing some entity from the model -- will be attached to the UI-Bus by virtue of a ''bus terminal'', establishing a bi-directional connection. This enables any UI element to invoke commands and persist presentation state (by sending //"state mark" mesages//), and in turn this allows the session and engine core to "cast" state updates towards the UI, without knowing in detail where and how to reach the relevant presentation entities.
 
 !!!topology
-The UI-Bus has a simple star shaped topology without hierarchical nesting and forwarding. It is not necessarily connected this way, since protocol and contracts do not assume any topology explicitly -- and the topology may be subject to change without further notice. We'll only assume there is a central hub somewhere in the bus, which maintains a routing table. Actually this hub represents the centre of the star and is known as ''Nexus''. Whenever an UI element connects to the bus, a routing entry is added, which allows the element to be directly invoked to receive messages. For that reason, every element connected this way must be at a fixed memory location.
+The UI-Bus has a simple star shaped topology without hierarchical nesting and forwarding. It is not necessarily connected this way, since protocol and contracts do not assume any topology explicitly -- and the topology may be subject to change without further notice. We'll only assume there is a central hub somewhere in the bus, which maintains a routing table. Actually this hub represents the centre of the star and is known as ''Nexus''. Whenever an UI element connects to the bus, a routing entry is added, which allows the element to be directly invoked to receive messages. For that reason, every element connected this way must stay put, at a fixed memory location.
 
 !!!degree of connectedness
 A connection point to the UI-Bus is called a ''Bus Terminal'' and implemented by class {{{BusTerm}}}. In fact, this class is the foundation of the ~UI-Bus network, with the Nexus being a specialisation. The //contract// involved with this connection requires the connected entity to respond to several messages, which effectively also means that this connected entity is a tangible UI-Element ({{{gui::model::Tangible}}} implements the necessary functions). But this pertains only to fully bidirectional bus connections, where the connected entity can receive messages addressed by it's ID. Beyond that, it is very much possible to operate a "free standing" bus terminal, which allows just to send "uplink" messages into the bus, without the need to add this terminal itself to the central routing table.
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm
index 7336c44ce..95c843a50 100644
--- a/wiki/thinkPad.ichthyo.mm
+++ b/wiki/thinkPad.ichthyo.mm
@@ -228,7 +228,7 @@
   
 
 
-
+
 
   
     
@@ -269,7 +269,7 @@
 
 
 
-
+
 
 
 
@@ -287,13 +287,58 @@
   
 
 
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+  
+    
+  
+  
+    

+ ...es könnte auch den Feedback des Users meinen +

+ +
+
+
+
+ + + +
- + + + @@ -320,13 +365,11 @@ - - + + - - @@ -547,8 +590,7 @@ Ein zu früher bzw. zu später Aufruf "fällt einfach hinten runter"

- -
+
@@ -562,13 +604,12 @@
- - + + - @@ -621,13 +662,14 @@ + - - + + @@ -658,9 +700,11 @@ - + - + + + @@ -668,7 +712,7 @@ - + @@ -709,7 +753,7 @@ - + @@ -824,7 +868,7 @@ - + @@ -834,7 +878,7 @@ - + @@ -922,10 +966,10 @@ - + - + @@ -1400,8 +1444,7 @@ mehr erscheint mir nicht sinnvoll; behandeln kann man solche Fehler ohnehin nicht.

- - +
@@ -1557,42 +1600,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- ...es könnte auch den Feedback des Users meinen -

- -
-
-
-
@@ -1804,7 +1811,7 @@ - + @@ -1818,7 +1825,10 @@

- + + + + @@ -1847,7 +1857,7 @@ - + @@ -2037,8 +2047,25 @@ - - + + + + + + + + + +

+ ...um den Umstand zu dokumentieren, +

+

+ daß hier ## Zeilen gelöscht wurden +

+ + +
+
@@ -2684,9 +2711,9 @@
- - - + + + @@ -2771,7 +2798,7 @@ - + @@ -2865,7 +2892,7 @@ - + @@ -2884,7 +2911,7 @@ - + @@ -3022,7 +3049,7 @@ - + From 23430f880052500c1fd2ebba7786932d39f6c7e5 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 30 Sep 2018 03:38:46 +0200 Subject: [PATCH 119/178] NotificationDisplay: fix improper state mark for "expand" (WIP) 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. --- src/lib/variant.hpp | 28 +++++++++++++-- src/proc/cmd/meta-cmd.cpp | 6 +++- wiki/thinkPad.ichthyo.mm | 73 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 102 insertions(+), 5 deletions(-) diff --git a/src/lib/variant.hpp b/src/lib/variant.hpp index 87621e14b..fe7720055 100644 --- a/src/lib/variant.hpp +++ b/src/lib/variant.hpp @@ -341,15 +341,16 @@ namespace lib { + static string indicateTypeMismatch (Buffer&); + + static Buff& downcast (Buffer& b) { Buff* buff = dynamic_cast (&b); if (!buff) - throw error::Logic("Variant type mismatch: " - "the given variant record does not hold " - "a value of the type requested here" + throw error::Logic(indicateTypeMismatch(b) ,error::LERR_(WRONG_TYPE)); else return *buff; @@ -550,6 +551,27 @@ namespace lib { return util::typedString (this->access()); } + /** + * error message when accessing the variant content with wrong type assumptions. + * @remark while this diagnostics can be crucial for finding bugs, we avoid + * including \ref format-string.hpp, since lib::Variant is used pervasively + * as part of lib::diff::GenNode. Especially in development builds, we observed + * a tangible leverage on executable size. Thus we implement the protection + * against follow-up exceptions explicitly here. + */ + template + template + inline string + Variant::Buff::indicateTypeMismatch(Variant::Buffer& target) + { + try { + return "Variant type mismatch: expected value of type «" + + lib::meta::typeStr()+"», " + + "however the given variant record is " + + string{target}; + } + catch(...) { return lib::meta::FAILURE_INDICATOR; } + } }// namespace lib diff --git a/src/proc/cmd/meta-cmd.cpp b/src/proc/cmd/meta-cmd.cpp index 6bed40e8c..25754046f 100644 --- a/src/proc/cmd/meta-cmd.cpp +++ b/src/proc/cmd/meta-cmd.cpp @@ -45,6 +45,7 @@ #include "lib/format-string.hpp" //////////////////////////////////////////////////////////////TICKET #1099 : include needed temporarily //#include "lib/symbol.hpp" +#include #include using lib::hash::LuidH; @@ -58,6 +59,7 @@ using lib::diff::GenNode; //using util::cStr; using util::_Fmt; //////////////////////////////////////////////////////////////TICKET #1099 : include needed temporarily using std::string; +using boost::lexical_cast; namespace proc { @@ -211,7 +213,9 @@ COMMAND_DEFINITION (test_meta_markAction) def.operation ([](string actionID, string message) { ID errorLogID = gui::interact::Wizard::getErrorLogID(); - GuiNotification::facade().mark (errorLogID, GenNode{actionID, message}); + GuiNotification::facade().mark (errorLogID + ,actionID=="expand"? GenNode{actionID, lexical_cast (message)} ///////////FIXME : lexical_cast is not suitable. We need a generic bool parse function! + : GenNode{actionID, message}); }) .captureUndo ([](string actionID, string message) -> string { diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 95c843a50..f5e4089e8 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -2911,7 +2911,7 @@ - + @@ -2944,6 +2944,77 @@ + + + + + + +

+ ...weil der Payload-Typ für diesen Aufruf bool ist, +

+

+ und nicht string wie für alle anderen state marks +

+ + +
+ +
+ + + + + + + + +

+ k +

+ + +
+ + + + + + + + + + + + + + + + + + + + + + +

+ ein ZombieCheck spricht an +

+ + +
+ + + + + + + + +
+
+
+
From 77c9a6a1daeb20bc5ae89eb3302e4c611d683304 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 1 Oct 2018 03:36:41 +0200 Subject: [PATCH 120/178] FailureHandling: investigate crash in emergency shutdown 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 --- src/lib/error-exception.cpp | 3 +- wiki/thinkPad.ichthyo.mm | 232 ++++++++++++++++++++++++++++++++++++ 2 files changed, 234 insertions(+), 1 deletion(-) diff --git a/src/lib/error-exception.cpp b/src/lib/error-exception.cpp index a1e4c64ce..67b54a001 100644 --- a/src/lib/error-exception.cpp +++ b/src/lib/error-exception.cpp @@ -116,7 +116,8 @@ namespace lumiera { , desc_{description} , cause_{extractCauseMsg(cause)} { - lumiera_error_set (this->id_, description.c_str()); + string detailInfo{description + (isnil(cause_)? "" : " | cause = "+cause_)}; + lumiera_error_set (this->id_, detailInfo.c_str()); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index f5e4089e8..1354dab03 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -2981,12 +2981,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Konzept ist nicht, sie gleich in die description zu übernehmen +

+ + +
+ +
+
+ + + + + + + + + + + + + + +

+ das System ist ohnehin schon angetötet +

+ +
+
+
+ + + + + + +

+ keines! Impl verwendet strdup() +

+ + +
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...denn wir verwenden das Lumiera-Exception-API gar nicht +

+ + +
+ +
+
+
+ + + + + + + + + +

+ der Handler im ProcDispatcher sorgt dafür +

+ + +
+
+ + + + + + + + + + + + + + +

+ ...welches keinen separaten Thread aufruft....? +

+ + +
+
+ + + +
+ + + +
+ + + + + + + + + +

+ nämlich im shutdownAll() weiter oben im Stack +

+ + +
+ +
+ + + + + + + + + + +

+ aber genau der andere Thread, der das gemacht hat... +

+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
@@ -3006,6 +3234,10 @@ + + + + From 70c8718258b0c700c69471fbf63f26aaeae1e948 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 1 Oct 2018 03:49:54 +0200 Subject: [PATCH 121/178] FailureHandling: rectify shortcomings in Proc-Command error handling and especially our provisional dummy code to execute some commands "right here" should also check and raise captured exceptions from command invocation --- src/common/subsystem-runner.hpp | 3 ++- src/proc/control/handling-pattern.cpp | 8 ++++-- src/proc/control/proc-dispatcher.cpp | 7 +++--- wiki/thinkPad.ichthyo.mm | 36 +++++++++++++++++++++++++-- 4 files changed, 46 insertions(+), 8 deletions(-) diff --git a/src/common/subsystem-runner.hpp b/src/common/subsystem-runner.hpp index e933737f2..fe9808d1d 100644 --- a/src/common/subsystem-runner.hpp +++ b/src/common/subsystem-runner.hpp @@ -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 diff --git a/src/proc/control/handling-pattern.cpp b/src/proc/control/handling-pattern.cpp index da1188175..043a25306 100644 --- a/src/proc/control/handling-pattern.cpp +++ b/src/proc/control/handling-pattern.cpp @@ -104,10 +104,14 @@ namespace control { /* ====== execution result state object ======= */ - /** @note we just grab and retain the error message. */ + /** @note just grab and retain the error message, but _clear_ the error flag. + * Rationale: by packaging into the ExecResult, the excepton counts as treated. + */ ExecResult::ExecResult (lumiera::Error const& problem) : log_(problem.what()) - { } + { + lumiera_error(); // ensure error flag is cleared + } bool diff --git a/src/proc/control/proc-dispatcher.cpp b/src/proc/control/proc-dispatcher.cpp index 8098da914..c15ec1ce2 100644 --- a/src/proc/control/proc-dispatcher.cpp +++ b/src/proc/control/proc-dispatcher.cpp @@ -247,8 +247,8 @@ namespace control { updateState(); } } - catch (lumiera::Error& problem) - { + catch (std::exception& problem) + { // could also be lumiera::Error errorMsg = problem.what(); lumiera_error(); // clear error flag } @@ -304,7 +304,8 @@ namespace control { if (util::startsWith (string(cmd.getID()), "test")) { INFO (command, "+++ -------->>> bang!"); - cmd(); + auto resultState = cmd(); + resultState.maybeThrow(); } //////////////////////////////////////////////////////TODO : magic to invoke commands from unit tests } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 1354dab03..9940b6772 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -3194,8 +3194,8 @@ - - + + @@ -3208,6 +3208,38 @@ + + + + + + + + + + + + + + +

+ ...dann sind wir sofort tot und das wird billigend in Kauf genommen. +

+

+ So ist das tatsächlich implementiert im Falle vom OutputDirector. +

+

+ Der startet nämlich einen Thread erst als Supervisor für den Shutdown. +

+

+ Wenn das nun wirklich fehlschlägt, bleibt uns sinnvollerweise nur noch die Selbstmord-Option +

+ + +
+ +
+
From 4e94dfd4d9f30bfbe17987b3b98663187751ffcb Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 1 Oct 2018 05:51:21 +0200 Subject: [PATCH 122/178] FailureHandling: improved ZombieCheck now capturing the Zombie's ID ==> surprise, its ClassLock --- src/lib/depend.hpp | 2 +- src/lib/sync-classlock.hpp | 3 +- src/lib/zombie-check.hpp | 33 ++++++++- tests/25fundamental.tests | 5 ++ tests/basics/zombie-check-test.cpp | 88 ++++++++++++++++++++++++ wiki/thinkPad.ichthyo.mm | 105 +++++++++++++++++++++++++---- 6 files changed, 219 insertions(+), 17 deletions(-) create mode 100644 tests/basics/zombie-check-test.cpp diff --git a/src/lib/depend.hpp b/src/lib/depend.hpp index 3dff59462..2a3e1c910 100644 --- a/src/lib/depend.hpp +++ b/src/lib/depend.hpp @@ -141,7 +141,7 @@ namespace lib { Deleter deleter_; public: - ZombieCheck zombieCheck; + ZombieCheck zombieCheck{util::typeStr()}; DependencyFactory() = default; ~DependencyFactory() diff --git a/src/lib/sync-classlock.hpp b/src/lib/sync-classlock.hpp index 22acbaec8..abc581ce4 100644 --- a/src/lib/sync-classlock.hpp +++ b/src/lib/sync-classlock.hpp @@ -39,6 +39,7 @@ #include "lib/nobug-init.hpp" #include "lib/zombie-check.hpp" +#include "lib/meta/util.hpp" #include "lib/sync.hpp" @@ -72,7 +73,7 @@ namespace lib { getPerClassMonitor() { static PerClassMonitor classMonitor; - static ZombieCheck zombieCheck; + static ZombieCheck zombieCheck{util::typeStr(this)}; zombieCheck(); return classMonitor; diff --git a/src/lib/zombie-check.hpp b/src/lib/zombie-check.hpp index 5c19079a7..b4f005d7e 100644 --- a/src/lib/zombie-check.hpp +++ b/src/lib/zombie-check.hpp @@ -30,6 +30,7 @@ ** we may plant an automatic "zombie detector" to give a clear indication of ** such a policy violation (Lumiera forbids to use dependencies from dtors). ** + ** @see ZombieCheck_test ** @see sync-classlock.hpp ** @see depend.hpp */ @@ -40,20 +41,38 @@ #include "lib/error.hpp" +#include +#include + namespace lib { namespace error = lumiera::error; + using std::string; + /** * Automatic lifecycle tracker, to produce an alarm when accessing objects after deletion. + * @warning ensure the ZombieCheck instance lives in static memory, otherwise it won't work. */ class ZombieCheck { bool deceased_ = false; + char zombieID_[42]{}; public: + /** + * install a zombie check, tagged with the given id. + * When invoked after death, the raised error::Fatal + * includes this ID in the diagnostic message. + * @remark recommended to use util::typeStr + */ + ZombieCheck (string id) + { + std::strncpy(zombieID_, id.c_str(), 41); + } + ZombieCheck() = default; ~ZombieCheck() { @@ -69,10 +88,20 @@ namespace lib { operator() () const { if (deceased_) - throw error::Fatal("Already deceased object called out of order during Application shutdown. " - "Lumiera Policy violated: Dependencies must not be used from destructors." + throw error::Fatal(buildDiagnosticMessage() ,error::LUMIERA_ERROR_LIFECYCLE); } + + private: + string + buildDiagnosticMessage() const + { + string msg{"Already deceased object called out of order during Application shutdown. " + "Lumiera Policy violated: Dependencies must not be used from destructors."}; + if (zombieID_[0]) + msg += " Offender = "+string{zombieID_}; + return msg; + } }; diff --git a/tests/25fundamental.tests b/tests/25fundamental.tests index f18b6dff9..f42fe79cb 100644 --- a/tests/25fundamental.tests +++ b/tests/25fundamental.tests @@ -257,3 +257,8 @@ return: 0 END +TEST "Zombie tripwire" ZombieCheck_test < + + 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 zombie-check-test.cpp + ** unit test \ref Singleton_test + */ + + +#include "lib/test/run.hpp" +#include "lib/test/test-helper.hpp" +#include "lib/zombie-check.hpp" +#include "lib/util.hpp" + +#include + +using util::contains; +using util::isnil; +using std::string; + + +namespace lib { +namespace test{ + + + + + + + /**********************************************************************//** + * @test verify the ability to trap access to deceased objects. + * @remark For actual usage, the zombie detector needs to be planted into + * static memory. This test can only verify the trigger mechanics. + * @see zombie-check.hpp + * @see lib::DependencyFactory::zombieCheck + */ + class ZombieCheck_test : public Test + { + + virtual void + run (Arg) + { + auto zombieID = randStr(50); + ZombieCheck trap{zombieID}; + CHECK (not trap); + + trap.~ZombieCheck(); // note: unconventional kill + CHECK (trap); // accessing deceased object... + + try { // + trap(); // trigger the tripwire + } + catch(lumiera::error::Fatal& casuality) + { + string obituary = casuality.what(); + CHECK (not isnil (obituary)); + CHECK ( contains (obituary, zombieID.substr(0,41))); + CHECK (not contains (obituary, zombieID)); + } + } + }; + + + + /** Register this test class... */ + LAUNCHER (ZombieCheck_test, "unit common"); + + + +}} // namespace lib::test diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 9940b6772..b37e342cc 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -2944,7 +2944,7 @@ - + @@ -2959,6 +2959,7 @@ + @@ -3017,7 +3018,7 @@ - + @@ -3055,8 +3056,8 @@ - - + + @@ -3079,14 +3080,14 @@ - - + + - + @@ -3098,7 +3099,7 @@ - + @@ -3125,7 +3126,7 @@ - + @@ -3194,7 +3195,7 @@ - + @@ -3264,11 +3265,89 @@ - + - - + + + + + + + + + +

+ 0000001180: INFO: subsystem-runner.hpp:208: worker_3: sigTerm: Subsystem 'Lumiera GTK GUI' terminated. +

+

+ 0000001181: NOTICE: appstate.cpp:170: thread_1: maybeWait: Shutting down Lumiera... +

+

+ 0000001182: ALERT: appstate.cpp:174: thread_1: maybeWait: Triggering emergency exit... +

+

+ 0000001243: WARNING: interfaceregistry.c:199: thread_1: lumiera_interfaceregistry_bulkremove_interfaces: ENTRY NOT FOUND in interfaceregistry at clean-up of interface lumieraorg_Gui, instance lumieraorg_GuiStarterPlugin +

+

+ +

+

+ (lumiera:9719): Gtk-CRITICAL **: gtk_widget_is_drawable: assertion 'GTK_IS_WIDGET (widget)' failed +

+

+ +

+

+ (lumiera:9719): Pango-CRITICAL **: pango_layout_get_cursor_pos: assertion 'index >= 0 && index <= layout->length' failed +

+

+ 0000001293: ERR: error-exception.cpp:185: worker_3: lumiera_unexpectedException: ### Lumiera halted due to an unexpected Error ### +

+

+ +

+

+ ### Lumiera halted due to an unexpected Error ### +

+

+ +

+

+ +

+

+ +++ Caught Exception LUMIERA_ERROR_LIFECYCLE:Lifecycle assumptions violated +

+

+ +

+

+ 0000001298: ERR: error-exception.cpp:196: worker_3: lumiera_unexpectedException: +++ caught error::LumieraError<error::LUMIERA_ERROR_FATAL, error::LumieraError<error::LUMIERA_ERROR_LOGIC, Error> > +

+

+ 0000001298! ERR: error-exception.cpp:196: worker_3: lumiera_unexpectedException: +++ messg: Sorry, Lumiera encountered an internal error. +

+

+ 0000001298! ERR: error-exception.cpp:196: worker_3: lumiera_unexpectedException: +++ descr: LUMIERA_ERROR_LIFECYCLE:Lifecycle assumptions violated (Already deceased object called out of order during Application shutdown. Lumiera Policy violated: Dependencies must not be used from destructors. Offender = ClassLock<gui::interact::LocationQuery, s). +

+

+ 0000001299: ERR: error-exception.cpp:212: worker_3: lumiera_unexpectedException: last registered error was.... +

+

+ 0000001299! ERR: error-exception.cpp:212: worker_3: lumiera_unexpectedException: LUMIERA_ERROR_LIFECYCLE:Lifecycle assumptions violated +

+

+ Abgebrochen +

+ + +
+ + + + +
From 7655960b23a9a83887db6669cc4b8d7d6a5ed62c Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Tue, 2 Oct 2018 02:45:01 +0200 Subject: [PATCH 123/178] Fix Zombie invocation in GUI shutdown (closes #1178) == 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` 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() --- src/gui/gtk-lumiera.cpp | 17 ++- src/gui/interact/ui-coord-resolver.hpp | 2 +- wiki/thinkPad.ichthyo.mm | 186 ++++++++++++++++++++++++- 3 files changed, 196 insertions(+), 9 deletions(-) diff --git a/src/gui/gtk-lumiera.cpp b/src/gui/gtk-lumiera.cpp index 674ef7f27..887824d89 100644 --- a/src/gui/gtk-lumiera.cpp +++ b/src/gui/gtk-lumiera.cpp @@ -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 diff --git a/src/gui/interact/ui-coord-resolver.hpp b/src/gui/interact/ui-coord-resolver.hpp index 36686f954..27635c58f 100644 --- a/src/gui/interact/ui-coord-resolver.hpp +++ b/src/gui/interact/ui-coord-resolver.hpp @@ -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. diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index b37e342cc..7be1572e7 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -3348,11 +3348,195 @@ + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ..damit ~GtkLumiera() garantiert vorher aufgerufen wird +

+ + +
+
+
+ + + + + + + +

+ 0000001028: INFO: proc-dispatcher.cpp:301: worker_2: processCommands: +++ dispatch Command("test_meta_markAction") {exec} +

+

+ 0000001029: INFO: proc-dispatcher.cpp:306: worker_2: processCommands: +++ -------->>> bang! +

+

+ 0000001031: WARNING: handling-pattern.cpp:90: worker_2: invoke: Invocation of Command("test_meta_markAction") {exec} failed: bad lexical cast: source type value could not be interpreted as target +

+

+ 0000001052: INFO: proc-dispatcher.cpp:159: worker_2: ~DispatcherLoop: Proc-Dispatcher stopped. +

+

+ 0000001053: INFO: subsystem-runner.hpp:208: worker_2: sigTerm: Subsystem 'Session' terminated. +

+

+ 0000001054: WARNING: subsystem-runner.hpp:209: worker_2: sigTerm: Irregular shutdown caused by: LUMIERA_ERROR_LOGIC:internal logic broken (Command execution failed: LUMIERA_ERROR_EXTERNAL:failure in external service -- caused by: bad lexical cast: source type value could not be interpreted as target). +

+

+ 0000001090: TODO: output-director.cpp:136: worker_4: bringDown: actually bring down the output generation +

+

+ 0000001091: INFO: subsystem-runner.hpp:208: worker_2: sigTerm: Subsystem 'Dummy-Player' terminated. +

+

+ 0000001092: NOTICE: notification-service.cpp:158: worker_2: triggerGuiShutdown: @GUI: shutdown triggered with explanation 'Application shutdown'.... +

+

+ 0000001111: NOTICE: notification-service.cpp:158: worker_2: triggerGuiShutdown: @GUI: shutdown triggered with explanation 'Application shutdown'.... +

+

+ 0000001119: INFO: subsystem-runner.hpp:208: worker_4: sigTerm: Subsystem 'PlayOut' terminated. +

+

+ 0000001120: NOTICE: notification-service.cpp:158: worker_4: triggerGuiShutdown: @GUI: shutdown triggered with explanation 'Application shutdown'.... +

+

+ 0000001128: INFO: display-service.hpp:147: worker_3: ~DisplayService: Display service dying... +

+

+ +

+

+ (lumiera:12160): Gtk-CRITICAL **: gtk_widget_is_drawable: assertion 'GTK_IS_WIDGET (widget)' failed +

+

+ +

+

+ (lumiera:12160): Pango-CRITICAL **: pango_layout_get_cursor_pos: assertion 'index >= 0 && index <= layout->length' failed +

+

+ 0000001160: INFO: workspace-window.cpp:53: worker_3: ~WorkspaceWindow: Closing workspace window... +

+

+ +

+

+ (lumiera:12160): Gdl-CRITICAL **: gdl_dock_master_get_controller: assertion 'master != NULL' failed +

+

+ +

+

+ (lumiera:12160): Gdl-CRITICAL **: gdl_dock_master_get_controller: assertion 'master != NULL' failed +

+

+ +

+

+ (lumiera:12160): Gdl-CRITICAL **: gdl_dock_master_get_controller: assertion 'master != NULL' failed +

+

+ +

+

+ (lumiera:12160): Gdl-CRITICAL **: gdl_dock_master_get_controller: assertion 'master != NULL' failed +

+

+ 0000001167: INFO: subsystem-runner.hpp:208: worker_3: sigTerm: Subsystem 'Lumiera GTK GUI' terminated. +

+

+ 0000001175: NOTICE: appstate.cpp:170: thread_1: maybeWait: Shutting down Lumiera... +

+

+ 0000001176: ALERT: appstate.cpp:174: thread_1: maybeWait: Triggering emergency exit... +

+

+ 0000001237: WARNING: interfaceregistry.c:199: thread_1: lumiera_interfaceregistry_bulkremove_interfaces: ENTRY NOT FOUND in interfaceregistry at clean-up of interface lumieraorg_Gui, instance lumieraorg_GuiStarterPlugin +

+

+ (hiv)~/devel/lumi-$ +

+

+ (hiv)~/devel/lumi-$ +

+

+ (hiv)~/devel/lumi-$ echo $? +

+

+ 2 +

+

+ (hiv)~/devel/lumi-$ +

+ + +
+ +
+
From c6b8811af03e744d5b28e63b9e78be65cfc053b1 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 3 Oct 2018 04:43:16 +0200 Subject: [PATCH 124/178] Library: utility to interpret a text as bool value (yes/no) ...also fixes the problem with the "expand" mark in DemoGuiRoundtrip --- src/lib/util.cpp | 43 ++++++- src/lib/util.hpp | 21 +++- src/proc/cmd/meta-cmd.cpp | 6 +- tests/15library.tests | 41 ++++--- tests/library/util-parse-bool-test.cpp | 106 ++++++++++++++++++ ...cpp => util-sanitised-identifier-test.cpp} | 10 +- wiki/thinkPad.ichthyo.mm | 19 +++- 7 files changed, 208 insertions(+), 38 deletions(-) create mode 100644 tests/library/util-parse-bool-test.cpp rename tests/library/{sanitised-identifier-test.cpp => util-sanitised-identifier-test.cpp} (85%) diff --git a/src/lib/util.cpp b/src/lib/util.cpp index 7ab8dfe67..93920c1a1 100644 --- a/src/lib/util.cpp +++ b/src/lib/util.cpp @@ -29,7 +29,9 @@ */ +#include "error.hpp" #include "lib/util.hpp" +#include "lib/format-string.hpp" #include #include @@ -40,12 +42,18 @@ using boost::algorithm::is_any_of; using boost::algorithm::is_alnum; using boost::algorithm::is_space; +#include + +using std::regex; +using std::regex_match; + +using std::function; +using util::_Fmt; namespace util { - using std::function; - typedef function ChPredicate; + using ChPredicate = function; ChPredicate operator! (ChPredicate p) { return not bind(p,_1); } // character classes used for sanitising a string @@ -63,12 +71,12 @@ namespace util { while ( i != e ) { while ( i != e && !isValid (*i) ) ++i; - while ( i != e && isValid (*i) ) *(j++) = *(i++); - if ( i != e && isPunct (*i) ) + while ( i != e && isValid (*i) ) *(j++) = *(i++); + if ( i != e && isPunct (*i) ) { *j++ = '_'; do ++i; - while ( i != e && isPunct (*i)); + while ( i != e && isPunct (*i)); } } res.erase(j,res.end()); @@ -79,7 +87,7 @@ namespace util { /** * @remarks this function just forwards to boost::algorithm::trim_copy. * Use this call when boost header inclusion is an issue, otherwise - * a direct invocation is likely to perform better, due to inlining. + * a direct invocation is likely to perform better, due to inlining. */ string trim (string const& org) @@ -87,6 +95,29 @@ namespace util { return boost::algorithm::trim_copy (org); } + + + + namespace { + regex trueTokens{ "\\s*(true|True|TRUE|yes|Yes|YES|1|\\+)\\s*", regex::ECMAScript | regex::optimize}; + regex falseTokens{"\\s*(false|False|FALSE|no|No|NO|0|\\-)\\s*", regex::ECMAScript | regex::optimize}; + } + + bool + boolVal (string const& textForm) + { + if (regex_match (textForm, trueTokens)) return true; + if (regex_match (textForm, falseTokens)) return false; + throw lumiera::error::Invalid(_Fmt{"String '%s' can not be interpreted as bool value"} % textForm); + } + + + bool + isYes (string const& textForm) noexcept + { + return regex_match (textForm, trueTokens); + } + } // namespace util diff --git a/src/lib/util.hpp b/src/lib/util.hpp index 4d35ff019..8e15d0872 100644 --- a/src/lib/util.hpp +++ b/src/lib/util.hpp @@ -359,8 +359,8 @@ namespace util { "mixed Ω garbage" --> 'mixed_garbage' "Bääääh!!" --> 'Bh' \endverbatim - * @see sanitised-identifier-test.cpp - * @see lib::meta::sanitisedSymbol() + * @see \ref UtilSanitizedIdentifier_test + * @see \ref lib::meta::sanitisedSymbol() */ string sanitise (string const& org); @@ -371,6 +371,23 @@ namespace util { string trim (string const& org); + /** interpret text representation of a boolean value. + * @remarks this function detects the relevant token rather strict.... + * - yields `true` for the tokens "true", "True", "TRUE", "yes", "Yes", "YES", "1", "+" + * - yields `false` for the tokens "false", "False", "FALSE", "no", "No, "NO", "0", "-" + * - leading and trailing whitespace is ignored + * @throws lumiera::error::Invalid for any other text content + */ + bool boolVal(string const&); + + + /** check the given text if it can be interpreted as affirmative answer (bool `true`). + * @remarks this function just fishes for the known `true` tokens and interprets + * all other content as `false`, including empty strings. Never throws. + */ + bool isYes(string const&) noexcept; + + /** convenience shortcut: conversion to c-String via string. diff --git a/src/proc/cmd/meta-cmd.cpp b/src/proc/cmd/meta-cmd.cpp index 25754046f..69f78dc93 100644 --- a/src/proc/cmd/meta-cmd.cpp +++ b/src/proc/cmd/meta-cmd.cpp @@ -44,8 +44,8 @@ #include "lib/idi/entry-id.hpp" #include "lib/format-string.hpp" //////////////////////////////////////////////////////////////TICKET #1099 : include needed temporarily //#include "lib/symbol.hpp" +#include "lib/util.hpp" -#include #include using lib::hash::LuidH; @@ -58,8 +58,8 @@ using gui::GuiNotification; using lib::diff::GenNode; //using util::cStr; using util::_Fmt; //////////////////////////////////////////////////////////////TICKET #1099 : include needed temporarily +using util::isYes; using std::string; -using boost::lexical_cast; namespace proc { @@ -214,7 +214,7 @@ COMMAND_DEFINITION (test_meta_markAction) { ID errorLogID = gui::interact::Wizard::getErrorLogID(); GuiNotification::facade().mark (errorLogID - ,actionID=="expand"? GenNode{actionID, lexical_cast (message)} ///////////FIXME : lexical_cast is not suitable. We need a generic bool parse function! + ,actionID=="expand"? GenNode{actionID, isYes(message)} : GenNode{actionID, message}); }) .captureUndo ([](string actionID, string message) -> string diff --git a/tests/15library.tests b/tests/15library.tests index 3aa51767f..a9779dc4c 100644 --- a/tests/15library.tests +++ b/tests/15library.tests @@ -493,22 +493,6 @@ return: 0 END -TEST "create sanitised identifiers" SanitizedIdentifier_test < 'Word' -out-lit: 'a Sentence' --> 'a_Sentence' -out-lit: 'trailing Withespace -out-lit: ' --> 'trailing_Withespace' -out-lit: 'with a lot -out-lit: of Whitespace' --> 'with_a_lot_of_Whitespace' -out-lit: 'with"much (punctuation)[]!' --> 'withmuch_(punctuation)' -out-lit: '§&Ω%€ leading garbage' --> 'leading_garbage' -out-lit: 'mixed Ω garbage' --> 'mixed_garbage' -out-lit: 'Bääääh!!' --> 'Bh' -out-lit: '§&Ω%€' --> '' -return: 0 -END - - TEST "ScopedHolder_test" ScopedHolder_test <... out: checking ScopedPtrHolder... @@ -588,6 +572,11 @@ return: 0 END +TEST "ownership of malloced data" UniqueMallocOwner_test < 'Word' +out-lit: 'a Sentence' --> 'a_Sentence' +out-lit: 'trailing Withespace +out-lit: ' --> 'trailing_Withespace' +out-lit: 'with a lot +out-lit: of Whitespace' --> 'with_a_lot_of_Whitespace' +out-lit: 'with"much (punctuation)[]!' --> 'withmuch_(punctuation)' +out-lit: '§&Ω%€ leading garbage' --> 'leading_garbage' +out-lit: 'mixed Ω garbage' --> 'mixed_garbage' +out-lit: 'Bääääh!!' --> 'Bh' +out-lit: '§&Ω%€' --> '' return: 0 END diff --git a/tests/library/util-parse-bool-test.cpp b/tests/library/util-parse-bool-test.cpp new file mode 100644 index 000000000..10a7d4db6 --- /dev/null +++ b/tests/library/util-parse-bool-test.cpp @@ -0,0 +1,106 @@ +/* + UtilParseBool(Test) - derive bool value from text form + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + 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 util-parse-bool-test.cpp + ** unit test \ref UtilParseBool_test + */ + + +#include "lib/test/run.hpp" +#include "lib/test/test-helper.hpp" +#include "lib/util.hpp" + +#include + + +using lumiera::error::LUMIERA_ERROR_INVALID; + + +namespace util { +namespace test { + + + class UtilParseBool_test : public Test + { + virtual void + run (Arg) + { + CHECK (boolVal ("true")); + CHECK (boolVal ("True")); + CHECK (boolVal ("TRUE")); + CHECK (boolVal ("yes")); + CHECK (boolVal ("Yes")); + CHECK (boolVal ("YES")); + CHECK (boolVal ("1")); + CHECK (boolVal ("+")); + + CHECK (not boolVal ("false")); + CHECK (not boolVal ("False")); + CHECK (not boolVal ("FALSE")); + CHECK (not boolVal ("no")); + CHECK (not boolVal ("No")); + CHECK (not boolVal ("NO")); + CHECK (not boolVal ("0")); + CHECK (not boolVal ("-")); + + CHECK (boolVal ("yes ")); + CHECK (boolVal (" Yes")); + CHECK (boolVal (" + ")); + CHECK (not boolVal (" \n0 ")); + + VERIFY_ERROR (INVALID, boolVal("") ); + VERIFY_ERROR (INVALID, boolVal(" ") ); + VERIFY_ERROR (INVALID, boolVal("yEs") ); + VERIFY_ERROR (INVALID, boolVal("tRuE") ); + VERIFY_ERROR (INVALID, boolVal("falsehood")); + VERIFY_ERROR (INVALID, boolVal("11") ); + VERIFY_ERROR (INVALID, boolVal("+1") ); + VERIFY_ERROR (INVALID, boolVal("↯") ); + + + CHECK (isYes ("true")); + CHECK (isYes ("True")); + CHECK (isYes ("TRUE")); + CHECK (isYes ("yes")); + CHECK (isYes ("Yes")); + CHECK (isYes ("1")); + CHECK (isYes ("+")); + + CHECK (isYes (" True ")); + CHECK (isYes (" \n\n 1 \t ")); + + CHECK (not isYes (" True and False")); + CHECK (not isYes ("tRuE")); + CHECK (not isYes ("+2")); + CHECK (not isYes ("no")); + CHECK (not isYes ("1010")); + CHECK (not isYes ("↯")); + CHECK (not isYes (" ")); + CHECK (not isYes ("")); + } + }; + + LAUNCHER (UtilParseBool_test, "unit common"); + + +}} // namespace util::test + diff --git a/tests/library/sanitised-identifier-test.cpp b/tests/library/util-sanitised-identifier-test.cpp similarity index 85% rename from tests/library/sanitised-identifier-test.cpp rename to tests/library/util-sanitised-identifier-test.cpp index 7d7ce6d18..cdfd9f05b 100644 --- a/tests/library/sanitised-identifier-test.cpp +++ b/tests/library/util-sanitised-identifier-test.cpp @@ -1,5 +1,5 @@ /* - SanitizedIdentifier(Test) - remove non-standard-chars and punctuation + UtilSanitizedIdentifier(Test) - remove non-standard-chars and punctuation Copyright (C) Lumiera.org 2008, Hermann Vosseler @@ -20,8 +20,8 @@ * *****************************************************/ -/** @file sanitised-identifier-test.cpp - ** unit test \ref SanitizedIdentifier_test +/** @file util-sanitised-identifier-test.cpp + ** unit test \ref UtilSanitizedIdentifier_test */ @@ -38,7 +38,7 @@ namespace util { namespace test { - class SanitizedIdentifier_test : public Test + class UtilSanitizedIdentifier_test : public Test { virtual void run (Arg) { @@ -60,7 +60,7 @@ namespace test { } }; - LAUNCHER (SanitizedIdentifier_test, "unit common"); + LAUNCHER (UtilSanitizedIdentifier_test, "unit common"); }} // namespace util::test diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 7be1572e7..4f616e56b 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -2962,8 +2962,8 @@
- - + + @@ -2978,7 +2978,7 @@ - + @@ -3419,7 +3419,9 @@ - + + + @@ -3536,12 +3538,21 @@ + + + + + + + + + From 4635d18265763e572883688b7ad92f509d5c2cb2 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 3 Oct 2018 17:50:20 +0200 Subject: [PATCH 125/178] NotificationDisplay: draft function to retain only the errors 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. --- src/gui/widget/error-log-display.hpp | 59 +++++++++--- wiki/thinkPad.ichthyo.mm | 129 +++++++++++++++------------ 2 files changed, 123 insertions(+), 65 deletions(-) diff --git a/src/gui/widget/error-log-display.hpp b/src/gui/widget/error-log-display.hpp index 19d2064f4..a818056e2 100644 --- a/src/gui/widget/error-log-display.hpp +++ b/src/gui/widget/error-log-display.hpp @@ -67,6 +67,7 @@ //#include "lib/util.hpp" //#include +#include #include @@ -76,7 +77,9 @@ namespace widget { using util::_Fmt; using lib::Literal; + using std::make_pair; using std::vector; + using std::move; namespace { @@ -107,17 +110,25 @@ namespace widget { /** - * @todo WIP-WIP as of 9/2017 - * Just a text display box with scrollbars. - * Need to add formatting etc. + * 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; + using Entry = std::pair; - vector errorMarks_; + vector errorMarks_; Gtk::TextView textLog_; public: @@ -188,10 +199,38 @@ namespace widget { expand (true); } + /** + * clear all mere information messages; + * retain just the errors with tags + * @remark in fact populates a new buffer + */ void clearInfoMsg() { - UNIMPLEMENTED ("clear all mere information messages; then re-insert the errors with tags"); + auto newBuff = Gtk::TextBuffer::create (textLog_.get_buffer()->get_tag_table()); + vector newMarks; + for (Entry& entry : errorMarks_) + { + newMarks.emplace_back( + make_pair ( + newBuff->create_mark (newBuff->end(), true), // "left gravity": stays to the left of inserted text + newBuff->create_mark (newBuff->end(), false))); // "right gravity": sticks right behind the inserted text)) + + newBuff->insert (newBuff->end() + ,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)); } void @@ -220,19 +259,19 @@ namespace widget { * [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 */ - Mark + Entry addEntry (string const& text, Literal markupTagName =nullptr) { auto buff = textLog_.get_buffer(); - auto cursor = buff->get_insert(); - buff->move_mark (cursor, buff->end()); + auto begin = buff->create_mark (buff->end(), true); // "left gravity": stays to the left of inserted text + auto after = buff->create_mark (buff->end(), false);// "right gravity": sticks right behind the inserted text if (markupTagName) buff->insert_with_tag(buff->end(), text, cuString{markupTagName}); else buff->insert (buff->end(), text); buff->insert (buff->end(), "\n"); - textLog_.scroll_to (cursor); - return cursor; + textLog_.scroll_to (after); + return make_pair (move(begin), move(after)); } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 4f616e56b..f8d26919a 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -2063,8 +2063,7 @@ daß hier ## Zeilen gelöscht wurden

- - +
@@ -2140,6 +2139,29 @@ + + + + + + + + + + + + + +

+ ...das liegt vermutlich daran, daß ich die zweite Marke mit "right gravity" einfüge, +

+

+ aber eben am absoluten Ende des Buffers. Damit wird sie hinter jeder Einfügung bleiben +

+ + +
+
@@ -2190,14 +2212,17 @@ - - + + - - + + - - + + + + + @@ -2223,9 +2248,9 @@ - - - + + + @@ -2263,9 +2288,9 @@ - - - + + + @@ -2276,8 +2301,15 @@ - - + + + + + + + + + @@ -2386,7 +2418,7 @@ - + @@ -2644,8 +2676,8 @@ - - + + @@ -2692,23 +2724,24 @@ - - - + + +

- TODO: how can the Log-Widget be 'revealed'?? + Lösung: delegiere über zwei Stufen...

- Possibly allocate Widget, expand it. But how to make it visible? + Neue Revealer-Komponente bindet einen Funktor

-
- + + +
@@ -2942,7 +2975,7 @@ - + @@ -2957,8 +2990,7 @@ und nicht string wie für alle anderen state marks

- -
+
@@ -2974,8 +3006,7 @@ k

- - + @@ -3013,8 +3044,7 @@ Konzept ist nicht, sie gleich in die description zu übernehmen

- - +
@@ -3049,8 +3079,7 @@ keines! Impl verwendet strdup()

- - +
@@ -3097,8 +3126,7 @@ ...denn wir verwenden das Lumiera-Exception-API gar nicht

- - +
@@ -3119,8 +3147,7 @@ der Handler im ProcDispatcher sorgt dafür

- - +
@@ -3143,8 +3170,7 @@ ...welches keinen separaten Thread aufruft....?

- - +
@@ -3167,8 +3193,7 @@ nämlich im shutdownAll() weiter oben im Stack

- - +
@@ -3185,8 +3210,7 @@ aber genau der andere Thread, der das gemacht hat...

- - +
@@ -3236,8 +3260,7 @@ Wenn das nun wirklich fehlschlägt, bleibt uns sinnvollerweise nur noch die Selbstmord-Option

- - +
@@ -3262,8 +3285,7 @@ ein ZombieCheck spricht an

- - + @@ -3341,8 +3363,7 @@ Abgebrochen

- - + @@ -3415,8 +3436,7 @@ ..damit ~GtkLumiera() garantiert vorher aufgerufen wird

- - +
@@ -3534,8 +3554,7 @@ (hiv)~/devel/lumi-$

- - +
From e9527d63044dbf654ec6afc0214749f84a298a9d Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 3 Oct 2018 19:13:39 +0200 Subject: [PATCH 126/178] NotificationDisplay: proper handling of marks at insert position 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 --- src/gui/widget/error-log-display.hpp | 44 ++++++---- wiki/thinkPad.ichthyo.mm | 118 ++++++++++++++++++++++++--- 2 files changed, 132 insertions(+), 30 deletions(-) diff --git a/src/gui/widget/error-log-display.hpp b/src/gui/widget/error-log-display.hpp index a818056e2..526013c69 100644 --- a/src/gui/widget/error-log-display.hpp +++ b/src/gui/widget/error-log-display.hpp @@ -109,7 +109,8 @@ namespace widget { } - /** + + /*********************************************************************//** * 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. @@ -168,6 +169,7 @@ namespace widget { textLog_.get_buffer()->set_text (placeholder); // discard existing content } + /** just add normal information message to buffer, * without special markup and without expanding the widget */ void @@ -201,7 +203,7 @@ namespace widget { /** * clear all mere information messages; - * retain just the errors with tags + * retain just the previously tagged errors * @remark in fact populates a new buffer */ void @@ -211,12 +213,15 @@ namespace widget { vector newMarks; for (Entry& entry : errorMarks_) { + newBuff->insert (newBuff->end(), "\n"); + auto pos = newBuff->end(); + --pos; newMarks.emplace_back( make_pair ( - newBuff->create_mark (newBuff->end(), true), // "left gravity": stays to the left of inserted text - newBuff->create_mark (newBuff->end(), false))); // "right gravity": sticks right behind the inserted text)) + 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 (newBuff->end() + newBuff->insert (pos // copy from old to new buffer, complete with formatting tag ,entry.first->get_iter() ,entry.second->get_iter() ); @@ -250,27 +255,32 @@ namespace widget { private:/* ===== Internals ===== */ /** 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"][insert-mark], - * which corresponds to the text cursor. Thus it suffices to navigate to text end, - * insert and scroll into view. + * @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 and set that text mark to 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(); - auto begin = buff->create_mark (buff->end(), true); // "left gravity": stays to the left of inserted text - auto after = buff->create_mark (buff->end(), false);// "right gravity": sticks right behind the inserted text - if (markupTagName) - buff->insert_with_tag(buff->end(), text, cuString{markupTagName}); - else - buff->insert (buff->end(), text); buff->insert (buff->end(), "\n"); - textLog_.scroll_to (after); + 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)); } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index f8d26919a..106e5c755 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -1773,7 +1773,8 @@ - + + @@ -2116,8 +2117,8 @@ - - + + @@ -2146,7 +2147,7 @@ - + @@ -2161,10 +2162,27 @@ - + + + - - + + + + + + +

+ ...dann Einfügestelle davor platzieren +

+ + +
+
+
+ + +
@@ -2210,8 +2228,8 @@
- - + + @@ -2756,8 +2774,8 @@ - - + + @@ -5014,9 +5032,20 @@ + + + + + + + + + + + @@ -5028,7 +5057,7 @@

- + @@ -37869,8 +37898,9 @@ - + + @@ -38812,6 +38842,68 @@ + + + + + + + + + + + + + + + + + + + + +

+ stets zwischen den Zeichen +

+ + +
+
+ + + + + + +

+ Danke! endlich bekommt das mal jemand korrekt hin +

+ + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + From 5aa28626ad2ac5344ec1747cebe89f4df77b1347 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 3 Oct 2018 19:33:28 +0200 Subject: [PATCH 127/178] NotificationDisplay: function to demote error entries into warnings ...and remove them from the mark-index for special handling --- src/gui/widget/error-log-display.hpp | 13 +++++++++- wiki/thinkPad.ichthyo.mm | 38 ++++++++++++++-------------- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/gui/widget/error-log-display.hpp b/src/gui/widget/error-log-display.hpp index 526013c69..3f5d316b1 100644 --- a/src/gui/widget/error-log-display.hpp +++ b/src/gui/widget/error-log-display.hpp @@ -238,10 +238,21 @@ namespace widget { addInfo (_Fmt{_("───════ %d old log lines removed ════───\n")} % (oldLines-newLines)); } + + /** visit all errors and downgrade the markup; discard all bookmarks */ void turnError_into_InfoMsg() { - UNIMPLEMENTED ("visit all errors and downgrade the markup; discard all bookmarks"); + 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(); } void diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 106e5c755..f582a7386 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -2072,9 +2072,9 @@ - + - + @@ -2112,12 +2112,12 @@ - + - + - + @@ -2243,14 +2243,14 @@ - - - + + + - + - - + + @@ -2669,8 +2669,8 @@ - - + + @@ -2807,16 +2807,16 @@ - - + + - - - - + + + + From fb4d9be2b405d3a4fda9b16bbd90475984ac175d Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 4 Oct 2018 04:06:30 +0200 Subject: [PATCH 128/178] draft generic decorator to make a widget flash not finished, having problems with Lumiera's stylesheet --- src/gui/model/expander-revealer.hpp | 2 +- src/gui/model/flash-deco.hpp | 93 +++++++++++++ src/gui/style-scheme.cpp | 44 ++++++ src/gui/style-scheme.hpp | 58 ++++++++ src/gui/widget/error-log-display.hpp | 5 +- wiki/thinkPad.ichthyo.mm | 195 ++++++++++++++++++++++++--- 6 files changed, 371 insertions(+), 26 deletions(-) create mode 100644 src/gui/model/flash-deco.hpp create mode 100644 src/gui/style-scheme.cpp create mode 100644 src/gui/style-scheme.hpp diff --git a/src/gui/model/expander-revealer.hpp b/src/gui/model/expander-revealer.hpp index bdf91bba0..41c5db905 100644 --- a/src/gui/model/expander-revealer.hpp +++ b/src/gui/model/expander-revealer.hpp @@ -35,7 +35,7 @@ ** ## Usage in the default implementation ** ** The base class of all [tangible UI elements](\ref Tangible) provides a default implementation - ** for these generic interaction mechanism: It offers slots to connect UI signals against, and + ** for these generic interaction mechanisms: It offers slots to connect UI signals against, and ** it understands the \em `mark` messages `"expand"` and `"revealYourself"`. 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 diff --git a/src/gui/model/flash-deco.hpp b/src/gui/model/flash-deco.hpp new file mode 100644 index 000000000..fd86071d5 --- /dev/null +++ b/src/gui/model/flash-deco.hpp @@ -0,0 +1,93 @@ +/* + FLASH-DECO.hpp - widget decorator to add a visual flash action + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + 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 users 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" + +//#include + + +namespace gui { +namespace model { + +// using std::move; + + + /** + * 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 + */ + template + class FlashDeco + { + static_assert (std::is_base_of() + ,"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*/ diff --git a/src/gui/style-scheme.cpp b/src/gui/style-scheme.cpp new file mode 100644 index 000000000..3809ab0cd --- /dev/null +++ b/src/gui/style-scheme.cpp @@ -0,0 +1,44 @@ +/* + StyleScheme - magic keys to access uniform styling scheme + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + 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 + ** Definition of access keys for uniform UI styling. + ** + ** @see gui::workspace::UiStyle + ** + */ + +#include "gui/gtk-base.hpp" +#include "gui/style-scheme.hpp" + +namespace gui { + + const Literal TAG_ERROR{"ERROR"}; ////////////////////////////////////////////////////////////TICKET #1168 : find a way to manage style of custom extended UI elements + const Literal TAG_WARN{"WARN"}; + + cuString CSS_CLASS_FLASH{"indication_flash"}; + const uint STYLE_FLASH_DURATION_in_ms = 1100; + + + +}// namespace gui diff --git a/src/gui/style-scheme.hpp b/src/gui/style-scheme.hpp new file mode 100644 index 000000000..383ae8987 --- /dev/null +++ b/src/gui/style-scheme.hpp @@ -0,0 +1,58 @@ +/* + STYLE_SCHEME.hpp - magic keys to access uniform styling scheme + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + 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*/ diff --git a/src/gui/widget/error-log-display.hpp b/src/gui/widget/error-log-display.hpp index 3f5d316b1..44e75ed23 100644 --- a/src/gui/widget/error-log-display.hpp +++ b/src/gui/widget/error-log-display.hpp @@ -60,6 +60,7 @@ #define GUI_WIDGET_ERROR_LOG_DISPLAY_H #include "gui/gtk-base.hpp" +#include "gui/style-scheme.hpp" #include "gui/model/expander-revealer.hpp" #include "include/gui-notification-facade.h" #include "lib/format-string.hpp" @@ -83,10 +84,6 @@ namespace widget { namespace { - const Literal TAG_ERROR{"ERROR"}; /////////////////////////////////////////////////////////////TICKET #1168 : find a way to manage style of custom extended UI elements - const Literal TAG_WARN{"WARN"}; - - using Tag = Glib::RefPtr; /** diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index f582a7386..ea598d09e 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -2095,12 +2095,21 @@ - + + + + + + + + + + @@ -2257,13 +2266,105 @@ - + + + + + + + + + +

+ ca 2014 finde ich einige Blog-Einträge, daß jetzt keine Theming-Engine mehr notwendig ist, +

+

+ weil man alles per CSS machen kann. Seit Jessie findet GTK die Engine nicht mehr, +

+

+ obwohl das Standard-Theme-Paket installiert ist. +

+

+ +

+

+ Dummerweise funktioniert nun aber auch das Übersteuern mit unseren Farben nicht mehr +

+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ denn dieses Design gibt die beste Balance zwischen den Belangen +

+
    +
  • + kein zusätzlicher Speicherbedarf +
  • +
  • + Callback ist automatisch abgesichert (sigc::mem_fun auf Trackable) +
  • +
  • + kann direkt auf das Styling des Parent zugreifen +
  • +
+

+ Einziger Nachteil ist die etwas verwirrende Schreibweise des dekorierten Typs +

+ + +
+
+ +
@@ -5189,6 +5290,9 @@ + + +
@@ -5338,6 +5442,40 @@ + + + + + + +

+ wenn die GTK-Loop angehalten wird, +

+

+ dann koppelt sich GTK anscheinend auch +

+

+ automatisch vom Window-Manager ab. +

+

+ +

+

+ Jedenfalls habe ich nun schon mehrfach den Shutdown "von unten" getriggert, +

+

+ und es wurden alle Fenster geschlossen. +

+

+ +

+

+ Allerdings habe ich an der Stelle immer noch GTK-Assertions +

+ + +
+
@@ -6366,7 +6504,7 @@ - + @@ -18612,7 +18750,7 @@ - + @@ -18829,7 +18967,7 @@ - + @@ -19004,7 +19142,7 @@ - + @@ -19019,7 +19157,7 @@ - + @@ -19051,7 +19189,7 @@ - + @@ -19104,7 +19242,7 @@ - + @@ -19119,7 +19257,7 @@ - + @@ -26017,7 +26155,7 @@ - + @@ -38031,13 +38169,28 @@ - - - - + + + + + + +

+ Glib::RefPtr<StyleContext> context = widget->get_style_context(); +

+

+ context->add_class("ohMy"); +

+ + +
+ + + - - + + + @@ -38071,8 +38224,8 @@ - - + +
@@ -38185,7 +38338,7 @@ - + @@ -38204,7 +38357,7 @@ - + From b7180735f9be25109e23c63d6673cabf6d355bf9 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 5 Oct 2018 00:29:48 +0200 Subject: [PATCH 129/178] StyleCSS: abandon using a theme engine 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 --- src/gui/gtk-lumiera.css | 5 ++--- src/gui/workspace/ui-style.cpp | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/gui/gtk-lumiera.css b/src/gui/gtk-lumiera.css index 20ffe9a52..6e22479ca 100644 --- a/src/gui/gtk-lumiera.css +++ b/src/gui/gtk-lumiera.css @@ -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 { diff --git a/src/gui/workspace/ui-style.cpp b/src/gui/workspace/ui-style.cpp index 2315866b1..065c2215b 100644 --- a/src/gui/workspace/ui-style.cpp +++ b/src/gui/workspace/ui-style.cpp @@ -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); } From e573d3cc96f3896c691a4ab5c0f8a3b1540aa484 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 5 Oct 2018 03:25:50 +0200 Subject: [PATCH 130/178] StyleCSS: add alternative stylesheet to be comined with the system theme (#1170) 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 --- data/config/setup.ini | 3 +- src/common/configfacade.cpp | 2 +- src/gui/lumiera-light-theme-complement.css | 25 ++++ src/gui/{gtk-lumiera.css => lumiera.css} | 5 +- wiki/thinkPad.ichthyo.mm | 144 ++++++++++++++++++++- 5 files changed, 175 insertions(+), 4 deletions(-) create mode 100644 src/gui/lumiera-light-theme-complement.css rename src/gui/{gtk-lumiera.css => lumiera.css} (98%) diff --git a/data/config/setup.ini b/data/config/setup.ini index aaa0e2f2a..bffb29e92 100644 --- a/data/config/setup.ini +++ b/data/config/setup.ini @@ -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 diff --git a/src/common/configfacade.cpp b/src/common/configfacade.cpp index 3841f14eb..74d915aea 100644 --- a/src/common/configfacade.cpp +++ b/src/common/configfacade.cpp @@ -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) */ diff --git a/src/gui/lumiera-light-theme-complement.css b/src/gui/lumiera-light-theme-complement.css new file mode 100644 index 000000000..841c07fa1 --- /dev/null +++ b/src/gui/lumiera-light-theme-complement.css @@ -0,0 +1,25 @@ +/* + lumiera-light-theme-complement.css - minimal supplement for GTK-3 light style sheet + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + 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 ---------- */ diff --git a/src/gui/gtk-lumiera.css b/src/gui/lumiera.css similarity index 98% rename from src/gui/gtk-lumiera.css rename to src/gui/lumiera.css index 6e22479ca..d9aa1a722 100644 --- a/src/gui/gtk-lumiera.css +++ b/src/gui/lumiera.css @@ -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 @@ -275,3 +275,6 @@ class "gtkmm__CustomObject_TimelineRuler" style:highest "timeline_ruler" class "gtkmm__CustomObject_TimelineHeaderWidget" style:highest "timeline_header_widget" */ + + +/* ---------- Styles for special markup ---------- */ diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index ea598d09e..0479d1cdb 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -5293,6 +5293,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ CSS genügt +

+ + +
+ +
+
+ + + + + + + + + + + + + + + +

+ füge möglichst hoch in der Hierarchie Regeln ein, +

+

+ die ein property auf den Wert 'inherit' setzten +

+ + +
+ + + + + +

+ z.B. in den Block mit den "default-Regeln" +

+

+   * { +

+

+     color:            inherit; /* by default inherit content colour from parent widget */ +

+

+     background-color: inherit; +

+

+ } +

+ + +
+
+
+
+ + + +
+
@@ -37990,14 +38121,25 @@ - + + + + + + + + + + + + From 33af82cf737547ed574dfcc488245dde84811393 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 5 Oct 2018 05:36:53 +0200 Subject: [PATCH 131/178] NotificationDisplay: now responding to the "Flash" message on UI-Bus 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 --- src/gui/ctrl/notification-hub.hpp | 14 +- src/gui/lumiera-light-theme-complement.css | 8 + src/gui/lumiera.css | 8 + src/gui/model/flash-deco.hpp | 2 + src/gui/style-scheme.cpp | 2 +- src/gui/widget/error-log-display.hpp | 17 +- wiki/thinkPad.ichthyo.mm | 198 ++++++++++++++------- 7 files changed, 171 insertions(+), 78 deletions(-) diff --git a/src/gui/ctrl/notification-hub.hpp b/src/gui/ctrl/notification-hub.hpp index c380c29c7..ec027d41f 100644 --- a/src/gui/ctrl/notification-hub.hpp +++ b/src/gui/ctrl/notification-hub.hpp @@ -26,8 +26,8 @@ ** 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 ErrorlogWidget for presentation - ** of those messages + ** On first use, an InfoboxPanel is allocated to hold an ErrorLogDisplay widget for + ** presentation of those messages ** ** # Notification Controller Behaviour ** @@ -47,7 +47,6 @@ ** to reveal or expand the widget (but if necessary, a new widget is allocated) ** - error messages also set an error marker state (*TODO* reflect this in the presentation), ** and they cause the display widget to be expanded - ** (*TODO* 8/18 consider also call the doRevealYourself() function) ** - 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. @@ -70,8 +69,6 @@ #include "gui/model/w-link.hpp" #include -//#include -//#include namespace gui { @@ -188,8 +185,8 @@ namespace ctrl { ~NotificationHub() { }; - private: + private: /** external operation to find or allocate an log display widget */ WidgetAllocator allocateWidget_; @@ -204,14 +201,9 @@ namespace ctrl { widget_.connect (allocateWidget_()); return *widget_; } - }; - /** */ - - - }}// namespace gui::ctrl #endif /*GUI_CTRL_NOTIFICATION_HUB_H*/ diff --git a/src/gui/lumiera-light-theme-complement.css b/src/gui/lumiera-light-theme-complement.css index 841c07fa1..1539e5609 100644 --- a/src/gui/lumiera-light-theme-complement.css +++ b/src/gui/lumiera-light-theme-complement.css @@ -23,3 +23,11 @@ /* ---------- Styles for special markup ---------- */ + + .indication_flash, + .indication_flash * { + background-color: LightYellow; +} + .indication_flash text { + border: 4px solid red; +} diff --git a/src/gui/lumiera.css b/src/gui/lumiera.css index d9aa1a722..38b117352 100644 --- a/src/gui/lumiera.css +++ b/src/gui/lumiera.css @@ -278,3 +278,11 @@ class "gtkmm__CustomObject_TimelineHeaderWidget" style:highest "timeline_header_ /* ---------- Styles for special markup ---------- */ + + .indication_flash, + .indication_flash * { + background-color: shade(@hover_bg_color, 1.6); +} + .indication_flash text { + border: 4px solid red; +} diff --git a/src/gui/model/flash-deco.hpp b/src/gui/model/flash-deco.hpp index fd86071d5..dbd5d290c 100644 --- a/src/gui/model/flash-deco.hpp +++ b/src/gui/model/flash-deco.hpp @@ -57,9 +57,11 @@ 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 FlashDeco + : public WIT { static_assert (std::is_base_of() ,"wrapped target type required to be a Gtk::Widget"); diff --git a/src/gui/style-scheme.cpp b/src/gui/style-scheme.cpp index 3809ab0cd..c0a9212eb 100644 --- a/src/gui/style-scheme.cpp +++ b/src/gui/style-scheme.cpp @@ -36,7 +36,7 @@ namespace gui { const Literal TAG_ERROR{"ERROR"}; ////////////////////////////////////////////////////////////TICKET #1168 : find a way to manage style of custom extended UI elements const Literal TAG_WARN{"WARN"}; - cuString CSS_CLASS_FLASH{"indication_flash"}; + cuString CSS_CLASS_FLASH{"indication_flash"}; ///< CSS class added temporarily to make a widget "flash" in response to the corresponding UI-Bus message const uint STYLE_FLASH_DURATION_in_ms = 1100; diff --git a/src/gui/widget/error-log-display.hpp b/src/gui/widget/error-log-display.hpp index 44e75ed23..3a2fce578 100644 --- a/src/gui/widget/error-log-display.hpp +++ b/src/gui/widget/error-log-display.hpp @@ -61,13 +61,12 @@ #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 #include #include @@ -125,10 +124,11 @@ namespace widget { using Mark = Glib::RefPtr; using Entry = std::pair; + using TextWidget = model::FlashDeco; vector errorMarks_; - Gtk::TextView textLog_; - + TextWidget textLog_; + public: ~ErrorLogDisplay() { }; @@ -154,7 +154,7 @@ namespace widget { model::Revealer reveal; - /** empty buffer and discard all error bookmarks */ + /** empty text buffer and discard all error bookmarks */ void clearAll() { @@ -252,10 +252,15 @@ namespace widget { errorMarks_.clear(); } + + /** temporarily change display style to prompt for attention; + * set callback-timeout to return to normal state. + * @see \ref gui::CSS_CLASS_FLASH + */ void triggerFlash() { - UNIMPLEMENTED ("paint and set callback-timeout to return to normal state"); + textLog_.flash(); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 0479d1cdb..0341d5f1a 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -2077,15 +2077,29 @@ - + - - + + + + + + + + + +

+ erscheint definitiv nicht als CSS-Node +

+ + +
+
@@ -2169,8 +2183,7 @@ aber eben am absoluten Ende des Buffers. Damit wird sie hinter jeder Einfügung bleiben

- -
+ @@ -2185,8 +2198,7 @@ ...dann Einfügestelle davor platzieren

- - +
@@ -2262,15 +2274,35 @@
- - - - + + + + - + + + + + + + + +

+ das Widget hat offensichtlich keine eigene graphische Repräsentation. +

+

+ dieser Fall dürfte häufiger auftreten +

+ + +
+
+
+ + @@ -2294,20 +2326,39 @@ Dummerweise funktioniert nun aber auch das Übersteuern mit unseren Farben nicht mehr

+
+
+ + + + + + + + + +

+ ...welches ich zusammen mit meinem +

+

+ normalen 'light' destkop-theme verwenden kann +

+
- - + + + - - + + @@ -2360,8 +2411,7 @@ Einziger Nachteil ist die etwas verwirrende Schreibweise des dekorierten Typs

- - +
@@ -2447,8 +2497,8 @@
- - + + @@ -2468,7 +2518,7 @@ - + @@ -2532,8 +2582,8 @@ - - + + @@ -2829,8 +2879,8 @@ - - + + @@ -2858,8 +2908,7 @@ Neue Revealer-Komponente bindet einen Funktor

- - +
@@ -2920,8 +2969,8 @@
- - + + @@ -2945,7 +2994,7 @@ - + @@ -3757,28 +3806,28 @@ - - - - + + + + - - + + - - + + - - + + - - + + - - + + - - + + @@ -5339,6 +5388,37 @@ + + + + + + + + + + + + + + + + + + + +

+ den Klassennamen ohne das Namespace-Präfix "GTK", und alles lower case. +

+

+ Im Zweifelsfall den GTK+ Inspector verwenden! +

+ + +
+
+
+
@@ -5364,8 +5444,7 @@ CSS genügt

- - +
@@ -5391,8 +5470,7 @@ die ein property auf den Wert 'inherit' setzten

- - + @@ -5414,8 +5492,7 @@ }

- -
+
@@ -5604,8 +5681,7 @@ Allerdings habe ich an der Stelle immer noch GTK-Assertions

- - +
@@ -38298,6 +38374,11 @@
+ + + + + @@ -38324,8 +38405,7 @@ context->add_class("ohMy");

- - + @@ -39161,8 +39241,7 @@ stets zwischen den Zeichen

- - +
@@ -39174,8 +39253,7 @@ Danke! endlich bekommt das mal jemand korrekt hin

- -
+
From 12344ae9d8bd914a3eb87db3d0da4d309d3cae08 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 5 Oct 2018 15:59:21 +0200 Subject: [PATCH 132/178] NotificationDisplay: add an Error-State and implement signal to trigger on change this is more or less gratitious functionality for now, yet I consider it a proof-of-concept --- src/gui/widget/error-log-display.hpp | 34 ++++++++++++++++++++++++++++ tests/gui/abstract-tangible-test.cpp | 4 ++-- wiki/thinkPad.ichthyo.mm | 32 +++++++++++++++++++++----- 3 files changed, 62 insertions(+), 8 deletions(-) diff --git a/src/gui/widget/error-log-display.hpp b/src/gui/widget/error-log-display.hpp index 3a2fce578..d33f87aa8 100644 --- a/src/gui/widget/error-log-display.hpp +++ b/src/gui/widget/error-log-display.hpp @@ -129,6 +129,9 @@ namespace widget { vector errorMarks_; TextWidget textLog_; + sigc::signal errorChangedSignal_; + + public: ~ErrorLogDisplay() { }; @@ -158,12 +161,17 @@ namespace widget { void clearAll() { + bool shallNotify = not errorMarks_.empty(); + errorMarks_.clear(); size_t lineCnt = textLog_.get_buffer()->get_line_count(); 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 (true); } @@ -192,10 +200,15 @@ namespace widget { void addError (string text) { + bool shallNotify = errorMarks_.empty(); + errorMarks_.emplace_back( addEntry ("ERROR: "+text, TAG_ERROR)); if (not expand.isExpanded()) expand (true); + + if (shallNotify) + errorChangedSignal_.emit (true); } /** @@ -240,6 +253,8 @@ namespace widget { void turnError_into_InfoMsg() { + bool shallNotify = not errorMarks_.empty(); + auto buff = textLog_.get_buffer(); for (Entry& entry : errorMarks_) { @@ -250,6 +265,9 @@ namespace widget { buff->apply_tag_by_name (uString{TAG_WARN}, begin,end); } errorMarks_.clear(); + + if (shallNotify) + errorChangedSignal_.emit (true); } @@ -264,6 +282,22 @@ namespace widget { } + /* ======= Error-State ======= */ + + bool + isError() const + { + return not errorMarks_.empty(); + } + + /** signal fired when error state changes */ + sigc::signal + signalErrorChanged() + { + return errorChangedSignal_; + } + + private:/* ===== Internals ===== */ diff --git a/tests/gui/abstract-tangible-test.cpp b/tests/gui/abstract-tangible-test.cpp index 087c4b61e..9382b4c9d 100644 --- a/tests/gui/abstract-tangible-test.cpp +++ b/tests/gui/abstract-tangible-test.cpp @@ -180,8 +180,8 @@ namespace test { invokeCommand(); markState(); revealer(); -// notify(); -// mutate(); + notify(); + mutate(); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 0341d5f1a..1285bcd45 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -2484,6 +2484,22 @@
+ + + + + + + + + + + + + + + +
@@ -2569,8 +2585,8 @@ - - + + @@ -2865,13 +2881,14 @@ - - + + - - + + + @@ -3793,6 +3810,9 @@ + + + From e8931bf4bfd0be05b205ef81a6635d1659151ccd Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 5 Oct 2018 18:26:26 +0200 Subject: [PATCH 133/178] NotificationDisplay: react on changes of the error state 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 --- src/gui/panel/infobox-panel.cpp | 56 +++++++++----- src/gui/panel/infobox-panel.hpp | 4 +- src/gui/widget/error-log-display.hpp | 8 +- wiki/thinkPad.ichthyo.mm | 107 +++++++++++++++------------ 4 files changed, 102 insertions(+), 73 deletions(-) diff --git a/src/gui/panel/infobox-panel.cpp b/src/gui/panel/infobox-panel.cpp index 3d9668ace..8e4aa66dd 100644 --- a/src/gui/panel/infobox-panel.cpp +++ b/src/gui/panel/infobox-panel.cpp @@ -28,10 +28,7 @@ #include "gui/gtk-base.hpp" #include "gui/panel/infobox-panel.hpp" #include "gui/widget/error-log-display.hpp" -#include "lib/format-string.hpp" -using util::_Fmt; -using Glib::ustring; namespace gui { namespace panel{ @@ -43,8 +40,8 @@ namespace panel{ : Panel(panelManager, dockItem, getTitle(), getStockID()) , twoParts_{Gtk::ORIENTATION_VERTICAL} , buttons_{} - , frame_{"UI Integration Experiments"} - , logExpander_{"Error Log"} + , frame_{_("System Information")} + , logExpander_{_("Error Log")} , theLog_{} { twoParts_.pack_start(frame_); @@ -52,20 +49,41 @@ namespace panel{ 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 ("Experiment 1:\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... // 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( + sigc::mem_fun(*this, &InfoBoxPanel::reflect_LogErrorState), false + )); } + const char* InfoBoxPanel::getTitle() { @@ -104,20 +122,20 @@ namespace panel{ 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"}; - - getLog().addError (msgTemplate % ++bangNo); + buttonClearErr_.set_visible (isError); + buttonClearInfo_.set_visible (isError); + INFO (gui, "Error = %d", isError); } diff --git a/src/gui/panel/infobox-panel.hpp b/src/gui/panel/infobox-panel.hpp index e000252a1..b7e0907c5 100644 --- a/src/gui/panel/infobox-panel.hpp +++ b/src/gui/panel/infobox-panel.hpp @@ -66,13 +66,13 @@ namespace panel{ private: Gtk::Box twoParts_; Gtk::ButtonBox buttons_; - Gtk::Button button_1_; + Gtk::Button buttonClear_, buttonClearInfo_, buttonClearErr_; Gtk::Frame frame_; Gtk::Expander logExpander_; std::unique_ptr theLog_; - void experiment_1(); + void reflect_LogErrorState (bool); }; diff --git a/src/gui/widget/error-log-display.hpp b/src/gui/widget/error-log-display.hpp index d33f87aa8..358c9e1ea 100644 --- a/src/gui/widget/error-log-display.hpp +++ b/src/gui/widget/error-log-display.hpp @@ -66,6 +66,7 @@ #include "include/gui-notification-facade.h" #include "lib/format-string.hpp" #include "lib/symbol.hpp" +#include "lib/util.hpp" #include #include @@ -75,6 +76,7 @@ namespace gui { namespace widget { + using util::max; using util::_Fmt; using lib::Literal; using std::make_pair; @@ -164,14 +166,14 @@ namespace widget { bool shallNotify = not errorMarks_.empty(); errorMarks_.clear(); - size_t lineCnt = textLog_.get_buffer()->get_line_count(); + 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 (true); + errorChangedSignal_.emit (false); } @@ -267,7 +269,7 @@ namespace widget { errorMarks_.clear(); if (shallNotify) - errorChangedSignal_.emit (true); + errorChangedSignal_.emit (false); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 1285bcd45..723417fae 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -1832,9 +1832,9 @@ - - - + + + @@ -2070,8 +2070,8 @@ - - + + @@ -2109,21 +2109,6 @@ - - - - - - - - - - - - - - - @@ -2207,7 +2192,8 @@ - + + @@ -2230,26 +2216,30 @@ - - + + + + + - - - - + + + + + - + - + @@ -2264,7 +2254,7 @@ - + @@ -2274,7 +2264,7 @@ - + @@ -2417,7 +2407,7 @@ - + @@ -2484,9 +2474,24 @@ - - + + + + + + + + + + + + + + + + + @@ -2496,13 +2501,16 @@ - - - - - - + + + + + + + + + @@ -2583,7 +2591,7 @@ - + @@ -2836,7 +2844,7 @@ - + @@ -2881,7 +2889,7 @@ - + @@ -2890,13 +2898,14 @@ - - + + + - + @@ -3006,9 +3015,9 @@ - + - + @@ -19484,7 +19493,7 @@ - + From cd557f50ec411c73c1c6ac56dfd6deef5435cdf7 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 6 Oct 2018 17:42:22 +0200 Subject: [PATCH 134/178] DemoGuiRoundtrip: successfully completed (closes #1999) --- doc/technical/gui/GuiConnect.txt | 80 ++++ .../gui/{guiTheme.txt => GuiTheme.txt} | 13 +- src/gui/ctrl/notification-hub.hpp | 9 +- src/gui/demo-gui-roundtrip.cpp | 112 ------ src/gui/dialog/test-control.hpp | 2 +- src/gui/interact/interaction-director.cpp | 5 - src/gui/model/flash-deco.hpp | 7 +- src/gui/panel/infobox-panel.cpp | 9 +- src/gui/panel/infobox-panel.hpp | 5 +- src/gui/style-scheme.cpp | 5 +- src/gui/widget/error-log-display.hpp | 30 +- src/proc/cmd/meta-cmd.cpp | 14 +- wiki/thinkPad.ichthyo.mm | 357 ++++++++++-------- 13 files changed, 338 insertions(+), 310 deletions(-) create mode 100644 doc/technical/gui/GuiConnect.txt rename doc/technical/gui/{guiTheme.txt => GuiTheme.txt} (84%) delete mode 100644 src/gui/demo-gui-roundtrip.cpp diff --git a/doc/technical/gui/GuiConnect.txt b/doc/technical/gui/GuiConnect.txt new file mode 100644 index 000000000..84458a8fc --- /dev/null +++ b/doc/technical/gui/GuiConnect.txt @@ -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). + diff --git a/doc/technical/gui/guiTheme.txt b/doc/technical/gui/GuiTheme.txt similarity index 84% rename from doc/technical/gui/guiTheme.txt rename to doc/technical/gui/GuiTheme.txt index f28327e57..2fa13d845 100644 --- a/doc/technical/gui/guiTheme.txt +++ b/doc/technical/gui/GuiTheme.txt @@ -17,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. @@ -53,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 ~~~~~~~~~~~~~ diff --git a/src/gui/ctrl/notification-hub.hpp b/src/gui/ctrl/notification-hub.hpp index ec027d41f..225bdc6da 100644 --- a/src/gui/ctrl/notification-hub.hpp +++ b/src/gui/ctrl/notification-hub.hpp @@ -41,12 +41,11 @@ ** 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. + ** 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 (*TODO* reflect this in the presentation), - ** and they cause the display widget to be expanded + ** - 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. @@ -91,7 +90,7 @@ namespace ctrl { { - /** content population and manipulation via UI-Bus */ + /** population and manipulation of persistent content via UI-Bus */ void buildMutator (lib::diff::TreeMutator::Handle buffer) override { diff --git a/src/gui/demo-gui-roundtrip.cpp b/src/gui/demo-gui-roundtrip.cpp deleted file mode 100644 index f22834533..000000000 --- a/src/gui/demo-gui-roundtrip.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/* - DemoGuiRoundtrip - placeholder to further progress of the GUI-Session connection - - Copyright (C) Lumiera.org - 2017, Hermann Vosseler - - 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 - - - -//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; - - public: - /** access point to set up the scaffolding. - * @internal this is a development helper and will be remoulded all the time. - */ - static lib::Depend 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 diff --git a/src/gui/dialog/test-control.hpp b/src/gui/dialog/test-control.hpp index 4a2796407..8d8b04e77 100644 --- a/src/gui/dialog/test-control.hpp +++ b/src/gui/dialog/test-control.hpp @@ -29,7 +29,7 @@ ** 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 #1074 gradually augment the self-diagnostics controls in the UI + ** @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. */ diff --git a/src/gui/interact/interaction-director.cpp b/src/gui/interact/interaction-director.cpp index 3b63432e0..0c272b6e1 100644 --- a/src/gui/interact/interaction-director.cpp +++ b/src/gui/interact/interaction-director.cpp @@ -109,11 +109,6 @@ namespace interact { void InteractionDirector::populateContent_afterStart() { - ///////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1099 trigger creation of the UI notification display here - //// TODO : this clearly is a preliminary solution. Later it might be possible, - //// : to create the notification display on demand. But right now (8/2018) - //// : we need a GuiComponentView with predictable ID instructed to exist "somewhere" - //// invoke (cmd::test_meta_activateContentDiff); ////////////////////////////////////////////TICKET #211 prefix test_ bypasses logging and immediately executes for now (2018) } diff --git a/src/gui/model/flash-deco.hpp b/src/gui/model/flash-deco.hpp index dbd5d290c..15889a76c 100644 --- a/src/gui/model/flash-deco.hpp +++ b/src/gui/model/flash-deco.hpp @@ -24,7 +24,7 @@ /** @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 users attention. + ** 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, @@ -44,13 +44,9 @@ #include "gui/gtk-base.hpp" #include "gui/style-scheme.hpp" -//#include - namespace gui { namespace model { - -// using std::move; /** @@ -90,6 +86,5 @@ namespace model { }; - }} // namespace gui::model #endif /*GUI_MODEL_FLASH_DECO_H*/ diff --git a/src/gui/panel/infobox-panel.cpp b/src/gui/panel/infobox-panel.cpp index 8e4aa66dd..24f1bcd5c 100644 --- a/src/gui/panel/infobox-panel.cpp +++ b/src/gui/panel/infobox-panel.cpp @@ -79,8 +79,8 @@ namespace panel{ // after the UI is actually mapped to screen. Glib::signal_idle() .connect_once ( sigc::bind( - sigc::mem_fun(*this, &InfoBoxPanel::reflect_LogErrorState), false - )); + sigc::mem_fun (*this, &InfoBoxPanel::reflect_LogErrorState), false) + ); } @@ -98,7 +98,7 @@ namespace panel{ /** on demand allocate display of information / error log - * @note we assume it stays alive forever, once allocated + * @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 @@ -113,7 +113,7 @@ namespace panel{ theLog_.reset (new ErrorLogDisplay{}); logExpander_.set_expanded (false); logExpander_.add (*theLog_); - theLog_->expand = model::Expander{[&]() { return logExpander_.get_expanded(); } + theLog_->expand = model::Expander{[&]() -> bool { return logExpander_.get_expanded(); } ,[&](bool yes) { logExpander_.set_expanded (yes); } }; theLog_->reveal = model::Revealer{[&]() { Panel::show(true); @@ -135,7 +135,6 @@ namespace panel{ { buttonClearErr_.set_visible (isError); buttonClearInfo_.set_visible (isError); - INFO (gui, "Error = %d", isError); } diff --git a/src/gui/panel/infobox-panel.hpp b/src/gui/panel/infobox-panel.hpp index b7e0907c5..4ef8c1b0f 100644 --- a/src/gui/panel/infobox-panel.hpp +++ b/src/gui/panel/infobox-panel.hpp @@ -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 */ @@ -53,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&); diff --git a/src/gui/style-scheme.cpp b/src/gui/style-scheme.cpp index c0a9212eb..1dd8cf8c6 100644 --- a/src/gui/style-scheme.cpp +++ b/src/gui/style-scheme.cpp @@ -36,7 +36,10 @@ namespace gui { const Literal TAG_ERROR{"ERROR"}; ////////////////////////////////////////////////////////////TICKET #1168 : find a way to manage style of custom extended UI elements const Literal TAG_WARN{"WARN"}; - cuString CSS_CLASS_FLASH{"indication_flash"}; ///< CSS class added temporarily to make a widget "flash" in response to the corresponding UI-Bus message + /** 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; diff --git a/src/gui/widget/error-log-display.hpp b/src/gui/widget/error-log-display.hpp index 358c9e1ea..9e75b0977 100644 --- a/src/gui/widget/error-log-display.hpp +++ b/src/gui/widget/error-log-display.hpp @@ -34,9 +34,9 @@ ** 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::tangible`, it is + ** service. Since ErrorLogDisplay inherits from `sigc::trackable`, it is ** automatically detached from the WLink implementing this cross-attachment, - ** when actually the dtor is called. + ** 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 @@ -51,7 +51,7 @@ ** 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 /////////////////////////////////////////////////////////////TICKET #1099 : build a prototype/dummy for UI <-> Proc integration + ** with our GUI framework (#1099) ** */ @@ -149,8 +149,8 @@ namespace widget { // the vertical scrollbar will always be necessary.... set_policy (Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS); - this->add (textLog_); textLog_.set_editable (false); + this->add (textLog_); populateStandardTextTags (textLog_.get_buffer()->get_tag_table()); } @@ -163,7 +163,7 @@ namespace widget { void clearAll() { - bool shallNotify = not errorMarks_.empty(); + bool shallNotify = this->isError(); errorMarks_.clear(); size_t lineCnt = max (0, textLog_.get_buffer()->get_line_count() - 1); @@ -202,10 +202,10 @@ namespace widget { void addError (string text) { - bool shallNotify = errorMarks_.empty(); + bool shallNotify = not this->isError(); errorMarks_.emplace_back( - addEntry ("ERROR: "+text, TAG_ERROR)); + addEntry ("ERROR: "+text, TAG_ERROR)); if (not expand.isExpanded()) expand (true); @@ -226,7 +226,7 @@ namespace widget { for (Entry& entry : errorMarks_) { newBuff->insert (newBuff->end(), "\n"); - auto pos = newBuff->end(); + auto pos = newBuff->end(); --pos; newMarks.emplace_back( make_pair ( @@ -255,7 +255,7 @@ namespace widget { void turnError_into_InfoMsg() { - bool shallNotify = not errorMarks_.empty(); + if (not isError()) return; auto buff = textLog_.get_buffer(); for (Entry& entry : errorMarks_) @@ -264,17 +264,15 @@ namespace widget { 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); + buff->apply_tag_by_name (uString{TAG_WARN}, begin,end); } errorMarks_.clear(); - - if (shallNotify) - errorChangedSignal_.emit (false); + errorChangedSignal_.emit (false); } /** temporarily change display style to prompt for attention; - * set callback-timeout to return to normal state. + * set callback-timeout for return to normal state. * @see \ref gui::CSS_CLASS_FLASH */ void @@ -308,7 +306,7 @@ namespace widget { * @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 and set that text mark to the [insert position][insert-mark]. + * 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... * @@ -324,7 +322,7 @@ namespace widget { 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 + 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 diff --git a/src/proc/cmd/meta-cmd.cpp b/src/proc/cmd/meta-cmd.cpp index 69f78dc93..5eeb7fcbd 100644 --- a/src/proc/cmd/meta-cmd.cpp +++ b/src/proc/cmd/meta-cmd.cpp @@ -39,10 +39,10 @@ #include "proc/control/command-def.hpp" //#include "proc/mobject/session.hpp" #include "include/gui-notification-facade.h" -#include "gui/interact/wizard.hpp" //////////////////////////////////////////////////////////////TICKET #1099 : include needed temporarily +#include "gui/interact/wizard.hpp" //////////////////////////////////////////////////////////////TICKET #1140 : include needed temporarily #include "lib/diff/gen-node.hpp" #include "lib/idi/entry-id.hpp" -#include "lib/format-string.hpp" //////////////////////////////////////////////////////////////TICKET #1099 : include needed temporarily +#include "lib/format-string.hpp" //////////////////////////////////////////////////////////////TICKET #1140 : include needed temporarily //#include "lib/symbol.hpp" #include "lib/util.hpp" @@ -57,7 +57,7 @@ using gui::NotifyLevel; using gui::GuiNotification; using lib::diff::GenNode; //using util::cStr; -using util::_Fmt; //////////////////////////////////////////////////////////////TICKET #1099 : include needed temporarily +using util::_Fmt; //////////////////////////////////////////////////////////////TICKET #1140 : include needed temporarily using util::isYes; using std::string; @@ -144,7 +144,7 @@ COMMAND_DEFINITION (meta_deactivateContentDiff) /* ===== Demo and Development ===== */ /** DemoGuiRoundtrip: push a notification info message back up into the UI. - * @todo this is a demo mock setup to further development of the Proc-UI integration //////////////////TICKET #1099 : prototypical Proc-GUI communication + * @todo this is a demo mock setup to further development of the Proc-UI integration //////////////////TICKET #1140 : prototypical Proc-GUI communication */ COMMAND_DEFINITION (test_meta_displayInfo) { @@ -164,7 +164,7 @@ COMMAND_DEFINITION (test_meta_displayInfo) /** DemoGuiRoundtrip: send a `markError` message back up into the UI. - * @todo this is a demo mock setup to further development of the Proc-UI integration //////////////////TICKET #1099 : prototypical Proc-GUI communication + * @todo this is a demo mock setup to further development of the Proc-UI integration //////////////////TICKET #1140 : prototypical Proc-GUI communication */ COMMAND_DEFINITION (test_meta_markError) { @@ -185,7 +185,7 @@ COMMAND_DEFINITION (test_meta_markError) /** DemoGuiRoundtrip: send a `markNote` message back up into the UI. - * @todo this is a demo mock setup to further development of the Proc-UI integration //////////////////TICKET #1099 : prototypical Proc-GUI communication + * @todo this is a demo mock setup to further development of the Proc-UI integration //////////////////TICKET #1140 : prototypical Proc-GUI communication */ COMMAND_DEFINITION (test_meta_markNote) { @@ -206,7 +206,7 @@ COMMAND_DEFINITION (test_meta_markNote) /** DemoGuiRoundtrip: send a generic `mark` message with given _action ID_ back up into the UI. - * @todo this is a demo mock setup to further development of the Proc-UI integration //////////////////TICKET #1099 : prototypical Proc-GUI communication + * @todo this is a demo mock setup to further development of the Proc-UI integration //////////////////TICKET #1140 : prototypical Proc-GUI communication */ COMMAND_DEFINITION (test_meta_markAction) { diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 723417fae..7f9f9ecc6 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -38,23 +38,45 @@ - - - + + + - - + + - - + + + + + + + + + + + + + +

+ TODO: Infos zusammentragen und dokumentieren +

+ + +
+
+ + + +
@@ -66,9 +88,10 @@ - - + + + @@ -216,7 +239,7 @@ - + @@ -228,6 +251,7 @@ + @@ -665,7 +689,7 @@ - + @@ -935,7 +959,7 @@ - + @@ -943,8 +967,9 @@ - + + @@ -996,8 +1021,10 @@ - - + + + + @@ -1006,12 +1033,12 @@ - + - + - + @@ -1035,9 +1062,9 @@ - + - + @@ -1270,7 +1297,7 @@ - + @@ -1281,7 +1308,7 @@ - + @@ -1363,7 +1390,7 @@ - + @@ -1373,7 +1400,7 @@ - + @@ -1384,7 +1411,7 @@ - + @@ -1450,7 +1477,7 @@ - + @@ -1465,7 +1492,7 @@ - + @@ -1534,14 +1561,14 @@ - + - + - - + + @@ -1554,11 +1581,12 @@ - - + + - + + @@ -1570,7 +1598,7 @@ - + @@ -1592,11 +1620,11 @@ - + - + @@ -1767,13 +1795,7 @@ - - - - - - - + @@ -2514,8 +2536,8 @@ - - + + @@ -2591,7 +2613,7 @@ - + @@ -2832,15 +2854,15 @@

- + - + - - + + @@ -3023,7 +3045,7 @@ - + @@ -3808,7 +3830,7 @@ - + @@ -3816,24 +3838,24 @@ - - + + - - + + - - + + - - + + - - + + - + - + @@ -3861,8 +3883,8 @@ - - + + @@ -3874,8 +3896,22 @@ - - + + + + + + + + + +

+ #1174 UI self diagnostics window +

+ + +
+
@@ -3883,7 +3919,7 @@
- + @@ -3897,6 +3933,7 @@

+ @@ -3917,7 +3954,7 @@ - + @@ -3978,7 +4015,7 @@ - + @@ -4009,7 +4046,7 @@ - + @@ -4025,7 +4062,7 @@ - + @@ -4112,7 +4149,7 @@ - + @@ -4180,7 +4217,7 @@ - + @@ -4194,7 +4231,7 @@ - + @@ -4247,7 +4284,7 @@ - + @@ -4345,7 +4382,7 @@ - + @@ -4458,7 +4495,7 @@ - + @@ -4541,7 +4578,7 @@ - + @@ -4662,7 +4699,7 @@ - + @@ -4752,9 +4789,9 @@ - + - + @@ -4778,9 +4815,9 @@ - + - + @@ -4792,7 +4829,7 @@ - + @@ -4868,7 +4905,7 @@ - + @@ -4905,7 +4942,7 @@ - + @@ -4933,7 +4970,7 @@ - + @@ -4970,7 +5007,7 @@ - + @@ -5000,7 +5037,7 @@ - + @@ -5039,7 +5076,7 @@ - + @@ -5124,7 +5161,8 @@ - + + @@ -5138,6 +5176,10 @@ + + + + @@ -5674,9 +5716,9 @@ - + - + @@ -18464,9 +18506,10 @@ - + - + + @@ -18479,62 +18522,43 @@ - + + - - - - - - - - - - -

- ...wird zwar vom Skript ausgelesen, -

-

- aber nicht weiterverwendet. -

-

- Die Icon-Größen ergeben sich aus den Boxes auf 'plate' -

- -
-
- - - - - + + + - - - - + + + + - - + + - - + + + + + - - + + - + + @@ -18794,9 +18818,9 @@ - + - + @@ -18937,9 +18961,12 @@ - + - + + + + @@ -26899,7 +26926,7 @@ - + @@ -26915,7 +26942,7 @@ - + @@ -26934,7 +26961,7 @@ - + @@ -26946,7 +26973,7 @@ - + @@ -27040,7 +27067,7 @@ - + @@ -27075,7 +27102,7 @@ - + @@ -27092,7 +27119,7 @@ - + @@ -27106,7 +27133,7 @@ - + @@ -40547,6 +40574,38 @@ + + + + + + + + + + + +

+ ...wird zwar vom Skript ausgelesen, +

+

+ aber nicht weiterverwendet. +

+

+ Die Icon-Größen ergeben sich aus den Boxes auf 'plate' +

+ +
+
+ + + + + + + + +
From 76dd4fb5dc7362dc67d4dfac7c21e5e0d87083c9 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 7 Oct 2018 03:44:00 +0200 Subject: [PATCH 135/178] ...tidy.up: prepare for working on the 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 --- research/SConscript | 9 +- research/gtk-canvas-experiment.cpp | 375 +++++++++++++++++++++++++++++ research/gtk-canvas-experiment.hpp | 165 +++++++++++++ research/gtk-canvas-main.cpp | 44 ++++ src/SConscript | 5 +- src/common/option.cpp | 4 +- src/gui/panel/timeline-panel.cpp | 4 +- src/gui/panel/timeline-panel.hpp | 2 +- wiki/thinkPad.ichthyo.mm | 346 +++++++++++++++----------- 9 files changed, 800 insertions(+), 154 deletions(-) create mode 100644 research/gtk-canvas-experiment.cpp create mode 100644 research/gtk-canvas-experiment.hpp create mode 100644 research/gtk-canvas-main.cpp diff --git a/research/SConscript b/research/SConscript index 4df559f6a..5e4b2d481 100644 --- a/research/SConscript +++ b/research/SConscript @@ -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) ] # diff --git a/research/gtk-canvas-experiment.cpp b/research/gtk-canvas-experiment.cpp new file mode 100644 index 000000000..272ffe9ef --- /dev/null +++ b/research/gtk-canvas-experiment.cpp @@ -0,0 +1,375 @@ +/* + GtkCanvasExperiment - Explore capabilities of the GTK canvas widget + + Copyright (C) Lumiera.org + 2016, Hermann Vosseler + + 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. + ** + ** @todo as of 10/2016 this is WIP-WIP-WIP : canvas widgets experiment + ** @todo as of 10/2018 we start to build a new timeline widget, connected to the UI-Bus + ** @see timeline-widget.hpp + ** + */ + + +#include "gtk-canvas-experiment.hpp" + +//#include "gui/workspace/workspace-window.hpp" +#include "lib/format-string.hpp" +#include "lib/format-cout.hpp" + +//#include "lib/util.hpp" +#include +#include +#include + + + +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::rand; +using std::max; + + +namespace gui { +namespace panel { + + + + TimelinePanel::TimelinePanel () + : 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("Experiment 1:\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("Experiment 2:\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("Experiment 3:\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("Experiment 4:\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("Experiment 5:\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(); + + // show everything.... + this->add(twoParts_); + this->show_all(); + } + + const char* + TimelinePanel::getTitle() + { + return _("Timeline"); + } + + const gchar* + TimelinePanel::getStockID() + { + return "panel_timeline"; + } + + + + 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 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 << "|=="< 0) + cout << " ↯↯ still "< + + 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. + ** + ** ## 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 + ** + ** @todo as of 10/2016 this is WIP-WIP-WIP : canvas widgets experiment + ** @todo as of 10/2018 we start to build a new timeline widget, connected to the UI-Bus + */ + + +#ifndef RESEARCH_GTK_CANVAS_EXPERIMENT_H +#define RESEARCH_GTK_CANVAS_EXPERIMENT_H + +#include "gui/gtk-base.hpp" + +//#include +#include + + + +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 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 TimelinePanel + : public Gtk::Box + { + public: + /** + * @param panel_manager The owner panel manager widget. + * @param dock_item The GdlDockItem that will host this panel. + */ + TimelinePanel(); + + + static const char* getTitle(); + static const gchar* getStockID(); + + + 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; + ChildV childz_; + + void experiment_1(); + void experiment_2(); + void experiment_3(); + void experiment_4(); + void experiment_5(); + }; + + +}}// namespace gui::panel +#endif /*RESEARCH_GTK_CANVAS_EXPERIMENT_H*/ diff --git a/research/gtk-canvas-main.cpp b/research/gtk-canvas-main.cpp new file mode 100644 index 000000000..db0dce685 --- /dev/null +++ b/research/gtk-canvas-main.cpp @@ -0,0 +1,44 @@ +/* + gtk-canvas-main.cpp - demo application to explore the abilities of Gtk::Canvas + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + 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. + */ + + +#include "gui/gtk-base.hpp" +#include "gtk-canvas-experiment.hpp" +#include "lib/error.hpp" + + +namespace { +} + + + +int +main (int argc, const char* argv[]) +{ + NOTICE (main, "*** GTK-Canvas Experiment ***"); + + return 0; +} diff --git a/src/SConscript b/src/SConscript index b21a72d23..34cccabb0 100644 --- a/src/SConscript +++ b/src/SConscript @@ -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') diff --git a/src/common/option.cpp b/src/common/option.cpp index ce59d986b..268bfa63b 100644 --- a/src/common/option.cpp +++ b/src/common/option.cpp @@ -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(), "session file to load (UNIMPLEMENTED)") ("script,s", op::value(), - "execute the given LUA script (UNIMPLEMENTED)") + "execute the given script (UNIMPLEMENTED)") ("headless", op::bool_switch(), "start without GUI") ("port,p", op::value(), diff --git a/src/gui/panel/timeline-panel.cpp b/src/gui/panel/timeline-panel.cpp index 698e8b344..462148208 100644 --- a/src/gui/panel/timeline-panel.cpp +++ b/src/gui/panel/timeline-panel.cpp @@ -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 ** */ @@ -54,8 +54,6 @@ 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; diff --git a/src/gui/panel/timeline-panel.hpp b/src/gui/panel/timeline-panel.hpp index df1fdc366..e93bdddcf 100644 --- a/src/gui/panel/timeline-panel.hpp +++ b/src/gui/panel/timeline-panel.hpp @@ -36,7 +36,7 @@ ** 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 */ diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 7f9f9ecc6..30f3dc6c2 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -5774,8 +5774,9 @@ - + + @@ -5864,7 +5865,7 @@ - + @@ -5885,7 +5886,7 @@ - + @@ -6007,7 +6008,8 @@ - + + @@ -6024,8 +6026,9 @@ - - + + + @@ -6037,7 +6040,7 @@ - + @@ -6096,7 +6099,7 @@ - + @@ -6163,7 +6166,8 @@ - + + @@ -6316,7 +6320,7 @@ - + @@ -6378,15 +6382,16 @@ - + + - + @@ -6417,7 +6422,7 @@ - + @@ -6468,8 +6473,10 @@ - + + + @@ -6491,7 +6498,7 @@ - + @@ -6515,7 +6522,7 @@ - + @@ -6700,7 +6707,7 @@ - + @@ -6788,9 +6795,9 @@ - + - + @@ -7201,7 +7208,7 @@ - + @@ -7613,7 +7620,7 @@ - + @@ -7676,7 +7683,7 @@ - + @@ -8262,7 +8269,7 @@ - + @@ -12207,7 +12214,7 @@ - + @@ -15943,7 +15950,7 @@ - + @@ -16144,7 +16151,7 @@ - + @@ -16336,8 +16343,8 @@ - - + + @@ -16448,7 +16455,7 @@ - + @@ -16697,8 +16704,9 @@ - + + @@ -17005,7 +17013,8 @@ - + + @@ -17090,7 +17099,7 @@ - + @@ -18578,7 +18587,7 @@ - + @@ -18889,7 +18898,7 @@ - + @@ -18922,9 +18931,9 @@ - - - + + + @@ -19011,7 +19020,7 @@ - + @@ -19431,7 +19440,7 @@ - + @@ -19910,14 +19919,14 @@ - - - + + + - + @@ -20079,7 +20088,7 @@ - + @@ -20280,7 +20289,7 @@ - + @@ -26278,7 +26287,7 @@ - + @@ -26326,7 +26335,7 @@ - + @@ -26342,7 +26351,7 @@ - + @@ -26444,7 +26453,7 @@ - + @@ -26603,7 +26612,7 @@ - + @@ -26627,7 +26636,7 @@ - + @@ -27190,7 +27199,7 @@ - + @@ -27313,7 +27322,7 @@ - + @@ -27375,7 +27384,7 @@ - + @@ -27400,7 +27409,7 @@ - + @@ -27503,7 +27512,7 @@ - + @@ -27579,7 +27588,7 @@ - + @@ -27598,6 +27607,7 @@ + @@ -27614,7 +27624,7 @@ - + @@ -27629,10 +27639,11 @@ + - + @@ -27654,6 +27665,10 @@ + + + + @@ -27792,7 +27807,7 @@ - + @@ -27801,7 +27816,7 @@ - + @@ -27810,7 +27825,7 @@ - + @@ -27821,7 +27836,7 @@ - + @@ -27839,7 +27854,7 @@ - + @@ -27849,7 +27864,7 @@ - + @@ -27922,7 +27937,7 @@ - + @@ -27986,7 +28001,7 @@ - + @@ -28154,7 +28169,7 @@ - + @@ -28292,7 +28307,7 @@ - + @@ -28563,8 +28578,8 @@ - - + + @@ -28665,7 +28680,7 @@ - + @@ -28833,7 +28848,7 @@ - + @@ -29030,7 +29045,7 @@ - + @@ -29256,7 +29271,7 @@ - + @@ -29376,13 +29391,13 @@ - + - + @@ -29525,7 +29540,7 @@ - + @@ -29545,11 +29560,11 @@ - - + + - + @@ -29571,7 +29586,7 @@ - + @@ -29625,7 +29640,7 @@ - + @@ -29700,7 +29715,7 @@ - + @@ -29767,7 +29782,7 @@ - + @@ -29815,7 +29830,7 @@ - + @@ -29881,7 +29896,7 @@ - + @@ -30055,7 +30070,7 @@ - + @@ -30187,7 +30202,7 @@ - + @@ -30357,7 +30372,7 @@
- + @@ -30387,12 +30402,12 @@ - - + + - + @@ -30587,7 +30602,7 @@ - + @@ -30774,7 +30789,7 @@ - + @@ -30793,7 +30808,7 @@ - + @@ -30803,14 +30818,14 @@ - + - + @@ -30818,7 +30833,7 @@ - + @@ -30992,7 +31007,7 @@ - + @@ -31174,7 +31189,7 @@ - + @@ -31867,10 +31882,10 @@ - + - + @@ -32026,7 +32041,8 @@ - + + @@ -32061,8 +32077,9 @@ - - + + + @@ -32119,7 +32136,7 @@ - + @@ -32193,7 +32210,7 @@ - + @@ -32231,8 +32248,31 @@ - - + + + + + + +

+ seit längerer Zeit ein Thema, +

+

+ für das ich verschiedene Lösungen ausprobiere +

+
    +
  • + IterExplorer : erster Anlauf, etwas schief +
  • +
  • + TreeExplorer : Pipeline-Builder, vielversprechend +
  • +
+ + +
+ + @@ -32274,7 +32314,7 @@ - + @@ -32455,7 +32495,7 @@ - + @@ -34584,8 +34624,8 @@ - - + + @@ -34593,27 +34633,45 @@ - - + + + + - + + + + + + +

+ Frühjahr 2018 komplett überarbeitet. +

+

+ Lösung scheint nun "rund" zu sein +

+ + +
+ - + + - + - + @@ -34638,7 +34696,7 @@ - + @@ -34699,8 +34757,9 @@ - - + + + @@ -34714,7 +34773,7 @@ - + @@ -34852,7 +34911,7 @@ - + @@ -34907,7 +34966,7 @@ - + @@ -35423,7 +35482,7 @@ - + @@ -36040,7 +36099,7 @@ - + @@ -36115,7 +36174,7 @@ - + @@ -36223,7 +36282,7 @@ - + @@ -36321,7 +36380,7 @@ - + @@ -36900,7 +36959,7 @@ - + @@ -37181,7 +37240,7 @@ - + @@ -37265,7 +37324,8 @@ - + + @@ -37510,8 +37570,8 @@ - - + + @@ -37594,7 +37654,7 @@ - + @@ -37793,7 +37853,7 @@ - + @@ -37850,7 +37910,7 @@ - + @@ -38004,7 +38064,7 @@ - + @@ -38080,8 +38140,8 @@ - - + + @@ -38093,7 +38153,7 @@ - + From 3f87ef43ecdefee398d4d54af53dd59cb4aee12e Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 7 Oct 2018 17:31:49 +0200 Subject: [PATCH 136/178] ...tidy.up: preserve the Gtk::Canvas experiment (see #1020) Turning this investigation experiment from 2016 into a stand-alone Gtk application. Using the research folder as final disposal site for now... --- research/gtk-canvas-experiment.cpp | 67 +++++++++++++----------------- research/gtk-canvas-experiment.hpp | 40 ++++-------------- research/gtk-canvas-main.cpp | 28 +++++++++++-- 3 files changed, 63 insertions(+), 72 deletions(-) diff --git a/research/gtk-canvas-experiment.cpp b/research/gtk-canvas-experiment.cpp index 272ffe9ef..af3132789 100644 --- a/research/gtk-canvas-experiment.cpp +++ b/research/gtk-canvas-experiment.cpp @@ -24,8 +24,8 @@ /** @file gtk-canvas-experiment.cpp ** Implementation of gtk canvas experiments. ** - ** @todo as of 10/2016 this is WIP-WIP-WIP : canvas widgets experiment - ** @todo as of 10/2018 we start to build a new timeline widget, connected to the UI-Bus + ** @remark as of 10/2018 we start to build a new timeline widget, + ** based on this technology demo. ** @see timeline-widget.hpp ** */ @@ -33,11 +33,10 @@ #include "gtk-canvas-experiment.hpp" -//#include "gui/workspace/workspace-window.hpp" #include "lib/format-string.hpp" #include "lib/format-cout.hpp" +#include "lib/util.hpp" -//#include "lib/util.hpp" #include #include #include @@ -45,9 +44,7 @@ using util::_Fmt; -//using std::shared_ptr; -//using std::weak_ptr; -//using util::contains; +using util::isnil; using Gtk::Widget; using sigc::mem_fun; using sigc::ptr_fun; @@ -56,12 +53,10 @@ using std::rand; using std::max; -namespace gui { -namespace panel { - +namespace demo { - TimelinePanel::TimelinePanel () + CanvasDemoPanel::CanvasDemoPanel () : Box{} , twoParts_(Gtk::ORIENTATION_VERTICAL) , buttons_() @@ -79,35 +74,35 @@ namespace panel { button_1_.set_use_underline(); button_1_.set_tooltip_markup("Experiment 1:\nplace new child widget\nat random position on the canvas"); button_1_.signal_clicked().connect( - mem_fun(*this, &TimelinePanel::experiment_1)); + 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("Experiment 2:\nmove all child widgets randomly"); button_2_.signal_clicked().connect( - mem_fun(*this, &TimelinePanel::experiment_2)); + 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("Experiment 3:\nalign all child widgets in a row\nwith silight random vertical offset"); button_3_.signal_clicked().connect( - mem_fun(*this, &TimelinePanel::experiment_3)); + 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("Experiment 4:\nextend arbitrary child widget's text"); button_4_.signal_clicked().connect( - mem_fun(*this, &TimelinePanel::experiment_4)); + 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("Experiment 5:\nkill arbitrary child widget"); button_5_.signal_clicked().connect( - mem_fun(*this, &TimelinePanel::experiment_5)); + mem_fun(*this, &CanvasDemoPanel::experiment_5)); buttons_.add(button_5_); toggleDraw_.set_label("draw"); @@ -121,7 +116,8 @@ namespace panel { frame_.add(scroller_); frame_.set_border_width(5); - scroller_.set_shadow_type(Gtk::SHADOW_NONE); + 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_); @@ -132,22 +128,9 @@ namespace panel { this->show_all(); } - const char* - TimelinePanel::getTitle() - { - return _("Timeline"); - } - - const gchar* - TimelinePanel::getStockID() - { - return "panel_timeline"; - } - - void - TimelinePanel::experiment_1() + CanvasDemoPanel::experiment_1() { frame_.set_label("Experiment 1... PLACE"); @@ -162,7 +145,7 @@ namespace panel { void - TimelinePanel::experiment_2() + CanvasDemoPanel::experiment_2() { frame_.set_label("Experiment 2... MOVE"); for (Widget* chld : childz_) @@ -181,7 +164,7 @@ namespace panel { void - TimelinePanel::experiment_3() + CanvasDemoPanel::experiment_3() { frame_.set_label("Experiment 3... ALIGN"); uint pos=0; @@ -198,9 +181,14 @@ namespace panel { void - TimelinePanel::experiment_4() + 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()+"***"); @@ -208,9 +196,14 @@ namespace panel { void - TimelinePanel::experiment_5() + 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; @@ -339,7 +332,7 @@ namespace panel { ChildEx* - TimelinePanel::makeChld() + CanvasDemoPanel::makeChld() { return Gtk::manage(new ChildEx); } @@ -372,4 +365,4 @@ namespace panel { -}} // namespace gui::panel +} // namespace demo diff --git a/research/gtk-canvas-experiment.hpp b/research/gtk-canvas-experiment.hpp index 565ff6635..84a22509f 100644 --- a/research/gtk-canvas-experiment.hpp +++ b/research/gtk-canvas-experiment.hpp @@ -1,5 +1,5 @@ /* - GTK-CANVAS-EXPERIMENT.hpp - Explore capabilities of the GTK canvas widget + GTK-CANVAS-EXPERIMENT.hpp - Explore capabilities of the GTK canvas widget Copyright (C) Lumiera.org 2016, Hermann Vosseler @@ -23,20 +23,8 @@ /** @file gtk-canvas-experiment.hpp ** A custom widget using a canvas to draw and to place widgets. - ** - ** ## 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 - ** - ** @todo as of 10/2016 this is WIP-WIP-WIP : canvas widgets experiment - ** @todo as of 10/2018 we start to build a new timeline widget, connected to the UI-Bus + ** @remark as of 10/2018 we start to build a new timeline widget, + ** based on this technology demo. */ @@ -45,18 +33,11 @@ #include "gui/gtk-base.hpp" -//#include #include - -namespace gui { -namespace model{ - class Sequence; - } -namespace panel { +namespace demo { -//using std::shared_ptr; /** * "experimental" child widget for investigation of Gtk::Layout @@ -117,10 +98,10 @@ namespace panel { * 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 + * 10. build a custom "clip" widget ✘ + * 11. retrofit all preceding tests to use this "clip" widget ✘ */ - class TimelinePanel + class CanvasDemoPanel : public Gtk::Box { public: @@ -128,12 +109,9 @@ namespace panel { * @param panel_manager The owner panel manager widget. * @param dock_item The GdlDockItem that will host this panel. */ - TimelinePanel(); + CanvasDemoPanel(); - static const char* getTitle(); - static const gchar* getStockID(); - private: Gtk::Box twoParts_; @@ -161,5 +139,5 @@ namespace panel { }; -}}// namespace gui::panel +}// namespace demo #endif /*RESEARCH_GTK_CANVAS_EXPERIMENT_H*/ diff --git a/research/gtk-canvas-main.cpp b/research/gtk-canvas-main.cpp index db0dce685..e949c675e 100644 --- a/research/gtk-canvas-main.cpp +++ b/research/gtk-canvas-main.cpp @@ -22,6 +22,10 @@ /** @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. */ @@ -30,15 +34,31 @@ #include "lib/error.hpp" -namespace { -} +namespace demo { + + class Window + : public Gtk::Window + { + CanvasDemoPanel demoPanel_; + + public: + Window() + : demoPanel_{} + { + this->add (demoPanel_); + } + }; +}//namespace int -main (int argc, const char* argv[]) +main (int argc, char* argv[]) { NOTICE (main, "*** GTK-Canvas Experiment ***"); - return 0; + auto guiApp = Gtk::Application::create (argc, argv); + + demo::Window window; + return guiApp->run(window); } From 08ed6e1ee8e7f38ea848478c425317b393bb5660 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 8 Oct 2018 02:26:38 +0200 Subject: [PATCH 137/178] Timeline: building the layout and control structure (#1016) 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. --- src/gui/timeline/clip-presenter.cpp | 1 + src/gui/timeline/clip-presenter.hpp | 1 + src/gui/timeline/layout-manager.cpp | 1 + src/gui/timeline/layout-manager.hpp | 1 + src/gui/timeline/marker-widget.cpp | 1 + src/gui/timeline/marker-widget.hpp | 1 + src/gui/timeline/timeline-controller.cpp | 2 +- src/gui/timeline/timeline-controller.hpp | 2 +- src/gui/timeline/timeline-widget.cpp | 2 +- src/gui/timeline/timeline-widget.hpp | 2 +- src/gui/timeline/track-presenter.cpp | 4 +- src/gui/timeline/track-presenter.hpp | 5 +- wiki/renderengine.html | 41 ++- wiki/thinkPad.ichthyo.mm | 371 +++++++++++++++++++---- 14 files changed, 358 insertions(+), 77 deletions(-) diff --git a/src/gui/timeline/clip-presenter.cpp b/src/gui/timeline/clip-presenter.cpp index 8a749412e..098fd7a8b 100644 --- a/src/gui/timeline/clip-presenter.cpp +++ b/src/gui/timeline/clip-presenter.cpp @@ -25,6 +25,7 @@ ** 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 ** */ diff --git a/src/gui/timeline/clip-presenter.hpp b/src/gui/timeline/clip-presenter.hpp index 86876bce1..5ecd0df1d 100644 --- a/src/gui/timeline/clip-presenter.hpp +++ b/src/gui/timeline/clip-presenter.hpp @@ -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 ** */ diff --git a/src/gui/timeline/layout-manager.cpp b/src/gui/timeline/layout-manager.cpp index 24d6b9b10..0fbcf6ba1 100644 --- a/src/gui/timeline/layout-manager.cpp +++ b/src/gui/timeline/layout-manager.cpp @@ -25,6 +25,7 @@ ** 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 ** */ diff --git a/src/gui/timeline/layout-manager.hpp b/src/gui/timeline/layout-manager.hpp index fb13c20f3..1f7bf5b2f 100644 --- a/src/gui/timeline/layout-manager.hpp +++ b/src/gui/timeline/layout-manager.hpp @@ -72,6 +72,7 @@ ** [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 ** */ diff --git a/src/gui/timeline/marker-widget.cpp b/src/gui/timeline/marker-widget.cpp index 52d6760f0..0208ea27b 100644 --- a/src/gui/timeline/marker-widget.cpp +++ b/src/gui/timeline/marker-widget.cpp @@ -25,6 +25,7 @@ ** 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 ** */ diff --git a/src/gui/timeline/marker-widget.hpp b/src/gui/timeline/marker-widget.hpp index e1e5c9e84..63e014b8d 100644 --- a/src/gui/timeline/marker-widget.hpp +++ b/src/gui/timeline/marker-widget.hpp @@ -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 ** */ diff --git a/src/gui/timeline/timeline-controller.cpp b/src/gui/timeline/timeline-controller.cpp index 80135b2c1..985b556e5 100644 --- a/src/gui/timeline/timeline-controller.cpp +++ b/src/gui/timeline/timeline-controller.cpp @@ -32,7 +32,7 @@ ** - 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 ** */ diff --git a/src/gui/timeline/timeline-controller.hpp b/src/gui/timeline/timeline-controller.hpp index 23b78b782..44df1e57c 100644 --- a/src/gui/timeline/timeline-controller.hpp +++ b/src/gui/timeline/timeline-controller.hpp @@ -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 ** */ diff --git a/src/gui/timeline/timeline-widget.cpp b/src/gui/timeline/timeline-widget.cpp index 52a9bc824..cbb72058c 100644 --- a/src/gui/timeline/timeline-widget.cpp +++ b/src/gui/timeline/timeline-widget.cpp @@ -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 ** */ diff --git a/src/gui/timeline/timeline-widget.hpp b/src/gui/timeline/timeline-widget.hpp index f968b3849..f8c7a7181 100644 --- a/src/gui/timeline/timeline-widget.hpp +++ b/src/gui/timeline/timeline-widget.hpp @@ -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 ** */ diff --git a/src/gui/timeline/track-presenter.cpp b/src/gui/timeline/track-presenter.cpp index cc6e3e993..bce8ed079 100644 --- a/src/gui/timeline/track-presenter.cpp +++ b/src/gui/timeline/track-presenter.cpp @@ -25,6 +25,7 @@ ** 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 ** */ @@ -85,10 +86,11 @@ namespace timeline { /** * @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) diff --git a/src/gui/timeline/track-presenter.hpp b/src/gui/timeline/track-presenter.hpp index 41df348f5..9e71323d4 100644 --- a/src/gui/timeline/track-presenter.hpp +++ b/src/gui/timeline/track-presenter.hpp @@ -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 ** */ diff --git a/wiki/renderengine.html b/wiki/renderengine.html index 655a0034a..a6a6f8ceb 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -1997,7 +1997,7 @@ Some observations: * Optimisation achieves access times around ≈ 1ns
-
+
Along the way of working out various [[implementation details|ImplementationDetails]], decisions need to be made on how to understand the different facilities and entities and how to tackle some of the problems. This page is mainly a collection of keywords, summaries and links to further the discussion. And the various decisions should allways be read as proposals to solve some problem at hand...
 
 ''Everything is an object'' &mdash; yes of course, that's a //no-brainer,// todays. Rather, important is to note what is not "an object", meaning it can't be arranged arbitrarily
@@ -2011,6 +2011,8 @@ We ''separate'' processing (rendering) and configuration (building). The [[Build
 ''Objects are [[placed|Placement]] rather'' than assembled, connected, wired, attached. This is more of a rule-based approach and gives us one central metaphor and abstraction, allowing us to treat everything in an uniform manner. You can place it as you like, and the builder tries to make sense out of it, silently disabling what doesn't make sense.
 An [[Sequence]] is just a collection of configured and placed objects (and has no additional, fixed structure). [["Tracks" (forks)|Fork]] form a mere organisational grid, they are grouping devices not first-class entities (a track doesn't "have" a pipe or "is" a video track and the like; it can be configured to behave in such manner by using placements though). [[Pipes|Pipe]] are hooks for making connections and are the only facility to build processing chains. We have global pipes, and each clip is built around a lokal [[source port|ClipSourcePort]] &mdash; and that's all. No special "media viewer" and "arranger", no special role for media sources, no commitment to some fixed media stream types (video and audio). All of this is sort of pushed down to be configuration, represented as asset of some kind. For example, we have [[processing pattern|ProcPatt]] assets to represent the way of building the source network for reading from some media file (including codecs treated like effect plugin nodes)
 
+The model in Proc-Layer is rather an //internal model.// What is exposed globally, is a structural understanding of this model. In this structural understanding, there are Assets and ~MObjects, which both represent the flip side of the same coin: Assets relate to bookkeeping, while ~MObjects relate to building and manipulation of the model. In the actual data represntation within the HighLevelModel, we settled upon some internal reductions, preferring either the //Asset side// or the //~MObject side// to represent some relevant entities. See &rarr; AssetModelConnection.
+
 Actual ''media data and handling'' is abstracted rigorously. Media is conceived as being stream-like data of distinct StreamType. When it comes to more low-level media handling, we build on the DataFrame abstraction. Media processing isn't the focus of Lumiera; we organise the processing but otherwise ''rely on media handling libraries.'' In a similar vein, multiplicity is understood as type variation. Consequently, we don't build an audio and video "section" and we don't even have audio tracks and video tracks. Lumiera uses tracks and clips, and clips build on media, but we're able to deal with [[multichannel|MultichannelMedia]] mixed-typed media natively.
 
 Lumiera is not a connection manager, it is not an audio-visual real time performance instrument, and it doesn't aim at running presentations. It's an ''environment for assembling and building up'' something (an edit, a session, a piece of media work). This decision is visible at various levels and contexts, like a reserved attitude towards hardware acceleration (it //will// be supported, but reliable proxy editing has a higher priority), or the decision, not to incorporate system level ports and connections directly into the session model (they are mapped to [[output designations|OutputDesignation]] rather)
@@ -2686,7 +2688,7 @@ As starting point, {{red{in winter 2016/17}}} the old (broken) timeline panel wa
 After that initial experiments, my focus shifted to the still unsatisfactory top-level UI structure, and I am working towards an initial integration with Proc-Layer since then.
 
-
+
//The representation of a [[media clip|Clip]] for manipulation by the user within the UI.//
 Within Lumiera, a clip is conceived as a //chunk of media,// which can be handled in compound. Clip as such is an abstract concept, which is treated with minimal assumptions...
 * we know that a clip has //media content,// which need not be uniform and can be inherently structured (e.g. several media, several channels)
@@ -2720,7 +2722,7 @@ Starting from the architecture as predetermined by the UI-Bus, it is clear that
 !!!how to carry out the clip appearances
 Initially, one could think that we'd need to build several widgets to realise the wide variety of clip appearances. But in fact it turns out that we're able to reshape a single base widget to encompass all the necessary presentation styles. This base widget is a simple, one-element container, with {{{Gtk::Frame}}} being the most obvious pick. This gives us already a rectangular covered space, and the ability to add a label widget, optionally with controlled alignment of the label. All the more elaborate presentation styles can be achieved by adding a canvas widget into this frame and then placing additional stuff on top of that. The only tricky part arises in overview display, when just some clip rectangle can stand-in for a whole series of clips, which themselves remain hidden as UI elements.
 
-A prime consideration regarding this whole clip presentation strategy is the performance concern. It is quite common for movie edits to encompass several hundred individual clips. Combined with several tracks and an elaborate audio edit, it may well happen that we end up with thousands of individual UI objects. If treated naively, this load might seriously degrade the responsiveness of the interface. Thus we need to care for the relevant infrastructure to enable optimisation of the display. For that reason, the memory footprint of the ClipPresenter and the basic widget has to be kept as small as possible. And moreover, since we do our own layout management in the timeline display, in theory it is possible any time just to add those widgets to the enclosing GTK container, which are actually about to become visible. (if we follow this approach, a problem yet to be solved is how to remove widgets falling out of sight, since removing N widgets easily turns into a quadratic operation).
+A prime consideration regarding this whole clip presentation strategy is the performance concern. It is quite common for movie edits to encompass several hundred individual clips. Combined with several tracks and an elaborate audio edit, it may well happen that we end up with thousands of individual UI objects. If treated naively, this load might seriously degrade the responsiveness of the interface. Thus we need to care for the relevant infrastructure to enable optimisation of the display. For that reason, the memory footprint of the ClipPresenter and the basic widget has to be kept as small as possible. And moreover, since we do our own layout management in the timeline display, in theory it is possible //only to add// those widgets to the enclosing GTK container, which are //actually about to become visible.// (if we follow this approach, a problem yet to be solved is how to remove widgets falling out of sight, since removing N widgets easily turns into a quadratic operation).
 
 !clip content rendering
 In a typical editing application, the user can expect to get some visual clue regarding the media content of a clip. For example, sound clips can be visualised as waveform, while movie clips might feature a sequence of images taken from the video. Our intention is to ''use our own rendering engine'' to produce these thumbnails. In fact, our engine is perfectly suited for this task: it has precisely the necessary media decoding and rendering abilities, plus it offers an elaborate system of priorities and deadlines, allowing to throttle the load produced by thumbnail generation. In addition to all those qualities, our engine is planned to be complemented by an "intelligent" frame cache, which, given proper parametrisation, ensures the frequently requested thumbnails will be available for quick display. For this approach to work, we need to provide some infrastructure
@@ -2730,7 +2732,7 @@ In a typical editing application, the user can expect to get some visual clue re
 * we still need to work out how buffer management for this task will be handled; it should be a derivative of typical buffer management for display rendering.
 * the clip widget needs to provide a simple placeholder drawing to mark the respective space in the interface, until the actual preview arrives.
 
-To start with, mostly this means to avoid a naive approach, like having code in the UI to pull in some graphics from media files. We certainly won't just render every media channel blindly. Rather, we acknowledge that we'll have a //strategy,// depending on the media content and some further parameters of the clip. This might well just be a single ''pivot image'' chosen explicitly by the editor to represent a given take. And the actual implementation of content preview rendering will largely be postponed until we get our rendering engine into a roughly working state.
+To start with, mostly this means to avoid a naive approach, like having code in the UI to pull in some graphics from media files. We certainly won't just render every media channel blindly. Rather, we acknowledge that we'll have a //strategy,// depending on the media content and some further parameters of the clip. This might well just be a single ''pivot image'' chosen explicitly by the editor to represent a given take. Seemingly, the proper place to hose that display strategy is ''within the session model'', not within the UI. And the actual implementation of content preview rendering will largely be postponed until we get our rendering engine into a roughly working state.
 
@@ -3423,7 +3425,7 @@ In accordance with the Lumiera application architecture in general, the UI is no
A specially configured LumieraPlugin, which actually contains or loads the complete code of the (GTK)GUI, and additionally is linked dynamically against the application core lib. During the [[UI startup process|GuiStart]], loading of this Plugin is triggered from {{{main()}}}. Actually this causes spawning of the GTK event thread and execution of the GTK main loop.
 
-
+
Within the Lumieara GUI, the [[Timeline]] structure(s) from the HighLevelModel are arranged and presented according to the following principles and conventions.
 Several timeline views may be present at the same time -- and there is not necessarily a relation between them, since »a Timeline« is the top-level concept within the [[Session]]. Obviously, there can also be several //views// based on the same »Timeline« model element, and in this latter case, these //coupled views// behave according to a linked common state. An entity »Timeline« as represented through the GUI, emerges from the combination of several model elements
 * a root level [[Binding|BindingMO]] acts as framework
@@ -3436,7 +3438,7 @@ Several timeline views may be present at the same time -- and there is not neces
 Session, Binding and Sequence are the mandatory ingredients.
 
 !Basic layout
-[>img[Clip presentation control|draw/UI-TimelineLayout-1.png]]The representation is split into a ''Header pane'' exposing structure and configuration, and a ''Content pane'' extending in time. The ''Time ruler'' running alongside the top of the content pane represents the //position in time.// Beyond this temporal dimension, the content area is conceived as a flexible working space. This working space //can// be structured hierarchically -- when interacting with the GUI, hierarchical nesting will be created and collapsed on demand. Contrast this with conventional editing applications which are built upon the rigid notion of "Tracks": Lumiera is based on //Pipes// rather than Tracks.
+[>img[Clip presentation control|draw/UI-TimelineLayout-1.png]]The representation is split into a ''Header pane'' exposing structure and configuration, and a ''Content pane'' extending in time. The ''Time ruler'' running alongside the top of the content pane represents the //position in time.// Beyond this temporal dimension, the content area is conceived as a flexible working space. This working space //can// be structured hierarchically -- when interacting with the GUI, hierarchical nesting will be created and collapsed on demand. Contrast this with conventional editing applications which are built upon the rigid notion of "Tracks": Lumiera is based on //Pipes// and //Scopes// rather than Tracks.
 
 In the temporal dimension, there is the usual scrolling and zooming of content, and possibly a selected time range, and after establishing a ViewerPlayConnection, there is an effective playback location featured as a "Playhead"
 The workspace dimension (vertical layout) is more like a ''Fork'', which can be expanded recursively. More specifically, each strip or layer or "track" can be featured in //collapsed// or //expanded state.//
@@ -3469,7 +3471,7 @@ In the most general case, there can be per-track content and nested content at t
 &rarr; important question: how to [[organise the widgets|GuiTimelineWidgetStructure]]
 
-
+
The Timeline is probably the most prominent place in the GUI where we need to come up with a custom UI design.
 Instead of combining standard components in one of the well-known ways, here we need to come up with our own handling solution -- which also means to write one or several custom GTK widgets. Thus the question of layout and screen space division and organisation becomes a crucial design decision. The ~GTK-2 Gui, as implemented currently, did already take some steps along this route, yet this kind of decision should be cast and documented explicitly (be it after the fact).
 
@@ -3514,7 +3516,7 @@ Applying a diff changes the structure, that is, the structure of the local model
 * when a model element happens to be destructed, the corresponding display element has to be removed.
 * such might be triggered indirectly, by clean-up of leftovers, since the {{{DiffApplicator}}} re-orders and deletes by leaving some data behind
 * the diff also re-orders model elements, which does not have an immediate effect on the display, but needs to be interpreted separately.
-Together this means we get a fix up stage after model changes, where the display is re-adjusted to fit the new situation. This works in concert with the [[display manager|TimelineDisplayManager]] representing only those elements as actual widgets, which get a real chance to become visible. This way we can build on the assumption that the actual number of widgets to be managed any time remains so small as to get away with simple linear list processing. It remains to be seen how far this assumption can be pushed -- the problem is that the GTK container components don't support anything beyond such simple linear list processing; there isn't even a call to remove all child widgets of a container in a single pass.
+Wrapping this together we get a fix up stage after model changes, where the display is re-adjusted to fit the new situation. This works in concert with the [[display manager|TimelineDisplayManager]] representing only those elements as actual widgets, which get a real chance to become visible. This way we can build on the assumption that the actual number of widgets to be managed any time remains so small as to get away with simple linear list processing. It remains to be seen how far this assumption can be pushed -- the problem is that the GTK container components don't support anything beyond such simple linear list processing; there isn't even a call to remove all child widgets of a container in a single pass.
 
@@ -3545,7 +3547,7 @@ Together, these entities form a cohesive circle of collaborating global managers Within the UI-Layer, we distinguish between //core concerns and UI concerns,// the latter encompassing anything related to UI mechanics, presentation state, interaction state and InteractionControl. Core concerns are delegated and handled by the lower layers of the architecture, while the UI plays a passive role. This is a fundamental decision and leads to a dichotomy, where -- depending on the context -- a given part might operate as a slave related to core concerns, while taking a leading or controlling position when it comes to UI concerns. Deliberately, both sides are bridged by being interwoven into the same entities, and any entity of relevance to core concerns is also attached to the UI-Bus. Regarding the build-up of the UI, parts and elements are made visible and accessible through widgets, but widgets are created, installed and operated by controllers. The top-level circle defines global actions, which are passed through the relevant controller to come into effect.
-
+
While the low-level model holds the data used for carrying out the actual media data processing (=rendering), the high-level model is what the user works upon when performing edit operations through the GUI (or script driven in &raquo;headless mode&laquo;). Its building blocks and combination rules determine largely what structures can be created within the [[Session]].
 On the whole, it is a collection of [[media objects|MObjects]] stuck together and arranged by [[placements|Placement]].
 
@@ -3577,12 +3579,12 @@ Within Lumiera, "tracks" (actually implemented as [[forks|Fork]]) are
 
 As placements have the ability to cooperate and derive any missing placement specifications, this creates a hierarchical structure throughout the session, where parts on any level behave similar if applicable. For example, when a fork ("track") is anchored to some external entity (label, sync point in sound, etc), all objects placed relatively to this track will adjust and follow automatically. This relation between the track tree and the individual objects is especially important for the wiring, which, if not defined locally within an ~MObject's placement, is derived by searching up this track tree and utilizing the wiring plug locating pins found there, if applicable. In the default configuration, the placement of an sequence's root track contains a wiring plug for video and another wiring plug for audio. This setup is sufficient for getting every object within this sequence wired up automatically to the correct global output pipe. Moreover, when adding another wiring plug to some sub track, we can intercept and reroute the connections of all objects creating output of this specific stream type within this track and on all child tracks.
 
-Besides routing to a global pipe, wiring plugs can also connect to the source port of an ''meta-clip''. In this example session, the outputs of ~Seq-2 as defined by locating pins in it's root track's placement, are directed to the source ports of a [[meta-cllip|VirtualClip]] placed within ~Seq-1. Thus, within ~Seq-1, the contents of ~Seq-2 appear like a pseudo-media, from which the (meta) clip has been taken. They can be adorned with effects and processed further completely similar to a real clip.
+Besides routing to a global pipe, wiring plugs can also connect to the source port of an ''meta-clip''. In this example session, the outputs of 'Seq-2' as defined by locating pins in it's root track's placement, are directed to the source ports of a [[meta-cllip|VirtualClip]] placed within 'Seq-1'. Thus, within 'Seq-1', the contents of 'Seq-2' appear like a pseudo-media, from which the (meta) clip has been taken. They can be adorned with effects and processed further completely similar to a real clip.
 
 Finally, this example shows an ''automation'' data set controlling some parameter of an effect contained in one of the global pipes. From the effect's POV, the automation is simply a ParamProvider, i.e a function yielding a scalar value over time. The automation data set may be implemented as a bézier curve, or by a mathematical function (e.g. sine or fractal pseudo random) or by some captured and interpolated data values. Interestingly, in this example the automation data set has been placed relatively to the meta clip (albeit on another track), thus it will follow and adjust when the latter is moved.
 
-
+
This wiki page is the entry point to detail notes covering some technical decisions, details and problems encountered in the course of the years, while building the Lumiera application.
 
 * [[Packages, Interfaces and Namespaces|InterfaceNamespaces]]
@@ -3619,6 +3621,7 @@ Finally, this example shows an ''automation'' data set controlling some paramete
 * how to shape the GuiConnection: with the help of a mediating GuiModel, which acts as UI-Bus, exchanging TreeDiffModel messages for GuiModelUpdate
 * shape the Backbone of the UI, the UI-Bus and the GuiTopLevel
 * build a framework for InteractionControl in the User Interface
+* establish a flexible layout structure for the GuiTimelineView
 
@@ -5950,7 +5953,7 @@ The backbone of the Lumiera UI is arranged such as to produce a data feed with s Run as part of the UiCoreServices, it is attached to the UI-Bus and listens to all ''state mark messages'' to distil the notion of relevant current state. On a basic level, the task is to group and store those messages in a way as to overwrite previous messages with new updates on the level of individual properties. Beyond that, there is the sensitivity to context, presentation perspective and work site, which means to impose an additional structure on this basic ''state snapshot'', to extract and replicate structured sets of state information.
-
+
Open issues, Things to be worked out, Problems still to be solved... 
 
 !!Parameter Handling
@@ -5987,9 +5990,10 @@ We need to work out guidelines for dealing with operations going on simultaneous
 
 !!the perils of data representation
 In software development, there is a natural inclination to cast "reality" into data, the structure of which has to be nailed down first. Then, everyone might "access reality" and work on it. Doing so might sounds rational, natural, even self-evident and sound, yet as compelling as it might be, this approach is fundamentally flawed. It is known to work well only for small, "handsome" projects, where you clearly know up-front what you're up to: namely to get away, after being paid, before anyone realises the fact you've built something that looks nice but does not fit.
-So the challenge of any major undertaking in software construction is //not to build an universal model.// Rather, we want to arrive at something that can be made to fit. Over and over again.
+So the challenge of any major undertaking in software construction is //not to build an universal model of truth.// Rather, we want to arrive at something that can be made to fit.
+Which can be remoulded, over and over again, without breaking down.
 
-More specifically, we start building something, and while under way, our understanding sharpens, and we learn that actually we want something entirely different. Yet still we know what we need and  we don't want just something arbitrary. There is a constant core in what we're headed at, and we need the ability to //settle matters.// We need a backbone to work against, a skeleton to support us with its firmness, while also offering joints and links, to be bent and remoulded without breakage. The distinctive idea to make such possible is the principle of subsidiarity. The links and joints between autonomous centres can be shaped to be in fact an exchange, a handover based on common understanding of the //specific matters to deal with,// at that given joint.
+More specifically, we start building something, and while under way, our understanding sharpens, and we learn that actually we want something entirely different. Yet still we know what we need and  we don't want just something arbitrary. There is a constant core in what we're headed at, and we need the ability to //settle matters.// We need a backbone to work against, a skeleton to support us with its firmness, while also offering joints and links, to be bent and remoulded without breakage. The distinctive idea to make such possible is the principle of ''Subsidiarity''. The links and joints between autonomous centres can be shaped to be in fact an exchange, a handover based on common understanding of the //specific matters to deal with,// at that given joint.
 
@@ -6895,7 +6899,7 @@ The concurrent nature of the problem is what makes this simple task somewhat mor * referrals to already known sequence points might be added later, and also from different threads ({{red{WIP 3/14}}} not sure if we need to expand on this observation -- see CalcStream &hArr; Segment) This structure looks like requiring a message passing approach: we can arrange for determining the actual dependencies and fulfillment state late, within a single consumer, which is responsible for invoking the triggers. The other clients (threads) just pass messages into a possibly lock-free messageing channel.
-
+
The Session contains all information, state and objects to be edited by the User. From a users view, the Session is synonymous to the //current Project//. It can be [[saved and loaded|SessionLifecycle]]. The individual Objects within the Session, i.e. Clips, Media, Effects, are contained in one (or several) collections within the Session, which we call [[Sequence]].
 &rarr; [[Session design overview|SessionOverview]]
 &rarr; Structure of the SessionInterface
@@ -6910,6 +6914,7 @@ The Session object is a singleton &mdash; actually it is a »~PImpl«-Facade
 * the [[Asset subsystem|AssetManager]] is tightly integrated; besides, there are some SessionServices for internal use
 
 &rarr; see [[relation of timeline, sequences and objects|TimelineSequences]]
+&rarr; see //clarification of some fine points// regarding [[relation of Assets and MObjects|AssetModelConnection]]
 
 
 !Session lifecycle
@@ -6962,7 +6967,7 @@ The session and the models rely on dependent objects beeing kept updated and con
 &rarr; see [[details here...|ModelDependencies]]
 
-
+
"Session Interface" has several meanings, depending on the context.
 ;application global
 :the session is a data structure, which can be saved and loaded, and manipulated by [[sending commands|CommandHandling]]
@@ -7006,6 +7011,10 @@ To the contrary, the ''generic'' API is related to a //current location (state),
 * to add
 * to destroy
 
+!!relation of [[Assets|Asset]] and MObjects
+{{red{WARNING -- still under construction 10/2018}}}. Basically these segments represent the flip sides of the same coin. //Assets relate to the bookkeeping view.// However, we build a data model, and thus use representations for the involved entities. This creates some redundancy at times; we made an effort to reduce this redundancy and minimise the necessary data model representation. This means, some things are rather handled and represented as Assets, while other stuff is primarily dealt with as ~MObject.
+&rarr; see //discussion of some fine points// regarding [[relation of Assets and MObjects|AssetModelConnection]]
+
 !!exploring session contents
 Typically, the list of timelines serves as starting point for exploring the model. Basically, any kind of object could be attached everywhere, but both the GUI and the Builder rely on assumptions regarding the [[overall model structure|HighLevelModel]] &mdash; silently ignoring content not in line. This corresponds to the //dedicated API functions// on specific kinds of objects, which allow to retrieve content according to this assumed structure conveniently and with strong typing. From the timeline and the associated sequence you'll get the root track, and from there the sub-tracks and the clips located on them, which in turn may have attachments (effects, transitions, labels).
 On the other hand, arbitrary structures can be retrieved using the //generic API:// Contents can be discovered on the QueryFocus, which automatically follows the //point of mutation,// but can be moved to any point using the {{{QueryFocus::attach(obj)}}} function.
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm
index 30f3dc6c2..e77f228e0 100644
--- a/wiki/thinkPad.ichthyo.mm
+++ b/wiki/thinkPad.ichthyo.mm
@@ -38,15 +38,18 @@
 
 
 
-
+
 
 
 
 
 
 
-
-
+
+
+
+
+
 
 
 
@@ -70,8 +73,7 @@
       TODO: Infos zusammentragen und dokumentieren
     

- - +
@@ -80,16 +82,17 @@ - - + + + - - + + @@ -1299,8 +1302,8 @@ - + @@ -1581,7 +1584,7 @@ - + @@ -2119,8 +2122,7 @@ erscheint definitiv nicht als CSS-Node

- - +
@@ -2309,8 +2311,7 @@ dieser Fall dürfte häufiger auftreten

- - +
@@ -2356,8 +2357,7 @@ normalen 'light' destkop-theme verwenden kann

- - +
@@ -3909,8 +3909,7 @@ #1174 UI self diagnostics window

- - +
@@ -5184,12 +5183,151 @@
- - + + - + + + + + + + + + +

+ »die Timeline« +

+ + +
+ + + + + + + +

+ Aufgabe: populate timeline +

+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + +

+ »der Viewer« +

+ + +
+ +
+ + + + + + + + + +
+
+ @@ -5222,7 +5360,9 @@ - + + + @@ -5485,8 +5625,7 @@ Im Zweifelsfall den GTK+ Inspector verwenden!

- - +
@@ -5795,8 +5934,8 @@
- + @@ -5865,7 +6004,7 @@ - + @@ -6473,7 +6612,7 @@ - + @@ -6707,7 +6846,7 @@ - + @@ -16343,7 +16482,7 @@ - + @@ -16704,7 +16843,16 @@ - + + + + + + + + + + @@ -16979,8 +17127,8 @@ - + @@ -17813,7 +17961,7 @@ - + @@ -17867,8 +18015,14 @@ + + + - + + + + @@ -18286,11 +18440,15 @@ - + + + - + + + @@ -18309,11 +18467,15 @@ - + + + - + + + @@ -18344,6 +18506,14 @@ + + + + + + + + @@ -18486,6 +18656,9 @@ + + + @@ -18585,6 +18758,13 @@ + + + + + + + @@ -18886,8 +19066,8 @@ - - + + @@ -18933,7 +19113,7 @@ - + @@ -19677,7 +19857,26 @@ - + + + + + + + + + + + + + +

+ Aufgabe: inject dummy content +

+ + +
+ @@ -19765,6 +19964,45 @@
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -26935,7 +27173,7 @@ - + @@ -26949,7 +27187,7 @@

- + @@ -29537,8 +29775,14 @@ - - + + + + + + + + @@ -32269,8 +32513,7 @@ - - + @@ -34653,8 +34896,7 @@ Lösung scheint nun "rund" zu sein

- -
+ @@ -37546,6 +37788,26 @@ + + + + + + + + +

+ Aufgabe: produce dummy content +

+ + +
+ + +
+
+ +
@@ -38272,7 +38534,8 @@
- + + @@ -38667,9 +38930,9 @@ - + @@ -39761,7 +40024,7 @@ - + @@ -39778,7 +40041,7 @@

- +
From 9894542bf9ecb89afcd8d434e17b6d59a0149156 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 8 Oct 2018 05:00:06 +0200 Subject: [PATCH 138/178] Introduce predefined constants for magic IDs in UI communication ...these magical strings are already spreading dangerously throughout the code base PS: also fixup for c6b8811af03 (broken whitespace in test definition) --- src/common/guifacade.cpp | 15 +--- src/common/ui-protocol.cpp | 62 ++++++++++++++++ src/gui/ctrl/notification-hub.hpp | 2 +- src/gui/ctrl/state-recorder.hpp | 11 +-- src/gui/dialog/test-control.hpp | 13 ++-- src/gui/model/expander-revealer.hpp | 2 +- src/gui/model/tangible.cpp | 51 ++++++------- src/gui/model/tangible.hpp | 6 +- src/gui/notification-service.cpp | 7 +- src/gui/timeline/clip-presenter.cpp | 7 +- src/gui/timeline/marker-widget.cpp | 5 +- src/gui/timeline/timeline-controller.cpp | 5 +- src/gui/timeline/track-presenter.cpp | 7 +- src/include/ui-protocol.hpp | 93 ++++++++++++++++++++++++ src/lib/diff/diff-language.hpp | 6 +- src/proc/cmd/meta-cmd.cpp | 6 +- tests/15library.tests | 4 +- tests/gui/abstract-tangible-test.cpp | 18 ++--- tests/gui/bus-term-test.cpp | 23 +++--- tests/gui/test/mock-elm.hpp | 13 ++-- 20 files changed, 258 insertions(+), 98 deletions(-) create mode 100644 src/common/ui-protocol.cpp create mode 100644 src/include/ui-protocol.hpp diff --git a/src/common/guifacade.cpp b/src/common/guifacade.cpp index 25779272a..212c431a1 100644 --- a/src/common/guifacade.cpp +++ b/src/common/guifacade.cpp @@ -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: -*/ diff --git a/src/common/ui-protocol.cpp b/src/common/ui-protocol.cpp new file mode 100644 index 000000000..4dfc4b13a --- /dev/null +++ b/src/common/ui-protocol.cpp @@ -0,0 +1,62 @@ +/* + UiProtocol - magic keys used for communication with the Lumiera UI + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + 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"}; + + + /* ======== 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 diff --git a/src/gui/ctrl/notification-hub.hpp b/src/gui/ctrl/notification-hub.hpp index 225bdc6da..84b897ff1 100644 --- a/src/gui/ctrl/notification-hub.hpp +++ b/src/gui/ctrl/notification-hub.hpp @@ -147,7 +147,7 @@ namespace ctrl { virtual void doMark (GenNode const& stateMark) override { - if (stateMark.idi.getSym() == "Warning") + if (stateMark.idi.getSym() == MARK_Warning) getWidget().addWarn (stateMark.data.get()); else // forward to default handler diff --git a/src/gui/ctrl/state-recorder.hpp b/src/gui/ctrl/state-recorder.hpp index edf89dd44..b3b4ee8fc 100644 --- a/src/gui/ctrl/state-recorder.hpp +++ b/src/gui/ctrl/state-recorder.hpp @@ -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 @@ -174,14 +175,14 @@ 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); } diff --git a/src/gui/dialog/test-control.hpp b/src/gui/dialog/test-control.hpp index 8d8b04e77..039c00ad3 100644 --- a/src/gui/dialog/test-control.hpp +++ b/src/gui/dialog/test-control.hpp @@ -44,6 +44,7 @@ #include "gui/ctrl/bus-term.hpp" #include "gui/model/tangible.hpp" #include "proc/cmd.hpp" +#include "include/ui-protocol.hpp" #include "include/gui-notification-facade.h" #include "lib/scoped-ptrvect.hpp" #include "lib/diff/gen-node.hpp" @@ -225,12 +226,12 @@ namespace dialog { trig_4_.set_tooltip_markup (_("trigger Proc-command, which in turn\n" "sends an state mark message, using\n" "the message action-ID from the combobox")); - actionID_.append ("Flash"); - actionID_.append ("revealYourself"); - actionID_.append ("clearErr"); - actionID_.append ("clearMsg"); - actionID_.append ("expand"); - actionID_.append ("reset"); + 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 mark message.\n" diff --git a/src/gui/model/expander-revealer.hpp b/src/gui/model/expander-revealer.hpp index 41c5db905..6bbe689dc 100644 --- a/src/gui/model/expander-revealer.hpp +++ b/src/gui/model/expander-revealer.hpp @@ -36,7 +36,7 @@ ** ** 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 `"revealYourself"`. These are implemented + ** 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 diff --git a/src/gui/model/tangible.cpp b/src/gui/model/tangible.cpp index e81daf3dd..0070bc029 100644 --- a/src/gui/model/tangible.cpp +++ b/src/gui/model/tangible.cpp @@ -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}); } @@ -156,7 +157,7 @@ namespace model { Tangible::slotExpand() { if (this->doExpand(true)) - uiBus_.note (GenNode("expand", true)); + uiBus_.note (GenNode{string{MARK_expand}, true}); } @@ -168,7 +169,7 @@ namespace model { Tangible::slotCollapse() { if (this->doExpand(false)) - uiBus_.note (GenNode("expand", false)); + uiBus_.note (GenNode{string{MARK_expand}, false}); } @@ -192,9 +193,9 @@ namespace model { * 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 "`revealYourself`". + * 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 ::doRevealYourself() extension point, or to + * 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. @@ -207,7 +208,7 @@ namespace model { void Tangible::slotReveal() { - this->doRevealYourself(); + this->doReveal(); } @@ -217,7 +218,7 @@ namespace model { * explicitly to enable this functionality. */ void - Tangible::doRevealYourself() + Tangible::doReveal() { if (reveal_.canReveal()) reveal_(); @@ -245,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()); else - if (stateMark.idi.getSym() == "Message") + if (stateMark.idi.getSym() == MARK_Message) this->markMsg (stateMark.data.get()); else this->doMark(stateMark); @@ -267,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. @@ -280,23 +281,23 @@ namespace model { void Tangible::doMark (GenNode const& stateMark) { - if (stateMark.idi.getSym() == "expand") + if (stateMark.idi.getSym() == MARK_expand) { if (this->doExpand (stateMark.data.get())) - uiBus_.note (GenNode("expand", true)); // possibly reentrant (yet harmless) + 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(); } diff --git a/src/gui/model/tangible.hpp b/src/gui/model/tangible.hpp index 55f8d73ae..c20601116 100644 --- a/src/gui/model/tangible.hpp +++ b/src/gui/model/tangible.hpp @@ -217,7 +217,7 @@ namespace model { virtual bool doClearMsg() =0; virtual bool doClearErr() =0; virtual bool doExpand (bool yes); - virtual void doRevealYourself(); + virtual void doReveal (); virtual bool doMsg (string) =0; virtual bool doErr (string) =0; @@ -287,9 +287,9 @@ namespace model { /** * Configure the (optional) functionality to bring the UI-Element into sight. * @param how_to_uncover_the_element a lambda or function to actually cause the necessary actions. - * @note unless this setup function is invoked, the "`revealYourself`" functionality remains disabled. + * @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 "`revealYourself`" action on the container. + * and also recursively invoking the "`reveal`" action on the container. */ inline void Tangible::installRevealer (Revealer::RevealeItFun how_to_uncover_the_element) diff --git a/src/gui/notification-service.cpp b/src/gui/notification-service.cpp index 529a60449..41cff9361 100644 --- a/src/gui/notification-service.cpp +++ b/src/gui/notification-service.cpp @@ -56,6 +56,7 @@ #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" @@ -113,7 +114,7 @@ namespace gui { markNote (errorLogID, text); break; case NOTE_WARN: - mark (errorLogID, GenNode{"Warning", text}); + mark (errorLogID, GenNode{string{MARK_Warning}, text}); break; default: throw lumiera::error::Logic (_Fmt{"UI Notification with invalid severity %d encountered. " @@ -124,14 +125,14 @@ namespace gui { 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) { - dispatchMsg (uiElement, GenNode{"Message", text}); + dispatchMsg (uiElement, GenNode{string{MARK_Message}, text}); } diff --git a/src/gui/timeline/clip-presenter.cpp b/src/gui/timeline/clip-presenter.cpp index 098fd7a8b..411e10c5d 100644 --- a/src/gui/timeline/clip-presenter.cpp +++ b/src/gui/timeline/clip-presenter.cpp @@ -31,6 +31,7 @@ #include "gui/gtk-base.hpp" +#include "include/ui-protocol.hpp" #include "gui/timeline/clip-presenter.hpp" #include "gui/timeline/marker-widget.hpp" @@ -94,7 +95,7 @@ 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 { @@ -113,7 +114,7 @@ namespace timeline { .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 { @@ -132,7 +133,7 @@ namespace timeline { .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 { diff --git a/src/gui/timeline/marker-widget.cpp b/src/gui/timeline/marker-widget.cpp index 0208ea27b..530a30c02 100644 --- a/src/gui/timeline/marker-widget.cpp +++ b/src/gui/timeline/marker-widget.cpp @@ -31,6 +31,7 @@ #include "gui/gtk-base.hpp" +#include "include/ui-protocol.hpp" #include "gui/timeline/marker-widget.hpp" //#include "gui/ui-bus.hpp" @@ -82,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; diff --git a/src/gui/timeline/timeline-controller.cpp b/src/gui/timeline/timeline-controller.cpp index 985b556e5..fa85ba73f 100644 --- a/src/gui/timeline/timeline-controller.cpp +++ b/src/gui/timeline/timeline-controller.cpp @@ -39,6 +39,7 @@ #include "gui/gtk-base.hpp" +#include "include/ui-protocol.hpp" #include "gui/timeline/timeline-controller.hpp" #include "gui/timeline/track-presenter.hpp" #include "gui/timeline/marker-widget.hpp" @@ -155,12 +156,12 @@ namespace timeline { target->buildMutator (buff); // - delegate to child to build nested TreeMutator return true; })) - .mutateAttrib("fork", [&](TreeMutator::Handle buff) + .mutateAttrib(ATTR_fork, [&](TreeMutator::Handle buff) { // »Attribute Mutator« : how enter an object field as nested scope REQUIRE (fork_); fork_->buildMutator(buff); }) - .change("name", [&](string val) + .change(ATTR_name, [&](string val) { // »Attribute Setter« : how assign a new value to some object field name_ = val; })); diff --git a/src/gui/timeline/track-presenter.cpp b/src/gui/timeline/track-presenter.cpp index bce8ed079..ea541c650 100644 --- a/src/gui/timeline/track-presenter.cpp +++ b/src/gui/timeline/track-presenter.cpp @@ -31,6 +31,7 @@ #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" @@ -104,7 +105,7 @@ 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 { @@ -123,7 +124,7 @@ namespace timeline { .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 { @@ -142,7 +143,7 @@ namespace timeline { .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 { diff --git a/src/include/ui-protocol.hpp b/src/include/ui-protocol.hpp new file mode 100644 index 000000000..999e95c27 --- /dev/null +++ b/src/include/ui-protocol.hpp @@ -0,0 +1,93 @@ +/* + UI-PROTOCOL.h - magic keys used for communication with the Lumiera UI + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + 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.hpp + ** Hard wired key constants and basic definitions for communication with the GUI. + ** The Lumiera UI is connected to the application core via message oriented interfaces, + ** running an asynchronous communication protocol. + ** - the [UI-Bus](\ref ui-bus.hpp) allows for some specific kinds of messages to be + ** sent either "upstream" (towards the CoreService) or "downstream" to individual + ** [UI-Elements](\ref gui::model::Tangible) known by ID. + ** - these ["tangible interface elements"](\ref tangible.hpp) themselves define a + ** basic set of actions and "state mark" messages, known as the "element protocol" + ** - and changes to the structure of tangible elements exposed through the UI are + ** conducted by pushing up [»diff messages«](\ref mutation-message.hpp) into the + ** UI via UI-Bus. These messages need to comply to an underlying structural + ** model of elements to be edited within the session and exposed for GUI + ** manipulation. This is referred to as "Session Model Scheme" + ** + ** These protocols are basically _implementation defined_ conventions and not + ** rigorously formalised. To a large extent, they are guided by the types and + ** corresponding API interfaces. However, since the lumiera session allows for + ** quite flexible structures, these protocols are kept open for local extensions. + ** At various levels, magic ID-Values are used to initiate specific interactions. + ** This header supplies the hard wired key constants and IDs thereby employed. + ** + ** @see common/ui-protocol.cpp (translation unit backing those constants) + */ + + +#ifndef GUI_INTERFACE_UI_PROTOCOL_H +#define GUI_INTERFACE_UI_PROTOCOL_H + + +#include "lib/symbol.hpp" + + +namespace Glib { +//class ustring; /////////////////////////////TODO needed? +} +namespace gui { + +//using lib::Literal; + using lib::Symbol; +//using cuString = const Glib::ustring; + + + extern const Symbol META_kind; + + extern const Symbol TYPE_Fork; + extern const Symbol TYPE_Clip; + extern const Symbol TYPE_Marker; + extern const Symbol TYPE_Channel; + extern const Symbol TYPE_Effect; + + extern const Symbol ATTR_name; + extern const Symbol ATTR_fork; + + + /* ======== UI-Element protocol ======== */ + + extern const Symbol MARK_reset; + extern const Symbol MARK_clearErr; + extern const Symbol MARK_clearMsg; + extern const Symbol MARK_expand; + extern const Symbol MARK_reveal; + extern const Symbol MARK_Flash; + extern const Symbol MARK_Error; + extern const Symbol MARK_Warning; + extern const Symbol MARK_Message; + + +}// namespace gui +#endif /*GUI_INTERFACE_UI_PROTOCOL_H*/ diff --git a/src/lib/diff/diff-language.hpp b/src/lib/diff/diff-language.hpp index f4683c2a5..c7fe57ceb 100644 --- a/src/lib/diff/diff-language.hpp +++ b/src/lib/diff/diff-language.hpp @@ -33,7 +33,8 @@ ** Such an architecture allows for tight cooperation between strictly separated ** components, without the need of a fixed, predefined and shared data structure. ** - ** \par Basic Assumptions + ** # Basic Assumptions + ** ** While the \em linearisation folds knowledge about the underlying data structure ** down into the actual diff, we deliberately assume that the data to be diffed is ** \em structured data. Moreover, we'll assume implicitly that this data is \em typed, @@ -44,7 +45,8 @@ ** send the actual content data this way, or to serve as redundancy to verify ** proper application of the changes at the diff receiver downstream. ** - ** \par Solution Pattern + ** # Solution Pattern + ** ** The representation of this linearised diff language relies on a specialised form ** of the visitor pattern: We assume the vocabulary of the diff language to be ** relatively fixed, while the actual effect when consuming the stream of diff tokens diff --git a/src/proc/cmd/meta-cmd.cpp b/src/proc/cmd/meta-cmd.cpp index 5eeb7fcbd..030a5ebfa 100644 --- a/src/proc/cmd/meta-cmd.cpp +++ b/src/proc/cmd/meta-cmd.cpp @@ -38,6 +38,7 @@ #include "proc/cmd.hpp" #include "proc/control/command-def.hpp" //#include "proc/mobject/session.hpp" +#include "include/ui-protocol.hpp" //////////////////////////////////////////////////////////////TICKET #1140 : verify if this include is required #include "include/gui-notification-facade.h" #include "gui/interact/wizard.hpp" //////////////////////////////////////////////////////////////TICKET #1140 : include needed temporarily #include "lib/diff/gen-node.hpp" @@ -54,6 +55,7 @@ using gui::NOTE_INFO; using gui::NOTE_WARN; using gui::NOTE_ERROR; using gui::NotifyLevel; +using gui::MARK_expand; using gui::GuiNotification; using lib::diff::GenNode; //using util::cStr; @@ -214,8 +216,8 @@ COMMAND_DEFINITION (test_meta_markAction) { ID errorLogID = gui::interact::Wizard::getErrorLogID(); GuiNotification::facade().mark (errorLogID - ,actionID=="expand"? GenNode{actionID, isYes(message)} - : GenNode{actionID, message}); + ,actionID==MARK_expand? GenNode{actionID, isYes(message)} + : GenNode{actionID, message}); }) .captureUndo ([](string actionID, string message) -> string { diff --git a/tests/15library.tests b/tests/15library.tests index a9779dc4c..c2a94f0cb 100644 --- a/tests/15library.tests +++ b/tests/15library.tests @@ -705,8 +705,8 @@ TEST "util: sanitised identifier" UtilSanitizedIdentifier_test < 'Word' out-lit: 'a Sentence' --> 'a_Sentence' out-lit: 'trailing Withespace -out-lit: ' --> 'trailing_Withespace' -out-lit: 'with a lot +out-lit: ' --> 'trailing_Withespace' +out-lit: 'with a lot out-lit: of Whitespace' --> 'with_a_lot_of_Whitespace' out-lit: 'with"much (punctuation)[]!' --> 'withmuch_(punctuation)' out-lit: '§&Ω%€ leading garbage' --> 'leading_garbage' diff --git a/tests/gui/abstract-tangible-test.cpp b/tests/gui/abstract-tangible-test.cpp index 9382b4c9d..c9ffc776a 100644 --- a/tests/gui/abstract-tangible-test.cpp +++ b/tests/gui/abstract-tangible-test.cpp @@ -470,7 +470,7 @@ namespace test { } - /** @test configure a handler for the (optional) "revealYourself" functionality. + /** @test configure a handler for the (optional) "reveal yourself" functionality. * We install a lambda to supply the actual implementation action, which can then * either be triggered by a signal/slot invocation, or by sending a "state mark". */ @@ -494,7 +494,7 @@ namespace test { bool revealed = false; mock.installRevealer([&]() - { // NOTE: our mock "implementation" of the revealYourself functionality + { // NOTE: our mock "implementation" of the »reveal yourself« functionality mock.slotExpand(); // explicitly prompts the element to expand itself, revealed = true; // and then via closure sets a flag we can verify. }); @@ -504,7 +504,7 @@ namespace test { CHECK (true == revealed); CHECK (mock.isExpanded()); CHECK (mock.verifyEvent("create","target") - .beforeCall("revealYourself") + .beforeCall("reveal") .beforeCall("expand").arg(true) .beforeEvent("expanded")); @@ -515,28 +515,28 @@ namespace test { // second test: the same can be achieved via UI-Bus message... revealed = false; - auto stateMark = GenNode{"revealYourself", 47}; // (payload argument irrelevant) + auto stateMark = GenNode{"reveal", 47}; // (payload argument irrelevant) auto& uiBus = gui::test::Nexus::testUI(); - CHECK (nexusLog.ensureNot("revealYourself")); + CHECK (nexusLog.ensureNot("reveal")); uiBus.mark (targetID, stateMark); // send the state mark message to reveal the element CHECK (true == revealed); - CHECK (mock.verifyMark("revealYourself", 47) + CHECK (mock.verifyMark("reveal", 47) .afterEvent("expanded") - .beforeCall("revealYourself") + .beforeCall("reveal") .beforeCall("expand").arg(true)); CHECK (nexusLog.verifyCall("mark").arg(targetID, stateMark) .after("handling state-mark") - .before("revealYourself") + .before("reveal") .beforeEvent("delivered mark")); // Note the fine point: the target element /was/ already expanded // and thus there is no second "expanded" event, nor is there a // second state mark emitted into the UI-Bus... CHECK (mock.ensureNot("expanded") - .afterCall("revealYourself") + .afterCall("reveal") .afterEvent("expanded")); CHECK (nexusLog.ensureNot("note") .afterCall("mark").arg(targetID, stateMark) diff --git a/tests/gui/bus-term-test.cpp b/tests/gui/bus-term-test.cpp index 1b5b91e28..931f20711 100644 --- a/tests/gui/bus-term-test.cpp +++ b/tests/gui/bus-term-test.cpp @@ -30,6 +30,7 @@ #include "lib/sync.hpp" #include "lib/sync-classlock.hpp" #include "backend/thread-wrapper.hpp" +#include "include/ui-protocol.hpp" #include "gui/ctrl/bus-term.hpp" #include "gui/ctrl/state-manager.hpp" #include "proc/control/command.hpp" @@ -190,8 +191,8 @@ namespace test { CHECK (nexusLog.verifyCall("note").on("TestNexus").arg(elmID, "GenNode-ID(\"expand\")-DataCap|«bool»|true")); // send a state mark down to the mock element - gui::test::Nexus::testUI().mark (elmID, GenNode("Flash", 23)); - CHECK (nexusLog.verifyCall("mark").on("TestNexus").arg(elmID, "Flash") + gui::test::Nexus::testUI().mark (elmID, GenNode(string{MARK_Flash}, 23)); + CHECK (nexusLog.verifyCall("mark").on("TestNexus").arg(elmID, MARK_Flash) .beforeEvent("TestNexus", "mark to bID-zeitgeist")); CHECK (elmLog.verifyCall("doFlash").on("zeitgeist")); @@ -202,9 +203,9 @@ namespace test { CHECK (nexusLog.verifyCall("routeDetach").on("TestNexus").arg(elmID) .beforeEvent("TestNexus", "removed route to bID-zeitgeist")); - gui::test::Nexus::testUI().mark (elmID, GenNode("Flash", 88)); + gui::test::Nexus::testUI().mark (elmID, GenNode({MARK_Flash}, 88)); CHECK (nexusLog.verify("removed route to bID-zeitgeist") - .beforeCall("mark").on("TestNexus").arg(elmID, "Flash") + .beforeCall("mark").on("TestNexus").arg(elmID, MARK_Flash) .beforeEvent("warn","discarding mark to unknown bID-zeitgeist")); CHECK (elmLog.ensureNot("Flash") .afterEvent("destroy","zeitgeist")); @@ -367,8 +368,8 @@ namespace test { CHECK (not mockB.isTouched()); CHECK (not mockC.isTouched()); - uiBus.mark (alpha, GenNode{"Message", "Centauri"}); - uiBus.mark (bravo, GenNode{"Flash", true}); + uiBus.mark (alpha, GenNode{"Message", "Centauri"}); + uiBus.mark (bravo, GenNode{"Flash", true}); uiBus.mark (charly, GenNode{"Message", "Delta"}); uiBus.mark (charly, GenNode{"Error", "Echo"}); @@ -497,8 +498,8 @@ namespace test { auto& stateManager = gui::test::Nexus::getMockStateManager(); CHECK (stateManager.currentState(alpha, "expand") == GenNode("expand", false )); CHECK (stateManager.currentState(bravo, "expand") == GenNode("expand", true )); - CHECK (stateManager.currentState(charly, "expand") == Ref::NO); - CHECK (stateManager.currentState(charly, "Error") == GenNode("Error", "Echo")); // sticky error state was recorded + CHECK (stateManager.currentState(charly,"expand") == Ref::NO); + CHECK (stateManager.currentState(charly, "Error") == GenNode("Error", "Echo")); // sticky error state was recorded // reset error state(s) uiBus.markAll (GenNode{"clearErr", true}); @@ -509,9 +510,9 @@ namespace test { CHECK (stateManager.currentState(alpha, "expand") == GenNode("expand", false )); CHECK (stateManager.currentState(bravo, "expand") == GenNode("expand", true )); - CHECK (stateManager.currentState(charly, "expand") == Ref::NO); - CHECK (stateManager.currentState(charly, "Error") == Ref::NO); // sticky error state was cleared, - // because charly sent a clearErr state mark notification back + CHECK (stateManager.currentState(charly,"expand") == Ref::NO); + CHECK (stateManager.currentState(charly, "Error") == Ref::NO); // sticky error state was cleared, + // because charly sent a clearErr state mark notification back // send global sweeping reset uiBus.markAll (GenNode{"reset", true}); diff --git a/tests/gui/test/mock-elm.hpp b/tests/gui/test/mock-elm.hpp index 82baec4b2..09cf2e996 100644 --- a/tests/gui/test/mock-elm.hpp +++ b/tests/gui/test/mock-elm.hpp @@ -61,6 +61,7 @@ #include "lib/error.hpp" +#include "include/ui-protocol.hpp" #include "lib/test/event-log.hpp" #include "gui/model/tangible.hpp" #include "lib/diff/record.hpp" @@ -126,7 +127,7 @@ namespace test{ virtual bool doReset() override { - log_.call(this->identify(), "reset"); + log_.call(this->identify(), string{MARK_reset}); if (virgin_) return false; // there was nothing to reset @@ -134,22 +135,22 @@ namespace test{ message_ = ""; expanded_ = false; virgin_ = true; - log_.event("reset"); + log_.event(string{MARK_reset}); return true; // we did indeed reset something } // and thus a state mark should be captured virtual bool doExpand (bool yes) override { - log_.call(this->identify(), "expand", yes); + log_.call(this->identify(), string{MARK_expand}, yes); return Tangible::doExpand (yes); } virtual void - doRevealYourself() override + doReveal() override { - log_.call(this->identify(), "revealYourself"); - Tangible::doRevealYourself(); // NOTE: without specific configuration this is NOP + log_.call(this->identify(), string{MARK_reveal}); + Tangible::doReveal(); // NOTE: without specific configuration this is NOP } virtual bool From 2adcabbef53d7bd900df3f4244c3946bd97e0f79 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 10 Oct 2018 05:45:46 +0200 Subject: [PATCH 139/178] Timeline: draft the root attachment point where timelines are created by diff MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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« --- src/common/ui-protocol.cpp | 1 + src/gui/interact/interaction-director.cpp | 38 ++++- src/gui/interact/interaction-director.hpp | 5 +- src/gui/setting/asset-controller.hpp | 6 +- src/include/ui-protocol.hpp | 1 + wiki/renderengine.html | 23 ++- wiki/thinkPad.ichthyo.mm | 174 +++++++++++++++++++++- 7 files changed, 232 insertions(+), 16 deletions(-) diff --git a/src/common/ui-protocol.cpp b/src/common/ui-protocol.cpp index 4dfc4b13a..f0041692e 100644 --- a/src/common/ui-protocol.cpp +++ b/src/common/ui-protocol.cpp @@ -41,6 +41,7 @@ namespace gui { const Symbol ATTR_name{"name"}; const Symbol ATTR_fork{"fork"}; + const Symbol ATTR_assets{"assets"}; /* ======== UI-Element protocol ======== */ diff --git a/src/gui/interact/interaction-director.cpp b/src/gui/interact/interaction-director.cpp index 0c272b6e1..bf7cdd92a 100644 --- a/src/gui/interact/interaction-director.cpp +++ b/src/gui/interact/interaction-director.cpp @@ -42,6 +42,7 @@ #include "gui/ctrl/ui-state.hpp" #include "gui/setting/asset-controller.hpp" #include "gui/timeline/timeline-controller.hpp" +#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 @@ -61,6 +62,8 @@ using lib::idi::EntryID; using lib::hash::LuidH; using lib::diff::TreeMutator; +using lib::diff::collection; +using std::make_unique; using util::toString; @@ -128,8 +131,30 @@ namespace interact { { 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, PTimelineCtrl const& elm) -> bool + { // »Matcher« : how to know we're dealing with the right timeline object + return spec.idi == ID(elm); + }) + .constructFrom ([&](GenNode const& spec) -> PTimelineCtrl + { // »Constructor« : what to do when the diff mentions a new entity + return injectTimeline (spec); + }) + .buildChildMutator ([&](PTimelineCtrl& 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); + })); } @@ -280,6 +305,15 @@ namespace interact { { return globalCtx_.windowLoc_.findActiveWindow(); } + + + /** @internal allocate a new TimelineWidget and attach it as child */ + InteractionDirector::PTimelineCtrl + InteractionDirector::injectTimeline (GenNode const& spec) + { + unimplemented ("allocate a TimelineWidget in some TimelinePanel and attach it's controller as child entity"); + } + diff --git a/src/gui/interact/interaction-director.hpp b/src/gui/interact/interaction-director.hpp index d6d22d076..6a82f43ed 100644 --- a/src/gui/interact/interaction-director.hpp +++ b/src/gui/interact/interaction-director.hpp @@ -80,6 +80,7 @@ namespace workspace { } namespace interact { + using lib::diff::GenNode; //using std::string; using std::unique_ptr; @@ -113,7 +114,8 @@ namespace interact { unique_ptr tracker_; // == Model globals == - using Timelines = std::vector>; + using PTimelineCtrl = unique_ptr; + using Timelines = std::vector; using Assets = unique_ptr; using State = unique_ptr; @@ -150,6 +152,7 @@ namespace interact { private: workspace::WorkspaceWindow& getWorkspaceWindow(); + PTimelineCtrl injectTimeline (GenNode const&); }; diff --git a/src/gui/setting/asset-controller.hpp b/src/gui/setting/asset-controller.hpp index fce280526..d4e6ad67e 100644 --- a/src/gui/setting/asset-controller.hpp +++ b/src/gui/setting/asset-controller.hpp @@ -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: }; diff --git a/src/include/ui-protocol.hpp b/src/include/ui-protocol.hpp index 999e95c27..6aa5cdb4a 100644 --- a/src/include/ui-protocol.hpp +++ b/src/include/ui-protocol.hpp @@ -74,6 +74,7 @@ namespace gui { extern const Symbol ATTR_name; extern const Symbol ATTR_fork; + extern const Symbol ATTR_assets; /* ======== UI-Element protocol ======== */ diff --git a/wiki/renderengine.html b/wiki/renderengine.html index a6a6f8ceb..02deabddb 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -2082,6 +2082,10 @@ The Method of pushing frames to the Viewer widget is changed fundamentally durin
+
+
//A self referential intermediary, used to collect and consolidate structural information about some element.//
+This structural information can then be emanated as ''population diff'' and is thus the foundation to build a further representation of the same structure //"elsewhere".// This //further representation// could be a model entity within the session's HighLevelModel, or it could be a widget tree in the UI. And, to close the circle, this other representation resulting from that diff could in turn again be a DiffConstituent.
+
The Dispatcher Tables are hosted within the [[Segmentation]] and serve as a ''strategy'' governing the play process.
 For each [[calculation stream|CalcStream]] there is a concrete implementation of the [[dispatcher|FrameDispatcher]] interface, based on a specific configuration of dispatcher tables.
@@ -3243,7 +3247,7 @@ More specifically, the integration is based on ''messaging''. To start with, the ---- In a preliminary attempt to establish an integration between the GUI and the lower layers, in 1/2009 we created an PlayerDummy, which "pulls" dummy frames from the (not yet existing) engine and displays them within an XV viewer widget. This highlighted the problems we're about to encounter and made us think about the more radically decoupled approach we followed thereafter...
-
+
Building a layered architecture is a challenge, since the lower layer //really// needs to be self-contained, while prepared for usage by the higher layer.
 A major fraction of all desktop applications is written in a way where operational logic is built around the invocation from UI events -- what should be a shell turns into a backbone. One possible way to escape from this common anti pattern is to introduce a mediating entity, to translate between two partially incompatible demands and concerns: Sure, the "tangible stuff" is what matters, but you can not build any significant piece of technology if all you want is to "serve" the user.
 
@@ -3258,6 +3262,11 @@ The whole Idea to have a "UI model" appears more questionable than eve
 * {{red{as of 8/2018}}}, matters on a large scale converge towards a structure //without a classical UI model.// Seemingly we can do without...
 * there is a working demonstration in {{{BusTerm_test}}}, which pushes mutation diffs against a mock UI-Element. The immediate response to receiving such a diff notification via the UI-Bus has now been coded; it incurs invoking a passed callback (functor), which performs within the originating context, but within the ~UI-Thread, and produces the actual diff //on-demand.// ("pull principle")
 
+!!!building the model structures
+A fundamental decision within the Lumiera UI is to build every model-like structure as immediate response to receiving a diff message pushed up into the UI.
+* either this happens when some change occured, which is directly reflected into the UI by a local diff
+* or a whole subtree of elements is built up step wise in response to a ''population diff''. This is an systematic description of a complete sub-structure in current shape, and is produced as emanation from a DiffConstituent.
+
 !synchronisation guarantees
 We acknowledge that the gui model is typically used from within the GUI event dispatch thread. This is //not// the thread where any session state is mutated. Thus it is the responsibility of this proxying model within the GUI to ensure that the retrieved structure is a coherent snapshot of the session state. Especially the {{{gui::model::SessionFacade}}} ensures that there was a read barrier between the state retrieval and any preceding mutation command. Actually, this is implemented down in Proc-Layer, with the help of the ProcDispatcher.
 
@@ -3425,7 +3434,7 @@ In accordance with the Lumiera application architecture in general, the UI is no
 
A specially configured LumieraPlugin, which actually contains or loads the complete code of the (GTK)GUI, and additionally is linked dynamically against the application core lib. During the [[UI startup process|GuiStart]], loading of this Plugin is triggered from {{{main()}}}. Actually this causes spawning of the GTK event thread and execution of the GTK main loop.
 
-
+
Within the Lumieara GUI, the [[Timeline]] structure(s) from the HighLevelModel are arranged and presented according to the following principles and conventions.
 Several timeline views may be present at the same time -- and there is not necessarily a relation between them, since »a Timeline« is the top-level concept within the [[Session]]. Obviously, there can also be several //views// based on the same »Timeline« model element, and in this latter case, these //coupled views// behave according to a linked common state. An entity »Timeline« as represented through the GUI, emerges from the combination of several model elements
 * a root level [[Binding|BindingMO]] acts as framework
@@ -3459,7 +3468,7 @@ This collapsed, expanded and possibly nested workspace structure is always exact
 !!!lifecycle and instances
 A given instance of the {{{TimelineWidget}}} is always dedicated to render the contents of //one specific timeline.// We never switch the data model while retaining the UI entities. This also means, a given instance is tied to one conversation with the core; it is created when the core tells us about this timeline with an initial population diff, and it lives until either this timeline is discarded in the core model, or the whole session is shut down.
 
-The dockable ''timeline pannel'' holds onto the existing {{{TimelineWidget}}} instances, allowing to make one of them visible for interaction. Yet those instances are managed by the InteractionDirector, who incorporates the role of representing the [[model root|ModelRootMO]]. Aside of the timeline display, there is also an ''asset pannel''; display of and interaction with those asset views is handled by dedicated widgets, backed by the {{{AssetControler}}} -- which is also a child of and managed by the InteractionDirector, since assets are modelled as global part of the [[Session]].
+The dockable ''timeline pannel'' holds onto the existing {{{TimelineWidget}}} instances, allowing to make one of them visible for interaction. Yet the timeline itself is represented by the {{{TimelineControler}}}, which lives //within this widget,// and is attached to and managed by the InteractionDirector, who incorporates the role of representing the [[model root|ModelRootMO]]. Aside of the timeline display, there is also an ''asset pannel''; display of and interaction with those asset views is handled by dedicated widgets, backed by the {{{AssetControler}}} -- which is also a child of and managed by the InteractionDirector, since assets are modelled as global part of the [[Session]].
 
 In case the UI starts with no session present in the core, an //empty timeline placeholder// will be displayed, which provides UI for creating a new session...
 
@@ -3471,7 +3480,7 @@ In the most general case, there can be per-track content and nested content at t
 &rarr; important question: how to [[organise the widgets|GuiTimelineWidgetStructure]]
 
-
+
The Timeline is probably the most prominent place in the GUI where we need to come up with a custom UI design.
 Instead of combining standard components in one of the well-known ways, here we need to come up with our own handling solution -- which also means to write one or several custom GTK widgets. Thus the question of layout and screen space division and organisation becomes a crucial design decision. The ~GTK-2 Gui, as implemented currently, did already take some steps along this route, yet this kind of decision should be cast and documented explicitly (be it after the fact).
 
@@ -3510,6 +3519,12 @@ Here, the "component" relevant for such structural changes is always t
 
 From these observations we can draw the conclusion, that we'll build a ''local structural model'', to reflect the logical relations between the parts comprising the timeline display. More precisely, these structuring components are not mere model objects, rather they are mediating entities used to guide and organise the actual view entities, which in turn are passive. They are more like a view model, while also bearing some local controller responsibilities. For this reason, we prefer to term these as ''presenters'' -- i.e. TrackPresenter and ClipPresenter. And each of these local representation components holds onto a display context, which generally links it into two different display widget stacks in the two parts of the actual timeline display. Adding a child component thus becomes a rather tricky operation, involving to link possibly two child widgets into two disjoint parent widgets, thereby forming a similar display context for the child presenter. Overall, the guiding idea is that of self similarity: on each level, we have to reproduce the same relations and collaborations as present in the parent level.
 
+!!!building the timeline representation structure
+It is a fundamental decision within the Lumiera UI that any structure as to be injected as diff by the core. We do not hold a structure or data model within some UI entity and then "interpret" that model into a widget structure -- rather we build the widget structure itself in response to a diff message describing the structure. Especially in the case of timelines, the receiver of those messages is the InteractionDirector, which corresponds to the model root. On being prompted to set up a timeline, it will allocate a {{{TimelineWidget}}} within a suitable timeline docking panel and then attach to the {{{TimelineController}}} embedded within the widget.
+{{red{Problem 10/2018}}} how can the InteractionDirector //manage// a timeline while the timeline widget physically resides within the panel? Can we exploit a simliar structure as we did for the error log?
+
+The diff describing and thus assembling the UI representation of a timeline is typically a ''population diff'' -- which means, it is a consolidated complete description of the whole sub-structure rooted below that timeline. Such a population diff is generated as emanation from the respective DiffConstituent.
+
 !!!interplay with diff mutation
 Applying a diff changes the structure, that is, the structure of the local model, not the structure of the display widgets. Because the latter are an entirely private concern of the UI and their structure is controlled by the model components in conjunction with the display manager. And since diff application effects the contents of the model such as to make the intended structural changes happen (indirectly), we are well advised to tie the display control and the widgets very closely to those local model elements, such as to //adjust the display automatically.//
 * when a new model element is added, it has to inject something automatically into the display
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm
index e77f228e0..0f2363833 100644
--- a/wiki/thinkPad.ichthyo.mm
+++ b/wiki/thinkPad.ichthyo.mm
@@ -45,7 +45,7 @@
 
 
 
-
+
 
 
 
@@ -5246,6 +5246,10 @@
 
 
 
+
+
+
+
 
 
 
@@ -5256,7 +5260,7 @@
 
 
 
-
+
 
 
 
@@ -16475,7 +16479,62 @@
 
 
 
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+  
+    
+  
+  
+    

+ how can the InteractionDirector manage a timeline +

+

+  while the timeline widget physically resides within the panel? +

+ + +
+ + + + + +

+ Can we exploit a simliar structure as we did for the error log? +

+ + +
+ +
+
+
+
@@ -19857,12 +19916,103 @@ - + + + + + + + + + + + + +
    +
  • + Komponente hat eine interne Struktur-Repräsentation +
  • +
  • + traversiert diese Struktur-Beschreibung +
  • +
  • + und übersetzt sie in UI-Widgets +
  • +
+ + +
+ +
+ + + + + + +
    +
  • + jedes Teilelement wird per Push injiziert +
  • +
  • + es wird in dem Moment sofort und ausschließlich als Widget realisiert +
  • +
+ + +
+ +
+
+ + + + + + + + + + + + + + + + +

+ Basis: DiffConstituent +

+ + +
+ + + + + +
    +
  • + ein selbstbezüglicher substanzloser Strukturkern +
  • +
  • + DiffConstituent -> emanation as diff +
  • +
+ + +
+ + + + + + @@ -34875,7 +35025,19 @@
- + + + + + + + + + + + + + @@ -37790,7 +37952,7 @@ - + From b65db50666c19839d15f03514905623be3665512 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 11 Oct 2018 14:29:55 +0200 Subject: [PATCH 140/178] Timeline: some considerations regarding timeline slave display (#1083) --- src/proc/asset/timeline.hpp | 3 +++ src/proc/mobject/session/binding.hpp | 1 + wiki/renderengine.html | 32 +++++++++++++++++++++++-- wiki/thinkPad.ichthyo.mm | 36 ++++++++++++++++++++++++---- 4 files changed, 65 insertions(+), 7 deletions(-) diff --git a/src/proc/asset/timeline.hpp b/src/proc/asset/timeline.hpp index a9975ac2e..1877d3674 100644 --- a/src/proc/asset/timeline.hpp +++ b/src/proc/asset/timeline.hpp @@ -47,6 +47,9 @@ ** was no opportunity to set this system really into motion; this is ** not necessarily a bad thing, since meanwhile we understand way ** better in which way the Session will actually be accessed... + ** @todo looks like we'll represent a secondary, attached slave display of the + ** same Timeline rather by materialising it into a TimelineClone within + ** the session. This topic has been postponed as of 10/2018 //////////////////////////////////////TICKET #1083 ** @see Session ** @see Sequence ** @see StructFactory diff --git a/src/proc/mobject/session/binding.hpp b/src/proc/mobject/session/binding.hpp index 1d9d638dc..e7e815e6f 100644 --- a/src/proc/mobject/session/binding.hpp +++ b/src/proc/mobject/session/binding.hpp @@ -25,6 +25,7 @@ ** MObject in session to represent the top-level binding of a sequence ** @todo stalled effort towards a session implementation from 2010 ** @todo 2016 likely to stay, but expect some extensive rework + ** @todo Also consider timeline-slave, a topic postponed as of 10/2018 /////////////////////////////////TICKET #1083 */ diff --git a/wiki/renderengine.html b/wiki/renderengine.html index 02deabddb..60b191eb7 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -3434,7 +3434,22 @@ In accordance with the Lumiera application architecture in general, the UI is no
A specially configured LumieraPlugin, which actually contains or loads the complete code of the (GTK)GUI, and additionally is linked dynamically against the application core lib. During the [[UI startup process|GuiStart]], loading of this Plugin is triggered from {{{main()}}}. Actually this causes spawning of the GTK event thread and execution of the GTK main loop.
 
-
+
+
There are various reasons why we might want to offer multiple equivalent UI representations of the same Timeline...
+* the user might want to see several remote parts of the same timeline simultaneously, in focussed display
+* we allow several indeptendent top-level windows (think several desktops), so it might just happen that the same timeline is selected in several windows
+* we might want to introduce a focussed view on a nested sequence or virtual clip
+
+Now, since we build our UI on the notion of mapping session contents via a messaging system onto suitable presenters in the UI, we get a conceptual mismatch. Basically we need to cut at some point and duplicate some connections. Either we need the ability within a timeline presentation entity to serve several sets of slave widgets, or we need the ability for those presentation entities to collaborate, where one of them becomes the leader and automatically forwards all notifications to the other members of the cluster. Or, alternatively, we could think of pushing that duplication down into the session, in which case we get a TimelineClone entity, and the Builder then needs to be aware of this situation and generate duplicated responses to be sent to the UI.
+
+{{red{While reconsidering this topic in 10/2018}}}, it looks like I am leaning towards the most systematic option, which is to represent this duplication already within the session as TimelineClone. The rationale is
+* splitting this way is likely to produce the least accidental complexity -- at that point we are forced to cross-cut only a small number of other concerns. Were we to cut and duplicate within the UI, we'd be forced to carry care for slave entities into a huge number of entirely unrelated UI concerns, like layout management or media display feedback.
+* in the case of a focussed view on a nested sequence we are even forced to go that route, since doing otherwise would carry over core session responsibilities into the presentation layer. Consequently, any other solution scheme causes duplicating of functionality.
+* however -- if we implement slave timelines already within the session, we still need to make the UI counterpart basically aware of the situation. Thus the {{{TimelineControler}}} needs the ability to delegate some behaviour to another primary controller. That being said -- I still confirm the decision to postpone that topic altogether...
+
+In any case, this is an advanced topic, and nowhere near trivial. It seems reasonable to reject opening duplicate timeline presentations as a first step, and then address this topic way later, when we've gained sufficient knowledge regarding all the subtleties of timeline presentation and editing.
+
+
Within the Lumieara GUI, the [[Timeline]] structure(s) from the HighLevelModel are arranged and presented according to the following principles and conventions.
 Several timeline views may be present at the same time -- and there is not necessarily a relation between them, since »a Timeline« is the top-level concept within the [[Session]]. Obviously, there can also be several //views// based on the same »Timeline« model element, and in this latter case, these //coupled views// behave according to a linked common state. An entity »Timeline« as represented through the GUI, emerges from the combination of several model elements
 * a root level [[Binding|BindingMO]] acts as framework
@@ -3473,6 +3488,10 @@ The dockable ''timeline pannel'' holds onto the existing {{{TimelineWidget}}} in
 In case the UI starts with no session present in the core, an //empty timeline placeholder// will be displayed, which provides UI for creating a new session...
 
 
+!!!slave Timelines
+It is reasonable to expect the ability to have multiple [[slave timeline presentations|GuiTimelineSlave]] to access the same underlying timeline structure.
+Currently {{red{as of 10/2018}}} there is a preference to deal with that problem on session level -- but for now we decide to postpone this topic &rarr; [[#1083|http://issues.lumiera.org/ticket/1083]]
+
 !!!nesting
 By principle, this workspace structure is //not a list of "Tracks"// -- it is a system of ''nested scopes''. The nesting emerges on demand.
 In the most general case, there can be per-track content and nested content at the same point in time. The GUI is able to represent this state. But, due to the semantics of Lumiera's HighLevelModel,  top-level content and nested content are siblings //within the same scope.// Thus, at a suitable point {{red{to be defined}}}, an equivalence transformation is applied to the GUI model, by prepending a new sibling track and moving top-level content there.
@@ -9030,7 +9049,7 @@ Currently (1/11), the strategy is implemented according to (1) and (4) above, le
 Implementation of this strategy is still broken: it doesn't work properly when actually the change passing over the zero point happens by propagation from lower digits. Because then -- given the way the mutators are implemented -- the //new value of the wrapping digit hasn't been stored.// It seems the only sensible solution is to change the definition of the functors, so that any value will be changed by side-effect {{red{Question 4/11 -- isn't this done and fixed by now??}}}
 
-
+
Timeline is the top level element within the [[Session (Project)|Session]]. It is visible within a [[timeline view in the GUI|GuiTimelineView]] and represents the effective (resulting) arrangement of media objects, to be rendered for output or viewed in a Monitor (viewer window). A timeline is comprised of:
 * a time axis in abolute time ({{red{WIP 1/10}}}: not clear if this is an entity or just a conceptual definition) 
 * a list of [[global Pipes|GlobalPipe]] representing the possible outputs (master busses)
@@ -9050,6 +9069,15 @@ Actually, Timeline is both an interface and acts as façade. Its an interface, b
 Besides building on the asset management, implementing Timeline (and Sequence) as StructAsset yields another benefit: ~StructAssets can be retrieved by query, allowing to specify more details of the configuration immediately on creation. //But on the short term, this approach causes problems:// there is no real inference engine integrated into Lumiera yet (as of 2/2010 the plan is to get an early alpha working end to end first). For now we're bound to use the {{{fake-configrules}}} and to rely on a hard wired simulation of the intended behaviour of a real query resolution. Just some special magic queries will work for now, but that's enough to get ahead.
 
+
+
As [[detailed here|GuiTimelineSlave]], there are various good reasons to provide several UI views onto the same timeline. Yet taking an architectural viewpoint, we prefer representing such a slave display attachment as first-class citizen, right within the session model. Some further problems remain to be settled
+* the primary Timeline entity need to be aware of the clone's presence, since any effects of a Builder run must be propagated through the latter
+* we need a way to duplicate the diff feed, so the UI element representing the clone gets notified appropriately
+* and even when following this design approach with a //materialised duplication,// we still need some awareness and collaboration among the UI elements involved
+
+This topic is {{red{postponed as of 10/2018}}} &rarr; [[#1083|http://issues.lumiera.org/ticket/1083]]
+
+
//guide and control the concrete display properties of the various sub components (tracks, clips) comprising a timeline display.//
 
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm
index 0f2363833..99905bb49 100644
--- a/wiki/thinkPad.ichthyo.mm
+++ b/wiki/thinkPad.ichthyo.mm
@@ -16463,7 +16463,7 @@
 
 
 
-
+
 
 
 
@@ -16534,6 +16534,14 @@
 
 
 
+
+
+
+
+
+
+
+
 
 
 
@@ -17759,7 +17767,8 @@
 
 
 
-
+
+
 
 
 
@@ -17771,12 +17780,29 @@
 
 
 
-
-
+
+
+
+
 
 
 
 
+
+
+  
+    
+  
+  
+    

+ ...für die dritte Lösung, die Repräsentation bereits in der Session +

+ + +
+ + +
@@ -27590,7 +27616,7 @@ - + From 82321a75946b564f65e4334000d550a6c875d4a7 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 11 Oct 2018 17:21:47 +0200 Subject: [PATCH 141/178] Timeline: draft solution how to delegate to the actual TimelineWidget 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 --- src/gui/interact/interaction-director.cpp | 28 ++++-- src/gui/interact/interaction-director.hpp | 8 +- src/gui/model/w-link.hpp | 8 +- src/gui/timeline/timeline-gui.cpp | 87 ++++++++++++++++++ src/gui/timeline/timeline-gui.hpp | 99 +++++++++++++++++++++ src/lib/opaque-holder.hpp | 7 +- wiki/thinkPad.ichthyo.mm | 102 ++++++++++++++-------- 7 files changed, 287 insertions(+), 52 deletions(-) create mode 100644 src/gui/timeline/timeline-gui.cpp create mode 100644 src/gui/timeline/timeline-gui.hpp diff --git a/src/gui/interact/interaction-director.cpp b/src/gui/interact/interaction-director.cpp index bf7cdd92a..fbe5ee31b 100644 --- a/src/gui/interact/interaction-director.cpp +++ b/src/gui/interact/interaction-director.cpp @@ -41,7 +41,8 @@ #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/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 @@ -61,6 +62,7 @@ //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 std::make_unique; @@ -136,18 +138,18 @@ namespace interact { { // »Selector« : require object-like sub scope return spec.data.isNested(); }) - .matchElement ([&](GenNode const& spec, PTimelineCtrl const& elm) -> bool + .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) -> PTimelineCtrl + .constructFrom ([&](GenNode const& spec) -> TimelineGui { // »Constructor« : what to do when the diff mentions a new entity return injectTimeline (spec); }) - .buildChildMutator ([&](PTimelineCtrl& targetTimeline, GenNode::ID const& subID, TreeMutator::Handle buff) -> bool + .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 + targetTimeline.buildMutator (buff); // - delegate to child(Timeline) to build nested TreeMutator return true; })) .mutateAttrib(ATTR_fork, [&](TreeMutator::Handle buff) @@ -307,11 +309,23 @@ namespace interact { } - /** @internal allocate a new TimelineWidget and attach it as child */ - InteractionDirector::PTimelineCtrl + /** @internal allocate a new TimelineWidget and attach it as child. + * @todo is it really necessary to make such strong assumptions + * regarding the format of the diff spec provided for + * "element construction"? + */ + TimelineGui InteractionDirector::injectTimeline (GenNode const& spec) { unimplemented ("allocate a TimelineWidget in some TimelinePanel and attach it's controller as child entity"); + + REQUIRE (spec.data.isNested()); + REQUIRE (spec.data.get().hasAttribute(string{ATTR_fork})); + REQUIRE (TYPE_Fork == spec.data.get().get(string{ATTR_fork}).data.recordType()); + + TimelineGui anchorProxy{spec.idi, spec.data.get().get(string{ATTR_fork}).idi}; + anchorProxy.buildTimelineWidget (this->uiBus_); ///////////////////////////////////////////TODO really create it right here? + return anchorProxy; } diff --git a/src/gui/interact/interaction-director.hpp b/src/gui/interact/interaction-director.hpp index 6a82f43ed..676bd05ff 100644 --- a/src/gui/interact/interaction-director.hpp +++ b/src/gui/interact/interaction-director.hpp @@ -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 @@ -92,6 +93,8 @@ namespace interact { class FocusTracker; class ViewLocator; + using timeline::TimelineGui; + /** @@ -114,8 +117,7 @@ namespace interact { unique_ptr tracker_; // == Model globals == - using PTimelineCtrl = unique_ptr; - using Timelines = std::vector; + using Timelines = std::vector; using Assets = unique_ptr; using State = unique_ptr; @@ -152,7 +154,7 @@ namespace interact { private: workspace::WorkspaceWindow& getWorkspaceWindow(); - PTimelineCtrl injectTimeline (GenNode const&); + TimelineGui injectTimeline (GenNode const&); }; diff --git a/src/gui/model/w-link.hpp b/src/gui/model/w-link.hpp index 923f0657f..b1807fda5 100644 --- a/src/gui/model/w-link.hpp +++ b/src/gui/model/w-link.hpp @@ -78,14 +78,15 @@ namespace model { * 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 WLink { - static_assert (std::is_base_of() - ,"target type required to be sigc::trackable"); TAR* widget_; @@ -208,6 +209,9 @@ namespace model { TAR* attachTo (TAR& target) { + static_assert (std::is_base_of() + ,"target type required to be sigc::trackable"); + try { target.add_destroy_notify_callback (&widget_ ,[](void* p) diff --git a/src/gui/timeline/timeline-gui.cpp b/src/gui/timeline/timeline-gui.cpp new file mode 100644 index 000000000..aabe1ae04 --- /dev/null +++ b/src/gui/timeline/timeline-gui.cpp @@ -0,0 +1,87 @@ +/* + TimelineGui - top-level anchor point and placeholder for "the timeline" + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + 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" + + +using lib::diff::TreeMutator; +using std::make_unique; + + +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} + { } + + + /** + * actually build a TimelineWidget to enact the role + * represented by this smart-handle + */ + std::unique_ptr + TimelineGui::buildTimelineWidget (BusTerm& nexus) + { + auto widget = std::make_unique (timelineID_, rootTrackID_, nexus); + this->connect (*widget); + return widget; + } + + + /** + * 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) + UNIMPLEMENTED ("actually delegate diff application to the target"); + } + + +}}// namespace gui::timeline diff --git a/src/gui/timeline/timeline-gui.hpp b/src/gui/timeline/timeline-gui.hpp new file mode 100644 index 000000000..fe03c30ea --- /dev/null +++ b/src/gui/timeline/timeline-gui.hpp @@ -0,0 +1,99 @@ +/* + TIMELINE-GUI.hpp - top-level anchor point and placeholder for "the timeline" + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + 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 + + +namespace gui { +namespace timeline { + + 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 + , public lib::diff::DiffMutable + { + using ID = BusTerm::ID; + + ID timelineID_; + ID rootTrackID_; + + public: + TimelineGui (ID identity, ID trackID); + + // standard copy operations aceptable + + + /** actually build a TimelineWidget to enact the role + * represented by this smart-handle */ + std::unique_ptr 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*/ diff --git a/src/lib/opaque-holder.hpp b/src/lib/opaque-holder.hpp index e6badc185..4718cf38d 100644 --- a/src/lib/opaque-holder.hpp +++ b/src/lib/opaque-holder.hpp @@ -704,9 +704,10 @@ namespace lib { * handle to allow for safe _»remote implantation«_ * of an unknown subclass into a given opaque InPlaceBuffer, * without having to disclose the concrete buffer type or size. - * @remarks this is especially geared towards use in APIs, allowing - * a not yet known implementation to implant an agent or collaboration - * partner into the likewise undisclosed innards of the exposed service. + * @remarks this copyable value object is especially geared towards use + * as handle in APIs, allowing a not yet known implementation to implant + * an agent or collaboration partner into the likewise undisclosed innards + * of the service exposed. * @warning the type BA must expose a virtual dtor, since the targeted * InPlaceBuffer has to take ownership of the implanted object. */ diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 99905bb49..d1b27e54d 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -16463,7 +16463,7 @@ - + @@ -16499,48 +16499,58 @@ - + + - - - - - - - - - -

- how can the InteractionDirector manage a timeline -

-

-  while the timeline widget physically resides within the panel? -

- - -
- - - - - -

- Can we exploit a simliar structure as we did for the error log? -

- - -
- -
- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -16549,7 +16559,7 @@ - + @@ -16661,7 +16671,7 @@ - + @@ -20072,6 +20082,10 @@ + + + + @@ -27616,7 +27630,7 @@ - + @@ -35062,6 +35076,10 @@ + + + + @@ -38276,6 +38294,16 @@ + + + + + + + + + + From fb93e349da4ae5a65a0a486af44e5c7a1a37b810 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 11 Oct 2018 23:56:33 +0200 Subject: [PATCH 142/178] TreeMutator: conjure up a black hole mutator ...which is a somewhat involved version of /dev/null --- .../diff/tree-mutator-gen-node-binding.hpp | 2 +- src/lib/diff/tree-mutator-noop-binding.hpp | 102 ++++++++++ src/lib/diff/tree-mutator.hpp | 6 +- tests/15library.tests | 5 + .../library/diff/diff-ignore-changes-test.cpp | 187 ++++++++++++++++++ wiki/thinkPad.ichthyo.mm | 60 ++++++ 6 files changed, 360 insertions(+), 2 deletions(-) create mode 100644 src/lib/diff/tree-mutator-noop-binding.hpp create mode 100644 tests/library/diff/diff-ignore-changes-test.cpp diff --git a/src/lib/diff/tree-mutator-gen-node-binding.hpp b/src/lib/diff/tree-mutator-gen-node-binding.hpp index 914c23aed..95f063edf 100644 --- a/src/lib/diff/tree-mutator-gen-node-binding.hpp +++ b/src/lib/diff/tree-mutator-gen-node-binding.hpp @@ -23,7 +23,7 @@ /** @file tree-mutator-gen-node-binding.hpp ** Special binding implementation for TreeMutator, allowing to map - ** tree diff operations onto an »External Tree Description«. Such is is a + ** tree diff operations onto an »External Tree Description«. This term denotes a ** DOM like representation of tree like structures, comprised of GenNode elements. ** TreeMutator is a customisable intermediary, which enables otherwise opaque ** implementation data structures to receive and respond to generic structural diff --git a/src/lib/diff/tree-mutator-noop-binding.hpp b/src/lib/diff/tree-mutator-noop-binding.hpp new file mode 100644 index 000000000..a07c26fa3 --- /dev/null +++ b/src/lib/diff/tree-mutator-noop-binding.hpp @@ -0,0 +1,102 @@ +/* + TREE-MUTATOR-NOOP-BINDING.hpp - diff::TreeMutator implementation building block + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + 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 tree-mutator-noop-binding.hpp + ** Special binding implementation for TreeMutator, allowing to accept + ** and ignore any tree diff without tangible effect. TreeMutator is a + ** customisable intermediary, which enables otherwise opaque implementation + ** data structures to receive and respond to generic structural change messages + ** ("tree diff"). + ** + ** Each concrete TreeMutator instance will be configured differently, and this + ** adaptation is done by combining various building blocks. This header defines + ** a special `/dev/null` building block, which behaves as if successfully consuming + ** the given diff without actually doing anything. Obviously, such a "black hole layer" + ** need to be below any other diff binding, and may be used to absorb any diff verbs + ** not matched and consumed by a more specific binding. Contrast this to the TreeMutator + ** default implementation, which likewise absorbs diff verbs, but in a way to trigger + ** a lumiera::error::LUMIERA_ERROR_DIFF_CONFLICT. + ** + ** @note the header tree-mutator-attribute-binding.hpp was split off for sake of readability + ** and is included automatically from bottom of tree-mutator.hpp + ** + ** @see tree-mutator-test.cpp + ** @see TreeMutator::build() + ** + */ + + +#ifndef LIB_DIFF_TREE_MUTATOR_NOOP_BINDING_H +#define LIB_DIFF_TREE_MUTATOR_NOOP_BINDING_H + + +#include "lib/error.hpp" +#include "lib/symbol.hpp" +#include "lib/diff/gen-node.hpp" +#include "lib/diff/tree-mutator.hpp" +#include "lib/format-string.hpp" +#include "lib/idi/entry-id.hpp" + +#include + + +namespace lib { +namespace diff{ + + namespace { // Mutator-Builder decorator components... + + + /** + * Diff binding for black holes, happily accepting anything. + * @note absorbs and silently ignores any diff verb. + */ + template + class BlackHoleMutation + : public PAR + { + + + public: + BlackHoleMutation (PAR&& chain) + : PAR{std::forward(chain)} + { } + + + }; + + + + /** Entry point for DSL builder */ + template + inline auto + Builder::ignoreAllChanges() + { + return chainedBuilder>(); + } + + + + }//(END)Mutator-Builder decorator components... + +}} // namespace lib::diff +#endif /*LIB_DIFF_TREE_MUTATOR_NOOP_BINDING_H*/ diff --git a/src/lib/diff/tree-mutator.hpp b/src/lib/diff/tree-mutator.hpp index 4a5c0df61..4e11d98c2 100644 --- a/src/lib/diff/tree-mutator.hpp +++ b/src/lib/diff/tree-mutator.hpp @@ -156,7 +156,7 @@ namespace diff{ /** initialisation immediately before start of diff application * @remark allows for setup of state which is dependent on memory location, - * like e.g. iterators. Due to the invokation via Builder DSL, the + * like e.g. iterators. Due to the invocation via Builder DSL, the * implementation object may be moved after construction, but prior * to invoking this hook */ @@ -439,6 +439,9 @@ namespace diff{ */ auto attachDummy (TestMutationTarget& dummy); + + /** set up a catch-all and ignore-everything layer */ + auto ignoreAllChanges(); }; }//(END) Mutator-Builder... @@ -460,4 +463,5 @@ namespace diff{ #include "lib/diff/tree-mutator-gen-node-binding.hpp" #include "lib/diff/tree-mutator-attribute-binding.hpp" #include "lib/diff/tree-mutator-collection-binding.hpp" +#include "lib/diff/tree-mutator-noop-binding.hpp" diff --git a/tests/15library.tests b/tests/15library.tests index c2a94f0cb..03944a017 100644 --- a/tests/15library.tests +++ b/tests/15library.tests @@ -122,6 +122,11 @@ return: 0 END +TEST "Diff: accept and ignore arbitrary diff" DiffIgnoreChanges_test < + + 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 diff-ignore-changes-test.cpp + ** unit test \ref DiffIgnoreChanges_test. + ** Covers the special case of a TreeMutator configured to accept + ** any diff without actually doing anything. + */ + + +#include "lib/test/run.hpp" +#include "lib/test/test-helper.hpp" +#include "lib/format-util.hpp" +#include "lib/diff/tree-diff-application.hpp" +#include "lib/diff/test-mutation-target.hpp" +#include "lib/format-string.hpp" +#include "lib/format-cout.hpp" +#include "lib/util.hpp" + +#include + +using lib::iter_stl::snapshot; +using lib::time::Time; +using std::string; + + +namespace lib { +namespace diff{ +namespace test{ + + namespace {//Test fixture.... + + // define some GenNode elements + // to act as templates within the concrete diff + // NOTE: everything in this diff language is by-value + const GenNode ATTRIB1("α", 1), // attribute α = 1 + ATTRIB2("β", int64_t(2)), // attribute α = 2L (int64_t) + ATTRIB3("γ", 3.45), // attribute γ = 3.45 (double) + TYPE_X("type", "ξ"), // a "magic" type attribute "Xi" + TYPE_Z("type", "ζ"), // + CHILD_A("a"), // unnamed string child node + CHILD_B('b'), // unnamed char child node + CHILD_T(Time(12,34,56,78)), // unnamed time value child + SUB_NODE = MakeRec().genNode(), // empty anonymous node used to open a sub scope + ATTRIB_NODE = MakeRec().genNode("δ"), // empty named node to be attached as attribute δ + GAMMA_PI("γ", 3.14159265); // happens to have the same identity (ID) as ATTRIB3 + + }//(End)Test fixture + + + + + + + + /**************************************************************************//** + * @test Special case: build a TreeMutator to accept any change and do nothing. + * + * @note for sake of symmetry, this test uses the same verb sequence used in + * all the other tree diff tests, assuming this sequence covers pretty + * much all features supported by the tree diff language. + * + * @see DiffComplexApplication_test test case which _indeed does a lot..._ + * @see TreeMutator_test base operations of the adapter + * @see diff-tree-application.hpp + * @see tree-diff.hpp + */ + class DiffIgnoreChanges_test + : public Test + , TreeDiffLanguage + { + using DiffSeq = iter_stl::IterSnapshot; + + DiffSeq + populationDiff() + { + return snapshot({ins(ATTRIB1) + , ins(ATTRIB3) + , ins(ATTRIB3) + , ins(CHILD_B) + , ins(CHILD_B) + , ins(CHILD_T) + }); + } // ==> ATTRIB1, ATTRIB3, (ATTRIB3), CHILD_B, CHILD_B, CHILD_T + + DiffSeq + reorderingDiff() + { + return snapshot({after(Ref::ATTRIBS) + , ins(ATTRIB2) + , del(CHILD_B) + , ins(SUB_NODE) + , find(CHILD_T) + , pick(CHILD_B) + , skip(CHILD_T) + }); + } // ==> ATTRIB1, ATTRIB3, (ATTRIB3), ATTRIB2, SUB_NODE, CHILD_T, CHILD_B + + DiffSeq + mutationDiff() + { + return snapshot({after(CHILD_B) + , after(Ref::END) + , set(GAMMA_PI) + , mut(SUB_NODE) + , ins(TYPE_X) + , ins(ATTRIB2) + , ins(CHILD_B) + , ins(CHILD_A) + , emu(SUB_NODE) + , ins(ATTRIB_NODE) + , mut(ATTRIB_NODE) + , ins(TYPE_Z) + , ins(CHILD_A) + , ins(CHILD_A) + , ins(CHILD_A) + , emu(ATTRIB_NODE) + }); + } // ==> ATTRIB1, ATTRIB3 := π, (ATTRIB3), ATTRIB2, + // ATTRIB_NODE{ type ζ, CHILD_A, CHILD_A, CHILD_A } + // SUB_NODE{ type ξ, ATTRIB2, CHILD_B, CHILD_A }, + // CHILD_T, CHILD_B + + + + + virtual void + run (Arg) + { + struct HappyBlackHole + { + bool grumpy = true; + + void + buildMutator (TreeMutator::Handle buff) + { + if (grumpy) + buff.create( + TreeMutator()); + else + buff.create( + TreeMutator::build() + .ignoreAllChanges()); + } + }; + + HappyBlackHole subject; + DiffApplicator application(subject); + + VERIFY_ERROR(DIFF_CONFLICT, application.consume(populationDiff()) ); + VERIFY_ERROR(DIFF_CONFLICT, application.consume(reorderingDiff()) ); + VERIFY_ERROR(DIFF_CONFLICT, application.consume(mutationDiff()) ); + + subject.grumpy = false; + + application.consume(populationDiff()); + application.consume(reorderingDiff()); + application.consume(mutationDiff()); + } + }; + + + /** Register this test class... */ + LAUNCHER (DiffIgnoreChanges_test, "unit common"); + + + +}}} // namespace lib::diff::test diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index d1b27e54d..8873e718f 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -16549,6 +16549,66 @@ + + + + + + + + + + + + + +

+ ...welches in eine Kind-Timeline absteigt, +

+

+ welche gegenwärtig im GUI nicht existiert +

+

+ und daher auf "inaktiv" geschaltet ist. +

+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
From 0d5f29446b52bebeccfa2937b39493d015ff3456 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 12 Oct 2018 01:07:13 +0200 Subject: [PATCH 143/178] TreeMutator: provide no-op implementation how to further your career with eight simple steps --- src/lib/diff/tree-mutator-noop-binding.hpp | 30 +++++++++++++++++----- wiki/thinkPad.ichthyo.mm | 25 ++++++++++++------ 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/src/lib/diff/tree-mutator-noop-binding.hpp b/src/lib/diff/tree-mutator-noop-binding.hpp index a07c26fa3..c8fea45fb 100644 --- a/src/lib/diff/tree-mutator-noop-binding.hpp +++ b/src/lib/diff/tree-mutator-noop-binding.hpp @@ -70,18 +70,34 @@ namespace diff{ * Diff binding for black holes, happily accepting anything. * @note absorbs and silently ignores any diff verb. */ - template class BlackHoleMutation - : public PAR + : public TreeMutator { - public: - BlackHoleMutation (PAR&& chain) - : PAR{std::forward(chain)} - { } + BlackHoleMutation () = default; + /* ==== TreeMutator API ==== */ + + using Elm = GenNode const&; + using Buff = TreeMutator::Handle; + + bool hasSrc() override { return true; } ///< always keen to do yet more + + bool injectNew (Elm) override { return true; } ///< pretend to inject a new element + bool matchSrc (Elm) override { return true; } ///< purport suitable element is waiting + bool acceptSrc (Elm) override { return true; } ///< claim to handle any diff task + bool accept_until (Elm) override { return true; } ///< profess to forward anywhere + bool findSrc (Elm) override { return true; } ///< sham to find anything + bool assignElm (Elm) override { return true; } ///< accept any assignment + + bool + mutateChild (Elm, Buff buff) override ///< bluff to care for children, while just reproducing ourselves + { + buff.create (BlackHoleMutation()); + return true; + } }; @@ -91,7 +107,7 @@ namespace diff{ inline auto Builder::ignoreAllChanges() { - return chainedBuilder>(); + return Builder (BlackHoleMutation{}); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 8873e718f..9ca2edb50 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -16549,8 +16549,8 @@
- - + + @@ -16579,10 +16579,10 @@ - - - - + + + + @@ -16596,8 +16596,8 @@ - - + + @@ -16606,6 +16606,15 @@ + + + + + + + + + From e81b0592d3e97eec96ff72ad26ad53e843594db0 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 12 Oct 2018 02:05:11 +0200 Subject: [PATCH 144/178] TreeMutator: combine no-op layer with selective other diff binding ...and complete unit test coverage. This is complex stuff and we'd better be careful it actually works --- src/lib/diff/tree-mutator-noop-binding.hpp | 22 +++--- .../library/diff/diff-ignore-changes-test.cpp | 72 ++++++++++++++++++- wiki/thinkPad.ichthyo.mm | 44 +++++++++--- 3 files changed, 119 insertions(+), 19 deletions(-) diff --git a/src/lib/diff/tree-mutator-noop-binding.hpp b/src/lib/diff/tree-mutator-noop-binding.hpp index c8fea45fb..51762e2e7 100644 --- a/src/lib/diff/tree-mutator-noop-binding.hpp +++ b/src/lib/diff/tree-mutator-noop-binding.hpp @@ -51,14 +51,10 @@ #include "lib/error.hpp" -#include "lib/symbol.hpp" #include "lib/diff/gen-node.hpp" #include "lib/diff/tree-mutator.hpp" -#include "lib/format-string.hpp" -#include "lib/idi/entry-id.hpp" - -#include +#include namespace lib { namespace diff{ @@ -85,7 +81,7 @@ namespace diff{ bool hasSrc() override { return true; } ///< always keen to do yet more - bool injectNew (Elm) override { return true; } ///< pretend to inject a new element + bool injectNew (Elm) override { return true; } ///< pretend to inject something new bool matchSrc (Elm) override { return true; } ///< purport suitable element is waiting bool acceptSrc (Elm) override { return true; } ///< claim to handle any diff task bool accept_until (Elm) override { return true; } ///< profess to forward anywhere @@ -93,7 +89,7 @@ namespace diff{ bool assignElm (Elm) override { return true; } ///< accept any assignment bool - mutateChild (Elm, Buff buff) override ///< bluff to care for children, while just reproducing ourselves + mutateChild (Elm, Buff buff) override ///< ignore inferiors, yet reproduce yourself { buff.create (BlackHoleMutation()); return true; @@ -102,11 +98,21 @@ namespace diff{ - /** Entry point for DSL builder */ + /** Entry point for DSL builder: create a binding which consumes any diff without effect. + * @warning must be used as bottom most layer in a custom TreeMutator, since it would + * otherwise shadow and disable any binding layer below. + * @note however it is possible to add a (typically selective) binding layer on top; + * any diff verb _not handled_ by such a top layer will fall through and + * be silently ignored. Such a setup can be used to "fish" for some specific + * attributes within a diff stream. + */ template inline auto Builder::ignoreAllChanges() { + static_assert (std::is_same() + ,"ignoreAllChanges() must be used as bottom layer."); + return Builder (BlackHoleMutation{}); } diff --git a/tests/library/diff/diff-ignore-changes-test.cpp b/tests/library/diff/diff-ignore-changes-test.cpp index 6252d715a..95c4f3a16 100644 --- a/tests/library/diff/diff-ignore-changes-test.cpp +++ b/tests/library/diff/diff-ignore-changes-test.cpp @@ -145,15 +145,35 @@ namespace test{ virtual void run (Arg) + { + fail_or_ignore(); + fish_for_content(); + } + + + /** @test fail or ignore, depending on toggle. + * - the TreeMutator default implementation produces a failure, + * when it actually has to handle some diff verb + * - yet a custom TreeMutator can be configured to `ignoreAllChanges()`, + * in which case it will consume any diff without effect. + * This test also demonstrates that the actual TreeMutator is built anew + * for each diff application (TreeMutator is meant to be disposable). + * Thus we may alter the behaviour of the diff binding dynamically. + * @remark the actual use case for this is the TimelineGui, which + * either forwards changes to a TimelineWidget, or silently + * ignores them when the corresponding timeline is not opened. + */ + void + fail_or_ignore() { struct HappyBlackHole { - bool grumpy = true; + bool diligent = true; void buildMutator (TreeMutator::Handle buff) { - if (grumpy) + if (diligent) buff.create( TreeMutator()); else @@ -170,12 +190,58 @@ namespace test{ VERIFY_ERROR(DIFF_CONFLICT, application.consume(reorderingDiff()) ); VERIFY_ERROR(DIFF_CONFLICT, application.consume(mutationDiff()) ); - subject.grumpy = false; + subject.diligent = false; application.consume(populationDiff()); application.consume(reorderingDiff()); application.consume(mutationDiff()); } + + + /** @test fish some content and ignore everything else. + * While the BlackHoleMutation binding generated by `ignoreAllChanges()` + * must be used as bottom layer of a custom TreeMutator, it is possible + * to layer a partial diff binding on top. This test demonstrates this + * with a single attribute binding, which just "fishes" any value + * mentioned in the diff for that specific attribute. Any other + * changes are silently ignored non the less... + */ + void + fish_for_content() + { + struct Scrounger + { + double loot = 0.0; + + void + buildMutator (TreeMutator::Handle buff) + { + buff.create( + TreeMutator::build() + .ignoreAllChanges() + .change("γ", [&](double val) { loot = val; })); + } + }; + + Scrounger subject; + DiffApplicator application(subject); + + const auto NOTHING = 0.0; + const auto VAL_1 = ATTRIB3.data.get(); + const auto VAL_2 = GAMMA_PI.data.get(); + + CHECK (subject.loot == NOTHING); + + application.consume(populationDiff()); + CHECK (subject.loot == VAL_1); + + application.consume(reorderingDiff()); + CHECK (subject.loot == VAL_1); + + application.consume(mutationDiff()); + CHECK (subject.loot == VAL_2); + } + }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 9ca2edb50..72a320157 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -16549,7 +16549,7 @@ - + @@ -16579,12 +16579,14 @@ - - + + - + + + @@ -16600,20 +16602,46 @@ + + + + + + + + + +

+ ...damit es nicht versehentlich über einen anderen Layer gelegt wird, +

+

+ welchen es dann überdecken und außer Gefecht setzen würde. +

+

+ +

+

+ Habe verifiziert, daß diese Assertion tatsächlich greift +

+ + +
+ +
- - + + - - + + From fa6ba76f855fad16ec4e64eb786f3a6f47fe0c5f Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 12 Oct 2018 23:42:56 +0200 Subject: [PATCH 145/178] investigate insidious ill-guided conversion 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 --- research/try.cpp | 46 +++++++++++-------- src/gui/interact/interaction-director.cpp | 4 +- src/gui/timeline/clip-presenter.cpp | 18 ++++---- src/gui/timeline/timeline-controller.cpp | 4 +- src/gui/timeline/timeline-gui.cpp | 2 + src/gui/timeline/timeline-gui.hpp | 6 ++- src/gui/timeline/track-presenter.cpp | 18 ++++---- src/lib/diff/gen-node.hpp | 1 + src/lib/test/test-helper.hpp | 7 +++ src/proc/asset.hpp | 2 +- src/proc/streamtype.hpp | 2 +- tests/core/proc/asset/basicpipetest.cpp | 33 ++++++------- .../mobject/session/defs-manager-test.cpp | 16 +++---- .../util-sanitised-identifier-test.cpp | 2 +- 14 files changed, 92 insertions(+), 69 deletions(-) diff --git a/research/try.cpp b/research/try.cpp index 9d2805acf..8f8108be1 100644 --- a/research/try.cpp +++ b/research/try.cpp @@ -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 #include -#include using std::string; -using VecS = std::vector; +using util::isSameObject; @@ -69,25 +70,32 @@ using VecS = std::vector; +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"; diff --git a/src/gui/interact/interaction-director.cpp b/src/gui/interact/interaction-director.cpp index fbe5ee31b..8b7470c2e 100644 --- a/src/gui/interact/interaction-director.cpp +++ b/src/gui/interact/interaction-director.cpp @@ -140,7 +140,7 @@ namespace interact { }) .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); + return spec.idi == ID{elm}; }) .constructFrom ([&](GenNode const& spec) -> TimelineGui { // »Constructor« : what to do when the diff mentions a new entity @@ -148,7 +148,7 @@ namespace interact { }) .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; + if (ID{targetTimeline} != subID) return false; targetTimeline.buildMutator (buff); // - delegate to child(Timeline) to build nested TreeMutator return true; })) diff --git a/src/gui/timeline/clip-presenter.cpp b/src/gui/timeline/clip-presenter.cpp index 411e10c5d..19e30f7c7 100644 --- a/src/gui/timeline/clip-presenter.cpp +++ b/src/gui/timeline/clip-presenter.cpp @@ -99,15 +99,15 @@ namespace timeline { }) .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(spec.idi, this->uiBus_); + return make_unique (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; })) @@ -118,15 +118,15 @@ namespace timeline { }) .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(spec.idi, this->uiBus_); + return make_unique (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; })) @@ -137,15 +137,15 @@ namespace timeline { }) .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(spec.idi, this->uiBus_); + return make_unique (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; }))); diff --git a/src/gui/timeline/timeline-controller.cpp b/src/gui/timeline/timeline-controller.cpp index fa85ba73f..ede6fef58 100644 --- a/src/gui/timeline/timeline-controller.cpp +++ b/src/gui/timeline/timeline-controller.cpp @@ -144,7 +144,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 @@ -152,7 +152,7 @@ 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; })) diff --git a/src/gui/timeline/timeline-gui.cpp b/src/gui/timeline/timeline-gui.cpp index aabe1ae04..8190b4d70 100644 --- a/src/gui/timeline/timeline-gui.cpp +++ b/src/gui/timeline/timeline-gui.cpp @@ -56,6 +56,8 @@ namespace timeline { , rootTrackID_{trackID} { } + TimelineGui::~TimelineGui() { } + /** * actually build a TimelineWidget to enact the role diff --git a/src/gui/timeline/timeline-gui.hpp b/src/gui/timeline/timeline-gui.hpp index fe03c30ea..59476467b 100644 --- a/src/gui/timeline/timeline-gui.hpp +++ b/src/gui/timeline/timeline-gui.hpp @@ -81,8 +81,12 @@ namespace timeline { public: TimelineGui (ID identity, ID trackID); + virtual ~TimelineGui(); - // standard copy operations aceptable + // standard copy operations acceptable + + + operator ID() const { return timelineID_; } /** actually build a TimelineWidget to enact the role diff --git a/src/gui/timeline/track-presenter.cpp b/src/gui/timeline/track-presenter.cpp index ea541c650..962e03a35 100644 --- a/src/gui/timeline/track-presenter.cpp +++ b/src/gui/timeline/track-presenter.cpp @@ -109,15 +109,15 @@ namespace timeline { }) .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(spec.idi, this->uiBus_); + return make_unique (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; })) @@ -128,15 +128,15 @@ namespace timeline { }) .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(spec.idi, this->uiBus_); + return make_unique (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; })) @@ -147,15 +147,15 @@ namespace timeline { }) .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(spec.idi, this->uiBus_); + return make_unique (spec.idi, this->uiBus_); }) .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; }))); diff --git a/src/lib/diff/gen-node.hpp b/src/lib/diff/gen-node.hpp index 6b77c0f3f..41606287f 100644 --- a/src/lib/diff/gen-node.hpp +++ b/src/lib/diff/gen-node.hpp @@ -224,6 +224,7 @@ namespace diff{ { } public: + explicit ID (GenNode const& node) : ID(node.idi) { } diff --git a/src/lib/test/test-helper.hpp b/src/lib/test/test-helper.hpp index bb4141efc..dc5325dfe 100644 --- a/src/lib/test/test-helper.hpp +++ b/src/lib/test/test-helper.hpp @@ -147,6 +147,13 @@ namespace test{ static_assert (not sizeof(X), "### Type Debugging ###"); }; + template + void + typeDebugger(X&& x) + { + static_assert (not sizeof(X), "### Type Debugging ###"); + } + namespace { // helper for printing type diagnostics diff --git a/src/proc/asset.hpp b/src/proc/asset.hpp index 1a0905176..58b456a30 100644 --- a/src/proc/asset.hpp +++ b/src/proc/asset.hpp @@ -347,7 +347,7 @@ namespace asset { /** marker constant denoting a NIL asset */ template - ID ID::INVALID = ID(0); + ID ID::INVALID = ID{0}; }} // namespace proc::asset diff --git a/src/proc/streamtype.hpp b/src/proc/streamtype.hpp index a6475cc60..7522dc1e6 100644 --- a/src/proc/streamtype.hpp +++ b/src/proc/streamtype.hpp @@ -75,7 +75,7 @@ namespace proc { class ImplFacade; class ImplConstraint; - typedef lib::idi::EntryID ID; + using ID = lib::idi::EntryID; Prototype const& prototype; diff --git a/tests/core/proc/asset/basicpipetest.cpp b/tests/core/proc/asset/basicpipetest.cpp index 476619c66..0947d2f6e 100644 --- a/tests/core/proc/asset/basicpipetest.cpp +++ b/tests/core/proc/asset/basicpipetest.cpp @@ -64,9 +64,10 @@ namespace test { */ class BasicPipe_test : public Test { - virtual void run(Arg arg) + virtual void + run (Arg arg) { - string pipeID = isnil(arg)? "Black Hole" : arg[1]; + string pipeID = isnil (arg)? "Black Hole" : arg[1]; string streamID = 2>arg.size()? "teststream" : arg[2] ; createExplicit (pipeID,streamID); @@ -80,7 +81,7 @@ namespace test { void createExplicit (string pID, string sID) { - string pID_sane (pID); + string pID_sane{pID}; normaliseID (pID_sane); CHECK (pID_sane != pID); @@ -89,7 +90,7 @@ namespace test { CHECK (thePipe); CHECK (thePipe->getProcPatt()); CHECK (thePipe->getPipeID() == pID_sane); - CHECK (thePipe->getStreamID() == StreamType::ID(sID)); + CHECK (thePipe->getStreamID() == StreamType::ID{sID}); CHECK (thePipe->shortDesc == pID_sane); Asset::Ident idi = thePipe->ident; @@ -97,9 +98,9 @@ namespace test { CHECK (contains (idi.name, thePipe->getPipeID())); CHECK (contains (idi.name, thePipe->getStreamID())); - Category cat (idi.category); - Category refcat (STRUCT,"pipes"); - CHECK ( cat.hasKind(STRUCT) ); + Category cat{idi.category}; + Category refcat{STRUCT,"pipes"}; + CHECK ( cat.hasKind (STRUCT) ); CHECK ( cat.isWithin(refcat) ); } @@ -131,25 +132,25 @@ namespace test { PPipe pipe1 = Pipe::query (""); // "the default pipe" PPipe pipe2; CHECK (pipe1); - CHECK (pipe1 == Session::current->defaults (Query())); + CHECK (pipe1 == Session::current->defaults (Query{})); CHECK (pipe1->ident.category.hasKind(VIDEO)); CHECK (pipe1->getProcPatt()); - PProcPatt propa = Session::current->defaults (Query("pipe(default)")); + PProcPatt propa = Session::current->defaults (Query{"pipe(default)"}); CHECK (propa == pipe1->getProcPatt()); // several variants to query for "the default pipe" - pipe2 = Session::current->defaults(Query ()); + pipe2 = Session::current->defaults(Query{}); CHECK (pipe2 == pipe1); - pipe2 = asset::Struct::retrieve (Query ()); + pipe2 = asset::Struct::retrieve (Query{}); CHECK (pipe2 == pipe1); - pipe2 = asset::Struct::retrieve (Query ("pipe(default)")); + pipe2 = asset::Struct::retrieve (Query{"pipe(default)"}); CHECK (pipe2 == pipe1); string sID = pipe1->getStreamID(); // sort of a "default stream type" PPipe pipe3 = Pipe::query ("stream("+sID+")"); CHECK (pipe3); - CHECK (pipe3->getStreamID() == StreamType::ID(sID)); - CHECK (pipe3->getProcPatt() == Session::current->defaults (Query("stream("+sID+")"))); + CHECK (pipe3->getStreamID() == StreamType::ID{sID}); + CHECK (pipe3->getProcPatt() == Session::current->defaults (Query{"stream("+sID+")"})); } @@ -174,7 +175,7 @@ namespace test { // default pipe for this pattern automatically PPipe pipe2x = Pipe::query ("pattern(another)"); CHECK (pattern2 == pipe2x->getProcPatt()); - CHECK (pipe2x == Session::current->defaults (Query("pattern(another)"))); + CHECK (pipe2x == Session::current->defaults (Query{"pattern(another)"})); thePipe->switchProcPatt(pattern2); CHECK ( dependencyCheck (thePipe, pattern2)); @@ -197,7 +198,7 @@ namespace test { PPipe pipe3x = Pipe::query ("pattern(another)"); pattern3 = pipe3x->getProcPatt(); /////TODO: transition to P<> CHECK (pattern3 != pattern2); // because pattern2 is already unlinked... - CHECK (pipe3x == Session::current->defaults (Query("pattern(another)"))); + CHECK (pipe3x == Session::current->defaults (Query{"pattern(another)"})); CHECK (pipe3x != pipe2x); // ..we got a new default pipe for "pattern(another)" too! diff --git a/tests/core/proc/mobject/session/defs-manager-test.cpp b/tests/core/proc/mobject/session/defs-manager-test.cpp index a7db7369e..8225dcae8 100644 --- a/tests/core/proc/mobject/session/defs-manager-test.cpp +++ b/tests/core/proc/mobject/session/defs-manager-test.cpp @@ -128,16 +128,16 @@ namespace test { retrieveConstrainedDefault (string pID, string sID) { PPipe pipe1 = Pipe::query (""); // "the default pipe" - CHECK ( pipe1->getStreamID() != StreamType::ID(sID), + CHECK ( pipe1->getStreamID() != StreamType::ID{sID}, "stream-ID \"%s\" not suitable for test, because " "the default-pipe \"%s\" happens to have the same " "stream-ID. We need it to be different", sID.c_str(), pID.c_str() ); - string query_for_sID ("stream("+sID+")"); + string query_for_sID{"stream("+sID+")"}; PPipe pipe2 = Pipe::query (query_for_sID); - CHECK (pipe2->getStreamID() == StreamType::ID(sID)); + CHECK (pipe2->getStreamID() == StreamType::ID{sID}); CHECK (pipe2 != pipe1); CHECK (pipe2 == Pipe::query (query_for_sID)); // reproducible } @@ -146,13 +146,13 @@ namespace test { void failureCreatesNewDefault() { - PPipe pipe1 = Session::current->defaults(Query ("")); // "the default pipe" + PPipe pipe1 = Session::current->defaults(Query{""}); // "the default pipe" string new_pID = _Fmt{"dummy_%s_%i"} % pipe1->getPipeID() % std::rand() ; // make random new pipeID - Query query_for_new ("pipe("+new_pID+")"); + Query query_for_new{"pipe("+new_pID+")"}; CHECK (!find (query_for_new)); // check it doesn't exist PPipe pipe2 = Session::current->defaults (query_for_new); // triggers creation @@ -170,13 +170,13 @@ namespace test { void verifyRemoval() { - Symbol pID ("some_pipe"); - Query query_for_pID ("pipe("+pID+")"); + Symbol pID{"some_pipe"}; + Query query_for_pID{"pipe("+pID+")"}; size_t hash; { // create new pipe and declare it to be a default PPipe pipe1 = Struct::retrieve.newInstance (pID); - Session::current->defaults.define(pipe1); + Session::current->defaults.define (pipe1); CHECK (2 == pipe1.use_count()); // the pipe1 smart-ptr and the AssetManager hash = pipe1->getID(); diff --git a/tests/library/util-sanitised-identifier-test.cpp b/tests/library/util-sanitised-identifier-test.cpp index cdfd9f05b..bf8288715 100644 --- a/tests/library/util-sanitised-identifier-test.cpp +++ b/tests/library/util-sanitised-identifier-test.cpp @@ -46,7 +46,7 @@ namespace test { print_clean ("a Sentence"); print_clean ("trailing Withespace\n \t"); print_clean ("with a \t lot\n of Whitespace"); - print_clean ("with\"much (punctuation)[]!"); + print_clean ("@with\".\'much ($punctuation)[]!"); print_clean ("§&Ω%€ leading garbage"); print_clean ("mixed Ω garbage"); print_clean ("Bääääh!!"); From d2309f003d91e8157ca1d36b3604526772abb5fa Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 12 Oct 2018 23:43:36 +0200 Subject: [PATCH 146/178] fix an off-by one, spotted by chance --- src/lib/symbol-impl.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/symbol-impl.cpp b/src/lib/symbol-impl.cpp index a8c7676b9..066d16246 100644 --- a/src/lib/symbol-impl.cpp +++ b/src/lib/symbol-impl.cpp @@ -112,9 +112,9 @@ namespace lib { size_t hash=0; if (literal) { + size_t cnt = 1; const char *pos = literal; - size_t maxpos = STRING_MAX_RELEVANT; - for ( ; *pos && --maxpos; ++pos) + for ( ; cnt <= STRING_MAX_RELEVANT and *pos ; ++cnt, ++pos ) hash_combine(hash, *pos); } From a77ecb6d5d516b2ecc35b359f3a2be8696be4204 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 12 Oct 2018 23:45:49 +0200 Subject: [PATCH 147/178] change util::sanitise to filter out ' Only reatain chars, numbers and -_.+$()@ Allowing the appostroph seems entirely random and unjustified here --- src/lib/util.cpp | 2 +- src/lib/util.hpp | 20 ++++++++++---------- tests/15library.tests | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/lib/util.cpp b/src/lib/util.cpp index 93920c1a1..bf192f190 100644 --- a/src/lib/util.cpp +++ b/src/lib/util.cpp @@ -57,7 +57,7 @@ namespace util { ChPredicate operator! (ChPredicate p) { return not bind(p,_1); } // character classes used for sanitising a string - ChPredicate isValid (is_alnum() or is_any_of("-_.+$'()@")); ///< characters to be retained + ChPredicate isValid (is_alnum() or is_any_of("-_.+$()@")); ///< characters to be retained ChPredicate isPunct (is_space() or is_any_of(",;:#*~´`?\\=/&%![]{}<>")); ///< punctuation to be replaced by '_' diff --git a/src/lib/util.hpp b/src/lib/util.hpp index 8e15d0872..493c25caa 100644 --- a/src/lib/util.hpp +++ b/src/lib/util.hpp @@ -336,8 +336,8 @@ namespace util { inline bool isSameObject (A const& a, B const& b) { - return static_cast (&a) - == static_cast (&b); + return static_cast (std::addressof(a)) + == static_cast (std::addressof(b)); } @@ -350,14 +350,14 @@ namespace util { * * @par Example Conversions \verbatim - "Word" --> 'Word' - "a Sentence" --> 'a_Sentence' - "trailing Withespace \t \n" --> 'trailing_Withespace' - "with a lot \nof Whitespace" --> 'with_a_lot_of_Whitespace' - "with\"much (punctuation)[]!" --> 'withmuch_(punctuation)' - "§&Ω%€ leading garbage" --> 'leading_garbage' - "mixed Ω garbage" --> 'mixed_garbage' - "Bääääh!!" --> 'Bh' + "Word" --> "Word" + "a Sentence" --> "a_Sentence" + "trailing Withespace \t \n" --> "trailing_Withespace" + "with a lot \nof Whitespace" --> "with_a_lot_of_Whitespace" + "@with\".\'much ($punctuation)[]!" --> "@with.much_($punctuation)" + "§&Ω%€ leading garbage" --> "leading_garbage" + "mixed Ω garbage" --> "mixed_garbage" + "Bääääh!!" --> "Bh" \endverbatim * @see \ref UtilSanitizedIdentifier_test * @see \ref lib::meta::sanitisedSymbol() diff --git a/tests/15library.tests b/tests/15library.tests index 03944a017..88c97555e 100644 --- a/tests/15library.tests +++ b/tests/15library.tests @@ -713,7 +713,7 @@ out-lit: 'trailing Withespace out-lit: ' --> 'trailing_Withespace' out-lit: 'with a lot out-lit: of Whitespace' --> 'with_a_lot_of_Whitespace' -out-lit: 'with"much (punctuation)[]!' --> 'withmuch_(punctuation)' +out-lit: '@with".'much (\$punctuation)[]!' --> '@with.much_(\$punctuation)' out-lit: '§&Ω%€ leading garbage' --> 'leading_garbage' out-lit: 'mixed Ω garbage' --> 'mixed_garbage' out-lit: 'Bääääh!!' --> 'Bh' From edf577187b810534ab266da3d2753b8aed2b7adc Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 13 Oct 2018 00:25:51 +0200 Subject: [PATCH 148/178] Timeline: now able to define delegating diff mutation binding --- src/gui/model/w-link.hpp | 6 +++++- src/gui/timeline/timeline-gui.cpp | 8 ++++++-- src/gui/timeline/timeline-widget.cpp | 8 +++++--- src/gui/timeline/timeline-widget.hpp | 5 ++++- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/gui/model/w-link.hpp b/src/gui/model/w-link.hpp index b1807fda5..a71e9b6f5 100644 --- a/src/gui/model/w-link.hpp +++ b/src/gui/model/w-link.hpp @@ -149,7 +149,7 @@ namespace model { explicit operator bool() const { - return bool(widget_); + return bool{widget_}; } TAR& @@ -167,6 +167,10 @@ namespace model { } + 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) diff --git a/src/gui/timeline/timeline-gui.cpp b/src/gui/timeline/timeline-gui.cpp index 8190b4d70..5ea8576d4 100644 --- a/src/gui/timeline/timeline-gui.cpp +++ b/src/gui/timeline/timeline-gui.cpp @@ -81,8 +81,12 @@ namespace timeline { void TimelineGui::buildMutator (lib::diff::TreeMutator::Handle buffer) { - if (*this) - UNIMPLEMENTED ("actually delegate diff application to the target"); + if (this->isActive()) + operator*().buildMutator (buffer); // delegate to TimelineController + else // else when no widget exists... + buffer.create( + TreeMutator::build() + .ignoreAllChanges()); // ...consume and ignore diff } diff --git a/src/gui/timeline/timeline-widget.cpp b/src/gui/timeline/timeline-widget.cpp index cbb72058c..7fcc6db8b 100644 --- a/src/gui/timeline/timeline-widget.cpp +++ b/src/gui/timeline/timeline-widget.cpp @@ -73,13 +73,15 @@ namespace timeline { UNIMPLEMENTED ("build the timeline UI"); } + TimelineWidget::~TimelineWidget() { } - TimelineWidget::~TimelineWidget() + + void + TimelineWidget::buildMutator (lib::diff::TreeMutator::Handle buff) { + control_->buildMutator (buff); } - - }}// namespace gui::timeline diff --git a/src/gui/timeline/timeline-widget.hpp b/src/gui/timeline/timeline-widget.hpp index f8c7a7181..cac87d043 100644 --- a/src/gui/timeline/timeline-widget.hpp +++ b/src/gui/timeline/timeline-widget.hpp @@ -113,12 +113,15 @@ 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); + public: /* ===== Signals ===== */ private:/* ===== Events ===== */ From bfca473dce814bfcac7382d9468ab96e1d3dfae2 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 13 Oct 2018 02:46:09 +0200 Subject: [PATCH 149/178] Timeline: decide upon the diff format expected for creating a timeline 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 --- src/gui/interact/interaction-director.cpp | 42 +- src/gui/timeline/timeline-controller.cpp | 4 +- src/lib/diff/diff-language.hpp | 3 +- src/lib/diff/diff.cpp | 1 + wiki/thinkPad.ichthyo.mm | 725 ++++++++++++++-------- 5 files changed, 512 insertions(+), 263 deletions(-) diff --git a/src/gui/interact/interaction-director.cpp b/src/gui/interact/interaction-director.cpp index 8b7470c2e..bd2092e5e 100644 --- a/src/gui/interact/interaction-director.cpp +++ b/src/gui/interact/interaction-director.cpp @@ -50,6 +50,7 @@ #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" @@ -58,6 +59,7 @@ //#include //using util::isnil; +using util::_Fmt; //using std::list; //using std::shared_ptr; using lib::idi::EntryID; @@ -65,6 +67,7 @@ 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; @@ -309,6 +312,38 @@ namespace interact { } + 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().hasAttribute(string{ATTR_fork}) + and TYPE_Fork == spec.data.get().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().get(string{ATTR_fork}).idi; + } + } + /** @internal allocate a new TimelineWidget and attach it as child. * @todo is it really necessary to make such strong assumptions * regarding the format of the diff spec provided for @@ -319,11 +354,8 @@ namespace interact { { unimplemented ("allocate a TimelineWidget in some TimelinePanel and attach it's controller as child entity"); - REQUIRE (spec.data.isNested()); - REQUIRE (spec.data.get().hasAttribute(string{ATTR_fork})); - REQUIRE (TYPE_Fork == spec.data.get().get(string{ATTR_fork}).data.recordType()); - - TimelineGui anchorProxy{spec.idi, spec.data.get().get(string{ATTR_fork}).idi}; + ID rootTrack = verifyDiffStructure_and_extract_RootTrack (spec); + TimelineGui anchorProxy{spec.idi, rootTrack}; anchorProxy.buildTimelineWidget (this->uiBus_); ///////////////////////////////////////////TODO really create it right here? return anchorProxy; } diff --git a/src/gui/timeline/timeline-controller.cpp b/src/gui/timeline/timeline-controller.cpp index ede6fef58..f0b359c9e 100644 --- a/src/gui/timeline/timeline-controller.cpp +++ b/src/gui/timeline/timeline-controller.cpp @@ -157,12 +157,12 @@ namespace timeline { return true; })) .mutateAttrib(ATTR_fork, [&](TreeMutator::Handle buff) - { // »Attribute Mutator« : how enter an object field as nested scope + { // »Attribute Mutator« : how to enter an object field as nested scope REQUIRE (fork_); fork_->buildMutator(buff); }) .change(ATTR_name, [&](string val) - { // »Attribute Setter« : how assign a new value to some object field + { // »Attribute Setter« : how to assign a new value to some object field name_ = val; })); } diff --git a/src/lib/diff/diff-language.hpp b/src/lib/diff/diff-language.hpp index c7fe57ceb..511108389 100644 --- a/src/lib/diff/diff-language.hpp +++ b/src/lib/diff/diff-language.hpp @@ -100,7 +100,8 @@ namespace diff{ namespace error = lumiera::error; - LUMIERA_ERROR_DECLARE(DIFF_CONFLICT); ///< Collision in diff application: contents of target not as expected. + LUMIERA_ERROR_DECLARE(DIFF_STRUCTURE); ///< Invalid diff structure: implicit rules and assumptions violated. + LUMIERA_ERROR_DECLARE(DIFF_CONFLICT); ///< Collision in diff application: contents of target not as expected. template diff --git a/src/lib/diff/diff.cpp b/src/lib/diff/diff.cpp index 95de683a0..54d5224df 100644 --- a/src/lib/diff/diff.cpp +++ b/src/lib/diff/diff.cpp @@ -39,6 +39,7 @@ namespace lib { namespace diff{ + LUMIERA_ERROR_DEFINE(DIFF_STRUCTURE, "Invalid diff structure: implicit rules and assumptions violated."); LUMIERA_ERROR_DEFINE(DIFF_CONFLICT, "Collision in diff application: contents of target not as expected."); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 72a320157..facc262a9 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -5333,7 +5333,7 @@ - + @@ -5363,13 +5363,14 @@ - + + - + @@ -5396,7 +5397,8 @@ - + + @@ -5411,7 +5413,7 @@ - + @@ -5421,7 +5423,8 @@ vorbereitete Grundstrukturen für immer wiederkehrende Setups

-
+ + @@ -5616,7 +5619,7 @@ - + @@ -5629,7 +5632,8 @@ Im Zweifelsfall den GTK+ Inspector verwenden!

-
+ +
@@ -5648,7 +5652,7 @@
- + @@ -5658,7 +5662,8 @@ CSS genügt

-
+ +
@@ -5671,7 +5676,7 @@ - + @@ -5706,7 +5711,8 @@ }

-
+ +
@@ -5780,7 +5786,7 @@
- + @@ -5793,10 +5799,11 @@ daß die alte, obsolete Timeline zurückgebaut ist

-
+ +
- + @@ -5812,7 +5819,8 @@ bevor die Notification-Facade geöffnet werden konnte

-
+ +
@@ -5864,7 +5872,7 @@ - + @@ -5895,7 +5903,8 @@ Allerdings habe ich an der Stelle immer noch GTK-Assertions

-
+ +
@@ -5943,7 +5952,7 @@ - + @@ -5971,7 +5980,8 @@ ist, daß Gio::Application sofort auch gleich eine dBus-Verbindung hochfährt.

-
+ +
@@ -16491,6 +16501,20 @@ + + + + + + + + + + + + + + @@ -16525,38 +16549,42 @@ - + - + - + - + + + - - - - + + + + + + - + - + - + - + - + @@ -16576,10 +16604,10 @@ - + - + @@ -16964,7 +16992,7 @@ - + @@ -16974,7 +17002,8 @@ ...denn das ist das vereinfachte Setup für "einfache" Applikationen

-
+ +
@@ -17350,7 +17379,7 @@ - + @@ -17360,9 +17389,10 @@ das Diff wird auf den Platzhalter angewendet

-
+ +
- + @@ -17375,7 +17405,8 @@ dann muß dieses automatisch deregistriert werden

-
+ +
@@ -17620,7 +17651,7 @@ - + @@ -17633,7 +17664,8 @@ und welchen Teil des Verhaltens überlassen wir GTK

-
+ +
@@ -17674,7 +17706,7 @@ - + @@ -17690,7 +17722,8 @@ Details im  TiddlyWiki....

-
+ +
@@ -17743,7 +17776,7 @@ - + @@ -17753,7 +17786,8 @@ braucht feste Speicher-Addresse

-
+ +
@@ -17762,7 +17796,7 @@ - + @@ -17775,7 +17809,8 @@ und sei es auch bloß über ein Interface!

-
+ +
@@ -17790,7 +17825,7 @@ - + @@ -17824,7 +17859,8 @@ - + + @@ -17912,6 +17948,37 @@ + + + + + + +

+ Problem: alle Timelines der Session repräsentieren? +

+ + +
+ + + + + + + + + + + + + + + + + + +
@@ -17964,7 +18031,7 @@ - + @@ -17980,9 +18047,10 @@ Implementiert würde sie vom jeweiligen Widget

-
+ +
- + @@ -18010,9 +18078,10 @@ Der Dekorator würde also auf dem TreeMutator sitzen...

-
+ +
- + @@ -18031,9 +18100,10 @@ Daher gibt es die matchSrc-Operation. Effektiv wird die aber nur bei einem Delete aufgerufen...

-
+ +
- + @@ -18054,7 +18124,8 @@ - + + @@ -18132,7 +18203,7 @@ - + @@ -18142,7 +18213,8 @@ d.h. eine LUID

-
+ +
@@ -18175,7 +18247,7 @@ - + @@ -18188,7 +18260,8 @@ Irgend eine BareEntryID genügt

-
+ +
@@ -18217,7 +18290,7 @@ - + @@ -18227,13 +18300,14 @@ ...abstraktes Interface

-
+ + - + @@ -18249,7 +18323,8 @@ um die Bindung herzustellen

-
+ +
@@ -18257,7 +18332,7 @@ - + @@ -18270,7 +18345,8 @@ oder eine Sequenz ohne root-Fork zulassen

-
+ + @@ -18280,7 +18356,7 @@ - + @@ -18301,7 +18377,8 @@ - + + @@ -18320,7 +18397,7 @@ - + @@ -18348,7 +18425,8 @@ typischerweise wird es das in einem Populationsdiff sofort als Nächstes machen.

-
+ +
@@ -18370,7 +18448,7 @@ - + @@ -18390,7 +18468,8 @@ "was kann denn schon passieren??"

-
+ +
@@ -18408,7 +18487,7 @@
- + @@ -18429,9 +18508,10 @@ - + + - + @@ -18441,7 +18521,8 @@ einen Fall, der praktisch nie auftritt

-
+ +
@@ -18450,7 +18531,7 @@
- + @@ -18468,12 +18549,13 @@ - + + - + @@ -18486,9 +18568,10 @@ und auch über ihre APIs dem Nutzer diese Unterscheidnung mit aufzwingen

-
+ + - + @@ -18513,13 +18596,14 @@ heimlich in den "wird schon nix passieren" Fall zu geraten.

-
+ +
- + @@ -18535,9 +18619,10 @@ Es ist keine weitere Argumentation notwendig.

-
+ +
- + @@ -18556,7 +18641,8 @@ "fokussiere Dich"

-
+ +
@@ -18575,7 +18661,7 @@ - + @@ -18598,12 +18684,13 @@ wenn ich mich überhaupt entscheiden konnte...

-
+ +
- + @@ -18627,7 +18714,8 @@ - + + @@ -18681,7 +18769,7 @@ - + @@ -18691,7 +18779,8 @@ ...sie verwenden dann ein LabelWidget zur Darstellung

-
+ +
@@ -18712,7 +18801,7 @@ - + @@ -18728,7 +18817,8 @@ Verwende das als Leitgedanke, um das Layout zu entwickeln

-
+ + @@ -19321,7 +19411,7 @@ - + @@ -20179,9 +20269,31 @@ - + + + + + + + + + + + + + + + + + + + + + + + @@ -20383,7 +20495,7 @@ - + @@ -20402,7 +20514,8 @@ erfordert bereits Kenntnis der Innereien

-
+ +
@@ -27460,7 +27573,7 @@ - + @@ -27473,11 +27586,12 @@ Implementierung der real-world-Variante fehlt!

-
+ + - + @@ -27493,7 +27607,8 @@ wie Session- und State-Managment, Commands etc.

-
+ +
@@ -32779,7 +32894,7 @@
- + @@ -32800,7 +32915,8 @@ - + + @@ -35173,7 +35289,7 @@ - + @@ -38173,7 +38289,7 @@ - + @@ -38189,14 +38305,15 @@ Ganz prominent fehlt hier also z.B: MIDI

-
+ +
- + @@ -38209,7 +38326,8 @@ die Aufgrund von Klassifikationen automatisch bereits existieren

-
+ +
@@ -38391,7 +38509,7 @@
- + @@ -38399,7 +38517,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -38410,7 +38555,7 @@ - + @@ -38904,7 +39049,7 @@ - + @@ -39038,7 +39183,7 @@ - + @@ -39051,7 +39196,8 @@ sofern das Widget mit entsprechendem Modus eingefügt wurde

-
+ +
@@ -39084,7 +39230,7 @@ - + @@ -39097,7 +39243,8 @@ context->add_class("ohMy");

-
+ + @@ -39176,7 +39323,7 @@ - + @@ -39192,7 +39339,8 @@ oder ist es eine Vererbungs-Hierarchie, wie sie für das CSS-Styling benötigt wird?

-
+ +
@@ -39213,7 +39361,7 @@ - + @@ -39237,7 +39385,8 @@ - + + @@ -39288,7 +39437,7 @@ - + @@ -39307,7 +39456,8 @@ Alles in ein Framework zwingen. Alternativlos, capisce?

-
+ +
@@ -39332,7 +39482,7 @@ - + @@ -39345,7 +39495,8 @@ welche ggfs C++ - Exceptions fängt

-
+ +
@@ -39358,7 +39509,7 @@ - + @@ -39377,11 +39528,12 @@ Nur das ist der Grund. Es geht gar nicht um die Event-Loop

-
+ +
- + @@ -39402,11 +39554,12 @@ - + + - + @@ -39434,7 +39587,8 @@ - + + @@ -39500,7 +39654,7 @@ - + @@ -39555,7 +39709,8 @@ }

-
+ + @@ -39569,7 +39724,7 @@ - + @@ -39636,7 +39791,8 @@ }

-
+ + @@ -39696,7 +39852,7 @@ - + @@ -39709,9 +39865,10 @@ aber macht in etwa die gleichen Operationen

-
+ +
- + @@ -39730,7 +39887,8 @@ Gtk-Main verwendet inzwischen den gleichen Mechanismus

-
+ +
@@ -39743,7 +39901,7 @@ - + @@ -39765,7 +39923,8 @@ Das ist eine subtile Falle.

-
+ +
@@ -39784,7 +39943,7 @@ - + @@ -39807,7 +39966,8 @@ alles das nicht aus dem GUI-Thread heraus geschieht

-
+ +
@@ -39863,7 +40023,7 @@ - + @@ -39873,9 +40033,10 @@ ...ob beim Expand/Collapse das umschließende Widget resized werden soll

-
+ +
- + @@ -39885,12 +40046,13 @@ ob eingeklappt oder ausgeklappt

-
+ +
- + @@ -39903,7 +40065,8 @@ was dazu führt, daß das Kind stets allen verfügbaren Platz nimmt

-
+ +
@@ -39975,7 +40138,7 @@
- + @@ -39988,7 +40151,8 @@ Siehe Beschreibung im Beispiel/Tutorial

-
+ +
@@ -40017,7 +40181,7 @@ - + @@ -40027,9 +40191,10 @@ ....how does the event dispatching deal with partially covered widgets

-
+ +
- + @@ -40039,7 +40204,8 @@ ...for embedded widgets

-
+ +
@@ -40072,7 +40238,7 @@ - + @@ -40085,7 +40251,8 @@ i.e. the enclosing parent widget also gets a chance to redraw itself

-
+ +
@@ -40106,7 +40273,7 @@ - + @@ -40116,13 +40283,14 @@ asked on stackoverflow...

-
+ +
- + @@ -40135,7 +40303,8 @@ by printing values from the on_draw() callback

-
+ +
@@ -40271,7 +40440,7 @@ - + @@ -40299,7 +40468,8 @@ Kind-Widgets noch gar nicht festgelegt war (denn das passiert erst beim draw).

-
+ +
@@ -40337,7 +40507,7 @@ - + @@ -40353,7 +40523,8 @@ und auch ein Signal für Parse-Fehler anschließt

-
+ +
@@ -40380,7 +40551,7 @@ - + @@ -40398,7 +40569,8 @@ Beachte: der Text-Cursor (Marker "insert") hat right gravity

-
+ +
@@ -40473,7 +40645,7 @@ - + @@ -40495,7 +40667,8 @@ The grip is implemented as a GdlDockItemGrip

-
+ +
@@ -40576,7 +40749,7 @@

- + @@ -40586,9 +40759,10 @@ kann eines der Templates im Zyklus vorrübergehend als "incomplete" gelten.

-
+ +
- + @@ -40610,7 +40784,8 @@ Konsequenz: man wählt dann z.B. eine subtil falsche Spezialisierung.

-
+ +
@@ -40636,7 +40811,7 @@ - + @@ -40664,13 +40839,14 @@ selber aus einem statischen Initialisierungs-Kontext heraus erfolgt.

-
+ +
- + @@ -40866,7 +41042,8 @@   }

-
+ +
@@ -41108,7 +41285,7 @@ - + @@ -41133,9 +41310,10 @@ Query<RES>::resolveBy

-
+ +
- + @@ -41170,14 +41348,15 @@ sonst kommt Doxygen durcheinander

-
+ +
- + @@ -41199,7 +41378,8 @@ wird hier kein Link erzeugt

-
+ +
@@ -41215,7 +41395,7 @@ - + @@ -41231,7 +41411,8 @@ Die Icon-Größen ergeben sich aus den Boxes auf 'plate'

-
+ +
@@ -41246,7 +41427,7 @@ - + @@ -41256,7 +41437,8 @@ ...im Besonderen die guten Diagramme für Pulse, ALSA und Jack

-
+ +
@@ -41381,7 +41563,7 @@
- + @@ -41404,10 +41586,11 @@ "-Wl,-rpath-link=target/modules"

-
+ +
- + @@ -41417,14 +41600,15 @@ laufen wieder alle

-
+ + - + @@ -41434,7 +41618,8 @@ test.sh Zeile 138

-
+ +
@@ -41478,7 +41663,7 @@ - + @@ -41488,12 +41673,13 @@ und wir verbringen unsere Zeit mit contention

-
+ +
- + @@ -41503,7 +41689,8 @@ ist klar, hab ich gebrochen

-
+ + @@ -41519,7 +41706,7 @@ - + @@ -41532,7 +41719,8 @@ Vorher hatte ich erste Kollisionen nach 25000 Nummern

-
+ +
@@ -41571,7 +41759,7 @@
- + @@ -41584,7 +41772,8 @@ Aug 10 04:51:39 flaucher kernel: traps: test-suite[8249] trap int3 ip:7ffff7deb241 sp:7fffffffe5c8 error:0

-
+ + @@ -41641,7 +41830,7 @@ - + @@ -41651,7 +41840,8 @@ bison dejagnu flex gobjc libncurses5-dev libreadline-dev liblzma-dev libbabeltrace-dev libbabeltrace-ctf-dev python3-dev

-
+ +
@@ -41667,7 +41857,7 @@ - + @@ -41701,7 +41891,8 @@ au weia LEUTE!

-
+ +
@@ -41725,7 +41916,7 @@
- + @@ -41741,10 +41932,11 @@ und tatsächlich: das ist daneben, GCC hat Recht!

-
+ +
- + @@ -41754,7 +41946,8 @@ aktualisieren und neu bauen

-
+ +
@@ -41774,7 +41967,7 @@ - + @@ -41784,7 +41977,8 @@ wähle Kompatibiltät genau so, daß Ubuntu-Trusty noch unterstützt wird.

-
+ + @@ -41818,7 +42012,7 @@ - + @@ -41831,9 +42025,10 @@ Ich meine also: zu Beginn vom Build sollte das Buildsystem einmal eine Infozeile ausgeben

-
+ +
- + @@ -41843,7 +42038,8 @@ ...denn die stören jeweils beim erzeugen eines Hotfix/Patch im Paketbau per dpkg --commit

-
+ +
@@ -41890,7 +42086,7 @@
- + @@ -41900,11 +42096,12 @@ Doku durchkämmen nach Müll

-
+ + - + @@ -41917,10 +42114,11 @@ WICHTIG: keine vorgreifende Infor publizieren!!!!!

-
+ +
- + @@ -41936,7 +42134,8 @@ insgesamt sorgfältig durchlesen

-
+ + @@ -41946,7 +42145,7 @@ - + @@ -41956,7 +42155,8 @@ knappe Kennzeichnung des Releases in den Kommentar

-
+ + @@ -41964,7 +42164,7 @@ - + @@ -41983,7 +42183,8 @@ denn wir wollen keine DEB-Info im Master haben!

-
+ + @@ -41996,7 +42197,7 @@
- + @@ -42009,7 +42210,8 @@ die unmittelbaren Release-Dokumente durchgehen

-
+ + @@ -42023,7 +42225,7 @@ - + @@ -42036,7 +42238,8 @@ Sollte konfliktfrei sein

-
+ +
@@ -42059,7 +42262,7 @@
- + @@ -42069,7 +42272,8 @@ ...das heißt bauen und hochladen

-
+ + @@ -42123,7 +42327,7 @@ - + @@ -42136,9 +42340,10 @@ unter Debian/Jessie wird das ignoriert

-
+ +
- + @@ -42164,7 +42369,8 @@ Die Wahrscheinlichkeit, daß irgend jemand Lumiera unter Ubuntu/Trusty installieren möchte, erscheint mir akademisch

-
+ +
@@ -42190,7 +42396,7 @@ - + @@ -42200,7 +42406,8 @@ in lib/hash-standard.hpp

-
+ +
@@ -42215,7 +42422,7 @@ - + @@ -42235,7 +42442,8 @@ es gibt Probleme beim Linken mit den Boost-Libraries, die auf Ubuntu/wily mit gcc-5 gebaut sind.

-
+ +
@@ -42243,7 +42451,7 @@ - + @@ -42253,7 +42461,8 @@ Wichtig: hier nur was wirklich gebaut ist und funktioniert!

-
+ +
@@ -42277,7 +42486,7 @@ - + @@ -42293,7 +42502,8 @@ bestehen, aber irgendwann müssen wir das schon glattziehen

-
+ +
@@ -42331,7 +42541,7 @@ - + @@ -42341,7 +42551,8 @@ seit gcc-4.8 ist kein static_assert mehr in der STDlib

-
+ +
@@ -42383,7 +42594,7 @@ - + @@ -42429,10 +42640,11 @@ END

-
+ + - + @@ -42442,11 +42654,12 @@ for I in `seq 1 50`; do target/test-suite CallQueue_test; done

-
+ +
- + @@ -42462,7 +42675,8 @@ und eine Doxygen-Seite im Browser geladen

-
+ +
@@ -42483,7 +42697,7 @@ - + @@ -42493,7 +42707,8 @@ weil sich die Threads gegenseitig ihre Counter inkrementieren.

-
+ +
From 202b1e4dbde0625f2e21a28289905e864f81cdc1 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 13 Oct 2018 03:47:31 +0200 Subject: [PATCH 150/178] Timeline: implement handling of INS verb to create new 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. --- src/gui/interact/interaction-director.cpp | 25 ++++++++---- src/gui/panel/timeline-panel.cpp | 7 ++++ src/gui/panel/timeline-panel.hpp | 10 +++-- src/gui/timeline/timeline-gui.cpp | 17 ++++++++ src/gui/timeline/timeline-gui.hpp | 6 ++- src/include/ui-protocol.hpp | 6 +++ wiki/thinkPad.ichthyo.mm | 50 +++++++++++++++++------ 7 files changed, 94 insertions(+), 27 deletions(-) diff --git a/src/gui/interact/interaction-director.cpp b/src/gui/interact/interaction-director.cpp index bd2092e5e..d4dcff6ff 100644 --- a/src/gui/interact/interaction-director.cpp +++ b/src/gui/interact/interaction-director.cpp @@ -41,8 +41,9 @@ #include "gui/workspace/workspace-window.hpp" #include "gui/ctrl/ui-state.hpp" #include "gui/setting/asset-controller.hpp" -#include "gui/timeline/timeline-widget.hpp" -#include "gui/timeline/timeline-controller.hpp" /////////////////TODO still required? +#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 @@ -344,19 +345,25 @@ namespace interact { } } - /** @internal allocate a new TimelineWidget and attach it as child. - * @todo is it really necessary to make such strong assumptions - * regarding the format of the diff spec provided for - * "element construction"? + /** + * @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) { - unimplemented ("allocate a TimelineWidget in some TimelinePanel and attach it's controller as child entity"); - ID rootTrack = verifyDiffStructure_and_extract_RootTrack (spec); TimelineGui anchorProxy{spec.idi, rootTrack}; - anchorProxy.buildTimelineWidget (this->uiBus_); ///////////////////////////////////////////TODO really create it right here? + + globalCtx_.windowLoc_ + .locatePanel() + .find_or_create() + .addTimeline ( + anchorProxy.buildTimelineWidget (this->uiBus_)); + return anchorProxy; } diff --git a/src/gui/panel/timeline-panel.cpp b/src/gui/panel/timeline-panel.cpp index 462148208..97131c60d 100644 --- a/src/gui/panel/timeline-panel.cpp +++ b/src/gui/panel/timeline-panel.cpp @@ -149,6 +149,13 @@ namespace panel { + void + TimelinePanel::addTimeline (std::unique_ptr&& widget) + { + UNIMPLEMENTED ("take ownership of the widget and place it into a new tab"); + } + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1016 : WIP kill everything below.... void TimelinePanel::experiment_1() { diff --git a/src/gui/panel/timeline-panel.hpp b/src/gui/panel/timeline-panel.hpp index e93bdddcf..75e9e1b1e 100644 --- a/src/gui/panel/timeline-panel.hpp +++ b/src/gui/panel/timeline-panel.hpp @@ -44,16 +44,17 @@ #define GUI_PANEL_TIMELINE_PANEL_H #include "gui/panel/panel.hpp" +#include "gui/timeline/timeline-widget.hpp" -//#include +#include #include namespace gui { namespace model{ - class Sequence; - } + class Sequence; +} namespace panel { //using std::shared_ptr; @@ -134,8 +135,11 @@ namespace panel { static const char* getTitle(); static const gchar* getStockID(); + /////////////////////////////////////////////////////////////TODO WIP + void addTimeline (std::unique_ptr&&); private: +/////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1016 : WIP kill everything below.... Gtk::Box twoParts_; Gtk::ButtonBox buttons_; Gtk::Button button_1_; diff --git a/src/gui/timeline/timeline-gui.cpp b/src/gui/timeline/timeline-gui.cpp index 5ea8576d4..b2295e862 100644 --- a/src/gui/timeline/timeline-gui.cpp +++ b/src/gui/timeline/timeline-gui.cpp @@ -38,6 +38,7 @@ #include "gui/timeline/timeline-gui.hpp" #include "gui/timeline/timeline-widget.hpp" +#include using lib::diff::TreeMutator; using std::make_unique; @@ -56,6 +57,22 @@ namespace timeline { , rootTrackID_{trackID} { } + /** + * @remark basically the default, just defined 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() { } diff --git a/src/gui/timeline/timeline-gui.hpp b/src/gui/timeline/timeline-gui.hpp index 59476467b..ad6b96a21 100644 --- a/src/gui/timeline/timeline-gui.hpp +++ b/src/gui/timeline/timeline-gui.hpp @@ -55,6 +55,7 @@ #include "gui/ctrl/bus-term.hpp" #include "gui/model/w-link.hpp" #include "lib/diff/diff-mutable.hpp" +#include "lib/nocopy.hpp" #include @@ -73,6 +74,7 @@ namespace timeline { class TimelineGui : public model::WLink , public lib::diff::DiffMutable + , util::Cloneable { using ID = BusTerm::ID; @@ -83,8 +85,8 @@ namespace timeline { TimelineGui (ID identity, ID trackID); virtual ~TimelineGui(); - // standard copy operations acceptable - + TimelineGui (TimelineGui const&); + TimelineGui (TimelineGui &&); operator ID() const { return timelineID_; } diff --git a/src/include/ui-protocol.hpp b/src/include/ui-protocol.hpp index 6aa5cdb4a..eb6b7c3b8 100644 --- a/src/include/ui-protocol.hpp +++ b/src/include/ui-protocol.hpp @@ -51,9 +51,14 @@ #define GUI_INTERFACE_UI_PROTOCOL_H +#include "lib/error.hpp" #include "lib/symbol.hpp" +namespace lib { +namespace diff{ + LUMIERA_ERROR_DECLARE(DIFF_STRUCTURE); ///< Invalid diff structure: implicit rules and assumptions violated. +}} namespace Glib { //class ustring; /////////////////////////////TODO needed? } @@ -62,6 +67,7 @@ namespace gui { //using lib::Literal; using lib::Symbol; //using cuString = const Glib::ustring; + using lib::diff::LERR_(DIFF_STRUCTURE); extern const Symbol META_kind; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index facc262a9..5eea72275 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -1706,7 +1706,7 @@
- + @@ -16490,17 +16490,19 @@ - + - + - - - + + + + + - - + + @@ -16515,12 +16517,18 @@ - - - + + - + + + + + + + + @@ -19045,6 +19053,22 @@ + + + + + + + + + + + + + + + + @@ -19234,7 +19258,7 @@ - + From 6e18452c376a2399eb70ae452862c1f7c88dc179 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 13 Oct 2018 21:56:10 +0200 Subject: [PATCH 151/178] Timeline: arrange for a tabbed notebook to hold the timeline widgets ...and remove all the leftover test and research code from 2016 which was archived to /research some days ago in 3f87ef43ec --- src/gui/dialog/test-control.hpp | 10 +- src/gui/panel/panel.cpp | 3 +- src/gui/panel/panel.hpp | 12 +- src/gui/panel/timeline-panel.cpp | 327 ++------------------- src/gui/panel/timeline-panel.hpp | 100 +------ src/gui/timeline/timeline-gui.cpp | 21 +- src/gui/timeline/timeline-gui.hpp | 3 +- src/gui/timeline/timeline-widget-empty.hpp | 2 + src/gui/timeline/timeline-widget.cpp | 2 +- src/gui/timeline/timeline-widget.hpp | 16 +- wiki/thinkPad.ichthyo.mm | 50 +++- 11 files changed, 129 insertions(+), 417 deletions(-) diff --git a/src/gui/dialog/test-control.hpp b/src/gui/dialog/test-control.hpp index 039c00ad3..f9644b474 100644 --- a/src/gui/dialog/test-control.hpp +++ b/src/gui/dialog/test-control.hpp @@ -105,7 +105,7 @@ namespace dialog { /** * 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 an setup is notoriously tedious, + * 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 @@ -157,10 +157,10 @@ namespace dialog { /** * Ticket #1099 : perform a dummy round-trip to verify Proc-GUI integration. - * This routine invokes the command `test_meta_displayInfo` 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. + * 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 { diff --git a/src/gui/panel/panel.cpp b/src/gui/panel/panel.cpp index a663db660..1b740d2af 100644 --- a/src/gui/panel/panel.cpp +++ b/src/gui/panel/panel.cpp @@ -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) { diff --git a/src/gui/panel/panel.hpp b/src/gui/panel/panel.hpp index d79c24305..8b34e9bdc 100644 --- a/src/gui/panel/panel.hpp +++ b/src/gui/panel/panel.hpp @@ -36,18 +36,18 @@ #include 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: diff --git a/src/gui/panel/timeline-panel.cpp b/src/gui/panel/timeline-panel.cpp index 97131c60d..38c6fe81f 100644 --- a/src/gui/panel/timeline-panel.cpp +++ b/src/gui/panel/timeline-panel.cpp @@ -33,105 +33,48 @@ #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 "gui/ui-bus.hpp" +//#include "lib/format-string.hpp" +//#include "lib/format-cout.hpp" //#include "lib/util.hpp" -#include -#include -#include +//#include +//#include +//#include -using util::_Fmt; +//using util::_Fmt; //using std::shared_ptr; -//using std::weak_ptr; +using std::make_unique; //using util::contains; -using Gtk::Widget; -using sigc::mem_fun; -using sigc::ptr_fun; -using std::string; -using std::rand; -using std::max; +//using Gtk::Widget; +//using sigc::mem_fun; +//using sigc::ptr_fun; +//using std::string; namespace gui { namespace panel { - + + 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("Experiment 1:\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("Experiment 2:\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("Experiment 3:\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("Experiment 4:\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("Experiment 5:\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(); + pages_.emplace_back (new TimelineWidgetEmpty{}); // show everything.... - this->add(twoParts_); + this->add(tabs_); this->show_all(); } @@ -150,236 +93,10 @@ namespace panel { void - TimelinePanel::addTimeline (std::unique_ptr&& widget) + TimelinePanel::addTimeline (PageHandle&& pTimelineWidget) { UNIMPLEMENTED ("take ownership of the widget and place it into a new tab"); } - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1016 : WIP kill everything below.... - 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 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 << "|=="< 0) - cout << " ↯↯ still "< #include #include 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 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; + using Timelines = std::vector; + + 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(); /////////////////////////////////////////////////////////////TODO WIP - void addTimeline (std::unique_ptr&&); + void addTimeline (PageHandle &&); private: -/////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1016 : WIP kill everything below.... - 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; - ChildV childz_; - - void experiment_1(); - void experiment_2(); - void experiment_3(); - void experiment_4(); - void experiment_5(); }; diff --git a/src/gui/timeline/timeline-gui.cpp b/src/gui/timeline/timeline-gui.cpp index b2295e862..332e4b456 100644 --- a/src/gui/timeline/timeline-gui.cpp +++ b/src/gui/timeline/timeline-gui.cpp @@ -41,7 +41,7 @@ #include using lib::diff::TreeMutator; -using std::make_unique; +using std::unique_ptr; namespace gui { @@ -77,15 +77,24 @@ namespace timeline { /** - * actually build a TimelineWidget to enact the role - * represented by this smart-handle + * 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`, 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 right here, which can than be handed over to TimelinePanel, + * yet still we _do inherit_ from WLink, 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. */ - std::unique_ptr + unique_ptr TimelineGui::buildTimelineWidget (BusTerm& nexus) { - auto widget = std::make_unique (timelineID_, rootTrackID_, nexus); + TimelineWidget* widget; + unique_ptr pageHandle {widget = new TimelineWidget{timelineID_, rootTrackID_, nexus}}; this->connect (*widget); - return widget; + return pageHandle; } diff --git a/src/gui/timeline/timeline-gui.hpp b/src/gui/timeline/timeline-gui.hpp index ad6b96a21..495645cde 100644 --- a/src/gui/timeline/timeline-gui.hpp +++ b/src/gui/timeline/timeline-gui.hpp @@ -63,6 +63,7 @@ namespace gui { namespace timeline { + class TimelinePage; class TimelineWidget; using ctrl::BusTerm; @@ -93,7 +94,7 @@ namespace timeline { /** actually build a TimelineWidget to enact the role * represented by this smart-handle */ - std::unique_ptr buildTimelineWidget (BusTerm&); + std::unique_ptr buildTimelineWidget (BusTerm&); /** @internal this method is invoked by the UI-Bus when dispatching a MutationMessage... */ void buildMutator (lib::diff::TreeMutator::Handle buffer) override; diff --git a/src/gui/timeline/timeline-widget-empty.hpp b/src/gui/timeline/timeline-widget-empty.hpp index 4bb681a19..3e9d17d6e 100644 --- a/src/gui/timeline/timeline-widget-empty.hpp +++ b/src/gui/timeline/timeline-widget-empty.hpp @@ -38,6 +38,7 @@ #include "gui/gtk-base.hpp" +#include "gui/timeline/timeline-widget.hpp" #include "lib/time/timevalue.hpp" //#include @@ -56,6 +57,7 @@ namespace timeline { * and a scrollable timeline body (right). The layout of both parts is aligned. */ class TimelineWidgetEmpty + : public TimelinePage { public: /** diff --git a/src/gui/timeline/timeline-widget.cpp b/src/gui/timeline/timeline-widget.cpp index 7fcc6db8b..9d6c7b787 100644 --- a/src/gui/timeline/timeline-widget.cpp +++ b/src/gui/timeline/timeline-widget.cpp @@ -66,7 +66,7 @@ namespace timeline { TimelineWidget::TimelineWidget (BusTerm::ID identity, BusTerm::ID trackID, BusTerm& nexus) - : Gtk::Paned{Gtk::ORIENTATION_VERTICAL} + : TimelinePage{} , control_{new TimelineController{identity, trackID, nexus}} , layout_{new LayoutManager} { diff --git a/src/gui/timeline/timeline-widget.hpp b/src/gui/timeline/timeline-widget.hpp index cac87d043..68581bc7b 100644 --- a/src/gui/timeline/timeline-widget.hpp +++ b/src/gui/timeline/timeline-widget.hpp @@ -78,6 +78,20 @@ namespace timeline { using ctrl::BusTerm; + /** + * Interface: GUI page holding a timeline display + */ + class TimelinePage + : public Gtk::Paned + { + public: + virtual ~TimelinePage() { } ///< this is an interface + + TimelinePage() + : Gtk::Paned{Gtk::ORIENTATION_VERTICAL} + { } + }; + /** * Core timeline display (custom widget). * Top level entry point to the timeline display component. @@ -86,7 +100,7 @@ namespace timeline { * and a scrollable timeline body (right). The layout of both parts is aligned. */ class TimelineWidget - : public Gtk::Paned + : public TimelinePage { std::unique_ptr control_; std::unique_ptr layout_; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 5eea72275..1a2cc5268 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -19053,11 +19053,17 @@ - - + + + + + + + + @@ -35312,6 +35318,46 @@ + + + + + + + + + + +

+ GUI und Session schicken Nachrichten. +

+

+ Während der Builder läuft, kann das GUI schon weitere Nachrichten geschickt haben, +

+

+ die dann noch in der ProcDispatcher-Queue hängen. Daher kann sich die Antwort +

+

+ als Resultat auf einen Builder-Lauf noch auf einen vorherigen Zustand beziehen. +

+

+ Es kann aber auch ein Builder-Lauf die kummulierten Ergebnisse von mehreren Commands behandeln. +

+

+ In jedem Fall muß der DiffConstituent genau wissen, was der zuletzt geschickte Stand war, +

+

+ damit er einen Diff erzeugt, der garantiert auf der anderen Seite anwendbar ist. +

+

+ Denn letzteres ist bei uns eine Grundannahme. Es gibt keine ungefähren Diffs! +

+ + +
+
+
+
From 67cccbdc5da195641fc1811f142aafd69add6ee7 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 13 Oct 2018 23:58:49 +0200 Subject: [PATCH 152/178] Timeline: actually accept and install the TimelineWidget As starting point, provide an empty placeholder widget to fill the void --- src/gui/panel/timeline-panel.cpp | 39 +++++------ src/gui/panel/timeline-panel.hpp | 20 +++--- src/gui/timeline/timeline-controller.hpp | 8 ++- src/gui/timeline/timeline-gui.cpp | 2 +- src/gui/timeline/timeline-widget-empty.cpp | 80 ---------------------- src/gui/timeline/timeline-widget-empty.hpp | 80 +++++++++++++--------- src/gui/timeline/timeline-widget.cpp | 8 +++ src/gui/timeline/timeline-widget.hpp | 10 ++- wiki/thinkPad.ichthyo.mm | 51 +++++++------- 9 files changed, 125 insertions(+), 173 deletions(-) delete mode 100644 src/gui/timeline/timeline-widget-empty.cpp diff --git a/src/gui/panel/timeline-panel.cpp b/src/gui/panel/timeline-panel.cpp index 38c6fe81f..7bd68d0b7 100644 --- a/src/gui/panel/timeline-panel.cpp +++ b/src/gui/panel/timeline-panel.cpp @@ -36,31 +36,13 @@ #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 -//#include -//#include - - - -//using util::_Fmt; -//using std::shared_ptr; using std::make_unique; -//using util::contains; -//using Gtk::Widget; -//using sigc::mem_fun; -//using sigc::ptr_fun; -//using std::string; namespace gui { namespace panel { + using timeline::TimelinePage; using timeline::TimelineWidget; using timeline::TimelineWidgetEmpty; @@ -71,7 +53,7 @@ namespace panel { , tabs_{} , pages_{} { - pages_.emplace_back (new TimelineWidgetEmpty{}); + addTimeline (PageHandle{new TimelineWidgetEmpty{}}); // show everything.... this->add(tabs_); @@ -91,11 +73,26 @@ namespace panel { } + namespace { + bool + isEmptyTimeline (auto& pages) + { + return 1 == pages.size() + and dynamic_cast (pages[0].get()); + } + } void TimelinePanel::addTimeline (PageHandle&& pTimelineWidget) { - UNIMPLEMENTED ("take ownership of the widget and place it into a new tab"); + if (isEmptyTimeline (pages_)) + { + tabs_.remove_page(); + pages_.clear(); + } + pages_.push_back (move (pTimelineWidget)); + TimelinePage& addedPage = *pages_.back(); + tabs_.append_page (addedPage, addedPage.getLabel()); } diff --git a/src/gui/panel/timeline-panel.hpp b/src/gui/panel/timeline-panel.hpp index 3d52aefa6..4691cd133 100644 --- a/src/gui/panel/timeline-panel.hpp +++ b/src/gui/panel/timeline-panel.hpp @@ -24,16 +24,14 @@ /** @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 as of 10/2018 we start to build a new timeline widget, connected to the UI-Bus @@ -80,7 +78,7 @@ namespace panel{ static const char* getTitle(); static const gchar* getStockID(); - /////////////////////////////////////////////////////////////TODO WIP + /** take ownership of the widget and place it into a new tab */ void addTimeline (PageHandle &&); private: diff --git a/src/gui/timeline/timeline-controller.hpp b/src/gui/timeline/timeline-controller.hpp index 44df1e57c..07ce4609c 100644 --- a/src/gui/timeline/timeline-controller.hpp +++ b/src/gui/timeline/timeline-controller.hpp @@ -99,7 +99,7 @@ namespace timeline { */ TimelineController (ID identity, ID trackID, ctrl::BusTerm& nexus); - ~TimelineController(); + ~TimelineController(); /** set up a binding to respond to mutation messages via UiBus */ @@ -108,6 +108,12 @@ namespace timeline { public: /* ===== Control interface ===== */ + string const& + getName() const + { + return name_; + } + public: /* ===== Signals ===== */ private:/* ===== Events ===== */ diff --git a/src/gui/timeline/timeline-gui.cpp b/src/gui/timeline/timeline-gui.cpp index 332e4b456..6e78f04c5 100644 --- a/src/gui/timeline/timeline-gui.cpp +++ b/src/gui/timeline/timeline-gui.cpp @@ -58,7 +58,7 @@ namespace timeline { { } /** - * @remark basically the default, just defined here explicitly + * @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) diff --git a/src/gui/timeline/timeline-widget-empty.cpp b/src/gui/timeline/timeline-widget-empty.cpp deleted file mode 100644 index 2874af081..000000000 --- a/src/gui/timeline/timeline-widget-empty.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/* - TimelineWidgetEmpty - placeholder widget for a missing timeline - - Copyright (C) Lumiera.org - 2016, Hermann Vosseler - - 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 -//#include - - - -//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 diff --git a/src/gui/timeline/timeline-widget-empty.hpp b/src/gui/timeline/timeline-widget-empty.hpp index 3e9d17d6e..6d587427f 100644 --- a/src/gui/timeline/timeline-widget-empty.hpp +++ b/src/gui/timeline/timeline-widget-empty.hpp @@ -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,13 +37,7 @@ #define GUI_TIMELINE_TIMELINE_WIDGET_EMPTY_H #include "gui/gtk-base.hpp" - #include "gui/timeline/timeline-widget.hpp" -#include "lib/time/timevalue.hpp" - -//#include -//#include - namespace gui { @@ -51,32 +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 ("-- the great actor Joseph Tura"); + 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_); + } }; diff --git a/src/gui/timeline/timeline-widget.cpp b/src/gui/timeline/timeline-widget.cpp index 9d6c7b787..90e037ff7 100644 --- a/src/gui/timeline/timeline-widget.cpp +++ b/src/gui/timeline/timeline-widget.cpp @@ -83,5 +83,13 @@ namespace timeline { } + cuString + TimelineWidget::getLabel() const + { + control_->getName(); + } + + + }}// namespace gui::timeline diff --git a/src/gui/timeline/timeline-widget.hpp b/src/gui/timeline/timeline-widget.hpp index 68581bc7b..9fcb9853b 100644 --- a/src/gui/timeline/timeline-widget.hpp +++ b/src/gui/timeline/timeline-widget.hpp @@ -85,10 +85,13 @@ namespace timeline { : public Gtk::Paned { public: - virtual ~TimelinePage() { } ///< this is an interface + virtual ~TimelinePage() { } ///< this is an interface + + virtual cuString getLabel() const =0; + TimelinePage() - : Gtk::Paned{Gtk::ORIENTATION_VERTICAL} + : Gtk::Paned{Gtk::ORIENTATION_HORIZONTAL} { } }; @@ -136,6 +139,9 @@ namespace timeline { /** allow for diff mutation (delegated to TimelineController */ void buildMutator (lib::diff::TreeMutator::Handle); + cuString getLabel() const override; + + public: /* ===== Signals ===== */ private:/* ===== Events ===== */ diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 1a2cc5268..d705c39c1 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -5243,18 +5243,18 @@
- + - - - + + + - - + + - - + + @@ -16501,8 +16501,8 @@ - - + + @@ -16517,19 +16517,19 @@ - + - + - + - + @@ -19050,29 +19050,32 @@ - + - + - - + + - - + + - + - - - + + + + + + From 77902d54a6c75f10d151976eafa99fbabb8d2b16 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 14 Oct 2018 17:24:13 +0200 Subject: [PATCH 153/178] DummySessionConnection: prepare schaffolding for fake-commands (see #1042) 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 --- .../session/dummy-session-connection.cpp | 119 ++++++++++++++++-- .../session/dummy-session-connection.hpp | 31 ++++- wiki/thinkPad.ichthyo.mm | 39 ++++++ 3 files changed, 174 insertions(+), 15 deletions(-) diff --git a/src/proc/mobject/session/dummy-session-connection.cpp b/src/proc/mobject/session/dummy-session-connection.cpp index edff76ccd..fb6c3e313 100644 --- a/src/proc/mobject/session/dummy-session-connection.cpp +++ b/src/proc/mobject/session/dummy-session-connection.cpp @@ -29,7 +29,7 @@ ** - some session content ** - commands to be invoked ** - ** @todo WIP as of 12/2016 ///////////////////////TICKET #1042 + ** @todo WIP as of 10/2018 ///////////////////////TICKET #1042 ** ** @see DummySessionConnection_test ** @@ -39,11 +39,27 @@ //#include "lib/symbol.hpp" //#include "include/logging.h" #include "proc/mobject/session/dummy-session-connection.hpp" +#include "proc/control/command-def.hpp" +#include "include/ui-protocol.hpp" +#include "include/gui-notification-facade.h" +#include "lib/diff/gen-node.hpp" +//#include "lib/idi/entry-id.hpp" +#include "lib/format-string.hpp" +#include "lib/format-cout.hpp" +//#include "lib/symbol.hpp" +#include "lib/util.hpp" #include -#include +//#include -using std::map; +using lib::diff::GenNode; +//using util::cStr; +using util::_Fmt; +using std::string; + +#include + +//using std::map; using std::string; using util::contains; @@ -57,13 +73,11 @@ namespace session { } //(End)Implementation details.... - DummySessionConnection::DummySessionConnection ( ) - { - } - DummySessionConnection::~DummySessionConnection ( ) + DummySessionConnection::~DummySessionConnection() { } + + DummySessionConnection::DummySessionConnection() { - // ////TODO Auto-generated destructor stub } @@ -78,4 +92,91 @@ namespace session { */ -}}} // namespace proc::mobject::session +}}// namespace proc::mobject::session + +namespace cmd { + + using lib::hash::LuidH; + using gui::ID; + using gui::NOTE_INFO; + using gui::NOTE_WARN; + using gui::NOTE_ERROR; +// using gui::NotifyLevel; +// using gui::MARK_expand; + using gui::GuiNotification; +// using util::isYes; + + + /* ============ dedicated Fake-Commands ============ */ + + /** Populate the Timeline in the UI with a typical simple Dummy sequence. + * This Proc-Layer command script fabricates a faked "population diff", which not + * corresponds to any existing session data structure, but looks as if emanated while + * loading current session state. + * - one single Timeline + * - just the root track + * - two clips placed on that track + * @todo use this to establish basic Timeline display in the UI //////////////////TICKET #1014 : produce Dummy content to populate timeline + */ +COMMAND_DEFINITION (test_fake_injectSequence_1) + { + def.operation ([](string dummyID) + { + string message{_Fmt{"fabricate Sequence_1 (dummyID='%s')"} % dummyID}; + GuiNotification::facade().displayInfo (NOTE_INFO, message); + }) + .captureUndo ([](string dummyID) -> string + { + return _Fmt{"fabricateSequence_1('%s')"} % dummyID; + }) + .undoOperation ([](string, string memento) + { + GuiNotification::facade().displayInfo (NOTE_WARN, "can not UNDO Dummy-Action: "+memento); + }); + }; + + + /** Populate the Timeline in the UI with a rather complex Dummy sequence. + * This command script fabricates a faked convoluted "population diff"... + * @todo use this to enact a complex layout structure in the Timeline-UI //////////////////TICKET #1014 : produce Dummy content to populate timeline + */ +COMMAND_DEFINITION (test_fake_injectSequence_2) + { + def.operation ([](string dummyID) + { + string message{_Fmt{"fabricate Sequence_2 (dummyID='%s')"} % dummyID}; + GuiNotification::facade().displayInfo (NOTE_INFO, message); + }) + .captureUndo ([](string dummyID) -> string + { + return _Fmt{"fabricateSequence_2('%s')"} % dummyID; + }) + .undoOperation ([](string, string memento) + { + GuiNotification::facade().displayInfo (NOTE_WARN, "can not UNDO Dummy-Action: "+memento); + }); + }; + + + /** Template for dummy-code.... + * @todo use this to enact a complex layout structure in the Timeline-UI //////////////////TICKET #1042 : further the DummySessionConnection + */ +COMMAND_DEFINITION (test_fake_blubb) + { + def.operation ([](string dummyID) + { + string message{_Fmt{"fabricate gulp (dummyID='%s')"} % dummyID}; + GuiNotification::facade().displayInfo (NOTE_INFO, message); + }) + .captureUndo ([](string dummyID) -> string + { + return _Fmt{"fabricateGulp('%s')"} % dummyID; + }) + .undoOperation ([](string, string memento) + { + GuiNotification::facade().displayInfo (NOTE_WARN, "can not UNDO Dummy-Action: "+memento); + }); + }; + + +}} // namespace proc::mobject::session diff --git a/src/proc/mobject/session/dummy-session-connection.hpp b/src/proc/mobject/session/dummy-session-connection.hpp index f83d47741..521256c16 100644 --- a/src/proc/mobject/session/dummy-session-connection.hpp +++ b/src/proc/mobject/session/dummy-session-connection.hpp @@ -26,14 +26,14 @@ ** parts of the application, which are to defined in the very attempt this placeholder ** is used for... ** - it can fake some session content - ** - it may expose some fake command implementations + ** - it exposes some fake command implementations ** - and all this together with suitable diagnostic facilities ** ** The plan is to push this helper along, as we proceed from the UI down towards the session. ** In the end, this placeholder service may be inserted into the session implementation proper, ** until we have the ability to load a persistent session from storage. ** - ** @todo WIP as of 12/2016 //////////////////////////////////////////////////////////////////////TICKET #1042 + ** @todo WIP as of 10/2018 //////////////////////////////////////////////////////////////////////TICKET #1042 ** ** @see ui-bus.hpp ** @see dummy-session-connection.cpp implementation @@ -48,9 +48,23 @@ #include "lib/nocopy.hpp" #include "lib/util.hpp" +#include "proc/control/command-setup.hpp" + #include namespace proc { +namespace cmd { + + using control::Command; + using control::CommandDef; + using control::CommandSetup; + + /* ============ dedicated Fake-Commands ============ */ + + extern CommandSetup test_fake_injectSequence_1; + extern CommandSetup test_fake_injectSequence_2; + +} namespace mobject { namespace session { @@ -58,10 +72,15 @@ namespace session { using std::string; /** - * Basic (abstracted) view of... + * Scaffolding to drive the evolution of the Lumiera application. + * @todo as of 2018, the most pressing problem is to establish the interplay between + * GUI-Layer and Proc-Layer, so the software gains some tangible functionality... + * @note readily-available singleton to enact prototypical behaviour as appropriate. * - * @see SomeSystem * @see DummySessionConnection_test + * @see gui::interact::InteractionDirector::buildMutator + * @see gui::interact::InteractionDirector::injectTimeline + * @see gui::timeline::TimelineController */ class DummySessionConnection : util::NonCopyable @@ -80,9 +99,9 @@ namespace session { static lib::Depend instance; - /* == X interface for == */ + /* == do X == */ - /* == Adapter interface for == */ + /* == forget Y == */ #if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #1042 void setSolution (string const& solution = "") diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index d705c39c1..018d4d292 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -5242,6 +5242,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From de5f0b85d4e430e24208b16d01e99756dff75108 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 14 Oct 2018 23:55:00 +0200 Subject: [PATCH 154/178] DummySessionConnection: new tab in the TestControl dialog box ...to trigger the new fake-functionality --- src/gui/dialog/test-control.hpp | 84 +++++++++++++++++++++++++++++++-- wiki/thinkPad.ichthyo.mm | 11 +++-- 2 files changed, 86 insertions(+), 9 deletions(-) diff --git a/src/gui/dialog/test-control.hpp b/src/gui/dialog/test-control.hpp index f9644b474..37631f597 100644 --- a/src/gui/dialog/test-control.hpp +++ b/src/gui/dialog/test-control.hpp @@ -43,12 +43,19 @@ #include "gui/dialog/dialog.hpp" #include "gui/ctrl/bus-term.hpp" #include "gui/model/tangible.hpp" -#include "proc/cmd.hpp" #include "include/ui-protocol.hpp" + +#include "proc/cmd.hpp" #include "include/gui-notification-facade.h" -#include "lib/scoped-ptrvect.hpp" +#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 #include @@ -59,6 +66,8 @@ namespace dialog { using std::string; using std::forward; using lib::diff::GenNode; + using util::sanitise; + using util::_Fmt; /** @@ -202,6 +211,7 @@ namespace dialog { "it will be passed down and sent back")); trig_1_.set_use_underline(); trig_1_.set_label ("_display text"); + trig_1_.property_xalign() = 0; trig_1_.set_tooltip_markup (_("Trigger Proc-GUI roundtrip\n" "Proc invokes GuiNotification::displayInfo")); @@ -223,6 +233,7 @@ namespace dialog { 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 state mark message, using\n" "the message action-ID from the combobox")); @@ -249,9 +260,9 @@ namespace dialog { seg_2_.pack_start (trig_3_); seg_2_.pack_start (markParam_); - pack_start (content_); - pack_start (seg_1_); - pack_start (seg_2_); + 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( @@ -266,6 +277,67 @@ namespace dialog { }; + /** + * 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 + getDummyID() + { + string dummyID = sanitise (dummy_.get_text()); + dummy_.set_text (dummyID); + return dummyID; + } + + + Page2 (Bus bus) + { + seq_1_.set_label ("Sequence 1"); + seq_1_.set_tooltip_markup (_("Push population diff up into the UI\n" + "provides the typical simple default timeline structure")); + + seq_2_.set_label ("Sequence 2"); + seq_2_.set_tooltip_markup (_("Push population diff up into the UI\n" + "provides a complex nested timeline structure")); + + dummy_.set_tooltip_markup (_("dummy ID value\n" + "used to build names in the generated content")); + dummy_.set_text (string{_Fmt{"d%s%02d"} % lib::test::randStr(2) % (1 + rand() % 99)}); + dummy_.set_max_width_chars(12); + + 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, getDummyID())); }); + seq_2_.signal_clicked().connect( + [&]{ bus.act (model::commandMessage (proc::cmd::test_fake_injectSequence_2, getDummyID())); }); + + mut_1_.signal_clicked().connect( + [&]{ cerr << "gelldaschaugst..." < (_("#1099"), uiBus_); + notebook_.buildPage (_("Populate"), uiBus_); + notebook_.set_current_page(-1); show_all(); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 018d4d292..0c771ab8d 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -5244,16 +5244,19 @@ - - + + - - + + + + + From 572bd38fec658a66b608a8d4406a8b0af304240c Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 15 Oct 2018 00:54:10 +0200 Subject: [PATCH 155/178] DummySessionConnection: produce a simple population diff message 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 --- .../session/dummy-session-connection.cpp | 40 +++++++++++++++++-- .../session/dummy-session-connection.hpp | 14 ++++++- tests/gui/abstract-tangible-test.cpp | 6 ++- tests/library/diff/mutation-message-test.cpp | 2 +- wiki/thinkPad.ichthyo.mm | 16 +++++--- 5 files changed, 66 insertions(+), 12 deletions(-) diff --git a/src/proc/mobject/session/dummy-session-connection.cpp b/src/proc/mobject/session/dummy-session-connection.cpp index fb6c3e313..e9c677785 100644 --- a/src/proc/mobject/session/dummy-session-connection.cpp +++ b/src/proc/mobject/session/dummy-session-connection.cpp @@ -39,9 +39,12 @@ //#include "lib/symbol.hpp" //#include "include/logging.h" #include "proc/mobject/session/dummy-session-connection.hpp" +#include "proc/mobject/session/root.hpp" #include "proc/control/command-def.hpp" #include "include/ui-protocol.hpp" #include "include/gui-notification-facade.h" +#include "lib/diff/tree-diff-application.hpp" +#include "lib/diff/mutation-message.hpp" #include "lib/diff/gen-node.hpp" //#include "lib/idi/entry-id.hpp" #include "lib/format-string.hpp" @@ -53,6 +56,8 @@ //#include using lib::diff::GenNode; +using lib::diff::MakeRec; +using lib::diff::MutationMessage; //using util::cStr; using util::_Fmt; using std::string; @@ -71,6 +76,16 @@ namespace session { namespace { //Implementation details.... + GenNode + emptyTimeline (string baseID) + { + return MakeRec() + .set(string{gui::ATTR_fork} + ,MakeRec() + .type(string{gui::TYPE_Fork}) + ) + .genNode(baseID); + } } //(End)Implementation details.... @@ -86,10 +101,21 @@ namespace session { /** - * - * @param id - * @return + * Build a population diff message to describe a specific session structure to add */ + MutationMessage + DummySessionConnection::fabricateSeq1 (string baseID) + { + return MutationMessage{ ins (emptyTimeline (baseID)) + }; + } + + + void + DummySessionConnection::applyCopy (MutationMessage const& diff) + { + TODO ("build internal diagnostic data structure, apply a copy of the message"); + } }}// namespace proc::mobject::session @@ -105,6 +131,10 @@ namespace cmd { // using gui::MARK_expand; using gui::GuiNotification; // using util::isYes; + + namespace session = proc::mobject::session; + + using DummySess = session::DummySessionConnection; /* ============ dedicated Fake-Commands ============ */ @@ -124,6 +154,10 @@ COMMAND_DEFINITION (test_fake_injectSequence_1) { string message{_Fmt{"fabricate Sequence_1 (dummyID='%s')"} % dummyID}; GuiNotification::facade().displayInfo (NOTE_INFO, message); + auto popuDiff = DummySess::instance().fabricateSeq1 (dummyID); + DummySess::instance().applyCopy (popuDiff); + auto rootID = session::Root::getID(); + GuiNotification::facade().mutate (rootID, move(popuDiff)); }) .captureUndo ([](string dummyID) -> string { diff --git a/src/proc/mobject/session/dummy-session-connection.hpp b/src/proc/mobject/session/dummy-session-connection.hpp index 521256c16..745785420 100644 --- a/src/proc/mobject/session/dummy-session-connection.hpp +++ b/src/proc/mobject/session/dummy-session-connection.hpp @@ -48,10 +48,15 @@ #include "lib/nocopy.hpp" #include "lib/util.hpp" +#include "lib/diff/tree-diff.hpp" #include "proc/control/command-setup.hpp" #include +namespace lib { +namespace diff{ + class MutationMessage; +}} namespace proc { namespace cmd { @@ -70,6 +75,7 @@ namespace session { //using util::isnil; using std::string; + using lib::diff::MutationMessage; /** * Scaffolding to drive the evolution of the Lumiera application. @@ -83,7 +89,8 @@ namespace session { * @see gui::timeline::TimelineController */ class DummySessionConnection - : util::NonCopyable + : lib::diff::TreeDiffLanguage + , util::NonCopyable { string nothing_; @@ -102,6 +109,11 @@ namespace session { /* == do X == */ /* == forget Y == */ + + MutationMessage fabricateSeq1 (string baseID); + + void applyCopy (MutationMessage const&); + #if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #1042 void setSolution (string const& solution = "") diff --git a/tests/gui/abstract-tangible-test.cpp b/tests/gui/abstract-tangible-test.cpp index c9ffc776a..2bc76eb68 100644 --- a/tests/gui/abstract-tangible-test.cpp +++ b/tests/gui/abstract-tangible-test.cpp @@ -641,8 +641,10 @@ namespace test { * For this to work, the receiver needs to create a custom _diff binding_. * Thus, each subclass of Tangible has to implement the virtual function * Tangible::buildMutator() and hook up those internal structures, which - * are exposed to changes via diff message. Note especially how child - * UI elements are added this way, to populate the contents of the UI. + * are exposed to changes via diff message. This is what we then call a + * "diff binding" (and MockElement is already outfitted this way). Note + * especially how child UI elements can be added recursively, allowing + * gradually to populate the contents of the UI. * * The diff itself is an iterable sequence of _diff verbs_. * Typically, such a diff is generated as the result of some operation diff --git a/tests/library/diff/mutation-message-test.cpp b/tests/library/diff/mutation-message-test.cpp index 6cd9a59cb..3bad00977 100644 --- a/tests/library/diff/mutation-message-test.cpp +++ b/tests/library/diff/mutation-message-test.cpp @@ -1,5 +1,5 @@ /* - MutationMessage(Test) - demonstrate the basics of tree diff representation + MutationMessage(Test) - verify diff mutation message container Copyright (C) Lumiera.org 2017, Hermann Vosseler diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 0c771ab8d..7d9f02839 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -5254,10 +5254,16 @@ - + + + + + + + @@ -5267,11 +5273,11 @@ - - + + - - + + From 7e562a4c669d9a3e6f50c7fff1b1aebdebc80768 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 19 Oct 2018 16:19:24 +0200 Subject: [PATCH 156/178] upgrade TiddlyWiki to v2.9.1 Basically, TiddlyWiki "classic" is in maintenance mode, but it is still supported. This minor upgarde brings some bugfixes, most notably a minor adjustment to work well with the new Firefox Quantum, which switched to the more restricted WebExtensions and discontinued support for the old-style XUL based plug-ins. This bold move by the firefox project placed a lot of well established, mature extensions on the brink of extinction. Especially TiddlyWiki has gradually lost its original appeal of a low-ceremony guerrilla wiki. However, it is still a sweet little gem for experienced users, albeit a bit brittle to use. You are now either - forced to enter the target destination on each save - or forced to arrange tiddly wiki to reside within your default download folder and use a firefox plug-in to automate the save process (I use at the moment https://addons.mozilla.org/en-GB/firefox/addon/file-backups/ - or forced to install a 100 MB blurb of java-script based local server to run TiddlyWikiDesktop The bitter irony of the situation is, what still works more-or-less painless is to place your TiddlyWiki into the cloud. Yikes, here we go. --- wiki/empty.html | 55 +++++++++++++++++++++++++----------------- wiki/renderengine.html | 53 ++++++++++++++++++++++++---------------- 2 files changed, 65 insertions(+), 43 deletions(-) diff --git a/wiki/empty.html b/wiki/empty.html index edfdd9076..e26bb319c 100644 --- a/wiki/empty.html +++ b/wiki/empty.html @@ -3,7 +3,7 @@ @@ -46,7 +46,7 @@ DAMAGE. - Empty TiddlyWiki 2.8.1 - a local web scrapbook in a single HTML page +TiddlyWiki 2.9.1 (classic)
loading Proc-Layer devel doku ...

Requires Javascript.
Engine - Building a Render Nodes Network from Objects in the Session +
loading Lumiera development notes ...

Requires Javascript.
-
+
+
+<style type="text/css">#contentWrapper {display:none;}</style>
+<div id="SplashScreen" style="border: 3px solid #ccc; display: block; text-align: center; width: 320px; margin: 100px auto; padding: 50px; color:#000; font-size: 28px; font-family:Serif; background-color:#eee;">loading <b>Lumiera</b> development notes ...<br/><br/><span style="font-size: 14px; color:red;">Requires Javascript.</span></div>
+
+
<!--{{{-->
 <link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml'/>
 <!--}}}-->
-
-<style type="text/css">#contentWrapper {display:none;}</style><div id="SplashScreen" style="border: 3px solid #ccc; display: block; text-align: center; width: 320px; margin: 100px auto; padding: 50px; color:#000; font-size: 28px; font-family:Tahoma; background-color:#eee;">loading <b>Proc-Layer</b> devel doku<blink> ...</blink><br><br><span style="font-size: 14px; color:red;">Requires Javascript.</span></div>
+
The Interface asset::Media is a //key abstraction// It ties together several concepts and enables to deal with them on the interfaces in a uniform manner. Besides, like every Asset kind, it belongs rather to the bookkeeping view: an asset::Media holds the specific properties and parametrisation of the media source it stands for. Regarding the __inward interface__ &mdash; as used from within the [[model|HighLevelModel]] or the [[render nodes|ProcNode]], it is irrelevant if any given asset::Media object stands for a complete media source, just a clip taken from this source or if a placeholder version of the real media source is used instead.
@@ -7279,7 +7284,7 @@ Shutdown is initiated by sending a message to the dispatcher loop. This causes t
 
{{red{killme}}}
-
+
/***
 
 ''Inspired by [[TiddlyPom|http://www.warwick.ac.uk/~tuspam/tiddlypom.html]]''
@@ -7323,14 +7328,14 @@ restart = function()
 }
 
 
-var oldText = store.getTiddlerText("MarkupPreHead");
+var oldText = store.getTiddlerText("MarkupPreBody") || '';
 if (oldText.indexOf("SplashScreen")==-1)
    {var siteTitle = store.getTiddlerText("SiteTitle");
    var splasher='\n\n<style type="text/css">#contentWrapper {display:none;}</style><div id="SplashScreen" style="border: 3px solid #ccc; display: block; text-align: center; width: 320px; margin: 100px auto; padding: 50px; color:#000; font-size: 28px; font-family:Tahoma; background-color:#eee;"><b>'+siteTitle +'</b> is loading<blink> ...</blink><br><br><span style="font-size: 14px; color:red;">Requires Javascript.</span></div>';
-   if (! store.tiddlerExists("MarkupPreHead"))
-       {var myTiddler = store.createTiddler("MarkupPreHead");}
+   if (! store.tiddlerExists("MarkupPreBody"))
+       {var myTiddler = store.createTiddler("MarkupPreBody");}
    else
-      {var myTiddler = store.getTiddler("MarkupPreHead");}
+      {var myTiddler = store.getTiddler("MarkupPreBody");}
       myTiddler.set(myTiddler.title,oldText+splasher,config.options.txtUserName,null,null);
       store.setDirty(true);
       var splashScreenInstall = true;
@@ -10964,7 +10969,7 @@ merge(config.shadowTiddlers,{
 	SiteUrl: "",
 	SideBarOptions: '<><><><><><><>',
 	SideBarTabs: '<>',
-	TabMore: '<>'
+	TabMore: '<>'
 	});
 
 merge(config.annotations,{

From e9d38d4987d0119e0af7ffd1da593762b2527723 Mon Sep 17 00:00:00 2001
From: Ichthyostega 
Date: Sat, 20 Oct 2018 04:35:49 +0200
Subject: [PATCH 160/178] TiddlyWiki: remove the SplashScreenPlugin

This is an obsolete feature, since the JavaScript engine in modern browsers
is way faster than it used to be, and people are accustomed to some loading time
due to all those "single page applications".

I haven't seen this splash screen even on the old Firefox for quite some time;
moreover, the TiddlyWiki 2.9.1 now displays a "JavaScript is required" alert
anyway, so there is really no need for all this messing around.
---
 wiki/renderengine.html | 73 +-----------------------------------------
 1 file changed, 1 insertion(+), 72 deletions(-)

diff --git a/wiki/renderengine.html b/wiki/renderengine.html
index a8a18225e..112088aea 100644
--- a/wiki/renderengine.html
+++ b/wiki/renderengine.html
@@ -42,7 +42,7 @@ DAMAGE.
 " />
 
 
-
+
 
 
 
@@ -64,8 +64,6 @@ DAMAGE.
 
 
 
-
-
loading Lumiera development notes ...

Requires Javascript.
-
-
-<style type="text/css">#contentWrapper {display:none;}</style>
-<div id="SplashScreen" style="border: 3px solid #ccc; display: block; text-align: center; width: 320px; margin: 100px auto; padding: 50px; color:#000; font-size: 28px; font-family:Serif; background-color:#eee;">loading <b>Lumiera</b> development notes ...<br/><br/><span style="font-size: 14px; color:red;">Requires Javascript.</span></div>
-
-
-
<!--{{{-->
-<link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml'/>
-<!--}}}-->
-
-
The Interface asset::Media is a //key abstraction// It ties together several concepts and enables to deal with them on the interfaces in a uniform manner. Besides, like every Asset kind, it belongs rather to the bookkeeping view: an asset::Media holds the specific properties and parametrisation of the media source it stands for. Regarding the __inward interface__ &mdash; as used from within the [[model|HighLevelModel]] or the [[render nodes|ProcNode]], it is irrelevant if any given asset::Media object stands for a complete media source, just a clip taken from this source or if a placeholder version of the real media source is used instead.
 [img[Asset Classess|uml/fig130437.png]]
@@ -7284,64 +7271,6 @@ Shutdown is initiated by sending a message to the dispatcher loop. This causes t
 
{{red{killme}}}
-
-
/***
-
-''Inspired by [[TiddlyPom|http://www.warwick.ac.uk/~tuspam/tiddlypom.html]]''
-
-|Name|SplashScreenPlugin|
-|Created by|SaqImtiaz|
-|Location|http://tw.lewcid.org/#SplashScreenPlugin|
-|Version|0.21 |
-|Requires|~TW2.08+|
-!Description:
-Provides a simple splash screen that is visible while the TW is loading.
-
-!Installation
-Copy the source text of this tiddler to your TW in a new tiddler, tag it with systemConfig and save and reload. The SplashScreen will now be installed and will be visible the next time you reload your TW.
-
-!Customizing
-Once the SplashScreen has been installed and you have reloaded your TW, the splash screen html will be present in the MarkupPreHead tiddler. You can edit it and customize to your needs.
-
-!History
-* 20-07-06 : version 0.21, modified to hide contentWrapper while SplashScreen is displayed.
-* 26-06-06 : version 0.2, first release
-
-!Code
-***/
-//{{{
-var old_lewcid_splash_restart=restart;
-
-restart = function()
-{   if (document.getElementById("SplashScreen"))
-        document.getElementById("SplashScreen").style.display = "none";
-      if (document.getElementById("contentWrapper"))
-        document.getElementById("contentWrapper").style.display = "block";
-    
-    old_lewcid_splash_restart();
-   
-    if (splashScreenInstall)
-       {if(config.options.chkAutoSave)
-			{saveChanges();}
-        displayMessage("TW SplashScreen has been installed, please save and refresh your TW.");
-        }
-}
-
-
-var oldText = store.getTiddlerText("MarkupPreBody") || '';
-if (oldText.indexOf("SplashScreen")==-1)
-   {var siteTitle = store.getTiddlerText("SiteTitle");
-   var splasher='\n\n<style type="text/css">#contentWrapper {display:none;}</style><div id="SplashScreen" style="border: 3px solid #ccc; display: block; text-align: center; width: 320px; margin: 100px auto; padding: 50px; color:#000; font-size: 28px; font-family:Tahoma; background-color:#eee;"><b>'+siteTitle +'</b> is loading<blink> ...</blink><br><br><span style="font-size: 14px; color:red;">Requires Javascript.</span></div>';
-   if (! store.tiddlerExists("MarkupPreBody"))
-       {var myTiddler = store.createTiddler("MarkupPreBody");}
-   else
-      {var myTiddler = store.getTiddler("MarkupPreBody");}
-      myTiddler.set(myTiddler.title,oldText+splasher,config.options.txtUserName,null,null);
-      store.setDirty(true);
-      var splashScreenInstall = true;
-}
-//}}}
-
A small (in terms of storage) and specifically configured StateProxy object which is created on the stack {{red{Really on the stack? 9/11}}} for each individual {{{pull()}}} call. It is part of the invocation state of such a call and participates in the buffer management. Thus, in a calldown sequence of {{{pull()}}} calls we get a corresponding sequence of "parent" states. At each level, the &rarr; WiringDescriptor of the respective node defines a Strategy how the call is passed on.
From c3d91d4ed3f52de3d9b8b3548531d98069cdf16c Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 27 Oct 2018 01:52:46 +0200 Subject: [PATCH 161/178] Timeline: draft for building the nested recursive display structure 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. --- src/gui/timeline/layout-manager.cpp | 7 +- src/gui/timeline/layout-manager.hpp | 23 +- src/gui/timeline/timeline-controller.cpp | 12 +- src/gui/timeline/timeline-controller.hpp | 6 +- src/gui/timeline/timeline-widget.cpp | 6 +- src/gui/timeline/timeline-widget.hpp | 8 +- src/gui/timeline/track-presenter.cpp | 19 +- src/gui/timeline/track-presenter.hpp | 49 +- wiki/renderengine.html | 20 +- wiki/thinkPad.ichthyo.mm | 689 +++++++++++++---------- 10 files changed, 502 insertions(+), 337 deletions(-) diff --git a/src/gui/timeline/layout-manager.cpp b/src/gui/timeline/layout-manager.cpp index 0fbcf6ba1..e266344a8 100644 --- a/src/gui/timeline/layout-manager.cpp +++ b/src/gui/timeline/layout-manager.cpp @@ -59,16 +59,21 @@ namespace timeline { + LayoutManager::~LayoutManager() { } LayoutManager::LayoutManager () { } - LayoutManager::~LayoutManager() + void + LayoutManager::installRootTrack(TrackHeadWidget& head, TrackBody& body) { + UNIMPLEMENTED ("attach the widgets for the root track display"); } + + diff --git a/src/gui/timeline/layout-manager.hpp b/src/gui/timeline/layout-manager.hpp index 1f7bf5b2f..464e9202f 100644 --- a/src/gui/timeline/layout-manager.hpp +++ b/src/gui/timeline/layout-manager.hpp @@ -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 @@ -58,12 +59,13 @@ ** 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](\ref Element::allocate). The element - ** in turn has the liability to walks its children and recursively initiate a nested evaluation + ** 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 @@ -92,6 +94,9 @@ namespace gui { namespace timeline { + class TrackHeadWidget; + class TrackBody; + /** * @todo WIP-WIP as of 12/2016 @@ -101,7 +106,9 @@ namespace timeline { public: LayoutManager (); ~LayoutManager(); - + + void installRootTrack(TrackHeadWidget&,TrackBody&); + private:/* ===== Internals ===== */ }; diff --git a/src/gui/timeline/timeline-controller.cpp b/src/gui/timeline/timeline-controller.cpp index f0b359c9e..6bf6d18ce 100644 --- a/src/gui/timeline/timeline-controller.cpp +++ b/src/gui/timeline/timeline-controller.cpp @@ -40,6 +40,7 @@ #include "gui/gtk-base.hpp" #include "include/ui-protocol.hpp" +#include "gui/timeline/layout-manager.hpp" #include "gui/timeline/timeline-controller.hpp" #include "gui/timeline/track-presenter.hpp" #include "gui/timeline/marker-widget.hpp" @@ -75,15 +76,20 @@ 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, LayoutManager& 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 + , fork_{new TrackPresenter{trackID, nexus, [&](TrackHeadWidget& head,TrackBody& body) + { + layoutManager.installRootTrack (head, body); + }}} { UNIMPLEMENTED ("how to make the controller operative..."); } diff --git a/src/gui/timeline/timeline-controller.hpp b/src/gui/timeline/timeline-controller.hpp index 07ce4609c..85ca2d9bf 100644 --- a/src/gui/timeline/timeline-controller.hpp +++ b/src/gui/timeline/timeline-controller.hpp @@ -72,6 +72,7 @@ namespace timeline { using std::string; class TrackPresenter; + class LayoutManager; class MarkerWidget; @@ -86,10 +87,11 @@ namespace timeline { class TimelineController : public model::Controller { + string name_; + vector> markers_; std::unique_ptr fork_; - string name_; public: /** @@ -97,7 +99,7 @@ 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, LayoutManager&); ~TimelineController(); diff --git a/src/gui/timeline/timeline-widget.cpp b/src/gui/timeline/timeline-widget.cpp index 90e037ff7..22ee125c5 100644 --- a/src/gui/timeline/timeline-widget.cpp +++ b/src/gui/timeline/timeline-widget.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/layout-manager.hpp" //#include "gui/workspace/workspace-window.hpp" //#include "gui/ui-bus.hpp" @@ -67,8 +69,8 @@ namespace timeline { TimelineWidget::TimelineWidget (BusTerm::ID identity, BusTerm::ID trackID, BusTerm& nexus) : TimelinePage{} - , control_{new TimelineController{identity, trackID, nexus}} , layout_{new LayoutManager} + , control_{new TimelineController{identity, trackID, nexus, *layout_}} { UNIMPLEMENTED ("build the timeline UI"); } @@ -86,7 +88,7 @@ namespace timeline { cuString TimelineWidget::getLabel() const { - control_->getName(); + return control_->getName(); } diff --git a/src/gui/timeline/timeline-widget.hpp b/src/gui/timeline/timeline-widget.hpp index 9fcb9853b..98290fb6f 100644 --- a/src/gui/timeline/timeline-widget.hpp +++ b/src/gui/timeline/timeline-widget.hpp @@ -56,8 +56,8 @@ #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" @@ -77,6 +77,8 @@ namespace gui { namespace timeline { using ctrl::BusTerm; + class TimelineController; + class LayoutManager; /** * Interface: GUI page holding a timeline display @@ -105,8 +107,8 @@ namespace timeline { class TimelineWidget : public TimelinePage { - std::unique_ptr control_; std::unique_ptr layout_; + std::unique_ptr control_; public: /** build a new timeline display and attach it to the UI-Bus. diff --git a/src/gui/timeline/track-presenter.cpp b/src/gui/timeline/track-presenter.cpp index 962e03a35..31a81ad6e 100644 --- a/src/gui/timeline/track-presenter.cpp +++ b/src/gui/timeline/track-presenter.cpp @@ -33,8 +33,6 @@ #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" @@ -66,16 +64,6 @@ 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() @@ -151,7 +139,12 @@ namespace timeline { }) .constructFrom ([&](GenNode const& spec) -> PFork { - return make_unique (spec.idi, this->uiBus_); + return make_unique (spec.idi + ,this->uiBus_ + ,[&](TrackHeadWidget& head,TrackBody& body) + { + display_.injectSubTrack (head, body); + }); }) .buildChildMutator ([&](PFork& target, GenNode::ID const& subID, TreeMutator::Handle buff) -> bool { diff --git a/src/gui/timeline/track-presenter.hpp b/src/gui/timeline/track-presenter.hpp index 9e71323d4..39d9c1a46 100644 --- a/src/gui/timeline/track-presenter.hpp +++ b/src/gui/timeline/track-presenter.hpp @@ -49,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 +#include #include @@ -65,8 +69,31 @@ 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 + DisplayFrame (FUN anchorDisplay) + : head{} + , body{} + { + anchorDisplay (head, body); + } + + 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 @@ -74,22 +101,28 @@ namespace timeline { class TrackPresenter : public model::Controller { + DisplayFrame display_; + vector> subFork_; vector> markers_; vector> 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 + TrackPresenter (ID identity, ctrl::BusTerm& nexus, FUN anchorDisplay) + : Controller{identity, nexus} + , display_{anchorDisplay} + , subFork_{} + , markers_{} + , clips_{} + { } /** set up a binding to respond to mutation messages via UiBus */ diff --git a/wiki/renderengine.html b/wiki/renderengine.html index 112088aea..ec3356728 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -3498,7 +3498,7 @@ In the most general case, there can be per-track content and nested content at t &rarr; important question: how to [[organise the widgets|GuiTimelineWidgetStructure]]
-
+
The Timeline is probably the most prominent place in the GUI where we need to come up with a custom UI design.
 Instead of combining standard components in one of the well-known ways, here we need to come up with our own handling solution -- which also means to write one or several custom GTK widgets. Thus the question of layout and screen space division and organisation becomes a crucial design decision. The ~GTK-2 Gui, as implemented currently, did already take some steps along this route, yet this kind of decision should be cast and documented explicitly (be it after the fact).
 
@@ -3507,7 +3507,7 @@ Right away, this topic touches a tricky design and architectural challenge: the
 In a nutshell, ~GTKmm offers several degrees of customisation, namely to build a custom widget class, to build a custom container widget, and to use the [[Gtk::Layout "canvas widget"|GtkLayoutWidget]], possibly combined with //custom drawing.// In addition to assembling a timeline widget class by combining several nested panes, the timeline display needs to rely on the latter approach to allow for the necessary flexible arrangement of [[clip widgets|GuiClipWidget]] within the [[track fork|Fork]].
 
 !the header pane problem
-From general considerations we draw the conclusion to have a track header pane area always visible to the left. For one this means that our top level widget organisation in the timeline will be a horizontal split. And then this means that we get two distinct sub widgets, whose vertical layout needs to be kept in sync. And even more so, it is likely the most adequate implementation technique is different in both parts: the header pane looks like a classical fit for the paradigm of nested boxes and grid layout within those boxes, while the right part, the actual track contents impose very specific layout constraints not served by any of the pre-existing layout containers, which means we have to resort to custom drawing on a canvas widget. Following this line of thought, we need an overarching layout manager to coordinate these two disjoint technologies. Any viable alternatives?
+From general considerations we draw the conclusion to have a track header pane area always visible to the left. For one this means that our top level widget organisation in the timeline will be a horizontal split. And then this means that we get two distinct sub widgets, whose vertical layout needs to be kept in sync. And even more so, presumably the most adequate implementation technique is different in both parts: the header pane looks like a classical fit for the paradigm of nested boxes and grid layout within those boxes, while the right part -- the actual track contents -- impose very specific layout constraints not served by any of the pre-existing layout containers, which means we have to resort to custom drawing on a canvas widget. Following this line of thought, we need an overarching layout manager to coordinate these two disjoint technologies. Any viable alternatives?
 
 !!!considering a table grid layout
 The layout mechanics we try to establish here by explicit implementation would be more or less a given, if instead we'd build the whole timeline display from one base widget, which needs to be a table layout, i.e. {{{Gtk::Grid}}}. We'd use two columns, one for the header pane area, one for the timeline display, and we'd use N+1 rows, with the head row holding the time ruler and the additional rows holding individual tracks. But to get the specific UI mechanics desirable for a timeline display, we had to introduce some twists:
@@ -3524,7 +3524,7 @@ While the special setup for scrolling doesn't really count (since it is necessar
 !!!follow-up to the obvious choices
 We came to this point of re-considering the overall organisation of widgets, after having to re-write the initial version of our timeline widget. This initial version was developed by Joel Holdsworth, and it followed a similar reasoning, involving a global timeline layout manager. The distinction between the two panes was not so clear though, and the access to the model code was awkward at places, so the necessity to re-write the timeline widget due to the transition to ~GTK-3 looks like a good opportunity to re-do the same basic reasoning a second time, verify decisions taken and improve matters turning out as difficult on first attempt.
 
-So we get a timeline custom widget, which at top level establishes this two-part layout, provides the global scrollbars and integrates custom widget components for both parts. And this top-level timeline widget owns a layout manager, plus it exposes a common view management interface, to be used both from internal components (e.g. zoom widgets within the UI) and from external actors controlling the timeline display from a global level. Also at this global level, we get to define a layout control interface, the TimelineDisplayManager, which has to be implemented within the recursively structured parts of the timeline display, and which is used by the global layout manager to execute its control over the layout as a whole. {{red{Note (11/2016)}}}: if this layout control interface works push or pull style is a decision yet to be worked out during the course of the implementation.
+So we get a timeline custom widget, which at top level establishes this two-part layout, provides the global scrollbars and integrates custom widget components for both parts. And this top-level timeline widget owns a layout manager, plus it exposes a common view management interface, to be used both from internal components (e.g. zoom widgets within the UI) and from external actors controlling the timeline display from a global level. Also at this global level, we get to define a layout control interface, the TimelineDisplayManager, which has to be implemented within the recursively structured parts of the timeline display, and which is used by the global layout manager to exert control over the layout as a whole. {{red{Note (11/2016)}}}: if this layout control interface works push or pull style is a decision yet to be worked out during the course of the implementation.
 
 
 !dealing with nested structures
@@ -3535,11 +3535,15 @@ The structure of the display is extended or altered under two circumstances:
 # the display (style) of some component is expanded or collapsed.
 Here, the "component" relevant for such structural changes is always the UI representation of a track. Beyond that, the layout can also be changed //without changing the display structure,// when some embedded component, be it placement (in the track heads / the patchbay) or a clip, effect or transition, is expanded or collapsed. In such a case, a resizing challenge needs to be directed towards the next enclosing track container.
 
-From these observations we can draw the conclusion, that we'll build a ''local structural model'', to reflect the logical relations between the parts comprising the timeline display. More precisely, these structuring components are not mere model objects, rather they are mediating entities used to guide and organise the actual view entities, which in turn are passive. They are more like a view model, while also bearing some local controller responsibilities. For this reason, we prefer to term these as ''presenters'' -- i.e. TrackPresenter and ClipPresenter. And each of these local representation components holds onto a display context, which generally links it into two different display widget stacks in the two parts of the actual timeline display. Adding a child component thus becomes a rather tricky operation, involving to link possibly two child widgets into two disjoint parent widgets, thereby forming a similar display context for the child presenter. Overall, the guiding idea is that of self similarity: on each level, we have to reproduce the same relations and collaborations as present in the parent level.
+From these observations we can draw the conclusion, that we'll build a ''local structural model'', to reflect the logical relations between the parts comprising the timeline display. More precisely, these structuring components are not mere model objects, rather they are mediating entities used to guide and organise the actual view entities, which in turn are passive. They are more like a view model, while also bearing some local controller responsibilities. For this reason, we prefer to term these as ''presenters'' -- i.e. TrackPresenter and ClipPresenter. And each of these local representation components holds onto a ''display context'', which generally links it //into two different display widget stacks// within the two parts of the actual timeline display. Adding a child component thus becomes a rather tricky operation, involving to link possibly two child widgets into two disjoint parent widgets, thereby forming a similar display context for the child presenter. Overall, the guiding idea is that of self similarity: on each level, we have to reproduce the same relations and collaborations as present in the parent level.
 
 !!!building the timeline representation structure
 It is a fundamental decision within the Lumiera UI that any structure as to be injected as diff by the core. We do not hold a structure or data model within some UI entity and then "interpret" that model into a widget structure -- rather we build the widget structure itself in response to a diff message describing the structure. Especially in the case of timelines, the receiver of those messages is the InteractionDirector, which corresponds to the model root. On being prompted to set up a timeline, it will allocate a {{{TimelineWidget}}} within a suitable timeline docking panel and then attach to the {{{TimelineController}}} embedded within the widget.
+
 {{red{Problem 10/2018}}} how can the InteractionDirector //manage// a timeline while the timeline widget physically resides within the panel? Can we exploit a simliar structure as we did for the error log?
+ * in part yes -- however we introduce an mediating entity, the {{{TimelineGui}}} proxy
+ * it uses a {{{WLink}}} to refer to the actual {{{TimelineWidget}}} -- meaning the widget need not exist, and will detach automatically when destroyed by its holder, which is the {{{TimelinePanel}}}
+ * {{red{TODO 11/2018}}} still need to care for removing a {{{TimelineWidget}}} when the logically managing parent, i.e. the InteractionDirector deletes the {{{TimelineGui}}} proxy
 
 The diff describing and thus assembling the UI representation of a timeline is typically a ''population diff'' -- which means, it is a consolidated complete description of the whole sub-structure rooted below that timeline. Such a population diff is generated as emanation from the respective DiffConstituent.
 
@@ -9122,12 +9126,18 @@ Placements are __resolved__ resulting in an ExplicitPlacement. In most cases thi
 &rarr; [[Definition|Pipe]] and [[handling of Pipes|PipeHandling]]
 
-
+
//mediating entity used to guide and control the track-like nested working space in the timeline display of the UI.//
 
 Similar to the ClipPresenter, from a global angle this element fulfils a model like role, while also guiding and controlling a mostly passive view component implemented as GTK widget. Here, the authority of the presenter over the widget must be total, since display management //needs to work automatically,// due to model updates and mutations arriving as [[diff messages|MutationMessage]]. In addition, this structure is prerequisite for (possibly) implementing UI rendering optimisations, since it allows us to leave out widgets entirely, when it is clear they won't become visible: A ''display evaluation pass'', which is effectively a //tree walk,// consecutively visits each part of the timeline structure, to negotiate its concrete display properties in collaboration with a global TimelineDisplayManager. As a result, the presenter knows where to show its corresponding view, and it knows if to show it at all, allowing to either adjust, create or destroy actual GTK widgets within its local reference frame.
 
 A special twist arises from the fact that track display has to happen aligned and in sync within the two display panes of the timeline at the same time. This means that each TrackPresenter has to hold and manage //two slave display elements,// each of which is inserted within a disjoint hierarchy of display elements. For one, there is the {{{timeline::TrackHeadWidget}}} which in turn renders a {{{PatchbayWidget}}}, and on the other side there is a {{{TrackBody}}} element, which is not strictly a widget of itself, but inserted into our custom drawing {{{timeline::BodyCanvasWidget}}} to manage the custom drawing of the respective track working area.
+
+To deal with this typical problem of recursive programming, we introduce a binding element, the {{{DisplayFrame}}}
+* the TrackPresenter holds a {{{DisplayFrame}}} member, which in turn houses the two head and body widgets. So basically the widgets are allocated within the presenter.
+* however, after constructing the {{{DisplayFrame}}}, it invokes an anchor functor, which is passed in from the recursive outer scope, and which does the actual work of installing those widgets into the apropriate parent widgets
+* but the {{{DisplayFrame}}} itself also exposes such an anchor functor, which can be passed down when (recursively) creating a TrackPresenter for a sub-Track
+* obviously, at top-level, we need to digress from that general pattern -- here it is the {{{timeline::LayoutManager}}} responsible for the display structure of the whole timeline which exposes the necessary anchor function to attach the root track for a timeline.
 
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 7d9f02839..369a7e0c9 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -1302,8 +1302,8 @@ - + @@ -5201,8 +5201,7 @@ »die Timeline«

- - + @@ -5214,8 +5213,7 @@ Aufgabe: populate timeline

- -
+ @@ -5364,8 +5362,7 @@ »der Viewer«

- - +
@@ -5411,8 +5408,7 @@ - - + @@ -5445,8 +5441,7 @@ - - + @@ -5471,8 +5466,7 @@ vorbereitete Grundstrukturen für immer wiederkehrende Setups

- - + @@ -5680,8 +5674,7 @@ Im Zweifelsfall den GTK+ Inspector verwenden!

- - +
@@ -5710,8 +5703,7 @@ CSS genügt

- - +
@@ -5759,8 +5751,7 @@ }

- - +
@@ -5847,8 +5838,7 @@ daß die alte, obsolete Timeline zurückgebaut ist

- - +
@@ -5867,8 +5857,7 @@ bevor die Notification-Facade geöffnet werden konnte

- - +
@@ -5951,8 +5940,7 @@ Allerdings habe ich an der Stelle immer noch GTK-Assertions

- - + @@ -5995,8 +5983,8 @@ - + @@ -6028,8 +6016,7 @@ ist, daß Gio::Application sofort auch gleich eine dBus-Verbindung hochfährt.

- - +
@@ -16656,8 +16643,7 @@ und daher auf "inaktiv" geschaltet ist.

- - +
@@ -16708,8 +16694,7 @@ Habe verifiziert, daß diese Assertion tatsächlich greift

- - +
@@ -17058,8 +17043,7 @@ ...denn das ist das vereinfachte Setup für "einfache" Applikationen

- - + @@ -17386,8 +17370,8 @@ - + @@ -17445,8 +17429,7 @@ das Diff wird auf den Platzhalter angewendet

- - +
@@ -17461,8 +17444,7 @@ dann muß dieses automatisch deregistriert werden

- -
+
@@ -17720,8 +17702,7 @@ und welchen Teil des Verhaltens überlassen wir GTK

- -
+
@@ -17778,8 +17759,7 @@ Details im  TiddlyWiki....

- - +
@@ -17842,8 +17822,7 @@ braucht feste Speicher-Addresse

- - +
@@ -17865,8 +17844,7 @@ und sei es auch bloß über ein Interface!

- - +
@@ -17915,8 +17893,7 @@ - - + @@ -17997,8 +17974,7 @@ ...für die dritte Lösung, die Repräsentation bereits in der Session

- - +
@@ -18014,8 +17990,7 @@ Problem: alle Timelines der Session repräsentieren?

- - + @@ -18103,8 +18078,7 @@ Implementiert würde sie vom jeweiligen Widget

- - +
@@ -18134,8 +18108,7 @@ Der Dekorator würde also auf dem TreeMutator sitzen...

- -
+
@@ -18156,8 +18129,7 @@ Daher gibt es die matchSrc-Operation. Effektiv wird die aber nur bei einem Delete aufgerufen...

- -
+
@@ -18180,8 +18152,7 @@ - - + @@ -18269,8 +18240,7 @@ d.h. eine LUID

- - +
@@ -18316,8 +18286,7 @@ Irgend eine BareEntryID genügt

- - +
@@ -18356,8 +18325,7 @@ ...abstraktes Interface

- - + @@ -18379,8 +18347,7 @@ um die Bindung herzustellen

- - +
@@ -18388,7 +18355,7 @@ - + @@ -18400,6 +18367,12 @@

oder eine Sequenz ohne root-Fork zulassen

+

+ Fazit: wir lassen das nicht zu, erzwingen das bereits per ctor +

+

+ und erwarten abweichend vom Standard ein vollständiges Skelett im INS-Verb +

@@ -18410,8 +18383,10 @@ - - + + + + @@ -18433,16 +18408,14 @@ - - + - + - - + @@ -18481,8 +18454,7 @@ typischerweise wird es das in einem Populationsdiff sofort als Nächstes machen.

- - +
@@ -18500,9 +18472,10 @@

- - + + + @@ -18524,12 +18497,25 @@ "was kann denn schon passieren??"

- -
+
+ + + + + + +

+ und wenn nicht, dann Crash +

+ + +
- + + + @@ -18564,8 +18550,7 @@ - - + @@ -18577,15 +18562,14 @@ einen Fall, der praktisch nie auftritt

- -
+
- + @@ -18605,8 +18589,7 @@ - - + @@ -18624,8 +18607,7 @@ und auch über ihre APIs dem Nutzer diese Unterscheidnung mit aufzwingen

- - + @@ -18652,8 +18634,7 @@ heimlich in den "wird schon nix passieren" Fall zu geraten.

- -
+
@@ -18675,8 +18656,7 @@ Es ist keine weitere Argumentation notwendig.

- - +
@@ -18697,27 +18677,11 @@ "fokussiere Dich"

- -
+
- - - - - - - -

- hier hab ich endlich mal die Gelegenheit, sauber zu arbeiten -

- -
-
- - - + @@ -18744,9 +18708,32 @@ + + + + + + +
    +
  • + die Lösung entspricht der inneren Natur der Dinge +
  • +
  • + sie bedeutet zwar (leider) eine zusätzliche Verpflichtung beim Erzeugen des Diff +
  • +
  • + aber im Fehlerfall scheitern wir halbwegs sauber (Exception, dysfunktionales UI) +
  • +
+ + +
+ +
- + + @@ -18770,9 +18757,10 @@ - - + + +
@@ -18800,6 +18788,208 @@ + + + + + + + + + + + + + + + + + + + + + + +

+ abstrahiert den Zugang zum zugehörigen Widget +

+ + +
+
+ + + + + + + + + + +

+ sub-Frame wird für unsere Kinder erzeugt +

+ + +
+
+ + + + + + + +

+ Fazit: DisplayFrame kann sich auf Parent verlassen +

+ + +
+ +
+
+ + + + + + + + + + +
    +
  • + die Parent/Anker-Widgets werden auf ein Interface reduziert, z.B. Gtk::Container +
  • +
  • + man reicht einen halbfertigen Frame rein, in dem bereits die Anker-Widgets gesetzt sind +
  • +
  • + dann ruft man eine Konstruktor-Funktion auf, die die eigentlichen Widgets erstellt und verdrahtet +
  • +
+ + +
+ +
+ + + + + + +
    +
  • + die Anker-Widgets kommen per Ref auf Interface +
  • +
  • + sie werden explizit an den ctor des Controllers übergeben +
  • +
  • + dieser verwendet die Referenzen, um in einem Streich den DisplayFrame incl. eigentlicher Widgets zu erzeugen +
  • +
+ + +
+ +
+ + + + + + +
    +
  • + der ctor des Controllers bekommt eine Ref. auf den Parent-Frame +
  • +
  • + dieser bietet eine virtuelle Methode, mit der sich die Widgets installieren lassen +
  • +
  • + wenn der Controller den DisplayFrame erzeugt, werden dort unmittelbar die Widgets konstruiert +
  • +
  • + sodann rufen wir die builder-Funktion auf, um sie im Parent-Kontext zu verankern +
  • +
+ + +
+ + + + + + + + +

+ ...aber die gesamte Verankerungs-Aktion läßt sich rein auf Widget-Interface-Ebene machen. +

+

+ D.h. wir rufen den parent-Frame, aber die Implementierung der Methode greift nicht direkt +

+

+ auf die Widgets im Frame zu, sondern delegiert an eine Hilfsfunktion.... +

+

+ STOP. Damit ist die virtuelle Methode nur verschoben +

+ + +
+ +
+ + + + + + +

+ der ctor des DisplayFrame nimmt einen generischen Typ (Template), +

+

+ von dem nur erwartet wird, daß er eine "Verankerungs"-Funktion bietet +

+ + +
+
+ + + + + + +

+ der ctor des DisplayFrame bekommt einen Funktor oder Lambda, +

+

+ welchen er aufruft, um seine neu erstellten Widgets zu verankern +

+ + +
+
+
+ + + + + + + +
+ + +
@@ -18835,8 +19025,7 @@ ...sie verwenden dann ein LabelWidget zur Darstellung

- - +
@@ -18873,8 +19062,7 @@ Verwende das als Leitgedanke, um das Layout zu entwickeln

- - + @@ -20249,8 +20437,7 @@ - - + @@ -20268,8 +20455,7 @@ - - + @@ -20293,8 +20479,7 @@ Basis: DiffConstituent

- - + @@ -20309,8 +20494,7 @@ - - + @@ -20328,8 +20512,7 @@ Aufgabe: inject dummy content

- - + @@ -20445,8 +20628,8 @@
- + @@ -20595,8 +20778,7 @@ erfordert bereits Kenntnis der Innereien

- - +
@@ -27667,8 +27849,7 @@ Implementierung der real-world-Variante fehlt!

- - + @@ -27688,8 +27869,7 @@ wie Session- und State-Managment, Commands etc.

- - +
@@ -32996,8 +33176,7 @@ - - + @@ -35404,8 +35583,7 @@ Denn letzteres ist bei uns eine Grundannahme. Es gibt keine ungefähren Diffs!

- -
+
@@ -38340,8 +38518,7 @@ Aufgabe: produce dummy content

- - +
@@ -38426,8 +38603,7 @@ Ganz prominent fehlt hier also z.B: MIDI

- - +
@@ -38447,8 +38623,7 @@ die Aufgrund von Klassifikationen automatisch bereits existieren

- - +
@@ -38631,9 +38806,9 @@
- - + + @@ -39317,8 +39492,7 @@ sofern das Widget mit entsprechendem Modus eingefügt wurde

- - +
@@ -39364,8 +39538,7 @@ context->add_class("ohMy");

- - + @@ -39460,8 +39633,7 @@ oder ist es eine Vererbungs-Hierarchie, wie sie für das CSS-Styling benötigt wird?

- - +
@@ -39506,16 +39678,15 @@ - - +
+ - @@ -39577,8 +39748,7 @@ Alles in ein Framework zwingen. Alternativlos, capisce?

- - +
@@ -39616,8 +39786,7 @@ welche ggfs C++ - Exceptions fängt

- - +
@@ -39649,8 +39818,7 @@ Nur das ist der Grund. Es geht gar nicht um die Event-Loop

- - +
@@ -39675,8 +39843,7 @@ - - +
@@ -39708,8 +39875,7 @@ - - +
@@ -39830,8 +39996,7 @@ }

- - + @@ -39912,8 +40077,7 @@ }

- - + @@ -39986,8 +40150,7 @@ aber macht in etwa die gleichen Operationen

- - +
@@ -40008,8 +40171,7 @@ Gtk-Main verwendet inzwischen den gleichen Mechanismus

- -
+
@@ -40044,8 +40206,7 @@ Das ist eine subtile Falle.

- - +
@@ -40087,8 +40248,7 @@ alles das nicht aus dem GUI-Thread heraus geschieht

- - +
@@ -40154,8 +40314,7 @@ ...ob beim Expand/Collapse das umschließende Widget resized werden soll

- - +
@@ -40167,8 +40326,7 @@ ob eingeklappt oder ausgeklappt

- -
+
@@ -40186,8 +40344,7 @@ was dazu führt, daß das Kind stets allen verfügbaren Platz nimmt

- - +
@@ -40272,8 +40429,7 @@ Siehe Beschreibung im Beispiel/Tutorial

- - +
@@ -40312,8 +40468,7 @@ ....how does the event dispatching deal with partially covered widgets

- - +
@@ -40325,8 +40480,7 @@ ...for embedded widgets

- -
+
@@ -40372,8 +40526,7 @@ i.e. the enclosing parent widget also gets a chance to redraw itself

- - +
@@ -40404,8 +40557,7 @@ asked on stackoverflow...

- - +
@@ -40424,8 +40576,7 @@ by printing values from the on_draw() callback

- - +
@@ -40589,8 +40740,7 @@ Kind-Widgets noch gar nicht festgelegt war (denn das passiert erst beim draw).

- - +
@@ -40644,8 +40794,7 @@ und auch ein Signal für Parse-Fehler anschließt

- - +
@@ -40690,8 +40839,7 @@ Beachte: der Text-Cursor (Marker "insert") hat right gravity

- - +
@@ -40788,8 +40936,7 @@ The grip is implemented as a GdlDockItemGrip

- - +
@@ -40880,8 +41027,7 @@ kann eines der Templates im Zyklus vorrübergehend als "incomplete" gelten.

- - +
@@ -40905,8 +41051,7 @@ Konsequenz: man wählt dann z.B. eine subtil falsche Spezialisierung.

- -
+
@@ -40960,8 +41105,7 @@ selber aus einem statischen Initialisierungs-Kontext heraus erfolgt.

- - +
@@ -41163,8 +41307,7 @@   }

- - +
@@ -41431,8 +41574,7 @@ Query<RES>::resolveBy

- - +
@@ -41469,8 +41611,7 @@ sonst kommt Doxygen durcheinander

- -
+
@@ -41499,8 +41640,7 @@ wird hier kein Link erzeugt

- - +
@@ -41532,8 +41672,7 @@ Die Icon-Größen ergeben sich aus den Boxes auf 'plate'

- - +
@@ -41558,8 +41697,7 @@ ...im Besonderen die guten Diagramme für Pulse, ALSA und Jack

- - +
@@ -41707,8 +41845,7 @@ "-Wl,-rpath-link=target/modules"

- - +
@@ -41721,8 +41858,7 @@ laufen wieder alle

- - + @@ -41739,8 +41875,7 @@ test.sh Zeile 138

- - +
@@ -41794,8 +41929,7 @@ und wir verbringen unsere Zeit mit contention

- - +
@@ -41810,8 +41944,7 @@ ist klar, hab ich gebrochen

- - + @@ -41840,8 +41973,7 @@ Vorher hatte ich erste Kollisionen nach 25000 Nummern

- - +
@@ -41893,8 +42025,7 @@ Aug 10 04:51:39 flaucher kernel: traps: test-suite[8249] trap int3 ip:7ffff7deb241 sp:7fffffffe5c8 error:0

- -
+ @@ -41961,8 +42092,7 @@ bison dejagnu flex gobjc libncurses5-dev libreadline-dev liblzma-dev libbabeltrace-dev libbabeltrace-ctf-dev python3-dev

- - +
@@ -42012,8 +42142,7 @@ au weia LEUTE!

- - +
@@ -42053,8 +42182,7 @@ und tatsächlich: das ist daneben, GCC hat Recht!

- - +
@@ -42067,8 +42195,7 @@ aktualisieren und neu bauen

- - +
@@ -42098,8 +42225,7 @@ wähle Kompatibiltät genau so, daß Ubuntu-Trusty noch unterstützt wird.

- - + @@ -42146,8 +42272,7 @@ Ich meine also: zu Beginn vom Build sollte das Buildsystem einmal eine Infozeile ausgeben

- - +
@@ -42159,8 +42284,7 @@ ...denn die stören jeweils beim erzeugen eines Hotfix/Patch im Paketbau per dpkg --commit

- -
+
@@ -42217,8 +42341,7 @@ Doku durchkämmen nach Müll

- - + @@ -42235,8 +42358,7 @@ WICHTIG: keine vorgreifende Infor publizieren!!!!!

- - +
@@ -42255,8 +42377,7 @@ insgesamt sorgfältig durchlesen

- - + @@ -42276,8 +42397,7 @@ knappe Kennzeichnung des Releases in den Kommentar

- - + @@ -42304,8 +42424,7 @@ denn wir wollen keine DEB-Info im Master haben!

- - + @@ -42331,8 +42450,7 @@ die unmittelbaren Release-Dokumente durchgehen

- - + @@ -42359,8 +42477,7 @@ Sollte konfliktfrei sein

- - +
@@ -42393,8 +42510,7 @@ ...das heißt bauen und hochladen

- - + @@ -42461,8 +42577,7 @@ unter Debian/Jessie wird das ignoriert

- - +
@@ -42490,8 +42605,7 @@ Die Wahrscheinlichkeit, daß irgend jemand Lumiera unter Ubuntu/Trusty installieren möchte, erscheint mir akademisch

- -
+
@@ -42527,8 +42641,7 @@ in lib/hash-standard.hpp

- - +
@@ -42563,8 +42676,7 @@ es gibt Probleme beim Linken mit den Boost-Libraries, die auf Ubuntu/wily mit gcc-5 gebaut sind.

- -
+
@@ -42582,8 +42694,7 @@ Wichtig: hier nur was wirklich gebaut ist und funktioniert!

- - +
@@ -42623,8 +42734,7 @@ bestehen, aber irgendwann müssen wir das schon glattziehen

- - +
@@ -42672,8 +42782,7 @@ seit gcc-4.8 ist kein static_assert mehr in der STDlib

- - +
@@ -42761,8 +42870,7 @@ END

- - + @@ -42775,8 +42883,7 @@ for I in `seq 1 50`; do target/test-suite CallQueue_test; done

- - +
@@ -42796,8 +42903,7 @@ und eine Doxygen-Seite im Browser geladen

- - +
@@ -42828,8 +42934,7 @@ weil sich die Threads gegenseitig ihre Counter inkrementieren.

- - +
From 2d4e58db028b34631aae859fde42f3989c8e55c6 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 27 Oct 2018 17:27:29 +0200 Subject: [PATCH 162/178] Timeline: consider how to manage size and layout of timeline contents 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 --- doc/technical/code/codingGuidelines.txt | 19 +- src/gui/timeline/timeline-controller.cpp | 4 +- src/gui/timeline/timeline-controller.hpp | 4 +- src/gui/timeline/timeline-gui.cpp | 6 +- ...layout-manager.cpp => timeline-layout.cpp} | 14 +- ...layout-manager.hpp => timeline-layout.hpp} | 21 +- src/gui/timeline/timeline-widget.cpp | 4 +- src/gui/timeline/timeline-widget.hpp | 6 +- wiki/thinkPad.ichthyo.mm | 275 +++++++++++++++++- 9 files changed, 315 insertions(+), 38 deletions(-) rename src/gui/timeline/{layout-manager.cpp => timeline-layout.cpp} (82%) rename src/gui/timeline/{layout-manager.hpp => timeline-layout.hpp} (91%) diff --git a/doc/technical/code/codingGuidelines.txt b/doc/technical/code/codingGuidelines.txt index 8ec17e34e..36b6ea44b 100644 --- a/doc/technical/code/codingGuidelines.txt +++ b/doc/technical/code/codingGuidelines.txt @@ -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. diff --git a/src/gui/timeline/timeline-controller.cpp b/src/gui/timeline/timeline-controller.cpp index 6bf6d18ce..bd18fc694 100644 --- a/src/gui/timeline/timeline-controller.cpp +++ b/src/gui/timeline/timeline-controller.cpp @@ -40,7 +40,7 @@ #include "gui/gtk-base.hpp" #include "include/ui-protocol.hpp" -#include "gui/timeline/layout-manager.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" @@ -82,7 +82,7 @@ namespace timeline { - TimelineController::TimelineController (ID identity, ID trackID, ctrl::BusTerm& nexus, LayoutManager& layoutManager) + 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_{} diff --git a/src/gui/timeline/timeline-controller.hpp b/src/gui/timeline/timeline-controller.hpp index 85ca2d9bf..eb157626e 100644 --- a/src/gui/timeline/timeline-controller.hpp +++ b/src/gui/timeline/timeline-controller.hpp @@ -72,7 +72,7 @@ namespace timeline { using std::string; class TrackPresenter; - class LayoutManager; + class TimelineLayout; class MarkerWidget; @@ -99,7 +99,7 @@ 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, LayoutManager&); + TimelineController (ID identity, ID trackID, ctrl::BusTerm& nexus, TimelineLayout&); ~TimelineController(); diff --git a/src/gui/timeline/timeline-gui.cpp b/src/gui/timeline/timeline-gui.cpp index 6e78f04c5..f68a385e7 100644 --- a/src/gui/timeline/timeline-gui.cpp +++ b/src/gui/timeline/timeline-gui.cpp @@ -73,7 +73,11 @@ namespace timeline { , rootTrackID_{rr.rootTrackID_} { } - TimelineGui::~TimelineGui() { } + TimelineGui::~TimelineGui() + { + if (this->isActive()) + TODO ("cascading destruction of the TimelineWidget"); /////////////////////////////////////////////TICKET 1016 : implement sane unwinding + } /** diff --git a/src/gui/timeline/layout-manager.cpp b/src/gui/timeline/timeline-layout.cpp similarity index 82% rename from src/gui/timeline/layout-manager.cpp rename to src/gui/timeline/timeline-layout.cpp index e266344a8..a57284a1b 100644 --- a/src/gui/timeline/layout-manager.cpp +++ b/src/gui/timeline/timeline-layout.cpp @@ -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 @@ -21,7 +21,7 @@ * *****************************************************/ -/** @file layout-manager.cpp +/** @file timeline-layout.cpp ** Implementation details of global timeline layout management. ** ** @todo WIP-WIP-WIP as of 12/2016 @@ -31,7 +31,7 @@ #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" @@ -59,15 +59,17 @@ namespace timeline { - LayoutManager::~LayoutManager() { } + TimelineLayout::~TimelineLayout() { } - LayoutManager::LayoutManager () + TimelineLayout::TimelineLayout () + : headerPane_{} + , bodyCanvas_{} { } void - LayoutManager::installRootTrack(TrackHeadWidget& head, TrackBody& body) + TimelineLayout::installRootTrack(TrackHeadWidget& head, TrackBody& body) { UNIMPLEMENTED ("attach the widgets for the root track display"); } diff --git a/src/gui/timeline/layout-manager.hpp b/src/gui/timeline/timeline-layout.hpp similarity index 91% rename from src/gui/timeline/layout-manager.hpp rename to src/gui/timeline/timeline-layout.hpp index 464e9202f..b7b714f60 100644 --- a/src/gui/timeline/layout-manager.hpp +++ b/src/gui/timeline/timeline-layout.hpp @@ -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 @@ -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. @@ -79,10 +79,12 @@ */ -#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" @@ -101,11 +103,14 @@ namespace timeline { /** * @todo WIP-WIP as of 12/2016 */ - class LayoutManager + class TimelineLayout { + HeaderPaneWidget headerPane_; + BodyCanvasWidget bodyCanvas_; + public: - LayoutManager (); - ~LayoutManager(); + TimelineLayout (); + ~TimelineLayout(); void installRootTrack(TrackHeadWidget&,TrackBody&); @@ -115,4 +120,4 @@ namespace timeline { }}// namespace gui::timeline -#endif /*GUI_TIMELINE_LAYOUT_MANAGER_H*/ +#endif /*GUI_TIMELINE_TIMELINE_LAYOUT_H*/ diff --git a/src/gui/timeline/timeline-widget.cpp b/src/gui/timeline/timeline-widget.cpp index 22ee125c5..be1d6f0fe 100644 --- a/src/gui/timeline/timeline-widget.cpp +++ b/src/gui/timeline/timeline-widget.cpp @@ -33,7 +33,7 @@ #include "gui/gtk-base.hpp" #include "gui/timeline/timeline-widget.hpp" #include "gui/timeline/timeline-controller.hpp" -#include "gui/timeline/layout-manager.hpp" +#include "gui/timeline/timeline-layout.hpp" //#include "gui/workspace/workspace-window.hpp" //#include "gui/ui-bus.hpp" @@ -69,7 +69,7 @@ namespace timeline { TimelineWidget::TimelineWidget (BusTerm::ID identity, BusTerm::ID trackID, BusTerm& nexus) : TimelinePage{} - , layout_{new LayoutManager} + , layout_{new TimelineLayout} , control_{new TimelineController{identity, trackID, nexus, *layout_}} { UNIMPLEMENTED ("build the timeline UI"); diff --git a/src/gui/timeline/timeline-widget.hpp b/src/gui/timeline/timeline-widget.hpp index 98290fb6f..974ed869f 100644 --- a/src/gui/timeline/timeline-widget.hpp +++ b/src/gui/timeline/timeline-widget.hpp @@ -62,6 +62,7 @@ #include "lib/time/timevalue.hpp" #include "lib/diff/diff-mutable.hpp" +#include "lib/nocopy.hpp" //#include //#include @@ -78,7 +79,7 @@ namespace timeline { using ctrl::BusTerm; class TimelineController; - class LayoutManager; + class TimelineLayout; /** * Interface: GUI page holding a timeline display @@ -106,8 +107,9 @@ namespace timeline { */ class TimelineWidget : public TimelinePage + , util::NonCopyable { - std::unique_ptr layout_; + std::unique_ptr layout_; std::unique_ptr control_; public: diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 369a7e0c9..bd89a5fbd 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -18288,11 +18288,44 @@ + + + + + + + + + + + + +

+ ...da wir unterstellen, daß das Gegenstück im Session-Modell, +

+

+ von dem der Diff ausgeht, ebenfalls den Root-Track als festes Attribut hält. +

+

+ Daher sollte eine inkompatible Strukturänderung überhaupt nicht auftreten können +

+ + +
+
+
+ + + + + + + - + @@ -18308,6 +18341,8 @@ + + @@ -18330,7 +18365,7 @@ - + @@ -18351,11 +18386,14 @@ + + - + + - + @@ -18768,6 +18806,26 @@ + + + + + + + + + + + + + + + + + + + + @@ -18993,11 +19051,26 @@ - + + - - + + + + + + + + + + + + + + + + @@ -19040,7 +19113,86 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -39458,13 +39610,15 @@ - - - - + + + + + + @@ -39474,6 +39628,9 @@ + + + @@ -40411,6 +40568,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...führen automatisch dazu, daß das Widget +

+

+ ggfs. neu gemapped und invalidiert wird, woraufhin es neu gezeichnet wird +

+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + @@ -40433,6 +40657,33 @@ + + + + + + + + + + + +

+ ist of einfacher und der bevorzugete Weg. +

+

+ Im Besonderen kann man sich an Signale anderer Widgets anhängen +

+ + +
+ + + + + + +
From c212ce94ca738af65f31ec753c64c5dc70edfbad Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 28 Oct 2018 01:30:02 +0200 Subject: [PATCH 163/178] Timeline: setup basic widget structure --- src/gui/timeline/body-canvas-widget.cpp | 16 ++++++++--- src/gui/timeline/body-canvas-widget.hpp | 14 ++++++++-- src/gui/timeline/header-pane-widget.cpp | 7 +++-- src/gui/timeline/header-pane-widget.hpp | 1 + src/gui/timeline/timeline-layout.cpp | 7 +++-- src/gui/timeline/timeline-layout.hpp | 7 +++-- src/gui/timeline/timeline-widget.cpp | 2 +- wiki/thinkPad.ichthyo.mm | 36 +++++++++++++++++++++++++ 8 files changed, 75 insertions(+), 15 deletions(-) diff --git a/src/gui/timeline/body-canvas-widget.cpp b/src/gui/timeline/body-canvas-widget.cpp index 52d4e8383..ddbf5f2df 100644 --- a/src/gui/timeline/body-canvas-widget.cpp +++ b/src/gui/timeline/body-canvas-widget.cpp @@ -57,17 +57,25 @@ namespace timeline { + 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() - { - } - diff --git a/src/gui/timeline/body-canvas-widget.hpp b/src/gui/timeline/body-canvas-widget.hpp index faf4ca6d4..a979e3060 100644 --- a/src/gui/timeline/body-canvas-widget.hpp +++ b/src/gui/timeline/body-canvas-widget.hpp @@ -70,17 +70,27 @@ namespace gui { namespace timeline { + class TimelineCanvas + : public Gtk::Layout + { + + }; + + /** * @todo WIP-WIP as of 12/2016 */ class BodyCanvasWidget + : public Gtk::ScrolledWindow { + TimelineCanvas canvas_; + public: BodyCanvasWidget(); ~BodyCanvasWidget(); - + private:/* ===== Internals ===== */ - + }; diff --git a/src/gui/timeline/header-pane-widget.cpp b/src/gui/timeline/header-pane-widget.cpp index 6590e3e4e..c16aec2ec 100644 --- a/src/gui/timeline/header-pane-widget.cpp +++ b/src/gui/timeline/header-pane-widget.cpp @@ -58,16 +58,15 @@ namespace timeline { + HeaderPaneWidget::~HeaderPaneWidget() { } + HeaderPaneWidget::HeaderPaneWidget () + : Gtk::Box{Gtk::ORIENTATION_VERTICAL} { } - HeaderPaneWidget::~HeaderPaneWidget() - { - } - diff --git a/src/gui/timeline/header-pane-widget.hpp b/src/gui/timeline/header-pane-widget.hpp index ef4bf8a29..a9feeaf16 100644 --- a/src/gui/timeline/header-pane-widget.hpp +++ b/src/gui/timeline/header-pane-widget.hpp @@ -60,6 +60,7 @@ namespace timeline { * @todo WIP-WIP as of 12/2016 */ class HeaderPaneWidget + : public Gtk::Box { public: HeaderPaneWidget(); diff --git a/src/gui/timeline/timeline-layout.cpp b/src/gui/timeline/timeline-layout.cpp index a57284a1b..b2c527eec 100644 --- a/src/gui/timeline/timeline-layout.cpp +++ b/src/gui/timeline/timeline-layout.cpp @@ -61,10 +61,13 @@ namespace timeline { TimelineLayout::~TimelineLayout() { } - TimelineLayout::TimelineLayout () - : headerPane_{} + TimelineLayout::TimelineLayout (Gtk::Paned& topLevelContainer) + : paneSplitPosition_{topLevelContainer.property_position()} + , headerPane_{} , bodyCanvas_{} { + topLevelContainer.add1 (headerPane_); + topLevelContainer.add2 (bodyCanvas_); } diff --git a/src/gui/timeline/timeline-layout.hpp b/src/gui/timeline/timeline-layout.hpp index b7b714f60..892958014 100644 --- a/src/gui/timeline/timeline-layout.hpp +++ b/src/gui/timeline/timeline-layout.hpp @@ -105,14 +105,17 @@ namespace timeline { */ class TimelineLayout { + Glib::PropertyProxy paneSplitPosition_; + HeaderPaneWidget headerPane_; BodyCanvasWidget bodyCanvas_; + public: - TimelineLayout (); + TimelineLayout (Gtk::Paned&); ~TimelineLayout(); - void installRootTrack(TrackHeadWidget&,TrackBody&); + void installRootTrack (TrackHeadWidget&,TrackBody&); private:/* ===== Internals ===== */ diff --git a/src/gui/timeline/timeline-widget.cpp b/src/gui/timeline/timeline-widget.cpp index be1d6f0fe..1c8b62dd1 100644 --- a/src/gui/timeline/timeline-widget.cpp +++ b/src/gui/timeline/timeline-widget.cpp @@ -69,7 +69,7 @@ namespace timeline { TimelineWidget::TimelineWidget (BusTerm::ID identity, BusTerm::ID trackID, BusTerm& nexus) : TimelinePage{} - , layout_{new TimelineLayout} + , layout_{new TimelineLayout{*this}} , control_{new TimelineController{identity, trackID, nexus, *layout_}} { UNIMPLEMENTED ("build the timeline UI"); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index bd89a5fbd..88d3146d4 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -19058,9 +19058,17 @@ + + + + + + + + @@ -35671,6 +35679,34 @@ + + + + + + + + + + + + + + + +

+ ...wie bekommt man dann den konvertierten Wert 'raus. +

+

+ Visitor ist entweder void, oder bool +

+ + +
+
+
+
+
From 3dd3fc7810da68a7cc9d9d99d21877221e107b3f Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 28 Oct 2018 01:56:24 +0200 Subject: [PATCH 164/178] Timeline: decide upon the organisation of the header pane we'll uses a recursive structure here, based on nested grids --- src/gui/timeline/header-pane-widget.cpp | 6 +++- src/gui/timeline/header-pane-widget.hpp | 11 +++++++ src/gui/timeline/navigator-widget.cpp | 13 ++++----- src/gui/timeline/navigator-widget.hpp | 1 + src/gui/timeline/patchbay-widget.cpp | 12 ++++---- src/gui/timeline/patchbay-widget.hpp | 5 ++++ wiki/thinkPad.ichthyo.mm | 39 ++++++++++++++++++------- 7 files changed, 62 insertions(+), 25 deletions(-) diff --git a/src/gui/timeline/header-pane-widget.cpp b/src/gui/timeline/header-pane-widget.cpp index c16aec2ec..20ff839af 100644 --- a/src/gui/timeline/header-pane-widget.cpp +++ b/src/gui/timeline/header-pane-widget.cpp @@ -61,9 +61,13 @@ namespace timeline { HeaderPaneWidget::~HeaderPaneWidget() { } - HeaderPaneWidget::HeaderPaneWidget () + HeaderPaneWidget::HeaderPaneWidget() : Gtk::Box{Gtk::ORIENTATION_VERTICAL} + , navigator_{} + , patchbay_{} { + this->pack_start (navigator_, Gtk::PACK_SHRINK); + this->pack_start (patchbay_, Gtk::PACK_EXPAND_WIDGET); } diff --git a/src/gui/timeline/header-pane-widget.hpp b/src/gui/timeline/header-pane-widget.hpp index a9feeaf16..13fa742f9 100644 --- a/src/gui/timeline/header-pane-widget.hpp +++ b/src/gui/timeline/header-pane-widget.hpp @@ -35,6 +35,12 @@ ** 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](\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" @@ -62,6 +70,9 @@ namespace timeline { class HeaderPaneWidget : public Gtk::Box { + NavigatorWidget navigator_; + PatchbayWidget patchbay_; + public: HeaderPaneWidget(); ~HeaderPaneWidget(); diff --git a/src/gui/timeline/navigator-widget.cpp b/src/gui/timeline/navigator-widget.cpp index 8559fbe65..eade9c30f 100644 --- a/src/gui/timeline/navigator-widget.cpp +++ b/src/gui/timeline/navigator-widget.cpp @@ -60,14 +60,13 @@ namespace timeline { + + NavigatorWidget::~NavigatorWidget() { } + + NavigatorWidget::NavigatorWidget () - { - } - - - NavigatorWidget::~NavigatorWidget() - { - } + : Gtk::Grid{} + { } diff --git a/src/gui/timeline/navigator-widget.hpp b/src/gui/timeline/navigator-widget.hpp index 4eb021125..791798a30 100644 --- a/src/gui/timeline/navigator-widget.hpp +++ b/src/gui/timeline/navigator-widget.hpp @@ -58,6 +58,7 @@ namespace timeline { * @todo WIP-WIP as of 12/2016 */ class NavigatorWidget + : public Gtk::Grid { public: NavigatorWidget (); diff --git a/src/gui/timeline/patchbay-widget.cpp b/src/gui/timeline/patchbay-widget.cpp index 32496d6b7..62c10a336 100644 --- a/src/gui/timeline/patchbay-widget.cpp +++ b/src/gui/timeline/patchbay-widget.cpp @@ -60,14 +60,12 @@ namespace timeline { + PatchbayWidget::~PatchbayWidget() { } + + PatchbayWidget::PatchbayWidget () - { - } - - - PatchbayWidget::~PatchbayWidget() - { - } + : Gtk::Grid{} + { } diff --git a/src/gui/timeline/patchbay-widget.hpp b/src/gui/timeline/patchbay-widget.hpp index f172d10be..2d232afaf 100644 --- a/src/gui/timeline/patchbay-widget.hpp +++ b/src/gui/timeline/patchbay-widget.hpp @@ -59,9 +59,14 @@ namespace timeline { /** + * 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::Grid { public: PatchbayWidget (); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 88d3146d4..6d9e8cfae 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -19056,17 +19056,36 @@ - + + - + + - - - - + + + + - - + + + + + + + + + + + + + + + + + + + @@ -19075,8 +19094,8 @@ - - + + From 8803af1a0a2b151c4b3f2d39d155d6e6ffad7af9 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 28 Oct 2018 18:56:04 +0100 Subject: [PATCH 165/178] Timeline: further steps towards attaching the widget structure --- src/gui/dialog/test-control.hpp | 2 +- src/gui/timeline/body-canvas-widget.cpp | 16 +++++ src/gui/timeline/body-canvas-widget.hpp | 5 ++ src/gui/timeline/header-pane-widget.cpp | 77 ------------------------- src/gui/timeline/header-pane-widget.hpp | 27 ++++++++- src/gui/timeline/patchbay-widget.cpp | 16 +++++ src/gui/timeline/patchbay-widget.hpp | 11 +++- src/gui/timeline/timeline-layout.cpp | 11 +++- src/gui/timeline/timeline-layout.hpp | 6 +- src/gui/timeline/timeline-widget.hpp | 10 ++-- wiki/renderengine.html | 56 +++++++++++------- wiki/thinkPad.ichthyo.mm | 44 ++++++++++++-- 12 files changed, 163 insertions(+), 118 deletions(-) delete mode 100644 src/gui/timeline/header-pane-widget.cpp diff --git a/src/gui/dialog/test-control.hpp b/src/gui/dialog/test-control.hpp index 37631f597..d17f6b857 100644 --- a/src/gui/dialog/test-control.hpp +++ b/src/gui/dialog/test-control.hpp @@ -351,9 +351,9 @@ namespace dialog { // construct and wire the pages... notebook_.buildPage (_("#1099"), uiBus_); notebook_.buildPage (_("Populate"), uiBus_); - notebook_.set_current_page(-1); show_all(); + notebook_.set_current_page(-1); } }; diff --git a/src/gui/timeline/body-canvas-widget.cpp b/src/gui/timeline/body-canvas-widget.cpp index ddbf5f2df..10e06f7be 100644 --- a/src/gui/timeline/body-canvas-widget.cpp +++ b/src/gui/timeline/body-canvas-widget.cpp @@ -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" @@ -77,6 +78,21 @@ namespace timeline { + /** + * 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& rootTrackHead) + { + UNIMPLEMENTED ("how actually to represent the track bode on the canvas"); + } + + }}// namespace gui::timeline diff --git a/src/gui/timeline/body-canvas-widget.hpp b/src/gui/timeline/body-canvas-widget.hpp index a979e3060..47d68e5e2 100644 --- a/src/gui/timeline/body-canvas-widget.hpp +++ b/src/gui/timeline/body-canvas-widget.hpp @@ -69,6 +69,8 @@ namespace gui { namespace timeline { + class TrackBody; + class TimelineCanvas : public Gtk::Layout @@ -89,6 +91,9 @@ namespace timeline { BodyCanvasWidget(); ~BodyCanvasWidget(); + /** @internal Initially install the contents corresponding to the root track fork */ + void installForkRoot (TrackBody& rootTrackBody); + private:/* ===== Internals ===== */ }; diff --git a/src/gui/timeline/header-pane-widget.cpp b/src/gui/timeline/header-pane-widget.cpp deleted file mode 100644 index 20ff839af..000000000 --- a/src/gui/timeline/header-pane-widget.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/* - HeaderPaneWidget - display pane for track headers within the timeline - - Copyright (C) Lumiera.org - 2016, Hermann Vosseler - - 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 header-pane-widget.cpp - ** Implementation of the track header pane within the timeline UI. - ** - ** @todo WIP-WIP-WIP as of 12/2016 - ** - */ - - -#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 -//#include - - - -//using util::_Fmt; -//using util::contains; -//using Gtk::Widget; -//using sigc::mem_fun; -//using sigc::ptr_fun; -//using std::cout; -//using std::endl; - - -namespace gui { -namespace timeline { - - - - - HeaderPaneWidget::~HeaderPaneWidget() { } - - - HeaderPaneWidget::HeaderPaneWidget() - : Gtk::Box{Gtk::ORIENTATION_VERTICAL} - , navigator_{} - , patchbay_{} - { - this->pack_start (navigator_, Gtk::PACK_SHRINK); - this->pack_start (patchbay_, Gtk::PACK_EXPAND_WIDGET); - } - - - - - -}}// namespace gui::timeline diff --git a/src/gui/timeline/header-pane-widget.hpp b/src/gui/timeline/header-pane-widget.hpp index 13fa742f9..3b5b6add0 100644 --- a/src/gui/timeline/header-pane-widget.hpp +++ b/src/gui/timeline/header-pane-widget.hpp @@ -63,6 +63,8 @@ namespace gui { namespace timeline { + class TrackHeadWidget; + /** * @todo WIP-WIP as of 12/2016 @@ -74,9 +76,28 @@ namespace timeline { PatchbayWidget patchbay_; public: - HeaderPaneWidget(); - ~HeaderPaneWidget(); - + ~HeaderPaneWidget() { } + + HeaderPaneWidget() + : Gtk::Box{Gtk::ORIENTATION_VERTICAL} + , navigator_{} + , patchbay_{} + { + 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 ===== */ }; diff --git a/src/gui/timeline/patchbay-widget.cpp b/src/gui/timeline/patchbay-widget.cpp index 62c10a336..01cfa6617 100644 --- a/src/gui/timeline/patchbay-widget.cpp +++ b/src/gui/timeline/patchbay-widget.cpp @@ -68,6 +68,22 @@ namespace timeline { { } + /** + * 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 + PatchbayWidget::installFork (TrackHeadWidget& rootTrackHead) + { + UNIMPLEMENTED ("how actually to represent the track in the patchbay"); + } + }}// namespace gui::timeline diff --git a/src/gui/timeline/patchbay-widget.hpp b/src/gui/timeline/patchbay-widget.hpp index 2d232afaf..42c33b4f1 100644 --- a/src/gui/timeline/patchbay-widget.hpp +++ b/src/gui/timeline/patchbay-widget.hpp @@ -56,7 +56,9 @@ namespace gui { namespace timeline { - + + class TrackHeadWidget; + /** * Header pane control area corresponding to a Track with nested child Tracks. @@ -69,9 +71,12 @@ namespace timeline { : public Gtk::Grid { public: - PatchbayWidget (); + PatchbayWidget(); ~PatchbayWidget(); - + + /** @internal Initially install the contents corresponding to this track fork */ + void installFork (TrackHeadWidget& rootTrackHead); + private:/* ===== Internals ===== */ }; diff --git a/src/gui/timeline/timeline-layout.cpp b/src/gui/timeline/timeline-layout.cpp index b2c527eec..a4fbface5 100644 --- a/src/gui/timeline/timeline-layout.cpp +++ b/src/gui/timeline/timeline-layout.cpp @@ -71,10 +71,19 @@ namespace timeline { } + /** + * 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) { - UNIMPLEMENTED ("attach the widgets for the root track display"); + headerPane_.installForkRoot (head); + bodyCanvas_.installForkRoot (body); } diff --git a/src/gui/timeline/timeline-layout.hpp b/src/gui/timeline/timeline-layout.hpp index 892958014..2f513eb3c 100644 --- a/src/gui/timeline/timeline-layout.hpp +++ b/src/gui/timeline/timeline-layout.hpp @@ -101,7 +101,10 @@ namespace timeline { /** - * @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 TimelineLayout { @@ -115,6 +118,7 @@ namespace timeline { TimelineLayout (Gtk::Paned&); ~TimelineLayout(); + /** @internal anchor the display of the root track into the two display panes */ void installRootTrack (TrackHeadWidget&,TrackBody&); private:/* ===== Internals ===== */ diff --git a/src/gui/timeline/timeline-widget.hpp b/src/gui/timeline/timeline-widget.hpp index 974ed869f..12f39757e 100644 --- a/src/gui/timeline/timeline-widget.hpp +++ b/src/gui/timeline/timeline-widget.hpp @@ -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](\ref 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 diff --git a/wiki/renderengine.html b/wiki/renderengine.html index ec3356728..85fd96bf2 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -2691,12 +2691,12 @@ As starting point, {{red{in winter 2016/17}}} the old (broken) timeline panel wa After that initial experiments, my focus shifted to the still unsatisfactory top-level UI structure, and I am working towards an initial integration with Proc-Layer since then.
-
+
//The representation of a [[media clip|Clip]] for manipulation by the user within the UI.//
 Within Lumiera, a clip is conceived as a //chunk of media,// which can be handled in compound. Clip as such is an abstract concept, which is treated with minimal assumptions...
-* we know that a clip has //media content,// which need not be uniform and can be inherently structured (e.g. several media, several channels)
+* we know that a clip has //media content,// which need not be uniform and can be inherently structured (e.g. several media, several channels, a processing function)
 * like anything within a timeline, a clip has a temporal extension (but not necessarily finite; it can //not be zero,// but it can be infinite)
-* by virtue of [[Placement]], a clip acquires a time position.
+* by virtue of [[Placement]], a clip acquires a time position. (&rarr; GuiPlacementDisplay)
 Due to this unspecific nature, a clip might take on various //appearances// within the UI.
 
 !clip appearances
@@ -3393,6 +3393,15 @@ The most fundamental principle is that of ''subsidiarity'': we understand both &
 Based on these foundations, we shape and form the core part of the interface, which is the [[timeline display|GuiTimelineView]]
 
+
+
A cross-cutting and somewhat tricky concern is how to represent and expose the [[MObject Placements|Placement]] within the UI.
+For one, a Placement is a set of rules picked up from enclosing scopes -- and it is one of the most fundamental traits of Lumiera that the user is able to edit those placement rules. Yet the combination and final application of those rules also materialises itself into actual properties of the objects of the edit session -- most notably the time position of any element. Consequently, parts of the Placement are unfolded to appear as properties of the placed object, as far as the UI representation is concerned. However, Placement as a generic building block is prominently exposed, insofar pretty much every entity you'll see in the UI has the ability to "edit its placement". This is indicated by a characteristic Icon decoration, leading to a common placement editor, where the user can
+* see all the explicitly given locating pins
+* see the effective, resulting ExplicitPlacement
+* add and manipulate existing rules
+To support this generic setup, pretty much every UI element needs to be outfitted with a "placement" attribute, to reflect those distinct information to be exposed in aforementioned placement edit UI.
+
+
Starting up the GUI is optional and is considered part of the Application start/stop and lifecycle.
 * main and {{{lumiera::AppState}}} activate the lifecyle methods on the ~GuiSubsysDescriptor, accessible via the GuiFacade
@@ -3448,7 +3457,7 @@ Now, since we build our UI on the notion of mapping session contents via a messa
 
 In any case, this is an advanced topic, and nowhere near trivial. It seems reasonable to reject opening duplicate timeline presentations as a first step, and then address this topic way later, when we've gained sufficient knowledge regarding all the subtleties of timeline presentation and editing.
-
+
Within the Lumieara GUI, the [[Timeline]] structure(s) from the HighLevelModel are arranged and presented according to the following principles and conventions.
 Several timeline views may be present at the same time -- and there is not necessarily a relation between them, since »a Timeline« is the top-level concept within the [[Session]]. Obviously, there can also be several //views// based on the same »Timeline« model element, and in this latter case, these //coupled views// behave according to a linked common state. An entity »Timeline« as represented through the GUI, emerges from the combination of several model elements
 * a root level [[Binding|BindingMO]] acts as framework
@@ -3482,10 +3491,13 @@ This collapsed, expanded and possibly nested workspace structure is always exact
 !!!lifecycle and instances
 A given instance of the {{{TimelineWidget}}} is always dedicated to render the contents of //one specific timeline.// We never switch the data model while retaining the UI entities. This also means, a given instance is tied to one conversation with the core; it is created when the core tells us about this timeline with an initial population diff, and it lives until either this timeline is discarded in the core model, or the whole session is shut down.
 
-The dockable ''timeline pannel'' holds onto the existing {{{TimelineWidget}}} instances, allowing to make one of them visible for interaction. Yet the timeline itself is represented by the {{{TimelineControler}}}, which lives //within this widget,// and is attached to and managed by the InteractionDirector, who incorporates the role of representing the [[model root|ModelRootMO]]. Aside of the timeline display, there is also an ''asset pannel''; display of and interaction with those asset views is handled by dedicated widgets, backed by the {{{AssetControler}}} -- which is also a child of and managed by the InteractionDirector, since assets are modelled as global part of the [[Session]].
+The dockable ''timeline pannel'' holds onto the existing {{{TimelineWidget}}} instances, allowing to make one of them visible for interaction. Yet the timeline itself is represented by the {{{TimelineControler}}}, which lives //within this widget,// and is attached to and managed by the InteractionDirector, who incorporates the role of representing the [[model root|ModelRootMO]]. To bridge this conflict of control, we introduce a {{{TimelineGui}}} proxy for each timeline. Which allows to close a timeline on widget level, thereby marking it as detached from model updates; and it enables child manipulation by the {{{InteractionDirector}}}, automatically forwarding to the respective {{{TimelineController}}} if applicable. Aside of the timeline display, there is also an ''asset pannel''; display of and interaction with those asset views is handled by dedicated widgets, backed by the {{{AssetControler}}} -- which is also a child of and managed by the InteractionDirector, since assets are modelled as global part of the [[Session]].
 
 In case the UI starts with no session present in the core, an //empty timeline placeholder// will be displayed, which provides UI for creating a new session...
 
+!!!Placements
+As indicated in the drawing above, pretty much every UI element exposes a button to //edit its placement.// &rarr; GuiPlacementDisplay
+
 
 !!!slave Timelines
 It is reasonable to expect the ability to have multiple [[slave timeline presentations|GuiTimelineSlave]] to access the same underlying timeline structure.
@@ -3498,16 +3510,16 @@ In the most general case, there can be per-track content and nested content at t
 &rarr; important question: how to [[organise the widgets|GuiTimelineWidgetStructure]]
 
-
+
The Timeline is probably the most prominent place in the GUI where we need to come up with a custom UI design.
-Instead of combining standard components in one of the well-known ways, here we need to come up with our own handling solution -- which also means to write one or several custom GTK widgets. Thus the question of layout and screen space division and organisation becomes a crucial design decision. The ~GTK-2 Gui, as implemented currently, did already take some steps along this route, yet this kind of decision should be cast and documented explicitly (be it after the fact).
+Instead of combining standard components in one of the well-known ways, here we need to come up with our own handling solution -- which also involves to build several custom GTK widgets. Thus the question of layout and screen space division and organisation becomes a crucial design decision. The ~GTK-2 UI, as implemented during the initial years of the Lumiera project, did already take some steps along this route, which was was valuable as foundation for assessment and further planning.
 
-Right away, this topic touches a tricky design and architectural challenge: the → question [[how to organise custom widgets|GuiCustomWidget]].
+As it stands, this topic touches a tricky design and architectural challenge: the → question [[how to organise custom widgets|GuiCustomWidget]].
 
 In a nutshell, ~GTKmm offers several degrees of customisation, namely to build a custom widget class, to build a custom container widget, and to use the [[Gtk::Layout "canvas widget"|GtkLayoutWidget]], possibly combined with //custom drawing.// In addition to assembling a timeline widget class by combining several nested panes, the timeline display needs to rely on the latter approach to allow for the necessary flexible arrangement of [[clip widgets|GuiClipWidget]] within the [[track fork|Fork]].
 
 !the header pane problem
-From general considerations we draw the conclusion to have a track header pane area always visible to the left. For one this means that our top level widget organisation in the timeline will be a horizontal split. And then this means that we get two distinct sub widgets, whose vertical layout needs to be kept in sync. And even more so, presumably the most adequate implementation technique is different in both parts: the header pane looks like a classical fit for the paradigm of nested boxes and grid layout within those boxes, while the right part -- the actual track contents -- impose very specific layout constraints not served by any of the pre-existing layout containers, which means we have to resort to custom drawing on a canvas widget. Following this line of thought, we need an overarching layout manager to coordinate these two disjoint technologies. Any viable alternatives?
+Based on principles of //conventional UI design,// we derive the necessity to have a track header pane area, always visible to the left, and scrolling vertically in sync with the actual track display to the right of the timeline area. This insight brings about several consequences. For one this means that our top level widget organisation in the timeline will be a horizontal split. And furthermore this means that we get two distinct sub widgets, whose vertical layout needs to be kept in sync. And even more so, presumably the most adequate implementation technique is different in both parts: the header pane looks like a classical fit for the paradigm of nested boxes and grid layout within those boxes, while the right part -- the actual track contents -- impose very specific layout constraints, not served by any of the pre-existing layout containers -- which means we have to resort to custom drawing on a canvas widget. Following this line of thought, we need an overarching layout manager to coordinate these two disjoint technologies. Any viable alternatives?
 
 !!!considering a table grid layout
 The layout mechanics we try to establish here by explicit implementation would be more or less a given, if instead we'd build the whole timeline display from one base widget, which needs to be a table layout, i.e. {{{Gtk::Grid}}}. We'd use two columns, one for the header pane area, one for the timeline display, and we'd use N+1 rows, with the head row holding the time ruler and the additional rows holding individual tracks. But to get the specific UI mechanics desirable for a timeline display, we had to introduce some twists:
@@ -3522,13 +3534,13 @@ On the other hand, what would be the //obvious benefits...?//
 While the special setup for scrolling doesn't really count (since it is necessary anyway), after this initial investigation it seems clear that a global grid layout doesn't yield enough benefit to justify all the quirks and limitations its use would impose.
 
 !!!follow-up to the obvious choices
-We came to this point of re-considering the overall organisation of widgets, after having to re-write the initial version of our timeline widget. This initial version was developed by Joel Holdsworth, and it followed a similar reasoning, involving a global timeline layout manager. The distinction between the two panes was not so clear though, and the access to the model code was awkward at places, so the necessity to re-write the timeline widget due to the transition to ~GTK-3 looks like a good opportunity to re-do the same basic reasoning a second time, verify decisions taken and improve matters turning out as difficult on first attempt.
+We came to this point of re-considering the overall organisation of widgets, after having to re-write the initial version of our timeline widget. This initial version was developed until 2011 by Joel Holdsworth, and it followed a similar reasoning, involving a global timeline layout manager. The distinction between the two panes was not so clear though, and the access to the model code was awkward at places, so the necessity to re-write the timeline widget due to the transition to ~GTK-3 looks like a good opportunity to re-do the same basic reasoning a second time, verify decisions taken and improve matters turning out as difficult on first attempt.
 
-So we get a timeline custom widget, which at top level establishes this two-part layout, provides the global scrollbars and integrates custom widget components for both parts. And this top-level timeline widget owns a layout manager, plus it exposes a common view management interface, to be used both from internal components (e.g. zoom widgets within the UI) and from external actors controlling the timeline display from a global level. Also at this global level, we get to define a layout control interface, the TimelineDisplayManager, which has to be implemented within the recursively structured parts of the timeline display, and which is used by the global layout manager to exert control over the layout as a whole. {{red{Note (11/2016)}}}: if this layout control interface works push or pull style is a decision yet to be worked out during the course of the implementation.
+So we get a timeline custom widget, which at top level establishes this two-part layout, provides the global scrollbars and integrates custom widget components for both parts. And this top-level timeline widget owns a layout manager, plus it exposes a common view management interface, to be used both from internal components (e.g. zoom widgets within the UI) and from external actors controlling the timeline display from a global level. Also at this global level, we get to define a layout control interface, the TimelineDisplayManager, which has to be implemented within the recursively structured parts of the timeline display, and which is used by the top-level structure to exert control over the layout as a whole. {{red{Note (11/2016)}}}: if this layout control interface works push or pull style is a decision yet to be worked out during the course of the implementation.
 
 
 !dealing with nested structures
-The handling of objects structured into nested scopes is a hallmark of the very specific approach taken by Lumiera when it comes to attaching, arranging and relating media objects. But here in the UI display of the timeline, this approach creates a special architectural challenge: the only sane way to deal with nested structures without exploding complexity is to find some way to exploit the ''recursive self similarity'' inherent in any tree structure. But the problematic consequence of this assessment is the tension, even contradiction it creates to the necessities of GUI programming, which forces us to come up with one single, definitive widget representation of what is going on eventually. The conclusion is that we need to come up with an interface such as to allow building and remoulding of the UI display through incremental steps -- where each of this incremental steps relies solely on relative, context based information. Because this is the only way we can deal with building a tree structure by recursive programming. We must not demand the individual step to know its arrangement within the tree, other than indicating a "current" or a "parent" reference point.
+The handling of objects structured into nested scopes is a hallmark of the very specific approach taken by Lumiera when it comes to attaching, arranging and relating media objects. But here in the UI display of the timeline, this approach creates a special architectural challenge: the only sane way to deal with nested structures without exploding complexity is to find some way to exploit the ''recursive self similarity'' inherent in any tree structure. But the problematic consequence of this assessment is the tension, even contradiction it creates to the necessities of GUI programming, which forces us to come up with one single, definitive widget representation of what is going on eventually. The conclusion is that we need to come up with an interface such as to allow building and remoulding of the UI display through incremental steps -- where each of this incremental steps relies solely on relative, context based information. Because this is the only way we can deal with building a tree structure by recursive programming. We must not allow the individual step to know its arrangement within the tree, other than indicating a "current" or a "parent" reference point.
 
 The structure of the display is extended or altered under two circumstances:
 # some component receives a [[diff mutation message|MutationMessage]], prompting to add or remove a //child component.//
@@ -3538,12 +3550,12 @@ Here, the "component" relevant for such structural changes is always t
 From these observations we can draw the conclusion, that we'll build a ''local structural model'', to reflect the logical relations between the parts comprising the timeline display. More precisely, these structuring components are not mere model objects, rather they are mediating entities used to guide and organise the actual view entities, which in turn are passive. They are more like a view model, while also bearing some local controller responsibilities. For this reason, we prefer to term these as ''presenters'' -- i.e. TrackPresenter and ClipPresenter. And each of these local representation components holds onto a ''display context'', which generally links it //into two different display widget stacks// within the two parts of the actual timeline display. Adding a child component thus becomes a rather tricky operation, involving to link possibly two child widgets into two disjoint parent widgets, thereby forming a similar display context for the child presenter. Overall, the guiding idea is that of self similarity: on each level, we have to reproduce the same relations and collaborations as present in the parent level.
 
 !!!building the timeline representation structure
-It is a fundamental decision within the Lumiera UI that any structure as to be injected as diff by the core. We do not hold a structure or data model within some UI entity and then "interpret" that model into a widget structure -- rather we build the widget structure itself in response to a diff message describing the structure. Especially in the case of timelines, the receiver of those messages is the InteractionDirector, which corresponds to the model root. On being prompted to set up a timeline, it will allocate a {{{TimelineWidget}}} within a suitable timeline docking panel and then attach to the {{{TimelineController}}} embedded within the widget.
+It is a fundamental decision within the Lumiera UI that any structure has to be injected as diff by the core. We do not hold a structure or data model within some UI entity and then "interpret" that model into a widget structure -- rather we build the widget structure itself in response to a diff message describing the structure. Especially in the case of timelines, the receiver of those messages is the InteractionDirector, which corresponds to the model root. On being prompted to set up a timeline, it will allocate a {{{TimelineWidget}}} within a suitable timeline docking panel and then attach to the {{{TimelineController}}} embedded within the widget.
 
 {{red{Problem 10/2018}}} how can the InteractionDirector //manage// a timeline while the timeline widget physically resides within the panel? Can we exploit a simliar structure as we did for the error log?
- * in part yes -- however we introduce an mediating entity, the {{{TimelineGui}}} proxy
- * it uses a {{{WLink}}} to refer to the actual {{{TimelineWidget}}} -- meaning the widget need not exist, and will detach automatically when destroyed by its holder, which is the {{{TimelinePanel}}}
- * {{red{TODO 11/2018}}} still need to care for removing a {{{TimelineWidget}}} when the logically managing parent, i.e. the InteractionDirector deletes the {{{TimelineGui}}} proxy
+* in part yes -- however we introduce a mediating entity, the {{{TimelineGui}}} proxy
+* it uses a {{{WLink}}} to refer to the actual {{{TimelineWidget}}} -- meaning the widget need not exist, and will detach automatically when destroyed by its holder, which is the {{{TimelinePanel}}}
+* {{red{TODO 11/2018}}} still need to care for removing a {{{TimelineWidget}}} when the logically managing parent, i.e. the InteractionDirector deletes the {{{TimelineGui}}} proxy
 
 The diff describing and thus assembling the UI representation of a timeline is typically a ''population diff'' -- which means, it is a consolidated complete description of the whole sub-structure rooted below that timeline. Such a population diff is generated as emanation from the respective DiffConstituent.
 
@@ -3553,7 +3565,7 @@ Applying a diff changes the structure, that is, the structure of the local model
 * when a model element happens to be destructed, the corresponding display element has to be removed.
 * such might be triggered indirectly, by clean-up of leftovers, since the {{{DiffApplicator}}} re-orders and deletes by leaving some data behind
 * the diff also re-orders model elements, which does not have an immediate effect on the display, but needs to be interpreted separately.
-Wrapping this together we get a fix up stage after model changes, where the display is re-adjusted to fit the new situation. This works in concert with the [[display manager|TimelineDisplayManager]] representing only those elements as actual widgets, which get a real chance to become visible. This way we can build on the assumption that the actual number of widgets to be managed any time remains so small as to get away with simple linear list processing. It remains to be seen how far this assumption can be pushed -- the problem is that the GTK container components don't support anything beyond such simple linear list processing; there isn't even a call to remove all child widgets of a container in a single pass.
+Wrapping this together we get a //fix up stage// after any model changes, where the display is re-adjusted to fit the new situation. This works in concert with the [[display manager|TimelineDisplayManager]] representing only those elements as actual widgets, which get a real chance to become visible. This way we can build on the assumption that the actual number of widgets to be managed any time remains so small as to get away with simple linear list processing. It remains to be seen how far this assumption can be pushed -- the problem is that the GTK container components don't support anything beyond such simple linear list processing; there isn't even a call to remove all child widgets of a container in a single pass.
 
@@ -9016,13 +9028,13 @@ Besides building on the asset management, implementing Timeline (and Sequence) a This topic is {{red{postponed as of 10/2018}}} &rarr; [[#1083|http://issues.lumiera.org/ticket/1083]]
-
+
//guide and control the concrete display properties of the various sub components (tracks, clips) comprising a timeline display.//
 
 The TimelineDisplayManager actually is an abstraction, a control interface, revolving around the guidance individual components need in order to settle on a proper display. Those components are represented as mediating entities, the TrackPresenter and the ClipPresenter, each of which controls and manages a mostly passive GTK widget. To this end, the presenters need to know at which virtual coordinates their corresponding widgets would show up, and they need to know if these widgets need to be actually present at the moment. Also, especially the ClipPresenter needs to know which ''clip appearance style'' to choose for the corresponding slave widget.
 
 !display evaluation pass
-[>img[Clip presentation control|uml/Timeline-display-evaluation.png]]Since the timeline display is formed by several nested collections of elements, a proper display layout must incorporate information from all those parts. A naive approach likely would just iterate over them and reach in to extract data -- or even worse, build a separate display data store, which then needs to be kept in sync with the component hierarchy. Any of this would lead to high coupling and turns any necessary adjustment and changes due to evolution of requirements into a liability. Such a dangerous situation can be avoided altogether by admitting the collaborative nature of the task at hand. To get there, we need to distil the abstraction shared between the global and the local concerns, and we need to shape it such as to build onto some kind of invariant, allowing that abstraction to re-emerge at several levels of locality.
+[>img[Clip presentation control|uml/Timeline-display-evaluation.png]]Since the timeline display is formed by several nested collections of elements, a proper display layout must incorporate information from all those parts. A naive approach likely would just iterate over them and reach in to extract data -- or even worse, build a separate display data store, which then needs to be kept in sync with the component hierarchy. Any of these naive working styles would lead to high coupling and it turns the necessary adjustment and changes into a liability, which raises due to evolution of requirements. Over time, such a dangerous situation can only be avoided altogether by admitting the collaborative nature of the task at hand. To get there, we need to distil the abstraction shared between the global and the local concerns, and we need to shape it such as to build onto some kind of invariant, allowing that abstraction to re-emerge at several levels of locality.
 
@@ -9126,10 +9138,10 @@ Placements are __resolved__ resulting in an ExplicitPlacement. In most cases thi &rarr; [[Definition|Pipe]] and [[handling of Pipes|PipeHandling]]
-
+
//mediating entity used to guide and control the track-like nested working space in the timeline display of the UI.//
 
-Similar to the ClipPresenter, from a global angle this element fulfils a model like role, while also guiding and controlling a mostly passive view component implemented as GTK widget. Here, the authority of the presenter over the widget must be total, since display management //needs to work automatically,// due to model updates and mutations arriving as [[diff messages|MutationMessage]]. In addition, this structure is prerequisite for (possibly) implementing UI rendering optimisations, since it allows us to leave out widgets entirely, when it is clear they won't become visible: A ''display evaluation pass'', which is effectively a //tree walk,// consecutively visits each part of the timeline structure, to negotiate its concrete display properties in collaboration with a global TimelineDisplayManager. As a result, the presenter knows where to show its corresponding view, and it knows if to show it at all, allowing to either adjust, create or destroy actual GTK widgets within its local reference frame.
+Similar to the ClipPresenter, judged from a global angle, this element fulfils a model-like role, while at the same time guiding and controlling a mostly passive view component, implemented as GTK widget. Here, the authority of the presenter over the widget must be total, since display management //needs to work automatically,// due to model updates and mutations arriving as [[diff messages|MutationMessage]]. In addition, this structure is prerequisite for (possibly) implementing UI rendering optimisations, since it allows us to leave out widgets entirely, when it is clear they won't become visible: A ''display evaluation pass'', which is effectively a //tree walk,// consecutively visits each part of the timeline structure, to negotiate its concrete display properties in collaboration with a global TimelineDisplayManager. As a result, the presenter knows where to show its corresponding view, and it knows if to show it at all, allowing to either adjust, create or destroy actual GTK widgets within its local reference frame.
 
 A special twist arises from the fact that track display has to happen aligned and in sync within the two display panes of the timeline at the same time. This means that each TrackPresenter has to hold and manage //two slave display elements,// each of which is inserted within a disjoint hierarchy of display elements. For one, there is the {{{timeline::TrackHeadWidget}}} which in turn renders a {{{PatchbayWidget}}}, and on the other side there is a {{{TrackBody}}} element, which is not strictly a widget of itself, but inserted into our custom drawing {{{timeline::BodyCanvasWidget}}} to manage the custom drawing of the respective track working area.
 
@@ -9137,7 +9149,7 @@ To deal with this typical problem of recursive programming, we introduce a bindi
 * the TrackPresenter holds a {{{DisplayFrame}}} member, which in turn houses the two head and body widgets. So basically the widgets are allocated within the presenter.
 * however, after constructing the {{{DisplayFrame}}}, it invokes an anchor functor, which is passed in from the recursive outer scope, and which does the actual work of installing those widgets into the apropriate parent widgets
 * but the {{{DisplayFrame}}} itself also exposes such an anchor functor, which can be passed down when (recursively) creating a TrackPresenter for a sub-Track
-* obviously, at top-level, we need to digress from that general pattern -- here it is the {{{timeline::LayoutManager}}} responsible for the display structure of the whole timeline which exposes the necessary anchor function to attach the root track for a timeline.
+* obviously, at top-level, we need to digress from that general pattern -- here it is the {{{timeline::TimelineLayout}}} responsible for the display structure of the whole timeline which exposes the necessary anchor function to attach the root track for a timeline.
 
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 6d9e8cfae..d6ed8a34f 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -18338,7 +18338,7 @@ - + @@ -19065,8 +19065,9 @@ - - + + + @@ -19087,6 +19088,27 @@ + + + + + + + + + + +

+ ...denn das Patchbay-Widget muß diverse Interna des Tracks beachten, +

+

+ so z.B. sein Placement, welches teilweise als Properties des Track abgebildet wird. +

+ + +
+
+
@@ -20806,7 +20828,7 @@
- + @@ -20844,6 +20866,9 @@ + + + @@ -30620,10 +30645,19 @@ - + + + + + + + + + + From 0aa4a8cb424f3c25d96dc482c7152b3e97054d87 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Tue, 30 Oct 2018 03:37:55 +0100 Subject: [PATCH 166/178] Timeline: make the Patchbay a Viewport container, to follow body scrolling 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 --- src/gui/timeline/header-pane-widget.hpp | 7 ++++-- src/gui/timeline/patchbay-widget.cpp | 33 ++++++++++++++----------- src/gui/timeline/patchbay-widget.hpp | 30 +++++++++------------- src/gui/timeline/timeline-layout.cpp | 2 +- src/gui/timeline/timeline-layout.hpp | 2 +- src/gui/timeline/track-head-widget.cpp | 22 ++++++++++++++--- src/gui/timeline/track-head-widget.hpp | 28 ++++++++++++++++++--- wiki/thinkPad.ichthyo.mm | 32 ++++++++++++++++++++++-- 8 files changed, 110 insertions(+), 46 deletions(-) diff --git a/src/gui/timeline/header-pane-widget.hpp b/src/gui/timeline/header-pane-widget.hpp index 3b5b6add0..e18cbc2fa 100644 --- a/src/gui/timeline/header-pane-widget.hpp +++ b/src/gui/timeline/header-pane-widget.hpp @@ -63,6 +63,9 @@ namespace gui { namespace timeline { + using PAdjustment = Glib::RefPtr; + + class TrackHeadWidget; @@ -78,10 +81,10 @@ namespace timeline { public: ~HeaderPaneWidget() { } - HeaderPaneWidget() + HeaderPaneWidget (PAdjustment const& vScroll) : Gtk::Box{Gtk::ORIENTATION_VERTICAL} , navigator_{} - , patchbay_{} + , patchbay_{vScroll} { this->pack_start (navigator_, Gtk::PACK_SHRINK); this->pack_start (patchbay_, Gtk::PACK_EXPAND_WIDGET); diff --git a/src/gui/timeline/patchbay-widget.cpp b/src/gui/timeline/patchbay-widget.cpp index 01cfa6617..e40d049d8 100644 --- a/src/gui/timeline/patchbay-widget.cpp +++ b/src/gui/timeline/patchbay-widget.cpp @@ -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; @@ -63,25 +64,29 @@ namespace timeline { PatchbayWidget::~PatchbayWidget() { } - PatchbayWidget::PatchbayWidget () - : Gtk::Grid{} + /** + * 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} { } - /** - * 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 PatchbayWidget::installFork (TrackHeadWidget& rootTrackHead) { - UNIMPLEMENTED ("how actually to represent the track in the patchbay"); + this->add (rootTrackHead); } diff --git a/src/gui/timeline/patchbay-widget.hpp b/src/gui/timeline/patchbay-widget.hpp index 42c33b4f1..03728a763 100644 --- a/src/gui/timeline/patchbay-widget.hpp +++ b/src/gui/timeline/patchbay-widget.hpp @@ -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 ** */ @@ -56,7 +48,9 @@ namespace gui { namespace timeline { - + + using PAdjustment = Glib::RefPtr; + class TrackHeadWidget; @@ -68,13 +62,13 @@ namespace timeline { * @todo WIP-WIP as of 12/2016 */ class PatchbayWidget - : public Gtk::Grid + : public Gtk::Viewport { public: - PatchbayWidget(); + PatchbayWidget (PAdjustment const& vScroll); ~PatchbayWidget(); - /** @internal Initially install the contents corresponding to this track fork */ + /** @internal Initially install the contents corresponding to the track fork root */ void installFork (TrackHeadWidget& rootTrackHead); private:/* ===== Internals ===== */ diff --git a/src/gui/timeline/timeline-layout.cpp b/src/gui/timeline/timeline-layout.cpp index a4fbface5..3028bce8f 100644 --- a/src/gui/timeline/timeline-layout.cpp +++ b/src/gui/timeline/timeline-layout.cpp @@ -63,8 +63,8 @@ namespace timeline { TimelineLayout::TimelineLayout (Gtk::Paned& topLevelContainer) : paneSplitPosition_{topLevelContainer.property_position()} - , headerPane_{} , bodyCanvas_{} + , headerPane_{bodyCanvas_.get_vadjustment()} // wire the header pane (Gtk::Viewport) to follow the body vertical scroll movement { topLevelContainer.add1 (headerPane_); topLevelContainer.add2 (bodyCanvas_); diff --git a/src/gui/timeline/timeline-layout.hpp b/src/gui/timeline/timeline-layout.hpp index 2f513eb3c..6a14b359d 100644 --- a/src/gui/timeline/timeline-layout.hpp +++ b/src/gui/timeline/timeline-layout.hpp @@ -110,8 +110,8 @@ namespace timeline { { Glib::PropertyProxy paneSplitPosition_; - HeaderPaneWidget headerPane_; BodyCanvasWidget bodyCanvas_; + HeaderPaneWidget headerPane_; public: diff --git a/src/gui/timeline/track-head-widget.cpp b/src/gui/timeline/track-head-widget.cpp index 3bb54282a..518ac9dc4 100644 --- a/src/gui/timeline/track-head-widget.cpp +++ b/src/gui/timeline/track-head-widget.cpp @@ -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,32 @@ namespace timeline { + TrackHeadWidget::~TrackHeadWidget() { } + + TrackHeadWidget::TrackHeadWidget () + : Gtk::Grid{} { } - TrackHeadWidget::~TrackHeadWidget() + /** + * @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 diff --git a/src/gui/timeline/track-head-widget.hpp b/src/gui/timeline/track-head-widget.hpp index 3de1be01e..cb448bf51 100644 --- a/src/gui/timeline/track-head-widget.hpp +++ b/src/gui/timeline/track-head-widget.hpp @@ -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 ** */ @@ -47,14 +59,22 @@ namespace timeline { /** + * 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 { public: TrackHeadWidget (); ~TrackHeadWidget(); - + + /** Integrate the control area for a nested sub track fork. */ + void injectSubFork (TrackHeadWidget& subForkHead); + private:/* ===== Internals ===== */ }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index d6ed8a34f..6f6782833 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -19088,9 +19088,12 @@ - + - + + + + @@ -19108,6 +19111,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + From 04b665afd1224beb9531b3d50ccfbb9517521ced Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 31 Oct 2018 03:52:24 +0100 Subject: [PATCH 167/178] Timeline: concept for the TrackBody helpers 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) --- src/gui/timeline/body-canvas-widget.cpp | 11 +++++-- src/gui/timeline/body-canvas-widget.hpp | 3 ++ src/gui/timeline/track-body.cpp | 42 ++++++++++++++++++------- src/gui/timeline/track-body.hpp | 24 ++++++++++++-- src/gui/timeline/track-presenter.hpp | 5 +++ wiki/thinkPad.ichthyo.mm | 22 +++++++++++-- 6 files changed, 89 insertions(+), 18 deletions(-) diff --git a/src/gui/timeline/body-canvas-widget.cpp b/src/gui/timeline/body-canvas-widget.cpp index 10e06f7be..284688036 100644 --- a/src/gui/timeline/body-canvas-widget.cpp +++ b/src/gui/timeline/body-canvas-widget.cpp @@ -58,6 +58,13 @@ namespace timeline { + TimelineCanvas::TimelineCanvas() + : Gtk::Layout{} + , rootBody_{nullptr} + { } + + + BodyCanvasWidget::~BodyCanvasWidget() { } @@ -87,9 +94,9 @@ namespace timeline { * of a Timeline, will also prepare the grounding for any other nested entities to be drawn on top. */ void - BodyCanvasWidget::installForkRoot (TrackBody& rootTrackHead) + BodyCanvasWidget::installForkRoot (TrackBody& rootTrackBody) { - UNIMPLEMENTED ("how actually to represent the track bode on the canvas"); + canvas_.rootBody_ = &rootTrackBody; } diff --git a/src/gui/timeline/body-canvas-widget.hpp b/src/gui/timeline/body-canvas-widget.hpp index 47d68e5e2..47b1743e6 100644 --- a/src/gui/timeline/body-canvas-widget.hpp +++ b/src/gui/timeline/body-canvas-widget.hpp @@ -75,7 +75,10 @@ namespace timeline { class TimelineCanvas : public Gtk::Layout { + public: + TrackBody* rootBody_; + TimelineCanvas(); }; diff --git a/src/gui/timeline/track-body.cpp b/src/gui/timeline/track-body.cpp index 081a85842..f953432bc 100644 --- a/src/gui/timeline/track-body.cpp +++ b/src/gui/timeline/track-body.cpp @@ -56,20 +56,40 @@ 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"); + } + + + /** + * 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 diff --git a/src/gui/timeline/track-body.hpp b/src/gui/timeline/track-body.hpp index df8479122..0abcdf4e5 100644 --- a/src/gui/timeline/track-body.hpp +++ b/src/gui/timeline/track-body.hpp @@ -45,7 +45,7 @@ //#include "lib/util.hpp" //#include -//#include +#include @@ -54,14 +54,32 @@ namespace timeline { /** - * @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; + + SubTracks subTracks_; + public: TrackBody (); ~TrackBody(); - + + uint calcHeight(); + private:/* ===== Internals ===== */ }; diff --git a/src/gui/timeline/track-presenter.hpp b/src/gui/timeline/track-presenter.hpp index 39d9c1a46..a2a2244ee 100644 --- a/src/gui/timeline/track-presenter.hpp +++ b/src/gui/timeline/track-presenter.hpp @@ -87,6 +87,11 @@ namespace timeline { anchorDisplay (head, body); } + ~DisplayFrame() + { + TODO ("cause the managed presentation elements to detach from their parents"); + } + void injectSubTrack (TrackHeadWidget& head, TrackBody& body) { diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 6f6782833..50f931a66 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -18908,7 +18908,7 @@ - + @@ -19039,7 +19039,7 @@ - + @@ -19146,6 +19146,24 @@ + + + + + + + + + + + + + + + + + + From 13286f4b909b133f7214291832ed8d4b89aaac8f Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 1 Nov 2018 20:37:36 +0100 Subject: [PATCH 168/178] ElementBox: define the desired properties of this fundamental building block (#1185) --- src/gui/timeline/marker-widget.hpp | 4 +- ...abel-widget.cpp => element-box-widget.cpp} | 10 +- ...abel-widget.hpp => element-box-widget.hpp} | 20 +- wiki/renderengine.html | 57 +- wiki/thinkPad.ichthyo.mm | 689 ++++++++++++------ 5 files changed, 538 insertions(+), 242 deletions(-) rename src/gui/widget/{label-widget.cpp => element-box-widget.cpp} (81%) rename src/gui/widget/{label-widget.hpp => element-box-widget.hpp} (76%) diff --git a/src/gui/timeline/marker-widget.hpp b/src/gui/timeline/marker-widget.hpp index 63e014b8d..45ee015e3 100644 --- a/src/gui/timeline/marker-widget.hpp +++ b/src/gui/timeline/marker-widget.hpp @@ -40,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" @@ -65,7 +65,7 @@ namespace timeline { */ class MarkerWidget : public model::Widget - , public widget::LabelWidget + , public widget::ElementBoxWidget { MarkerKind kind_; string name_; diff --git a/src/gui/widget/label-widget.cpp b/src/gui/widget/element-box-widget.cpp similarity index 81% rename from src/gui/widget/label-widget.cpp rename to src/gui/widget/element-box-widget.cpp index 1882506df..7156a0e92 100644 --- a/src/gui/widget/label-widget.cpp +++ b/src/gui/widget/element-box-widget.cpp @@ -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 @@ -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() { } diff --git a/src/gui/widget/label-widget.hpp b/src/gui/widget/element-box-widget.hpp similarity index 76% rename from src/gui/widget/label-widget.hpp rename to src/gui/widget/element-box-widget.hpp index 904a4b5eb..530961ef0 100644 --- a/src/gui/widget/label-widget.hpp +++ b/src/gui/widget/element-box-widget.hpp @@ -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 + 2018, Hermann Vosseler 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*/ diff --git a/wiki/renderengine.html b/wiki/renderengine.html index 85fd96bf2..83c8b3293 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -2691,7 +2691,7 @@ As starting point, {{red{in winter 2016/17}}} the old (broken) timeline panel wa After that initial experiments, my focus shifted to the still unsatisfactory top-level UI structure, and I am working towards an initial integration with Proc-Layer since then.
-
+
//The representation of a [[media clip|Clip]] for manipulation by the user within the UI.//
 Within Lumiera, a clip is conceived as a //chunk of media,// which can be handled in compound. Clip as such is an abstract concept, which is treated with minimal assumptions...
 * we know that a clip has //media content,// which need not be uniform and can be inherently structured (e.g. several media, several channels, a processing function)
@@ -2723,7 +2723,7 @@ This creates a tension related to the kind of architecture we want to build with
 Starting from the architecture as predetermined by the UI-Bus, it is clear that we have to add a UI-Element for every tangible child entity represented in the (abstracted) model. And the parent entity receiving a diff and adding such a child element is also in charge of that element, controlling its lifecycle. Yet this child element is not necessarily bound to be a widget. This is our chance here: what "adding" means is completely within the scope of the parent element and basically an implementation detail. So the parent may create and own a tracker or controller object to stand for the clip, and the latter may create and adjust the actual widget in negotiation with a display strategy. So effectively we're introducing a //mediator,// which represents the clip (as far as the modelling is concerned) and which at the same time communicates with a global display manager. We call this mediating entity a ClipPresenter
 
 !!!how to carry out the clip appearances
-Initially, one could think that we'd need to build several widgets to realise the wide variety of clip appearances. But in fact it turns out that we're able to reshape a single base widget to encompass all the necessary presentation styles. This base widget is a simple, one-element container, with {{{Gtk::Frame}}} being the most obvious pick. This gives us already a rectangular covered space, and the ability to add a label widget, optionally with controlled alignment of the label. All the more elaborate presentation styles can be achieved by adding a canvas widget into this frame and then placing additional stuff on top of that. The only tricky part arises in overview display, when just some clip rectangle can stand-in for a whole series of clips, which themselves remain hidden as UI elements.
+Initially, one could think that we'd need to build several widgets to realise the wide variety of clip appearances. But in fact it turns out that we're able to reshape a single base widget to encompass all the presentation styles. Almost all of the necessary styles, to be more precise, omitting the mere overview drawing where several entities are combined into a single box. However, this basic [[element-box-widget|GuiElementBoxWidget]] can be generalised, and even used beyond the clip display itself. It is a simple, one-element container, with an Icon and a textual label. This gives us already a rectangular covered space, and the ability to include a menu and to control the alignment of the label. All the more elaborate presentation styles can be achieved by adding a canvas widget into this frame and then placing additional stuff on top of that. The only tricky part arises in overview display, when just some clip rectangle can stand-in for a whole series of clips, which themselves remain hidden as UI elements.
 
 A prime consideration regarding this whole clip presentation strategy is the performance concern. It is quite common for movie edits to encompass several hundred individual clips. Combined with several tracks and an elaborate audio edit, it may well happen that we end up with thousands of individual UI objects. If treated naively, this load might seriously degrade the responsiveness of the interface. Thus we need to care for the relevant infrastructure to enable optimisation of the display. For that reason, the memory footprint of the ClipPresenter and the basic widget has to be kept as small as possible. And moreover, since we do our own layout management in the timeline display, in theory it is possible //only to add// those widgets to the enclosing GTK container, which are //actually about to become visible.// (if we follow this approach, a problem yet to be solved is how to remove widgets falling out of sight, since removing N widgets easily turns into a quadratic operation).
 
@@ -3231,6 +3231,48 @@ Each top-level window //holds a single dock,// which in turn might hold several
 !!!Panel Locator
 In accordance with this structure, we introduce a central component, the {{{PanelLocator}}} -- to keep track of all {{{DockArea}}} elements within the UI. The latter are responsible for managing the docking panels //within a specific// top-level {{{WorkspaceWindow}}}.
 
+
+
+
+
//A building block used pervasively throughout the Lumiera UI  to represent a named entity.//
+This widget presents a horizontally extended body, which holds a characteristic ''Head-Triplet'' of visual Elements:
+* an //Icon// to create the visual anchor point. In many cases, this will be ''the Placment Icon'' (a hallmark of Lumiera's UI)
+* a //Menu Button// -- in fact a small downward arrow, which can be flipped upwards (expand/collapse)
+* a //Text Label// designating the element
+
+This characteristic cadence of elements gives us a horizontal box with distinct extension, and an arrangement to attach menu actions and a possibly extended display style. This arrangement can be used for...
+* Markers
+* Elements in the Bins (Clips, Media, Effects, Assets....)
+* abridged display of Clips (and Effects)
+* the Effect-bar to mark the presence of an Effect
+* for the Track heads in the Timeline Header pane
+* for ~Clip-Placement and name
+
+&rarr; see also [[Ticket #1185|https://issues.lumiera.org/ticket/1185]].
+
+!Properties
+* the Icon shall be easily configurable (from the coding view point, of course this is all hard wired in the code or the theme)
+** the default is to configure the ''Placement Icon''
+** alternatively a ~Stock-ID can be given
+* the horizontal extension is easy to control, through several options
+** natural extension; don't give any ''size request'', let GTK figure out the extension rather
+** set a fixed length-limit, causing the text label to be abridged eventually
+** proportional head-placement
+* the body may optionally hold another GTK widget, in which case the head-triplet aligns to the upper side
+
+We offer pre-arranged options for standard wiring of interaction response
+* when the Icon is a Placement, it will launch the [[Placement-Pop-up|GuiPlacementDisplay]] -- which in turn can be de-modalised, thereby allocating a place in the [[Property grid pane|GuiPropertyPane]]. When such a placement control already is open and allocated, the click to the placement icon will lead there.
+* in the most basic case, there is only one common binding, which is a signal, emitted when clicking on the icon or the menu button
+* yet there can be two distinct bindings (signals) for the icon and the button
+* the ''Expander'' (which is part of the UI-Element protocol) is pre-wired with the arrow on the menu button, to yield a visible clue for the expand/collapse state
+* each of the bindings can be easily replaced by a [[pop-up menu|GuiPopupMenu]]
+
+!!!proportional Head placement
+This behaviour pattern (see [[#1186|https://issues.lumiera.org/ticket/1186]]) is a distinguishing trait of the Lumiera timeline display. It indicates elements with a temporal extension surpassing the current display window. Such elements are typically represented by an {{{ElementBoxWidget}}} with an large horizontal extension. Yet when scrolling, the head-triplet shall always remain within the visible area, but it will slowly glide from one side to the other, thereby indicating the our relative position. This pattern of behaviour matters, since, together with the scolled content display, it creates the dominant movement when scrolling an extended timeline
+
+''Technically'', this has to be build as a self-contained component, which can be unit-tested. The input and reference is a "Window" (in fact an interval) together with its start position //relative// to the element's own start position. The component has to derive from this input the actual relational configuration of the window's extension with respect to its own extension. For context, the //window// corresponds to the visible area, which can be larger, smaller disjoint, partially overlapping and fully contained. Please note that ''handling the relations of intervals is a notoriously challenging problem.''
+
+Especially when the component determines a placement of the //window// within its own extension, it has to derive a proportional placement position, which can be used to arrange the head-triplet of the {{{EventBoxWidget}}}. This proportional placement needs to reflect the location of the window within the overall extension, similar to the hand of a scrollbar. However, when the extension of the head-triplet, together with its placement disallows to show the the triplet unclipped, the triplets shall just be clipped naturally. Ideally, there would be a soft graceful transition between those two representation modes.
 
@@ -3393,15 +3435,24 @@ The most fundamental principle is that of ''subsidiarity'': we understand both & Based on these foundations, we shape and form the core part of the interface, which is the [[timeline display|GuiTimelineView]]
-
+
A cross-cutting and somewhat tricky concern is how to represent and expose the [[MObject Placements|Placement]] within the UI.
 For one, a Placement is a set of rules picked up from enclosing scopes -- and it is one of the most fundamental traits of Lumiera that the user is able to edit those placement rules. Yet the combination and final application of those rules also materialises itself into actual properties of the objects of the edit session -- most notably the time position of any element. Consequently, parts of the Placement are unfolded to appear as properties of the placed object, as far as the UI representation is concerned. However, Placement as a generic building block is prominently exposed, insofar pretty much every entity you'll see in the UI has the ability to "edit its placement". This is indicated by a characteristic Icon decoration, leading to a common placement editor, where the user can
 * see all the explicitly given locating pins
 * see the effective, resulting ExplicitPlacement
 * add and manipulate existing rules
 To support this generic setup, pretty much every UI element needs to be outfitted with a "placement" attribute, to reflect those distinct information to be exposed in aforementioned placement edit UI.
+&rarr; see also [[Ticket #1188|https://issues.lumiera.org/ticket/1188]].
 
+
+
//Organisation of Pop-up menus in the Lumiera UI.//
+A pop-up is created on right click on the associated element, which thereby exposes its //methods for manipulation.// As such this arrangement incorporates the principle of ''object orientation'' into the interactive user interface.
+
+The foundation of population and display of pop-up menues is provided by GTK -- however the //»object orientation«// is our actual design concern here.
+
+{{red{OMG 11/2018 -- I have no idea where to start here...}}}
+
Starting up the GUI is optional and is considered part of the Application start/stop and lifecycle.
 * main and {{{lumiera::AppState}}} activate the lifecyle methods on the ~GuiSubsysDescriptor, accessible via the GuiFacade
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm
index 50f931a66..564be6475 100644
--- a/wiki/thinkPad.ichthyo.mm
+++ b/wiki/thinkPad.ichthyo.mm
@@ -5378,7 +5378,7 @@
 
 
 
-
+
 
   
     
@@ -5408,13 +5408,14 @@
       
     
   
-
+
+
 
 
 
 
 
-
+
 
   
     
@@ -5441,7 +5442,8 @@
       
     
   
-
+
+
 
 
 
@@ -5456,7 +5458,7 @@
 
 
 
-
+
 
   
     
@@ -5466,7 +5468,8 @@
       vorbereitete Grundstrukturen für immer wiederkehrende Setups
     

-
+ + @@ -5661,7 +5664,7 @@ - + @@ -5674,7 +5677,8 @@ Im Zweifelsfall den GTK+ Inspector verwenden!

-
+ +
@@ -5693,7 +5697,7 @@
- + @@ -5703,7 +5707,8 @@ CSS genügt

-
+ +
@@ -5716,7 +5721,7 @@ - + @@ -5751,7 +5756,8 @@ }

-
+ +
@@ -5825,7 +5831,7 @@
- + @@ -5838,10 +5844,11 @@ daß die alte, obsolete Timeline zurückgebaut ist

-
+ +
- + @@ -5857,7 +5864,8 @@ bevor die Notification-Facade geöffnet werden konnte

-
+ +
@@ -5909,7 +5917,7 @@ - + @@ -5940,7 +5948,8 @@ Allerdings habe ich an der Stelle immer noch GTK-Assertions

-
+ +
@@ -5988,7 +5997,7 @@ - + @@ -6016,7 +6025,8 @@ ist, daß Gio::Application sofort auch gleich eine dBus-Verbindung hochfährt.

-
+ +
@@ -16627,7 +16637,7 @@ - + @@ -16643,7 +16653,8 @@ und daher auf "inaktiv" geschaltet ist.

-
+ +
@@ -17033,7 +17044,7 @@ - + @@ -17043,7 +17054,8 @@ ...denn das ist das vereinfachte Setup für "einfache" Applikationen

-
+ +
@@ -17419,7 +17431,7 @@ - + @@ -17429,9 +17441,10 @@ das Diff wird auf den Platzhalter angewendet

-
+ +
- + @@ -17444,7 +17457,8 @@ dann muß dieses automatisch deregistriert werden

-
+ +
@@ -17586,7 +17600,129 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ d.h. das Widget unternimmt selber nichts, und überläßt GTK die Größenbestimmung +

+ + +
+
+ + + + + + +

+ d.h. das Text-Label bekommt ggfs. eine Längenbeschränkung. +

+

+ Und sonst wird er Körper/Hintergrund ausgedehnt +

+ + +
+
+ + + + + + +

+ Vorgabe ist ein Fenster(=Intervall) und dessen Position relativ zum Widget-Start. +

+

+ Das Widget bestimmt daraus selbständig +

+
    +
  • + wie das Fenster relativ zu seiner eigenen Ausdehnung liegt (Logik von Intervallen ist komplex!) +
  • +
  • + ob es im Besonderen in seinem Inneren liegt, und proportional wo +
  • +
  • + und wählt dem entsprechend die Platzierung des "Kopf"-Handles (Icon+Label) +
  • +
+

+ Das Resultat ist, daß der Kopf dynamisch und proportional mitrollt, +

+

+ ähnlich wie das Handle einer Scrollbar. Damit bleibt der Kopf +

+

+ stets zugreifbar, und gibt einen Hinweis auf die Gesamtlage. +

+

+ Sehr wichtig für die Anzeige von langen Clips und Effekten. +

+ + +
+ + + + +
+
+ + + + + + + + + + + + + + + + + + + @@ -17689,7 +17825,7 @@ - + @@ -17702,7 +17838,8 @@ und welchen Teil des Verhaltens überlassen wir GTK

-
+ +
@@ -17743,7 +17880,7 @@ - + @@ -17759,7 +17896,8 @@ Details im  TiddlyWiki....

-
+ +
@@ -17812,7 +17950,7 @@ - + @@ -17822,7 +17960,8 @@ braucht feste Speicher-Addresse

-
+ +
@@ -17831,7 +17970,7 @@ - + @@ -17844,7 +17983,8 @@ und sei es auch bloß über ein Interface!

-
+ +
@@ -17859,7 +17999,7 @@ - + @@ -17893,7 +18033,8 @@ - + + @@ -17964,7 +18105,7 @@ - + @@ -17974,7 +18115,8 @@ ...für die dritte Lösung, die Repräsentation bereits in der Session

-
+ +
@@ -18062,7 +18204,7 @@ - + @@ -18078,9 +18220,10 @@ Implementiert würde sie vom jeweiligen Widget

-
+ +
- + @@ -18108,9 +18251,10 @@ Der Dekorator würde also auf dem TreeMutator sitzen...

-
+ +
- + @@ -18129,9 +18273,10 @@ Daher gibt es die matchSrc-Operation. Effektiv wird die aber nur bei einem Delete aufgerufen...

-
+ +
- + @@ -18152,7 +18297,8 @@ - + + @@ -18230,7 +18376,7 @@ - + @@ -18240,7 +18386,8 @@ d.h. eine LUID

-
+ +
@@ -18273,7 +18420,7 @@ - + @@ -18286,7 +18433,8 @@ Irgend eine BareEntryID genügt

-
+ +
@@ -18350,7 +18498,7 @@ - + @@ -18360,13 +18508,14 @@ ...abstraktes Interface

-
+ + - + @@ -18382,7 +18531,8 @@ um die Bindung herzustellen

-
+ +
@@ -19079,7 +19229,12 @@ - + + + + + + @@ -19183,7 +19338,7 @@ - + @@ -19193,7 +19348,8 @@ ...sie verwenden dann ein LabelWidget zur Darstellung

-
+ +
@@ -19203,11 +19359,19 @@ + + + + + + + + @@ -19293,7 +19457,7 @@ - + @@ -19309,7 +19473,8 @@ Verwende das als Leitgedanke, um das Layout zu entwickeln

-
+ + @@ -20666,7 +20831,7 @@ - + @@ -20684,10 +20849,11 @@ - + + - + @@ -20702,7 +20868,8 @@ - + + @@ -20716,7 +20883,7 @@ - + @@ -20741,7 +20908,8 @@ - + + @@ -21009,7 +21177,7 @@ - + @@ -21028,7 +21196,8 @@ erfordert bereits Kenntnis der Innereien

-
+ +
@@ -28086,7 +28255,7 @@ - + @@ -28099,11 +28268,12 @@ Implementierung der real-world-Variante fehlt!

-
+ + - + @@ -28119,7 +28289,8 @@ wie Session- und State-Managment, Commands etc.

-
+ +
@@ -33414,7 +33585,7 @@
- + @@ -33435,7 +33606,8 @@ - + + @@ -35839,7 +36011,7 @@ - + @@ -35870,7 +36042,8 @@ Denn letzteres ist bei uns eine Grundannahme. Es gibt keine ungefähren Diffs!

-
+ +
@@ -38874,7 +39047,7 @@ - + @@ -38890,14 +39063,15 @@ Ganz prominent fehlt hier also z.B: MIDI

-
+ +
- + @@ -38910,7 +39084,8 @@ die Aufgrund von Klassifikationen automatisch bereits existieren

-
+ +
@@ -39771,7 +39946,7 @@ - + @@ -39784,7 +39959,8 @@ sofern das Widget mit entsprechendem Modus eingefügt wurde

-
+ +
@@ -39817,7 +39993,7 @@ - + @@ -39830,7 +40006,8 @@ context->add_class("ohMy");

-
+ + @@ -39909,7 +40086,7 @@ - + @@ -39925,7 +40102,8 @@ oder ist es eine Vererbungs-Hierarchie, wie sie für das CSS-Styling benötigt wird?

-
+ +
@@ -39946,7 +40124,7 @@ - + @@ -39970,7 +40148,8 @@ - + + @@ -40021,7 +40200,7 @@ - + @@ -40040,7 +40219,8 @@ Alles in ein Framework zwingen. Alternativlos, capisce?

-
+ +
@@ -40065,7 +40245,7 @@ - + @@ -40078,7 +40258,8 @@ welche ggfs C++ - Exceptions fängt

-
+ +
@@ -40091,7 +40272,7 @@ - + @@ -40110,11 +40291,12 @@ Nur das ist der Grund. Es geht gar nicht um die Event-Loop

-
+ +
- + @@ -40135,11 +40317,12 @@ - + + - + @@ -40167,7 +40350,8 @@ - + + @@ -40233,7 +40417,7 @@ - + @@ -40288,7 +40472,8 @@ }

-
+ + @@ -40302,7 +40487,7 @@ - + @@ -40369,7 +40554,8 @@ }

-
+ + @@ -40429,7 +40615,7 @@ - + @@ -40442,9 +40628,10 @@ aber macht in etwa die gleichen Operationen

-
+ +
- + @@ -40463,7 +40650,8 @@ Gtk-Main verwendet inzwischen den gleichen Mechanismus

-
+ +
@@ -40476,7 +40664,7 @@ - + @@ -40498,7 +40686,8 @@ Das ist eine subtile Falle.

-
+ +
@@ -40517,7 +40706,7 @@ - + @@ -40540,7 +40729,8 @@ alles das nicht aus dem GUI-Thread heraus geschieht

-
+ +
@@ -40596,7 +40786,7 @@ - + @@ -40606,9 +40796,10 @@ ...ob beim Expand/Collapse das umschließende Widget resized werden soll

-
+ +
- + @@ -40618,12 +40809,13 @@ ob eingeklappt oder ausgeklappt

-
+ +
- + @@ -40636,7 +40828,8 @@ was dazu führt, daß das Kind stets allen verfügbaren Platz nimmt

-
+ +
@@ -40775,7 +40968,7 @@
- + @@ -40788,7 +40981,8 @@ Siehe Beschreibung im Beispiel/Tutorial

-
+ +
@@ -40844,7 +41038,7 @@ - + @@ -40854,9 +41048,10 @@ ....how does the event dispatching deal with partially covered widgets

-
+ +
- + @@ -40866,7 +41061,8 @@ ...for embedded widgets

-
+ +
@@ -40899,7 +41095,7 @@ - + @@ -40912,7 +41108,8 @@ i.e. the enclosing parent widget also gets a chance to redraw itself

-
+ +
@@ -40933,7 +41130,7 @@ - + @@ -40943,13 +41140,14 @@ asked on stackoverflow...

-
+ +
- + @@ -40962,7 +41160,8 @@ by printing values from the on_draw() callback

-
+ +
@@ -41098,7 +41297,7 @@ - + @@ -41126,7 +41325,8 @@ Kind-Widgets noch gar nicht festgelegt war (denn das passiert erst beim draw).

-
+ +
@@ -41164,7 +41364,7 @@ - + @@ -41180,7 +41380,8 @@ und auch ein Signal für Parse-Fehler anschließt

-
+ +
@@ -41207,7 +41408,7 @@ - + @@ -41225,7 +41426,8 @@ Beachte: der Text-Cursor (Marker "insert") hat right gravity

-
+ +
@@ -41300,7 +41502,7 @@ - + @@ -41322,7 +41524,8 @@ The grip is implemented as a GdlDockItemGrip

-
+ +
@@ -41403,7 +41606,7 @@

- + @@ -41413,9 +41616,10 @@ kann eines der Templates im Zyklus vorrübergehend als "incomplete" gelten.

-
+ +
- + @@ -41437,7 +41641,8 @@ Konsequenz: man wählt dann z.B. eine subtil falsche Spezialisierung.

-
+ +
@@ -41463,7 +41668,7 @@ - + @@ -41491,13 +41696,14 @@ selber aus einem statischen Initialisierungs-Kontext heraus erfolgt.

-
+ +
- + @@ -41693,7 +41899,8 @@   }

-
+ +
@@ -41935,7 +42142,7 @@ - + @@ -41960,9 +42167,10 @@ Query<RES>::resolveBy

-
+ +
- + @@ -41997,14 +42205,15 @@ sonst kommt Doxygen durcheinander

-
+ +
- + @@ -42026,7 +42235,8 @@ wird hier kein Link erzeugt

-
+ +
@@ -42042,7 +42252,7 @@ - + @@ -42058,7 +42268,8 @@ Die Icon-Größen ergeben sich aus den Boxes auf 'plate'

-
+ +
@@ -42073,7 +42284,7 @@ - + @@ -42083,7 +42294,8 @@ ...im Besonderen die guten Diagramme für Pulse, ALSA und Jack

-
+ +
@@ -42208,7 +42420,7 @@
- + @@ -42231,10 +42443,11 @@ "-Wl,-rpath-link=target/modules"

-
+ +
- + @@ -42244,14 +42457,15 @@ laufen wieder alle

-
+ + - + @@ -42261,7 +42475,8 @@ test.sh Zeile 138

-
+ +
@@ -42305,7 +42520,7 @@ - + @@ -42315,12 +42530,13 @@ und wir verbringen unsere Zeit mit contention

-
+ +
- + @@ -42330,7 +42546,8 @@ ist klar, hab ich gebrochen

-
+ + @@ -42346,7 +42563,7 @@ - + @@ -42359,7 +42576,8 @@ Vorher hatte ich erste Kollisionen nach 25000 Nummern

-
+ +
@@ -42398,7 +42616,7 @@
- + @@ -42411,7 +42629,8 @@ Aug 10 04:51:39 flaucher kernel: traps: test-suite[8249] trap int3 ip:7ffff7deb241 sp:7fffffffe5c8 error:0

-
+ + @@ -42468,7 +42687,7 @@ - + @@ -42478,7 +42697,8 @@ bison dejagnu flex gobjc libncurses5-dev libreadline-dev liblzma-dev libbabeltrace-dev libbabeltrace-ctf-dev python3-dev

-
+ +
@@ -42494,7 +42714,7 @@ - + @@ -42528,7 +42748,8 @@ au weia LEUTE!

-
+ +
@@ -42552,7 +42773,7 @@
- + @@ -42568,10 +42789,11 @@ und tatsächlich: das ist daneben, GCC hat Recht!

-
+ +
- + @@ -42581,7 +42803,8 @@ aktualisieren und neu bauen

-
+ +
@@ -42601,7 +42824,7 @@ - + @@ -42611,7 +42834,8 @@ wähle Kompatibiltät genau so, daß Ubuntu-Trusty noch unterstützt wird.

-
+ + @@ -42645,7 +42869,7 @@ - + @@ -42658,9 +42882,10 @@ Ich meine also: zu Beginn vom Build sollte das Buildsystem einmal eine Infozeile ausgeben

-
+ +
- + @@ -42670,7 +42895,8 @@ ...denn die stören jeweils beim erzeugen eines Hotfix/Patch im Paketbau per dpkg --commit

-
+ +
@@ -42717,7 +42943,7 @@
- + @@ -42727,11 +42953,12 @@ Doku durchkämmen nach Müll

-
+ + - + @@ -42744,10 +42971,11 @@ WICHTIG: keine vorgreifende Infor publizieren!!!!!

-
+ +
- + @@ -42763,7 +42991,8 @@ insgesamt sorgfältig durchlesen

-
+ + @@ -42773,7 +43002,7 @@ - + @@ -42783,7 +43012,8 @@ knappe Kennzeichnung des Releases in den Kommentar

-
+ + @@ -42791,7 +43021,7 @@ - + @@ -42810,7 +43040,8 @@ denn wir wollen keine DEB-Info im Master haben!

-
+ + @@ -42823,7 +43054,7 @@
- + @@ -42836,7 +43067,8 @@ die unmittelbaren Release-Dokumente durchgehen

-
+ + @@ -42850,7 +43082,7 @@ - + @@ -42863,7 +43095,8 @@ Sollte konfliktfrei sein

-
+ +
@@ -42886,7 +43119,7 @@
- + @@ -42896,7 +43129,8 @@ ...das heißt bauen und hochladen

-
+ + @@ -42950,7 +43184,7 @@ - + @@ -42963,9 +43197,10 @@ unter Debian/Jessie wird das ignoriert

-
+ +
- + @@ -42991,7 +43226,8 @@ Die Wahrscheinlichkeit, daß irgend jemand Lumiera unter Ubuntu/Trusty installieren möchte, erscheint mir akademisch

-
+ +
@@ -43017,7 +43253,7 @@ - + @@ -43027,7 +43263,8 @@ in lib/hash-standard.hpp

-
+ +
@@ -43042,7 +43279,7 @@ - + @@ -43062,7 +43299,8 @@ es gibt Probleme beim Linken mit den Boost-Libraries, die auf Ubuntu/wily mit gcc-5 gebaut sind.

-
+ +
@@ -43070,7 +43308,7 @@ - + @@ -43080,7 +43318,8 @@ Wichtig: hier nur was wirklich gebaut ist und funktioniert!

-
+ +
@@ -43104,7 +43343,7 @@ - + @@ -43120,7 +43359,8 @@ bestehen, aber irgendwann müssen wir das schon glattziehen

-
+ +
@@ -43158,7 +43398,7 @@ - + @@ -43168,7 +43408,8 @@ seit gcc-4.8 ist kein static_assert mehr in der STDlib

-
+ +
@@ -43210,7 +43451,7 @@ - + @@ -43256,10 +43497,11 @@ END

-
+ + - + @@ -43269,11 +43511,12 @@ for I in `seq 1 50`; do target/test-suite CallQueue_test; done

-
+ +
- + @@ -43289,7 +43532,8 @@ und eine Doxygen-Seite im Browser geladen

-
+ +
@@ -43310,7 +43554,7 @@ - + @@ -43320,7 +43564,8 @@ weil sich die Threads gegenseitig ihre Counter inkrementieren.

-
+ +
From 320539344b2e05eb407b71af6067dbbaa40c71f6 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 1 Nov 2018 20:44:31 +0100 Subject: [PATCH 169/178] Timeline: make the injected timeline basically visible --- src/gui/timeline/timeline-controller.cpp | 4 +--- src/gui/timeline/timeline-widget.cpp | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/gui/timeline/timeline-controller.cpp b/src/gui/timeline/timeline-controller.cpp index bd18fc694..5403f6b6c 100644 --- a/src/gui/timeline/timeline-controller.cpp +++ b/src/gui/timeline/timeline-controller.cpp @@ -90,9 +90,7 @@ namespace timeline { { layoutManager.installRootTrack (head, body); }}} - { - UNIMPLEMENTED ("how to make the controller operative..."); - } + { } TimelineController::~TimelineController() diff --git a/src/gui/timeline/timeline-widget.cpp b/src/gui/timeline/timeline-widget.cpp index 1c8b62dd1..6ae5c7e63 100644 --- a/src/gui/timeline/timeline-widget.cpp +++ b/src/gui/timeline/timeline-widget.cpp @@ -72,7 +72,7 @@ namespace timeline { , layout_{new TimelineLayout{*this}} , control_{new TimelineController{identity, trackID, nexus, *layout_}} { - UNIMPLEMENTED ("build the timeline UI"); + show_all(); } TimelineWidget::~TimelineWidget() { } From 8ab6c54b9d771c3a72443ce088882e30090a9b0c Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 5 Nov 2018 04:56:01 +0100 Subject: [PATCH 170/178] DummySessionConnection: switch the dummy-ID on each use ...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) --- src/gui/dialog/test-control.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/gui/dialog/test-control.hpp b/src/gui/dialog/test-control.hpp index d17f6b857..6934bf789 100644 --- a/src/gui/dialog/test-control.hpp +++ b/src/gui/dialog/test-control.hpp @@ -290,10 +290,10 @@ namespace dialog { Gtk::Button mut_1_; string - getDummyID() + pickDummyID() { string dummyID = sanitise (dummy_.get_text()); - dummy_.set_text (dummyID); + dummy_.set_text (string{_Fmt{"d%s%02d"} % lib::test::randStr(2) % (1 + rand() % 99)}); return dummyID; } @@ -310,8 +310,8 @@ namespace dialog { dummy_.set_tooltip_markup (_("dummy ID value\n" "used to build names in the generated content")); - dummy_.set_text (string{_Fmt{"d%s%02d"} % lib::test::randStr(2) % (1 + rand() % 99)}); 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); @@ -328,9 +328,9 @@ namespace dialog { // define the action triggers... seq_1_.signal_clicked().connect( - [&]{ bus.act (model::commandMessage (proc::cmd::test_fake_injectSequence_1, getDummyID())); }); + [&]{ 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, getDummyID())); }); + [&]{ bus.act (model::commandMessage (proc::cmd::test_fake_injectSequence_2, pickDummyID())); }); mut_1_.signal_clicked().connect( [&]{ cerr << "gelldaschaugst..." < Date: Tue, 6 Nov 2018 01:01:00 +0100 Subject: [PATCH 171/178] Timeline: add preliminary track-head display just some labels, so that we can see the added content TODO: unsolved problem: how to pass the track name --- src/gui/timeline/track-body.cpp | 2 +- src/gui/timeline/track-body.hpp | 5 ++++- src/gui/timeline/track-head-widget.cpp | 8 +++++++- src/gui/timeline/track-head-widget.hpp | 7 ++++++- src/gui/timeline/track-presenter.hpp | 12 ++++++------ wiki/thinkPad.ichthyo.mm | 27 ++++++++++++++++++++++++-- 6 files changed, 49 insertions(+), 12 deletions(-) diff --git a/src/gui/timeline/track-body.cpp b/src/gui/timeline/track-body.cpp index f953432bc..9412dbe93 100644 --- a/src/gui/timeline/track-body.cpp +++ b/src/gui/timeline/track-body.cpp @@ -64,7 +64,7 @@ namespace timeline { - TrackBody::TrackBody() + TrackBody::TrackBody (ID identity) : overviewHeight_{DEFAULT_OVERVIEW_HEIGHT_px} , contentHeight_{DEFAULT_OVERVIEW_HEIGHT_px} , subTracks_{} diff --git a/src/gui/timeline/track-body.hpp b/src/gui/timeline/track-body.hpp index 0abcdf4e5..3cb59e26a 100644 --- a/src/gui/timeline/track-body.hpp +++ b/src/gui/timeline/track-body.hpp @@ -41,6 +41,7 @@ #define GUI_TIMELINE_TRACK_BODY_H #include "gui/gtk-base.hpp" +#include "gui/ctrl/bus-term.hpp" //#include "lib/util.hpp" @@ -52,6 +53,8 @@ namespace gui { namespace timeline { + using ID = ctrl::BusTerm::ID; + /** * Helper to organise and draw the space allocated for a fork of sub-tracks. @@ -75,7 +78,7 @@ namespace timeline { SubTracks subTracks_; public: - TrackBody (); + TrackBody (ID identity); ~TrackBody(); uint calcHeight(); diff --git a/src/gui/timeline/track-head-widget.cpp b/src/gui/timeline/track-head-widget.cpp index 518ac9dc4..506c62410 100644 --- a/src/gui/timeline/track-head-widget.cpp +++ b/src/gui/timeline/track-head-widget.cpp @@ -62,9 +62,15 @@ namespace timeline { - TrackHeadWidget::TrackHeadWidget () + TrackHeadWidget::TrackHeadWidget (ID identity) : Gtk::Grid{} + , nameTODO_{identity.getSym()} + , treeTODO_{"X"} { + this->attach (nameTODO_, 0,0, 2,1); + this->attach (treeTODO_, 0,1, 1,1); + + this->show_all(); } diff --git a/src/gui/timeline/track-head-widget.hpp b/src/gui/timeline/track-head-widget.hpp index cb448bf51..2bf611cba 100644 --- a/src/gui/timeline/track-head-widget.hpp +++ b/src/gui/timeline/track-head-widget.hpp @@ -46,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" @@ -57,6 +58,7 @@ namespace gui { namespace timeline { + using ID = ctrl::BusTerm::ID; /** * Header pane control area corresponding to a Track with nested child Tracks. @@ -68,8 +70,11 @@ namespace timeline { class TrackHeadWidget : public Gtk::Grid { + Gtk::Label nameTODO_; + Gtk::Label treeTODO_; + public: - TrackHeadWidget (); + TrackHeadWidget (ID identity); ~TrackHeadWidget(); /** Integrate the control area for a nested sub track fork. */ diff --git a/src/gui/timeline/track-presenter.hpp b/src/gui/timeline/track-presenter.hpp index a2a2244ee..926b88f98 100644 --- a/src/gui/timeline/track-presenter.hpp +++ b/src/gui/timeline/track-presenter.hpp @@ -80,9 +80,9 @@ namespace timeline { TrackBody body; template - DisplayFrame (FUN anchorDisplay) - : head{} - , body{} + DisplayFrame (ID id, FUN anchorDisplay) + : head{id} + , body{id} { anchorDisplay (head, body); } @@ -121,9 +121,9 @@ namespace timeline { * @param nexus a way to connect this Controller to the UI-Bus. */ template - TrackPresenter (ID identity, ctrl::BusTerm& nexus, FUN anchorDisplay) - : Controller{identity, nexus} - , display_{anchorDisplay} + TrackPresenter (ID id, ctrl::BusTerm& nexus, FUN anchorDisplay) + : Controller{id, nexus} + , display_{id, anchorDisplay} , subFork_{} , markers_{} , clips_{} diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 564be6475..4505370ec 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -18490,7 +18490,13 @@ - + + + + + + + @@ -19236,7 +19242,7 @@ - + @@ -40834,6 +40840,23 @@ + + + + + + +

+ Gtk::Grid +

+ + +
+ + + + +
From 83394a6f0156008dd4a29432b6b1a10a96481167 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 9 Nov 2018 20:19:45 +0100 Subject: [PATCH 172/178] GenNode: investigate Problem with non-unique attribute IDs 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 --- src/lib/diff/gen-node.hpp | 28 ++++++- src/lib/diff/record.hpp | 13 ++- wiki/thinkPad.ichthyo.mm | 168 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 193 insertions(+), 16 deletions(-) diff --git a/src/lib/diff/gen-node.hpp b/src/lib/diff/gen-node.hpp index 41606287f..300770c72 100644 --- a/src/lib/diff/gen-node.hpp +++ b/src/lib/diff/gen-node.hpp @@ -52,6 +52,16 @@ ** addressable by-name and an (ordered) collection of elements treated ** as children within the scope of the given record. ** + ** ## The GenNode ID + ** + ** Each GenNode holds an ID tag, allowing to establish _identical_ and _distinct_ + ** elements within a scope. This ID is based on lib::idi::BareEntryID, thereby + ** providing a human readable symbolic part, and a hash value. By default, these + ** GenNode IDs are fabricated such as to hold a non-reproducible, random hash + ** value -- however, there are construction flavours allowing to pass in an + ** pre-existing distinct Entry-ID. + ** + ** ** # Requirements ** ** GenNode elements are to be used in the diff detection and implementation. @@ -66,7 +76,7 @@ ** typed context, either based on some kind of embedded type tag, or ** alternatively by visitation and matching ** - finally, the handling of changes prompts us to support installation - ** of a specifically typed change handling closure. + ** of a specifically typed _change handling closure_. ** ** ## monadic nature? ** @@ -223,6 +233,10 @@ namespace diff{ idi::getTypeHash()) { } + ID (string const& symbolicID, HashVal seed) + : idi::BareEntryID (symbolicID, seed) + { } + public: explicit ID (GenNode const& node) @@ -433,7 +447,15 @@ namespace diff{ } }; - + template + static GenNode + forAttribute (string const& key, X&& payload) + { + return GenNode{ID(key, std::rand()) // NOTE: random hash seed + ,DataCap(forward (payload))}; + } + + protected: /** @internal for dedicated builder subclasses */ GenNode (ID&& id, DataCap&& d) @@ -845,7 +867,7 @@ namespace diff{ inline GenNode Rec::buildAttribute (string const& key, X&& payload) { - return GenNode(key, std::forward(payload)); + return GenNode::forAttribute (key, forward(payload)); } diff --git a/src/lib/diff/record.hpp b/src/lib/diff/record.hpp index 0e2f8ce03..35e21e3bb 100644 --- a/src/lib/diff/record.hpp +++ b/src/lib/diff/record.hpp @@ -25,17 +25,22 @@ ** Special collection to represent object-like data. ** To be used in a context where introspection, open, extensible definitions ** and loose coupling of data representation matters. Typically, structures - ** defined in terms of Record elements are linked to the actual \em core + ** defined in terms of Record elements are linked to the _actual core_ ** representation of the same entities relying on [diff messages](\ref diff-language.hpp). ** Record is one of the supported flavours within the DataCap of GenNode elements, ** which in turn serve as the standard handle to refer to other elements, entities, ** attributes or references within the "backbone" of the Lumiera GUI. ** + ** A Record holds + ** - (optionally) a type-ID string + ** - a collection of _named attributes_ (key-value data) + ** - a list of _enclosed children_ (contents within the "scope" of this Record) + ** ** ## design decisions ** The Record type is shaped from its intended use: It serves to symbolically represent - ** _objects_ in the "external tree description". Here, "objects" means objects _for real_, - ** i.e. with types, fields and an enclosed scope. But \em external means that we do not work - ** on these objects right here, we only represent them, for later referral, _symbolically_. + ** *objects* in the "external tree description". Here, "objects" means objects _for real_, + ** i.e. with types, fields and an enclosed scope. Yet the term *external* means that we do not + ** work on these objects right here, we only represent them, for later referral, _symbolically_. ** ** \par rationale ** The underlying theme and motivation of this design is negative: we do not want diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 4505370ec..7ce8628d3 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -6671,11 +6671,11 @@ - + - + @@ -6703,7 +6703,9 @@ - + + + @@ -20098,7 +20100,7 @@ - + @@ -20979,6 +20981,10 @@ + + + + @@ -21033,6 +21039,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +

+ weil wir einen Mechanismus haben, die <cnt>-Dekoration pro Typ global hochzuzählen (treadsafe) +

+ + +
+
+ + + + + + + + + +

+ Problem: key nur innerhalb des Objektes eindeutig +

+ + +
+ +
+ + + + + + + + + + + + + + + +

+ ...denn dann müßte das Attribut +

+

+ von dem Container (GenNode) wissen, der es zwei Ebenen höher enthält und umschließt +

+ + +
+ +
+ + +
+ + + + + + +

+ ...es kommt (vielleicht) überhaupt nicht darauf an, +

+

+ daß der Hash einer Attribut-ID reproduzierbar ist (auf Basis der symbilschen ID). +

+

+ Der Hash kann genausogut eine Zufallszahl sein +

+ + +
+ + + + + + + + +

+ ...nämlich diejenigen, die selber Modell-Elemente sind. +

+

+ Und genau diese speziellen Objekt-Attribute sind der Anlaß, +

+

+ sich über dieses Problem Gedanken zu machen +

+ + +
+
+
+
+
+
+
@@ -21253,7 +21381,7 @@ - + @@ -21414,7 +21542,7 @@ - + @@ -21528,6 +21656,22 @@ + + + + + + +

+ Identität == Bus-ID +

+ + +
+ + + + @@ -21615,7 +21759,7 @@ - + @@ -39273,7 +39417,7 @@ - + @@ -39304,6 +39448,12 @@ + + + + + + @@ -39319,7 +39469,7 @@ - + From 1bbe903202fdc1ba1e2232d42ea0dd47ab7d5d69 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 9 Nov 2018 22:30:35 +0100 Subject: [PATCH 173/178] GenNode: revert -- better not handle this problem on ETD level ...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 --- src/lib/diff/gen-node.hpp | 25 ++++--- src/lib/diff/record.hpp | 5 +- tests/library/diff/gen-node-basic-test.cpp | 9 +++ wiki/thinkPad.ichthyo.mm | 86 +++++++++++++++++++++- 4 files changed, 114 insertions(+), 11 deletions(-) diff --git a/src/lib/diff/gen-node.hpp b/src/lib/diff/gen-node.hpp index 300770c72..1e8a122c7 100644 --- a/src/lib/diff/gen-node.hpp +++ b/src/lib/diff/gen-node.hpp @@ -233,8 +233,8 @@ namespace diff{ idi::getTypeHash()) { } - ID (string const& symbolicID, HashVal seed) - : idi::BareEntryID (symbolicID, seed) + ID (idi::BareEntryID&& rawD) + : idi::BareEntryID{move (rawD)} { } public: @@ -447,12 +447,12 @@ namespace diff{ } }; + /** fabricate a GenNode with the literally given ID */ template static GenNode - forAttribute (string const& key, X&& payload) + asAttribute (idi::BareEntryID && rawID, X&& payload) { - return GenNode{ID(key, std::rand()) // NOTE: random hash seed - ,DataCap(forward (payload))}; + return GenNode{ID{move (rawID)}, DataCap{forward (payload)}}; } @@ -794,14 +794,21 @@ namespace diff{ inline GenNode MakeRec::genNode() { - return GenNode(std::move(record_)); + return GenNode{std::move(record_)}; } template<> inline GenNode - MakeRec::genNode(string const& symbolicID) + MakeRec::genNode (idi::BareEntryID rawID) { - return GenNode(symbolicID, std::move(record_)); + return GenNode::asAttribute (std::move(rawID), std::move(record_)); + } + + template<> + inline GenNode + MakeRec::genNode (string const& symbolicID) + { + return GenNode{symbolicID, std::move(record_)}; } @@ -867,7 +874,7 @@ namespace diff{ inline GenNode Rec::buildAttribute (string const& key, X&& payload) { - return GenNode::forAttribute (key, forward(payload)); + return GenNode{key, forward(payload)}; } diff --git a/src/lib/diff/record.hpp b/src/lib/diff/record.hpp index 35e21e3bb..f00ae35a8 100644 --- a/src/lib/diff/record.hpp +++ b/src/lib/diff/record.hpp @@ -112,7 +112,9 @@ namespace lib { template class PlantingHandle; - +namespace idi { + class BareEntryID; +} namespace diff{ namespace error = lumiera::error; @@ -562,6 +564,7 @@ namespace diff{ */ VAL genNode(); + VAL genNode(idi::BareEntryID rawID); VAL genNode(string const& symbolicID); template diff --git a/tests/library/diff/gen-node-basic-test.cpp b/tests/library/diff/gen-node-basic-test.cpp index 2b73c72f8..5967d656b 100644 --- a/tests/library/diff/gen-node-basic-test.cpp +++ b/tests/library/diff/gen-node-basic-test.cpp @@ -211,6 +211,15 @@ namespace test{ // but while o4 was based on o2, // adding all the additional contents didn't mutate o2 CHECK (isnil(o2.data.get())); + + // special case: can create an (Attribute) GenNode with specifically crafted ID + idi::EntryID veryspecialID{"quasi niente"}; + auto o5 = MakeRec().genNode(veryspecialID); + CHECK (o5 != MakeRec().genNode()); + CHECK (o5 != MakeRec().genNode("quasi niente")); + CHECK (o5 == MakeRec().genNode(veryspecialID)); + CHECK (name(o5) == "quasi_niente"); // Note: EntryID sanitised the string + CHECK (o5.idi == veryspecialID); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 7ce8628d3..d2f2ca438 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -21056,7 +21056,12 @@ - + + + + + + @@ -21156,6 +21161,85 @@ + + + + + + +

+ aber: zufällige ID macht Objekt-builder stateful +

+ + +
+ + +
+
+ + + + + +
+
+ + + + + + + + + + + + + + + +

+ das heißt. +

+

+ die Konstruktion des Domain-Modelles ist dafür zuständig +

+

+ für global eindeutige IDs an den relevanten Stellen zu sorgen +

+ + +
+ + + + + + + +

+ ...dem man eine EntryID geben kann +

+ + +
+ +
+ + + + + + + + + + + + + +
From 7cc68fadea60cb995129ee9ecbb1dd4e4635f92f Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 9 Nov 2018 22:55:08 +0100 Subject: [PATCH 174/178] GenNode / Timeline: allow to mutate attribute objects with explicitly given 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... --- src/gui/timeline/timeline-controller.cpp | 7 ++++--- src/lib/diff/tree-mutator-attribute-binding.hpp | 9 +++++++++ src/lib/diff/tree-mutator.hpp | 7 ++++++- wiki/thinkPad.ichthyo.mm | 6 +++--- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/gui/timeline/timeline-controller.cpp b/src/gui/timeline/timeline-controller.cpp index 5403f6b6c..8a55e588e 100644 --- a/src/gui/timeline/timeline-controller.cpp +++ b/src/gui/timeline/timeline-controller.cpp @@ -138,6 +138,7 @@ namespace timeline { TimelineController::buildMutator (TreeMutator::Handle buffer) { using PMarker = unique_ptr; + auto rootForkID = ID{*fork_}; buffer.create ( TreeMutator::build() @@ -160,13 +161,13 @@ namespace timeline { target->buildMutator (buff); // - delegate to child to build nested TreeMutator return true; })) - .mutateAttrib(ATTR_fork, [&](TreeMutator::Handle buff) - { // »Attribute Mutator« : how to 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(ATTR_name, [&](string val) - { // »Attribute Setter« : how to assign a new value to some object field + { // »Attribute Setter« : how to assign a new value to the name field (object member) name_ = val; })); } diff --git a/src/lib/diff/tree-mutator-attribute-binding.hpp b/src/lib/diff/tree-mutator-attribute-binding.hpp index 69221a1d2..398aa5963 100644 --- a/src/lib/diff/tree-mutator-attribute-binding.hpp +++ b/src/lib/diff/tree-mutator-attribute-binding.hpp @@ -359,6 +359,15 @@ namespace diff{ return chainedBuilder> (key, mutatorBuilderClosure); } + /** Entry point for DSL builder; variation to handle an attribute-object with given ID */ + template + template + inline auto + Builder::mutateAttrib (idi::BareEntryID const& rawID, CLO mutatorBuilderClosure) + { + return chainedBuilder> (rawID, mutatorBuilderClosure); + } + }//(END)Mutator-Builder decorator components... diff --git a/src/lib/diff/tree-mutator.hpp b/src/lib/diff/tree-mutator.hpp index 4e11d98c2..07a12fc57 100644 --- a/src/lib/diff/tree-mutator.hpp +++ b/src/lib/diff/tree-mutator.hpp @@ -373,7 +373,12 @@ namespace diff{ template auto mutateAttrib (Symbol attributeID, CLO mutatorBuilderClosure); - ///////////////////////////////////////TODO define variant taking a GenNode::ID ?? + /** + * @param rawID the explicitly given ID of an attribute object, + * used literally to match the attribute in question + */ + template + auto mutateAttrib (idi::BareEntryID const& rawID, CLO mutatorBuilderClosure); /** set up a binding to a structure of "child objects", diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index d2f2ca438..3f7be1a3c 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -21049,7 +21049,7 @@ - + @@ -21212,7 +21212,7 @@ - + @@ -21224,7 +21224,7 @@ - + From 8432420726ca26038ab62941916253c22d264691 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 9 Nov 2018 23:09:16 +0100 Subject: [PATCH 175/178] Library: fix unwanted implicit conversion ...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 --- src/lib/idi/entry-id.hpp | 2 +- tests/core/proc/asset/basicpipetest.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/idi/entry-id.hpp b/src/lib/idi/entry-id.hpp index 8a860a9c5..5da10074f 100644 --- a/src/lib/idi/entry-id.hpp +++ b/src/lib/idi/entry-id.hpp @@ -265,7 +265,7 @@ namespace idi { return static_cast (bID); } - + explicit operator string() const; friend bool operator< (EntryID const& i1, EntryID const& i2) { return i1.getSym() < i2.getSym(); } diff --git a/tests/core/proc/asset/basicpipetest.cpp b/tests/core/proc/asset/basicpipetest.cpp index 0947d2f6e..f0bf2126b 100644 --- a/tests/core/proc/asset/basicpipetest.cpp +++ b/tests/core/proc/asset/basicpipetest.cpp @@ -96,7 +96,7 @@ namespace test { Asset::Ident idi = thePipe->ident; CHECK (idi.org == "lumi"); CHECK (contains (idi.name, thePipe->getPipeID())); - CHECK (contains (idi.name, thePipe->getStreamID())); + CHECK (contains (idi.name, string{thePipe->getStreamID()})); Category cat{idi.category}; Category refcat{STRUCT,"pipes"}; @@ -146,7 +146,7 @@ namespace test { pipe2 = asset::Struct::retrieve (Query{"pipe(default)"}); CHECK (pipe2 == pipe1); - string sID = pipe1->getStreamID(); // sort of a "default stream type" + auto sID = string{pipe1->getStreamID()}; // sort of a "default stream type" PPipe pipe3 = Pipe::query ("stream("+sID+")"); CHECK (pipe3); CHECK (pipe3->getStreamID() == StreamType::ID{sID}); From a4c37ed99c131bf27a32fad0d919f28176fb7811 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 10 Nov 2018 01:01:59 +0100 Subject: [PATCH 176/178] Library: allow for an explicitly random EntryID 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 --- src/gui/timeline/timeline-controller.cpp | 2 +- src/gui/timeline/track-body.cpp | 2 +- src/gui/timeline/track-body.hpp | 2 +- src/gui/timeline/track-head-widget.cpp | 6 ++-- src/gui/timeline/track-head-widget.hpp | 2 +- src/gui/timeline/track-presenter.cpp | 10 ++++--- src/gui/timeline/track-presenter.hpp | 10 ++++--- src/lib/idi/entry-id.hpp | 38 ++++++++++++++++++++++-- wiki/renderengine.html | 14 ++++----- wiki/thinkPad.ichthyo.mm | 12 ++++++-- 10 files changed, 71 insertions(+), 27 deletions(-) diff --git a/src/gui/timeline/timeline-controller.cpp b/src/gui/timeline/timeline-controller.cpp index 8a55e588e..5634a301c 100644 --- a/src/gui/timeline/timeline-controller.cpp +++ b/src/gui/timeline/timeline-controller.cpp @@ -84,7 +84,7 @@ namespace timeline { 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 + , name_{identity.getSym()} // fallback initialise name from human-readable ID symbol , markers_{} , fork_{new TrackPresenter{trackID, nexus, [&](TrackHeadWidget& head,TrackBody& body) { diff --git a/src/gui/timeline/track-body.cpp b/src/gui/timeline/track-body.cpp index 9412dbe93..dca82cb9d 100644 --- a/src/gui/timeline/track-body.cpp +++ b/src/gui/timeline/track-body.cpp @@ -64,7 +64,7 @@ namespace timeline { - TrackBody::TrackBody (ID identity) + TrackBody::TrackBody (cuString trackName) : overviewHeight_{DEFAULT_OVERVIEW_HEIGHT_px} , contentHeight_{DEFAULT_OVERVIEW_HEIGHT_px} , subTracks_{} diff --git a/src/gui/timeline/track-body.hpp b/src/gui/timeline/track-body.hpp index 3cb59e26a..839006066 100644 --- a/src/gui/timeline/track-body.hpp +++ b/src/gui/timeline/track-body.hpp @@ -78,7 +78,7 @@ namespace timeline { SubTracks subTracks_; public: - TrackBody (ID identity); + TrackBody (cuString trackName); ~TrackBody(); uint calcHeight(); diff --git a/src/gui/timeline/track-head-widget.cpp b/src/gui/timeline/track-head-widget.cpp index 506c62410..da1366bf8 100644 --- a/src/gui/timeline/track-head-widget.cpp +++ b/src/gui/timeline/track-head-widget.cpp @@ -62,10 +62,10 @@ namespace timeline { - TrackHeadWidget::TrackHeadWidget (ID identity) + TrackHeadWidget::TrackHeadWidget (cuString& trackName) : Gtk::Grid{} - , nameTODO_{identity.getSym()} - , treeTODO_{"X"} + , nameTODO_{trackName} + , treeTODO_{"↳"} { this->attach (nameTODO_, 0,0, 2,1); this->attach (treeTODO_, 0,1, 1,1); diff --git a/src/gui/timeline/track-head-widget.hpp b/src/gui/timeline/track-head-widget.hpp index 2bf611cba..f632e83fb 100644 --- a/src/gui/timeline/track-head-widget.hpp +++ b/src/gui/timeline/track-head-widget.hpp @@ -74,7 +74,7 @@ namespace timeline { Gtk::Label treeTODO_; public: - TrackHeadWidget (ID identity); + TrackHeadWidget (cuString& trackName); ~TrackHeadWidget(); /** Integrate the control area for a nested sub track fork. */ diff --git a/src/gui/timeline/track-presenter.cpp b/src/gui/timeline/track-presenter.cpp index 31a81ad6e..829b460dc 100644 --- a/src/gui/timeline/track-presenter.cpp +++ b/src/gui/timeline/track-presenter.cpp @@ -66,9 +66,7 @@ namespace timeline { - TrackPresenter::~TrackPresenter() - { - } + TrackPresenter::~TrackPresenter() { } @@ -151,7 +149,11 @@ namespace timeline { 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 + name_ = val; + })); } diff --git a/src/gui/timeline/track-presenter.hpp b/src/gui/timeline/track-presenter.hpp index 926b88f98..6befa66a0 100644 --- a/src/gui/timeline/track-presenter.hpp +++ b/src/gui/timeline/track-presenter.hpp @@ -80,9 +80,9 @@ namespace timeline { TrackBody body; template - DisplayFrame (ID id, FUN anchorDisplay) - : head{id} - , body{id} + DisplayFrame (cuString& name, FUN anchorDisplay) + : head{name} + , body{name} { anchorDisplay (head, body); } @@ -106,6 +106,7 @@ namespace timeline { class TrackPresenter : public model::Controller { + string name_; DisplayFrame display_; vector> subFork_; @@ -123,7 +124,8 @@ namespace timeline { template TrackPresenter (ID id, ctrl::BusTerm& nexus, FUN anchorDisplay) : Controller{id, nexus} - , display_{id, anchorDisplay} + , name_{id.getSym()} // fallback initialise track-name from human-readable ID symbol + , display_{name_, anchorDisplay} , subFork_{} , markers_{} , clips_{} diff --git a/src/lib/idi/entry-id.hpp b/src/lib/idi/entry-id.hpp index 5da10074f..a32cf0978 100644 --- a/src/lib/idi/entry-id.hpp +++ b/src/lib/idi/entry-id.hpp @@ -32,6 +32,13 @@ ** @note as of 3/2010 this is an experimental setup and exists somewhat in parallel ** to the assets. We're still in the process of finding out what's really required ** to keep track of all the various kinds of objects. ///////////////////TICKET #739 + ** @todo as of 11/2018 the basic design seems adequate, but the actual solution looks fishy. + ** Even more so, since we now use subclasses of BareEntryID + ** - as identity tag within lib::diff::GenNode + ** - as identity tag for all [tangible UI elements](\ref gui::model::Tangible) + ** There are various quirks and hacks to make all of this happen, and especially + ** the hashed-in type information feels gratuitous at places, when all we actually + ** need is a distinct identity plus a human readable symbol. ** ** @see asset::Asset::Ident ** @see entry-id-test.cpp @@ -101,7 +108,7 @@ namespace idi { * which would require both to yield the same hash values.... * @warning there is a weakness in boost::hash for strings of running numbers, * causing collisions already for a small set with less than 100000 entries. - * To ameliorate the problem, we hash in the trailing digits, and + * To mitigate the problem, we hash in the trailing digits, and * spread them by the #KNUTH_MAGIC /////////TICKET #865 * @warning this code isn't portable and breaks if sizeof(size_t) < sizeof(void*) * @see HashGenerator_test#verify_Knuth_workaround @@ -146,11 +153,18 @@ namespace idi { * encoded into a hash seed. Thus even the same symbolicID * generates differing hash-IDs for different type parameters */ - BareEntryID (string const& symbolID, HashVal seed =0) + BareEntryID (string const& symbolID, HashVal seed) : symbol_(symbolID) , hash_(buildHash (symbol_, seed)) { } + /** store the symbol but use a random hash part */ + explicit + BareEntryID (string const& symbolID) + : symbol_(symbolID) + , hash_{} // random + { } + public: /* default copyable and assignable */ @@ -281,6 +295,26 @@ namespace idi { + /** + * Entry-ID with a symbolic tag but just a plain random hash part. + * @remarks use this flavour when it is _not relevant_ to tag with + * some type information nor to reproduce the hash value. + */ + struct RandID + : BareEntryID + { + RandID (string const& symbolID) + : BareEntryID{util::sanitise (symbolID)} + { } + RandID (const char* symbolID) + : BareEntryID{util::sanitise (symbolID)} + { } + RandID (Symbol const& internalSymbol) + : BareEntryID{string{internalSymbol}} + { } + }; + + /** try to upcast this BareEntryID to a fully typed EntryID. * Effectively, this is the attempt to reverse a type erasure; * thus the caller needs to know the type information (as provided diff --git a/wiki/renderengine.html b/wiki/renderengine.html index 83c8b3293..7a5b7fb2c 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -2025,18 +2025,18 @@ Deliberately there is an limitaion on the flexibility of what can be added to th A strong emphaisis is placed on ''Separation of Concerns'' and especially on ''Open Closed'' design. We resist the temptation to build an //universal model.// Rather, we understand our whole unterdaking a ''transformation of metadata'' -- which, as a whole, remains conceptual and is only ever implemented partially within context. A [[language of diff exchanges|TreeDiffFundamentals]] serves as integrating backbone. It works against a rather symbolic representation of strucutred matters, conceived as an ExternalTreeDescription. In fact, this desctiption is only materialised in parts, if at all.
-
-
This __proc-Layer__ and ~Render-Engine implementation started out as a design-draft by [[Ichthyo|mailto:Ichthyostega@web.de]] in summer 2007. The key idea of this design-draft is to use the [[Builder Pattern|http://en.wikipedia.org/wiki/Builder_pattern]] for the Render Engine, thus separating completely the //building// of the Render Pipeline from //running,// i.e. doing the actual Render. The Nodes in this Pipeline should process Video/Audio and do nothing else. No more decisions, tests and conditional operations when running the Pipeline. Move all of this out into the configuration of the pipeline, which is done by the Builder.
+
+
This ~Proc-Layer and ~Render-Engine implementation started out as a design-draft by [[Ichthyo|mailto:Ichthyostega@web.de]] in summer 2007. The key idea of this design-draft is to use the [[Builder Pattern|http://en.wikipedia.org/wiki/Builder_pattern]] for the Render Engine, thus separating completely the //building// of the Render Pipeline from //running,// i.e. doing the actual Render. The Nodes in this Pipeline should process Video/Audio and do nothing else. No more decisions, tests and conditional operations when running the Pipeline. Move all of this out into the configuration of the pipeline, which is done by the Builder.
 
 !Why doesn't the current Cinelerra-2 Design succeed?
-The design of Cinelerra-2 basically follows a similar design, but __fails because of two reasons__
-# too much differentiation is put into the class hierarchy instead of configuring Instances differently.<br>This causes overly heavy use of virtual functions and -- in order to ameliorate this -- falling back to hard wired branching
+The design of Cinelerra-2 basically follows a similar design, but [[fails because of two reasons...|https://lumiera.org/project/background/history/CinelerraWoes.html]]
+# too much differentiation is put into the class hierarchy instead of configuring Instances differently.<br/>This causes overly heavy use of virtual functions, intricate implementation dependencies, and -- in order to mitigate this -- falling back to hard wired branching in the end.
 # far too much coupling and back-coupling to the internals of the »EDL«, forcing a overly rigid structure on the latter
 
 !Try to learn from the Problems of the current Cinelerra-2 codebase
-* build up an [[Node Abstraction|ProcNode]] powerful enough to express //all necessary Operations// without the need to recure on the actual object type
+* build up an [[Node Abstraction|ProcNode]] powerful enough to express //all necessary Operations// without the need to recur on the actual object type
 * need to redesign the internals of the Session in a far more open manner. Everything is an [[M-Object|MObjects]] which is [[placed|Placement]] in some manner
-* strive at a StrongSeparation between Session and Render Engine, encapsulate and allways implement against interfaces
+* strive at a StrongSeparation between Session and Render Engine, encapsulate and always implement against interfaces
 
 
 !Design Goals
@@ -2044,7 +2044,7 @@ As always, the main goal is //to cut down complexity// by the usual approach to
 
 To achieve this, here we try to separate ''Configuration'' from ''Processing''. Further, in Configuration we try to separate the ''high level view'' (users view when editing) from the ''low level view'' (the actual configuration effective for the calculations). Finally, we try to factor out and encapsulate ''State'' in order to make State explicit.
 
-The main tool used to implement this separation is the [[Builder Pattern|http://en.wikipedia.org/wiki/Builder_pattern]]. Here especially we move all decisions and parametrization into the BuildProcess. The Nodes in the render pipeline should process Video/Audio and do nothing else. All decisions, tests and conditional operations are factored out of the render process and handled as configuration of the pipeline, which is done by the Builder. The actual color model and number of ports is configured in by a pre-built wiring descriptor. All Nodes are of equal footing with each other, able to be connected freely within the limitations of the necessary input and output. OpenGL and renderfarm support can be configured in as an alternate implementation of some operations together with an alternate signal flow (usable only if the whole Pipeline can be built up to support this changed signal flow), thus factoring out all the complexities of managing the data flow between core and hardware accelerated rendering. We introduce separate control data connections for the [[automation data|Automation]], separating the case of true multi-channel-effects from the case where one node just gets remote controlled by another node (or the case of two nodes using the same automation data).
+The main tool used to implement this separation is the [[Builder Pattern|http://en.wikipedia.org/wiki/Builder_pattern]]. Here especially we move all decisions and parametrization into the BuildProcess. The Nodes in the render pipeline should process Video/Audio and do nothing else. All decisions, tests and conditional operations are factored out of the render process and handled as configuration of the pipeline, which is done by the Builder. The actual colour model and number of ports is configured in by a pre-built wiring descriptor. All Nodes are of equal footing with each other, able to be connected freely within the limitations of the necessary input and output. OpenGL and renderfarm support can be configured in as an alternate implementation of some operations together with an alternate signal flow (usable only if the whole Pipeline can be built up to support this changed signal flow), thus factoring out all the complexities of managing the data flow between core and hardware accelerated rendering. We introduce separate control data connections for the [[automation data|Automation]], separating the case of true multi-channel-effects from the case where one node just gets remote controlled by another node (or the case of two nodes using the same automation data).
 
 Another pertinent theme is to make the basic building blocks simpler, while on the other hand gaining much more flexibility for combining these building blocks. For example we try to unfold any "internal-multi" effects into separate instances (e.g. the possibility of having an arbitrary number of single masks at any point of the pipeline instead of having one special masking facility encompassing multiple sub-masks. Similarly, we treat the Objects in the Session in a more uniform manner and gain the possibility to [[place|Placement]] them in various ways.
 
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 3f7be1a3c..11713b164 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -17603,7 +17603,7 @@ - + @@ -19374,7 +19374,7 @@ - + @@ -20984,6 +20984,12 @@ + + + + + + @@ -21049,7 +21055,7 @@ - + From c8dc5a24a8a2fdd7bc580d043d9964fe11a2e3c9 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 10 Nov 2018 02:39:17 +0100 Subject: [PATCH 177/178] DummySessionConnection: extend population diff to send distinct root-track 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 --- src/gui/model/tangible.hpp | 2 +- src/lib/diff/tree-diff-application.hpp | 2 +- src/lib/diff/tree-diff.cpp | 2 +- src/lib/diff/tree-mutator.hpp | 1 + .../session/dummy-session-connection.cpp | 25 ++++++++++++++----- wiki/thinkPad.ichthyo.mm | 4 +-- 6 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/gui/model/tangible.hpp b/src/gui/model/tangible.hpp index c20601116..f8e06fe99 100644 --- a/src/gui/model/tangible.hpp +++ b/src/gui/model/tangible.hpp @@ -235,7 +235,7 @@ namespace model { friend constexpr size_t treeMutatorSize (const Tangible*) { - return 256; + return 512; } }; diff --git a/src/lib/diff/tree-diff-application.hpp b/src/lib/diff/tree-diff-application.hpp index e5e275495..336df1888 100644 --- a/src/lib/diff/tree-diff-application.hpp +++ b/src/lib/diff/tree-diff-application.hpp @@ -158,7 +158,7 @@ namespace diff{ public: virtual ~ScopeManager(); ///< this is an interface - virtual TreeMutator::Handle openScope() =0; + virtual TreeMutator::Handle openScope() =0; ///< @remark picks up `treeMutatorSize(...)` virtual TreeMutator& closeScope() =0; virtual void clear() =0; diff --git a/src/lib/diff/tree-diff.cpp b/src/lib/diff/tree-diff.cpp index 301767708..8c61b297f 100644 --- a/src/lib/diff/tree-diff.cpp +++ b/src/lib/diff/tree-diff.cpp @@ -205,7 +205,7 @@ namespace diff{ void TreeDiffMutatorBinding::mut (GenNode const& n) { - TreeMutator::Handle buffHandle = scopeManger_->openScope(); + TreeMutator::Handle buffHandle = scopeManger_->openScope(); // hint: treeMutatorSize(...) if (not treeMutator_->mutateChild(n, buffHandle)) __failMismatch("enter nested scope", n); diff --git a/src/lib/diff/tree-mutator.hpp b/src/lib/diff/tree-mutator.hpp index 07a12fc57..17ce59b5d 100644 --- a/src/lib/diff/tree-mutator.hpp +++ b/src/lib/diff/tree-mutator.hpp @@ -83,6 +83,7 @@ ** ** @see tree-mutator-test.cpp ** @see tree-mutator-binding-test.cpp + ** @see [usage for tree diff application](\ref tree-diff-application.hpp) ** @see diff-language.hpp ** @see DiffDetector ** diff --git a/src/proc/mobject/session/dummy-session-connection.cpp b/src/proc/mobject/session/dummy-session-connection.cpp index e9c677785..5913ea8cd 100644 --- a/src/proc/mobject/session/dummy-session-connection.cpp +++ b/src/proc/mobject/session/dummy-session-connection.cpp @@ -55,9 +55,11 @@ #include //#include +using lib::diff::MutationMessage; using lib::diff::GenNode; using lib::diff::MakeRec; -using lib::diff::MutationMessage; +using lib::diff::Ref; +using lib::idi::RandID; //using util::cStr; using util::_Fmt; using std::string; @@ -77,12 +79,12 @@ namespace session { namespace { //Implementation details.... GenNode - emptyTimeline (string baseID) + emptyTimeline (string baseID, RandID const& forkRootID) { return MakeRec() - .set(string{gui::ATTR_fork} - ,MakeRec() - .type(string{gui::TYPE_Fork}) + .set(MakeRec() + .type (string{gui::TYPE_Fork}) + .genNode(forkRootID) ) .genNode(baseID); } @@ -106,7 +108,18 @@ namespace session { MutationMessage DummySessionConnection::fabricateSeq1 (string baseID) { - return MutationMessage{ ins (emptyTimeline (baseID)) + const RandID forkRootID{gui::ATTR_fork}; + const GenNode timeline = emptyTimeline (baseID, forkRootID); + const GenNode rootTrackName{string{gui::ATTR_name}, "Track-"+baseID} + , FORK_ROOT = MakeRec().genNode(forkRootID) + ; + + return MutationMessage{ ins (timeline) + , mut (timeline) + , mut (FORK_ROOT) + , set (rootTrackName) + , emu (FORK_ROOT) + , emu (timeline) }; } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 11713b164..4e321b39e 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -31117,7 +31117,7 @@ - + @@ -32179,7 +32179,7 @@ - + From 866d7efe0ac3f7eb569767a173f349da389b6bff Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 10 Nov 2018 02:59:23 +0100 Subject: [PATCH 178/178] Timeline: push the trackname attribute down into the widget/display ...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 --- src/gui/timeline/track-body.cpp | 9 +++++- src/gui/timeline/track-body.hpp | 4 ++- src/gui/timeline/track-head-widget.cpp | 10 +++++-- src/gui/timeline/track-head-widget.hpp | 4 ++- src/gui/timeline/track-presenter.cpp | 2 +- src/gui/timeline/track-presenter.hpp | 28 ++++++++++++++----- .../session/dummy-session-connection.cpp | 6 ++-- wiki/thinkPad.ichthyo.mm | 6 ++++ 8 files changed, 53 insertions(+), 16 deletions(-) diff --git a/src/gui/timeline/track-body.cpp b/src/gui/timeline/track-body.cpp index dca82cb9d..ac485de3a 100644 --- a/src/gui/timeline/track-body.cpp +++ b/src/gui/timeline/track-body.cpp @@ -64,7 +64,7 @@ namespace timeline { - TrackBody::TrackBody (cuString trackName) + TrackBody::TrackBody() : overviewHeight_{DEFAULT_OVERVIEW_HEIGHT_px} , contentHeight_{DEFAULT_OVERVIEW_HEIGHT_px} , subTracks_{} @@ -77,6 +77,13 @@ namespace timeline { } + 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 diff --git a/src/gui/timeline/track-body.hpp b/src/gui/timeline/track-body.hpp index 839006066..f9f60401c 100644 --- a/src/gui/timeline/track-body.hpp +++ b/src/gui/timeline/track-body.hpp @@ -78,9 +78,11 @@ namespace timeline { SubTracks subTracks_; public: - TrackBody (cuString trackName); + TrackBody(); ~TrackBody(); + void setTrackName (cuString&); + uint calcHeight(); private:/* ===== Internals ===== */ diff --git a/src/gui/timeline/track-head-widget.cpp b/src/gui/timeline/track-head-widget.cpp index da1366bf8..e4748e996 100644 --- a/src/gui/timeline/track-head-widget.cpp +++ b/src/gui/timeline/track-head-widget.cpp @@ -62,9 +62,9 @@ namespace timeline { - TrackHeadWidget::TrackHeadWidget (cuString& trackName) + TrackHeadWidget::TrackHeadWidget() : Gtk::Grid{} - , nameTODO_{trackName} + , nameTODO_{"?"} , treeTODO_{"↳"} { this->attach (nameTODO_, 0,0, 2,1); @@ -74,6 +74,12 @@ namespace timeline { } + 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._ diff --git a/src/gui/timeline/track-head-widget.hpp b/src/gui/timeline/track-head-widget.hpp index f632e83fb..19d158ca4 100644 --- a/src/gui/timeline/track-head-widget.hpp +++ b/src/gui/timeline/track-head-widget.hpp @@ -74,9 +74,11 @@ namespace timeline { Gtk::Label treeTODO_; public: - TrackHeadWidget (cuString& trackName); + TrackHeadWidget(); ~TrackHeadWidget(); + void setTrackName (cuString&); + /** Integrate the control area for a nested sub track fork. */ void injectSubFork (TrackHeadWidget& subForkHead); diff --git a/src/gui/timeline/track-presenter.cpp b/src/gui/timeline/track-presenter.cpp index 829b460dc..e84610543 100644 --- a/src/gui/timeline/track-presenter.cpp +++ b/src/gui/timeline/track-presenter.cpp @@ -152,7 +152,7 @@ namespace timeline { })) .change(ATTR_name, [&](string val) { // »Attribute Setter« : receive a new value for the track name field - name_ = val; + this->setTrackName (val); })); } diff --git a/src/gui/timeline/track-presenter.hpp b/src/gui/timeline/track-presenter.hpp index 6befa66a0..70b4d8c97 100644 --- a/src/gui/timeline/track-presenter.hpp +++ b/src/gui/timeline/track-presenter.hpp @@ -80,9 +80,9 @@ namespace timeline { TrackBody body; template - DisplayFrame (cuString& name, FUN anchorDisplay) - : head{name} - , body{name} + DisplayFrame (FUN anchorDisplay) + : head{} + , body{} { anchorDisplay (head, body); } @@ -92,6 +92,13 @@ namespace timeline { 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) { @@ -106,7 +113,6 @@ namespace timeline { class TrackPresenter : public model::Controller { - string name_; DisplayFrame display_; vector> subFork_; @@ -124,12 +130,13 @@ namespace timeline { template TrackPresenter (ID id, ctrl::BusTerm& nexus, FUN anchorDisplay) : Controller{id, nexus} - , name_{id.getSym()} // fallback initialise track-name from human-readable ID symbol - , display_{name_, anchorDisplay} + , 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 */ @@ -137,6 +144,13 @@ namespace timeline { private:/* ===== Internals ===== */ + + /** invoked via diff to show a (changed) track name */ + void + setTrackName (string name) + { + display_.setTrackName (name); + } }; diff --git a/src/proc/mobject/session/dummy-session-connection.cpp b/src/proc/mobject/session/dummy-session-connection.cpp index 5913ea8cd..fcbd37adc 100644 --- a/src/proc/mobject/session/dummy-session-connection.cpp +++ b/src/proc/mobject/session/dummy-session-connection.cpp @@ -109,9 +109,9 @@ namespace session { DummySessionConnection::fabricateSeq1 (string baseID) { const RandID forkRootID{gui::ATTR_fork}; - const GenNode timeline = emptyTimeline (baseID, forkRootID); - const GenNode rootTrackName{string{gui::ATTR_name}, "Track-"+baseID} - , FORK_ROOT = MakeRec().genNode(forkRootID) + const GenNode timeline = emptyTimeline (baseID, forkRootID) + , rootTrackName = GenNode{string{gui::ATTR_name}, "Track-"+baseID} + , FORK_ROOT = MakeRec().genNode(forkRootID) ; return MutationMessage{ ins (timeline) diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 4e321b39e..d1482fd3d 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -19326,6 +19326,12 @@ + + + + + +