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
This commit is contained in:
Fischlurch 2018-09-29 19:04:27 +02:00
parent 5fd3fb3d7b
commit f97beaa774
12 changed files with 631 additions and 541 deletions

View file

@ -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)); }

View file

@ -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

View file

@ -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

View file

@ -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<string>());
else
// forward to default handler
Controller::doMark (stateMark);
}
virtual void
doFlash() override
{

View file

@ -50,21 +50,12 @@
#include "lib/nocopy.hpp"
#include <utility>
#include <string> /////////TODO
#include <string>
#if true /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1099 : WIP-WIP-WIP
namespace proc {
namespace asset {
namespace meta {
class ErrorLog;
extern lib::idi::EntryID<ErrorLog> 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});
}
};

View file

@ -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??

View file

@ -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 <utility>
#include <string>
@ -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<lib::idi::BareEntryID const*> (element), text);
}
)
, LUMIERA_INTERFACE_INLINE (mark,
void, (const void* element, void* stateMark),
{
if (!_instance) lumiera_error_set (LUMIERA_ERROR_LIFECYCLE, "passing state mark");
else
_instance().mark (*static_cast<lib::idi::BareEntryID const*> (element), move(*reinterpret_cast<GenNode*> (stateMark)));
}
)
, LUMIERA_INTERFACE_INLINE (mutate,
void, (const void* element, void* diff),
{
@ -265,9 +301,7 @@ namespace gui {
)
);
} // (END) facade implementation details
} //(END) facade implementation details

View file

@ -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

View file

@ -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;
}
}
};

View file

@ -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*)),
);

View file

@ -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 <string>
@ -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
{

File diff suppressed because it is too large Load diff