diff --git a/src/gui/ctrl/bus-controller.hpp b/src/gui/ctrl/bus-controller.hpp index 3b3ba6202..b3754a6ea 100644 --- a/src/gui/ctrl/bus-controller.hpp +++ b/src/gui/ctrl/bus-controller.hpp @@ -22,14 +22,23 @@ /** @file bus-controller.hpp + ** The service actually operating the [UI-Bus][ui-bus.hpp]. + ** This service includes state management for the bus operations + ** as a whole, it includes the setup of a [routing table][ctrl::Nexus] + ** and it includes the management of connection to the [core service][core-service.hpp] ** Service for bus-controller. ** This header defines the basics of... ** - ** @note as of X/2015 this is complete bs - ** @todo WIP ///////////////////////TICKET # + ** @note as of 12/2015 this is merely placeholder code. The primary goal is to + ** write and cover model::Tangible and ctrl::BusTerm, so to define the operation + ** of the backbone components. Then the next step will be to replace the existing + ** (and obsolete) gui::controller::Controller by the gui::UiBus, which in turn + ** will have to instantiate a [BusController] instance. + ** @todo WIP ///////////////////////TICKET #959 ** ** @see ////TODO_test usage example - ** @see bus-controller.cpp implementation + ** @see [implementation][bus-controller.cpp] + ** @see [front-end and lifecycle][UiBus] ** */ diff --git a/src/gui/ctrl/bus-term.hpp b/src/gui/ctrl/bus-term.hpp index 2237d82fc..639dc1ac8 100644 --- a/src/gui/ctrl/bus-term.hpp +++ b/src/gui/ctrl/bus-term.hpp @@ -41,7 +41,7 @@ ** ** @todo as of 11/2015 this is complete WIP-WIP-WIP ** - ** @see ////TODO_test usage example + ** @see [BusTerm_test] ** */ diff --git a/src/gui/model/tangible.cpp b/src/gui/model/tangible.cpp index 0c2a5acfa..a710bfff9 100644 --- a/src/gui/model/tangible.cpp +++ b/src/gui/model/tangible.cpp @@ -25,41 +25,31 @@ ** Common base implementation of all tangible and connected interface elements. ** ** @see abstract-tangible-test.cpp + ** @see [explanation of the fundamental interactions][tangible.hpp] ** */ -//#include "lib/util.hpp" -//#include "lib/symbol.hpp" -//#include "include/logging.h" #include "gui/model/tangible.hpp" #include "gui/model/widget.hpp" #include "gui/model/controller.hpp" -//#include -//#include -//#include - -//using std::map; -//using std::string; - -//using util::contains; -//using util::isnil; namespace gui { namespace model { - namespace { // internal details - - } // internal details - Tangible::~Tangible() { } // Emit VTables here... - /** */ + /** invoke the generic reset hook + * @note the actual subclass has to override the doReset() hook + * to perform the actual clean-up work. + * @remarks the intention is that, after invoking reset(), the + * interface element or controller is in pristine (presentation) state + */ void Tangible::reset() { @@ -123,7 +113,13 @@ namespace model { /** - * @todo not clear yet what needs to be done + * @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. */ void Tangible::slotReveal(ID child) diff --git a/src/gui/model/tangible.hpp b/src/gui/model/tangible.hpp index 8efb5cf5d..cf3f59acb 100644 --- a/src/gui/model/tangible.hpp +++ b/src/gui/model/tangible.hpp @@ -23,11 +23,100 @@ /** @file tangible.hpp ** Abstraction: a tangible element of the User Interface. - ** Any such element is connected to the UIBus... + ** 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]. ** - ** @todo as of 1/2015 this is complete WIP-WIP-WIP + ** \par rationale + ** Simple user interfaces can be built by wiring up the actions right within the + ** code processing the trigger of actions. This leads to core functionality littered + ** and tangled with presentation code. The next step towards a more sane architecture + ** would be to code a forwarding call into every UI action, invoking some core facade + ** in turn. This approach works, but is repetitive and thus lures the lazy programmer + ** into taking shortcuts. Since we can foresee the Lumiera UI to become quite challenging + ** in itself, we prefer to introduce a **mediating backbone**, 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. ** - ** @see ////TODO_test usage example + ** The MVC-Pattern as such is fine, and probably the best we know for construction of + ** user interfaces. But it doesn't scale well towards the integration into a larger and + ** more structured system. There is a tension between the Controller in the UI and other + ** parts of an application, which as well need to be _in control._ And, even more important, + ** there is a tension between the demands of UI elements for support by a model, and the + ** demands to be placed on a core domain model of a large scale application. This tension is + ** resolved by enacting these roles while transforming the requests and demands into _Messages._ + ** + ** This way, we separate between immediate local control of UI state and the more global, + ** generic concerns of interaction control and command binding. The immediately tangible + ** "mechanics" of the UI shall be implemented in a conventional way, right within the + ** concrete widget (or controller) code. But, since any widget concerned with more than + ** local behaviour will inherit from [Tangible], the embedded [UI-Bus terminal][Tangible::uiBus_] + ** can be used for interaction with core services. + ** + ** \par the generic interface element API + ** The _generic interface element_ based on [Tangible] covers a set of behaviour common to + ** all elements of the interface. This behaviour is targeted towards the _integration_ with the + ** core application. Beyond that, there are still several concerns regarding presentation, like + ** a common styling. These are addressed the conventional way, through a common [WindowManager]. + ** The following discussion focuses on the aspects of integration with the core. + ** + ** For one reason ore another, any element in the UI can appear and go away. + ** This lifecycle behaviour corresponds to attachment and deregistration at the UI-Bus + ** + ** In regular, operative state, an interface element may initiate _actions_, which translate + ** into _commands_ at the session interface. To complicate matters, there might be higher-level, + ** cooperative _gestures_ implemented within the interface, leading to actions being formed + ** similar to sentences of spoken language, with the help of a FocusConcept -- this means, + ** in the end, there is a _subject_ and a _predicate_. These need to be bound in order to + ** form an _action_. And some interface element takes on or relates to the role of the + ** underlying, the subject, the **tangible element**. + ** Some actions are very common and can be represented by a shorthand. + ** An example would be to tweak some property, which means to mutate the attribute of a + ** model element known beforehand. Such tweaks are often caused by direct interaction, + ** and thus have the tendency to appear in flushes, which we want to batch in order to + ** remove some load from the lower layers. + ** + ** And then there are manipulations that _alter presentation state:_ Scrolling, canvas dragging, + ** expanding and collapsing, moving by focus or manipulation of a similar presentation control. + ** These manipulations in itself do not constitute an action. But there typically is some widget + ** or controller, which is responsible for the touched presentation state. If this entity judges + ** the state change to be relevant and persistent, it may [send][BusTerm::note()] a **state mark** + ** into the UI-Bus -- expecting this marked state to be remembered. + ** In turn this means the bus terminal might feed a state mark back into the tangible element, + ** expecting this state to be restored. + ** + ** A special case of state marking is the presentation of _transient feedback._ + ** Such feedback is pushed from "somewhere" towards given elements, which react through an + ** implementation dependent visual state change (flushing, colour change, marker icon). + ** If such state marking is to be persistent, the interface element has in turn to send + ** a specific state mark. An example would be a permanent error flag with an explanatory + ** text showed in mouse over. + ** + ** 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. + ** + ** \par Interactions + ** - **lifecycle**: connect to an existing term, supply the [EntryID][Tangible::ID] of the new element. + ** This interaction also implies, that the element automatically detaches itself at end of life. + ** - **act**: send a [GenNode] representing the action + ** - **note**: _send_ a GenNode representing the _state mark_ + ** - **mark**: _receive_ a [GenNode] representing the _feedback_ or a replayed _state mark_ + ** - **diff**: ask to retrieve a diff, which + ** - either is an incremental status update + ** - or is a from-scratch reconfiguration + ** + ** Beside these basic interactions, the generic element also exposes some common signal slots + ** - slotExpand() prompts the element to transition into expanded / unfolded state. + ** If this state is to be sticky, the element answers with a _state mark_ + ** - slotReveal() prompts the element to bring the indicated child into sight. + ** Typically, this request will "bubble up" recursively. + ** These slots are defined to be `sigc::trackable` for automated disconnection + ** see [Ticket #940][http://issues.lumiera.org/ticket/940#comment:3] for an explanation. + ** + ** + ** @see [AbstractTangible_test] + ** @see [BusTerm_test] ** */ @@ -40,8 +129,6 @@ #include "gui/ctrl/bus-term.hpp" #include "gui/interact/invocation-trail.hpp" #include "lib/idi/entry-id.hpp" -//#include "lib/symbol.hpp" -//#include "lib/util.hpp" #include #include @@ -51,8 +138,6 @@ namespace gui { namespace model { -// using lib::HashVal; -// using util::isnil; using std::string; @@ -62,8 +147,7 @@ namespace model { * 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 * and has to be formed starting from an already existing bus nexus. - * - * @todo write type comment... + * @see [explanation of the basic interactions][tangible.hpp] */ class Tangible : public sigc::trackable @@ -115,7 +199,7 @@ namespace model { private: }; - + /** generic handler for all incoming "state mark" messages */ inline void diff --git a/src/gui/ui-bus.hpp b/src/gui/ui-bus.hpp index 6fbf32e75..1d0bed798 100644 --- a/src/gui/ui-bus.hpp +++ b/src/gui/ui-bus.hpp @@ -36,8 +36,60 @@ ** written by Joel Holdsworth, while building the new UI-Bus frontend ** to take on this central role eventually. ** + ** \par rationale + ** The UI-Bus acts as a **mediating backbone**, 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. + ** + ** The MVC-Pattern as such is fine, and probably the best we know for construction of + ** user interfaces. But it doesn't scale well towards the integration into a larger and + ** more structured system. There is a tension between the Controller in the UI and other + ** parts of an application, which as well need to be _in control._ And, even more important, + ** there is a tension between the demands of UI elements for support by a model, and the + ** demands to be placed on a core domain model of a large scale application. This tension is + ** resolved by enacting these roles while transforming the requests and demands into _Messages._ + ** + ** Through this architectural decision, we introduce the distinction between the _local, tangible + ** UI "mechanics"_ on one side, and the common, _generic interaction patterns_ on the other side. + ** The former, the mere "mechanics" of the UI shall be kept simple and reduced to immediate + ** feedback and reactions to operating some interface controls. Any actual operations and + ** actions relevant to the application as a whole, are to be sent as messages into the + ** UI-Bus. The interface code can assume some "core services" to be available _somewhere;_ + ** these core services will receive the messages, act on them and _respond asynchronously_. + ** + ** \par Bus interactions + ** The UI-Bus has a star shaped topology, with a central "bus master" hub, the ["Nexus"][Nexus], + ** which maintains a routing table. Attachment and detachment of elements can be managed automatically, + ** since all of the UI-Bus operations _perform within the UI event thread._ + ** + ** 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]. + ** + ** - **act**: send a [GenNode] representing the action + ** - in a first step, a command prototype is [outfitted][InvocationTrail::bind()] with actual + ** parameter values. -> see [InvocationTrail] + ** - the actual command invocation is triggered by a ["bang" message][InvocationTrail::bang()] + ** - **note**: send a [GenNode] representing the _state mark;_ + ** some (abstracted) presentation state manager is expected to listen to these messages, + ** possibly recording state to be restored later. The contents of the _state mark_ message + ** are implementation defined; knowledge about these is shared between individual widget + ** implementations and (partially, to some degree) the presentation state manager. + ** - **mark**: down-link communication to _feed back_ state updates or + ** to replay previously recorded _state marks_ + ** + ** @warning deliberately the UI-Bus is **not threadsafe**. + ** Only [Tangible] elements performing in the UI-event thread are allowed to talk to the bus. + ** + ** @see bus-controller.hpp + ** @see bus-term.hpp + ** @see ctrl/nexus.hpp + ** @see ctrl/core-service.hpp + ** + ** @todo as of 1/2015, this header needs to be reshaped ////////////////////TICKET #959 ** - ** @todo as of 1/2015, this needs to be reshaped ////////////////////TICKET #959 */ diff --git a/tests/gui/abstract-tangible-test.cpp b/tests/gui/abstract-tangible-test.cpp index cb4f44944..c71aece07 100644 --- a/tests/gui/abstract-tangible-test.cpp +++ b/tests/gui/abstract-tangible-test.cpp @@ -52,22 +52,14 @@ #include "test/test-nexus.hpp" #include "lib/idi/entry-id.hpp" #include "lib/error.hpp" -//#include "gui/model/session-facade.hpp" -//#include "gui/model/diagnostics.hpp" //#include "lib/util.hpp" -//#include #include -//#include -//#include using gui::test::MockElm; using lib::test::EventLog; using lib::idi::EntryID; -//using boost::lexical_cast; -//using util::contains; -//using std::string; using std::cout; using std::endl; @@ -124,6 +116,17 @@ namespace test { * to this mocked interface. And since this mock element embodies an * [event log][EventLog], the unit test code can verify the occurrence * of expected events, invocations and responses. + * + * \par connectivity + * Any mock element will automatically connect against the [Test-Nexus][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 + * "post mortem" situations tend to be tricky for UI code, we provide a dedicated + * ["zombification"][gui::test::TestNexus::zombificate()] feature: a [MockElm] can be turned + * into an _almost dead_ state, while still hanging around. It will be detached from the + * "living" Test-Nexus and re-wired to some special, hidden "Zombie Nexus", causing any + * further messaging activity to be logged and ignored. */ void verify_mockManipulation () @@ -169,6 +172,7 @@ namespace test { .before("reset") .before("lorem ipsum"); + // create further mock elements... MockElm foo("foo"), bar("bar"); foo.verify("ctor").arg("foo"); bar.verify("ctor").arg("bar"); @@ -178,6 +182,8 @@ namespace test { mock.ensureNot("foo"); CHECK (!foo.ensureNot("foo")); + // now join the logs together, + // allowing to watch the combined events bar.joinLog(mock); foo.joinLog(mock); CHECK (log.verifyEvent("logJoin","bar") @@ -191,18 +197,21 @@ namespace test { .beforeEvent("create","bar") .beforeEvent("create","foo")); + mock.kill(); foo.markMsg("dummy killed"); CHECK (log.verifyEvent("destroy","dummy") .beforeCall("doMsg").on("foo")); + // Access the log on the Test-Nexus hub EventLog nexusLog = gui::test::Nexus::getLog(); CHECK (nexusLog.verifyEvent("destroy","dummy") .beforeEvent("dummy successfully zombificated")); - mock.slotExpand(); + mock.slotExpand(); // attempt to operate the zombie CHECK (nexusLog.verifyEvent("dummy successfully zombificated") - .beforeCall("note").on("ZombieNexus").arg("defunct-dummy", "expand")); + .beforeCall("note").on("ZombieNexus").arg("defunct-dummy", "expand") + .beforeEvent("error","sent note message to ZombieNexus")); cout << "____Event-Log_________________\n" diff --git a/tests/gui/test/mock-elm.hpp b/tests/gui/test/mock-elm.hpp index 7872183dc..a84b3e6cd 100644 --- a/tests/gui/test/mock-elm.hpp +++ b/tests/gui/test/mock-elm.hpp @@ -31,7 +31,25 @@ ** of a specially prepared Tangible instance. This gui::test::MockElm provides the ** necessary instrumentation to observe what has been invoked and received. ** - ** @todo initial draft and WIP-WIP-WIP as of 11/2015 + ** Since the purpose of a mock interface element is to test interactions and responses + ** targeted at a generic interface element, the MockElm incorporates an implementation + ** independent from the real gui::model::Widget or gui::model::Controller. This + ** mock implementation is basically NOP, while logging any invocation. Matters get + ** a bit fuzzy, when it comes to the distinction between _widget_ and _controller_. + ** Yet we should note that the purpose of this setup is to cover the connectivity + ** and integration with the UI, not the tangible "mechanics" of the UI itself. + ** It can be argued that covering the latter with unit tests is pretty much + ** moot and will result just in a huge pile of code duplication and + ** maintenance burden. + ** + ** People typically start to look into unit testing of user interfaces + ** when faced with a largely dysfunctional architecture, where core functionality + ** is littered and tangled into the presentation code. While in a system knowingly + ** built with a distinct core, the UI should not contain anything not tangible enough + ** as just to be verified by watching it in action. The push of a button should just + ** invoke an action, and the action itself should be self contained enough to be + ** tested in isolation. The UI-Bus and the [generic widget base][gui::model::Tangible] + ** was built to serve as a foundation to achieve that goal. ** ** @see abstract-tangible-test.cpp ** @@ -43,8 +61,6 @@ #include "lib/error.hpp" -//#include "lib/idi/entry-id.hpp" -//#include "lib/util.hpp" #include "lib/test/event-log.hpp" #include "gui/model/tangible.hpp" #include "lib/diff/record.hpp" @@ -65,10 +81,6 @@ namespace gui { namespace test{ -// using lib::HashVal; -// using util::isnil; -// using lib::idi::EntryID; -// using lib::diff::Record; using lib::Symbol; using std::string; using std::cout; @@ -82,7 +94,7 @@ namespace test{ * on the [Tangible] interface, which we mock here for unit testing. * This special implementation is instrumented to [log][lib::test::EventLog] * any invocation and any messages sent or received through the UI Backbone, - * which is formed by the [UiBus]. + * which is formed by the [UI-Bus][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 274ab0303..ba4d12a47 100644 --- a/tests/gui/test/test-nexus.cpp +++ b/tests/gui/test/test-nexus.cpp @@ -24,7 +24,9 @@ /** @file test/test-nexus.cpp ** Implementation of a fake UI backbone for testing. ** This compilation unit provides the actual setup for running a faked - ** user interface from unit tests. + ** user interface from unit tests. Test code is assumed to access those + ** features through the [front-end][gui::test::TestNexus], while the + ** actual implementation instances are placed [as singletons][depend.hpp] ** ** @todo initial draft and WIP-WIP-WIP as of 11/2015 ** @@ -33,9 +35,7 @@ */ -//#include "lib/util.hpp" -//#include "lib/symbol.hpp" -//#include "include/logging.h" +#include "lib/error.hpp" #include "test/test-nexus.hpp" #include "lib/test/event-log.hpp" #include "gui/ctrl/nexus.hpp" @@ -43,8 +43,8 @@ #include "lib/idi/entry-id.hpp" #include "lib/idi/genfunc.hpp" #include "lib/depend.hpp" +//#include "lib/util.hpp" -//#include #include #include //#include @@ -54,7 +54,6 @@ using std::cerr; using std::endl; using std::string; -//using lib::idi::EntryID; using lib::test::EventLog; using lib::diff::GenNode; using gui::ctrl::BusTerm; diff --git a/tests/gui/test/test-nexus.hpp b/tests/gui/test/test-nexus.hpp index 28554bdfc..558c118a6 100644 --- a/tests/gui/test/test-nexus.hpp +++ b/tests/gui/test/test-nexus.hpp @@ -25,15 +25,18 @@ ** 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] ** 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][::testUI] + ** backbone to run any test probes against. The test::Nexus allows to [hook up][testUI()] ** a generic interface element, to participate in a simulated interface interaction. ** ** This class test::Nexus acts as front-end for unit tests, while the actual implementation - ** of a test rigged mock interface backbone remains an implementation detail. + ** of a test rigged mock interface backbone remains an implementation detail. The purpose + ** of this setup is to capture messages sent from elements operated within a test setup + ** and directed at "core services" (that is, towards a presentation state manager or + ** towards the Proc-Layer for command invocation). Test code may then verify the + ** proper shape and incidence of these messages. ** - ** @todo initial draft and WIP-WIP-WIP as of 12/2015 - ** - ** @see abstract-tangible-test.cpp + ** @see [abstract-tangible-test.cpp] + ** @see [BusTerm_test] ** */ @@ -43,12 +46,8 @@ #include "lib/error.hpp" -//#include "lib/idi/entry-id.hpp" #include "gui/ctrl/bus-term.hpp" #include "lib/test/event-log.hpp" -//#include "lib/util.hpp" -//#include "gui/model/tangible.hpp" -//#include "lib/diff/record.hpp" #include #include @@ -57,19 +56,16 @@ namespace gui { namespace test{ -// using lib::HashVal; -// using util::isnil; -// using lib::idi::EntryID; -// using lib::diff::Record; -// using std::string; - /** * Mock UI backbone for unit testing. * In the absence of a real UI, this simulated [UI-Bus][ui-bus.hpp] * can be used to wire a [test probe][MockElm] and address it in unit testing. * - * @todo some usage details + * @note behind the scenes, this is a singleton. Use the provided + * attachment point testUI() in order to wire and hook up new + * interface elements. When using or deriving from [MockElm] this + * wiring happens automatically within the ctor. * @see abstract-tangible-test.cpp */ class Nexus diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 482a54d7d..6532d92d0 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -212,7 +212,80 @@ - + + + + + + + + + + +

+ gemeint, eine ENUM von verschiedenen Graden der Aufgeklappt-heit +

+

+ Dann mußte das allerdigns jeweils für alle Elemente sinnvoll sein +

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

+ und der muß vom konkreten Widget implementiert werden +

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

+ dann wird eine state mark ausgesendet +

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

+ need to bubble up +

+ + +
+
+ + +