/* NotificationService - public service allowing to push information into the GUI Copyright (C) Lumiera.org 2008, 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-service.cpp ** Implementation of notifications and updates within the GUI. ** This is the actual service implementation and runs within the GUI plugin. ** ** Since GTK is _not threadsafe by design,_ any external invocation passed through ** this facade service will be dispatched explicitly into the GTK event loop thread. ** The implementation of this dispatch is based upon `Glib::Dispatcher` and thus ** requires this service instance to be created from within the thread performing ** the GTK event loop. Moreover, to avoid segmentation faults on shutdown, the ** 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 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 ** */ #include "stage/ctrl/ui-manager.hpp" #include "stage/ctrl/ui-dispatcher.hpp" #include "stage/notification-service.hpp" #include "stage/interact/wizard.hpp" #include "include/ui-protocol.hpp" #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" extern "C" { #include "common/interface-descriptor.h" } #include #include using lib::diff::GenNode; using lib::diff::TreeMutator; using lib::diff::MutationMessage; using stage::ctrl::UiDispatcher; using stage::ctrl::BusTerm; using util::cStr; using util::_Fmt; using std::string; using std::move; namespace stage { /** @internal helper to _move_ a given UI-Bus message (GenNode) * into the closure of an event-lambda, which then is handed over * to the UI event thread through the dispatcher queue. */ void NotificationService::dispatchMsg (ID uiElement, lib::diff::GenNode&& uiMessage) { dispatch_->event ([=]() { ctrl::BusTerm::mark (uiElement, uiMessage); }); } void NotificationService::displayInfo (NotifyLevel severity, string const& text) { ///////////////////////////////////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{string{MARK_Warning}, text}); break; default: throw lumiera::error::Logic (_Fmt{"UI Notification with invalid severity %d encountered. " "Given message text was '%s'"} % severity % text); } } void NotificationService::markError (ID uiElement, string const& text) { dispatchMsg (uiElement, GenNode{string{MARK_Error}, text}); } void NotificationService::markNote (ID uiElement, string const& text) { dispatchMsg (uiElement, GenNode{string{MARK_Message}, text}); } void NotificationService::mark (ID uiElement, GenNode&& stateMarkMsg) { dispatchMsg (uiElement, move (stateMarkMsg)); } void NotificationService::mutate (ID uiElement, MutationMessage&& diff) { dispatch_->event ([=]() { // apply and consume diff message stored within closure this->change (uiElement, move(unConst(diff))); }); } void NotificationService::triggerGuiShutdown (string const& cause) { NOTICE (stage, "@GUI: shutdown triggered with explanation '%s'....", cStr(cause)); displayInfo (NOTE_ERROR, cause); dispatch_->event ([this]() { uiManager_.terminateUI(); }); } namespace { // facade implementation details /* ================== define an lumieraorg_GuiNotification instance ======================= */ LUMIERA_INTERFACE_INSTANCE (lumieraorg_interfacedescriptor, 0 ,lumieraorg_GuiNotificationFacade_descriptor , NULL, NULL, NULL , LUMIERA_INTERFACE_INLINE (name, const char*, (LumieraInterface ifa), { (void)ifa; return "GuiNotification"; } ) , LUMIERA_INTERFACE_INLINE (brief, const char*, (LumieraInterface ifa), { (void)ifa; return "Stage Interface: push state update and notification of events into the GUI"; } ) , LUMIERA_INTERFACE_INLINE (homepage, const char*, (LumieraInterface ifa), { (void)ifa; return "http://www.lumiera.org/develompent.html" ;} ) , LUMIERA_INTERFACE_INLINE (version, const char*, (LumieraInterface ifa), { (void)ifa; return "0.1~pre"; } ) , LUMIERA_INTERFACE_INLINE (author, const char*, (LumieraInterface ifa), { (void)ifa; return "Hermann Vosseler"; } ) , LUMIERA_INTERFACE_INLINE (email, const char*, (LumieraInterface ifa), { (void)ifa; return "Ichthyostega@web.de"; } ) , LUMIERA_INTERFACE_INLINE (copyright, const char*, (LumieraInterface ifa), { (void)ifa; return "Copyright (C) Lumiera.org\n" " 2008 Hermann Vosseler "; } ) , LUMIERA_INTERFACE_INLINE (license, const char*, (LumieraInterface ifa), { (void)ifa; return "This program is free software; you can redistribute it and/or modify\n" "it under the terms of the GNU General Public License as published by\n" "the Free Software Foundation; either version 2 of the License, or\n" "(at your option) any later version.\n" "\n" "This program is distributed in the hope that it will be useful,\n" "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" "GNU General Public License for more details.\n" "\n" "You should have received a copy of the GNU General Public License\n" "along with this program; if not, write to the Free Software\n" "Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA"; } ) , LUMIERA_INTERFACE_INLINE (state, int, (LumieraInterface ifa), {(void)ifa; return LUMIERA_INTERFACE_EXPERIMENTAL; } ) , LUMIERA_INTERFACE_INLINE (versioncmp, int, (const char* a, const char* b), {(void)a;(void)b; return 0;} ////////////////////////////////////////////TODO define version ordering ) ); using lumiera::error::LERR_(LIFECYCLE); lib::Depend _instance; ///< a backdoor for the C Language impl to access the actual SessionCommand implementation... LUMIERA_INTERFACE_INSTANCE (lumieraorg_GuiNotification, 0 ,lumieraorg_GuiNotificationService , LUMIERA_INTERFACE_REF(lumieraorg_interfacedescriptor, 0, lumieraorg_GuiNotificationFacade_descriptor) , NULL /* on open */ , NULL /* on close */ , LUMIERA_INTERFACE_INLINE (displayInfo, void, (uint severity, const char* text), { if (!_instance) lumiera_error_set (LUMIERA_ERROR_LIFECYCLE, text); else _instance().displayInfo (NotifyLevel(severity), text); } ) , LUMIERA_INTERFACE_INLINE (markError, void, (const void* element, const char* text), { if (!_instance) lumiera_error_set (LUMIERA_ERROR_LIFECYCLE, text); else _instance().markError (*static_cast (element), text); } ) , LUMIERA_INTERFACE_INLINE (markNote, void, (const void* element, const char* text), { if (!_instance) lumiera_error_set (LUMIERA_ERROR_LIFECYCLE, text); else _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), { if (!_instance) lumiera_error_set (LUMIERA_ERROR_LIFECYCLE, "passing diff message"); else _instance().mutate (*static_cast (element), move(*reinterpret_cast (diff))); } ) , LUMIERA_INTERFACE_INLINE (triggerGuiShutdown, void, (const char* cause), { if (!_instance) lumiera_error_set (LUMIERA_ERROR_LIFECYCLE, cause); else _instance().triggerGuiShutdown (cause); } ) ); } //(END) facade implementation details /** * 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. * @remark internally this service relies on a UiDispatcher queue to hand over any invocations * into the GTK event loop thread. */ NotificationService::NotificationService (ctrl::BusTerm& upLink, ctrl::UiManager& uiManager) : BusTerm{lib::idi::EntryID{}, upLink} , dispatch_{new UiDispatcher{[this](string msg){ displayInfo (NOTE_ERROR, msg); }}} , uiManager_{uiManager} , serviceInstance_( LUMIERA_INTERFACE_REF (lumieraorg_GuiNotification, 0,lumieraorg_GuiNotificationService)) { INFO (stage, "GuiNotification Facade opened."); } NotificationService::~NotificationService() { } // emit dtors of embedded objects here... } // namespace stage