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:
parent
5fd3fb3d7b
commit
f97beaa774
12 changed files with 631 additions and 541 deletions
|
|
@ -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)); }
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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??
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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*)),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in a new issue