From dddcbe9994e3a3bd3206751382a81036e02a5873 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 27 Dec 2015 01:58:15 +0100 Subject: [PATCH] DOC: supply basics of UI-Bus and generic UI-element protocol the initial draft of this concept is in place now, and the first round of unit tests pass. I've got some understanding of the purpose of the interactions and involved elements and I'm confident this design is evolving in a sane way. Note: extensive documentation is in the TiddlyWiki, here I've just pasted and reworded some paragraphs from there and integrated them into the Doxygen docs --- src/gui/ctrl/bus-controller.hpp | 15 +++- src/gui/ctrl/bus-term.hpp | 2 +- src/gui/model/tangible.cpp | 32 ++++----- src/gui/model/tangible.hpp | 104 ++++++++++++++++++++++++--- src/gui/ui-bus.hpp | 54 +++++++++++++- tests/gui/abstract-tangible-test.cpp | 29 +++++--- tests/gui/test/mock-elm.hpp | 28 +++++--- tests/gui/test/test-nexus.cpp | 11 ++- tests/gui/test/test-nexus.hpp | 28 ++++---- wiki/thinkPad.ichthyo.mm | 75 ++++++++++++++++++- 10 files changed, 304 insertions(+), 74 deletions(-) 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 +

+ + +
+
+ + +