From 429bd9b55c54164d133276ed5b1fd54e565eb1b4 Mon Sep 17 00:00:00 2001 From: brad kittenbrink Date: Wed, 8 Jul 2009 12:07:31 -0700 Subject: Added LLNotificationsListener to hook LLNotifications to the event system according to https://wiki.lindenlab.com/wiki/Incremental_Viewer_Automation/Event_API reviewed by palmer. --- indra/llui/CMakeLists.txt | 378 ++-- indra/llui/llnotifications.cpp | 3005 ++++++++++++++++---------------- indra/llui/llnotifications.h | 1823 +++++++++---------- indra/llui/llnotificationslistener.cpp | 28 + indra/llui/llnotificationslistener.h | 31 + indra/newview/llappviewer.cpp | 6 + 6 files changed, 2672 insertions(+), 2599 deletions(-) create mode 100644 indra/llui/llnotificationslistener.cpp create mode 100644 indra/llui/llnotificationslistener.h diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index 117e8e28ab..a6a5ef1f92 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -1,188 +1,190 @@ -# -*- cmake -*- - -project(llui) - -include(00-Common) -include(LLAudio) -include(LLCommon) -include(LLImage) -include(LLMath) -include(LLMessage) -include(LLRender) -include(LLWindow) -include(LLVFS) -include(LLXML) - -include_directories( - ${LLAUDIO_INCLUDE_DIRS} - ${LLCOMMON_INCLUDE_DIRS} - ${LLIMAGE_INCLUDE_DIRS} - ${LLMATH_INCLUDE_DIRS} - ${LLMESSAGE_INCLUDE_DIRS} - ${LLRENDER_INCLUDE_DIRS} - ${LLWINDOW_INCLUDE_DIRS} - ${LLVFS_INCLUDE_DIRS} - ${LLXML_INCLUDE_DIRS} - ) - -set(llui_SOURCE_FILES - llalertdialog.cpp - llbutton.cpp - llcheckboxctrl.cpp - llclipboard.cpp - llcombobox.cpp - llconsole.cpp - llcontainerview.cpp - llctrlselectioninterface.cpp - lldraghandle.cpp - lleditmenuhandler.cpp - llf32uictrl.cpp - llfloater.cpp - llfloaterreg.cpp - llflyoutbutton.cpp - llfocusmgr.cpp - llfunctorregistry.cpp - lliconctrl.cpp - llinitparam.cpp - llkeywords.cpp - lllayoutstack.cpp - lllineeditor.cpp - llmenugl.cpp - llmodaldialog.cpp - llmultifloater.cpp - llmultislider.cpp - llmultisliderctrl.cpp - llnotifications.cpp - llpanel.cpp - llprogressbar.cpp - llradiogroup.cpp - llresizebar.cpp - llresizehandle.cpp - llresmgr.cpp - llscrollbar.cpp - llscrollcontainer.cpp - llscrollingpanellist.cpp - llscrolllistcell.cpp - llscrolllistcolumn.cpp - llscrolllistctrl.cpp - llscrolllistitem.cpp - llsdparam.cpp - llsearcheditor.cpp - llslider.cpp - llsliderctrl.cpp - llspinctrl.cpp - llstatbar.cpp - llstatgraph.cpp - llstatview.cpp - llstyle.cpp - lltabcontainer.cpp - lltextbox.cpp - lltexteditor.cpp - lltextparser.cpp - lltrans.cpp - llui.cpp - lluicolortable.cpp - lluictrl.cpp - lluictrlfactory.cpp - lluiimage.cpp - lluistring.cpp - llundo.cpp - llviewborder.cpp - llviewmodel.cpp - llview.cpp - llviewquery.cpp - ) - -set(llui_HEADER_FILES - CMakeLists.txt - - llalertdialog.h - llbutton.h - llcallbackmap.h - llcheckboxctrl.h - llclipboard.h - llcombobox.h - llconsole.h - llcontainerview.h - llctrlselectioninterface.h - lldraghandle.h - lleditmenuhandler.h - llf32uictrl.h - llfloater.h - llfloaterreg.h - llflyoutbutton.h - llfocusmgr.h - llfunctorregistry.h - llhtmlhelp.h - lliconctrl.h - llinitparam.h - llkeywords.h - lllayoutstack.h - lllazyvalue.h - lllineeditor.h - llmenugl.h - llmodaldialog.h - llmultifloater.h - llmultisliderctrl.h - llmultislider.h - llnotifications.h - llpanel.h - llprogressbar.h - llradiogroup.h - llregistry.h - llresizebar.h - llresizehandle.h - llresmgr.h - llsearcheditor.h - llscrollbar.h - llscrollcontainer.h - llscrollingpanellist.h - llscrolllistcell.h - llscrolllistcolumn.h - llscrolllistctrl.h - llscrolllistitem.h - llsdparam.h - llsliderctrl.h - llslider.h - llspinctrl.h - llstatbar.h - llstatgraph.h - llstatview.h - llstyle.h - lltabcontainer.h - lltextbox.h - lltexteditor.h - lltextparser.h - lltrans.h - lluicolortable.h - lluiconstants.h - lluictrlfactory.h - lluictrl.h - lluifwd.h - llui.h - lluiimage.h - lluistring.h - llundo.h - llviewborder.h - llviewmodel.h - llview.h - llviewquery.h - ) - -set_source_files_properties(${llui_HEADER_FILES} - PROPERTIES HEADER_FILE_ONLY TRUE) - -list(APPEND llui_SOURCE_FILES ${llui_HEADER_FILES}) - -add_library (llui ${llui_SOURCE_FILES}) -# Libraries on which this library depends, needed for Linux builds -# Sort by high-level to low-level -target_link_libraries(llui - llrender - llwindow - llimage - llvfs # ugh, just for LLDir - llxml - llcommon # must be after llimage, llwindow, llrender - llmath - ) +# -*- cmake -*- + +project(llui) + +include(00-Common) +include(LLAudio) +include(LLCommon) +include(LLImage) +include(LLMath) +include(LLMessage) +include(LLRender) +include(LLWindow) +include(LLVFS) +include(LLXML) + +include_directories( + ${LLAUDIO_INCLUDE_DIRS} + ${LLCOMMON_INCLUDE_DIRS} + ${LLIMAGE_INCLUDE_DIRS} + ${LLMATH_INCLUDE_DIRS} + ${LLMESSAGE_INCLUDE_DIRS} + ${LLRENDER_INCLUDE_DIRS} + ${LLWINDOW_INCLUDE_DIRS} + ${LLVFS_INCLUDE_DIRS} + ${LLXML_INCLUDE_DIRS} + ) + +set(llui_SOURCE_FILES + llalertdialog.cpp + llbutton.cpp + llcheckboxctrl.cpp + llclipboard.cpp + llcombobox.cpp + llconsole.cpp + llcontainerview.cpp + llctrlselectioninterface.cpp + lldraghandle.cpp + lleditmenuhandler.cpp + llf32uictrl.cpp + llfloater.cpp + llfloaterreg.cpp + llflyoutbutton.cpp + llfocusmgr.cpp + llfunctorregistry.cpp + lliconctrl.cpp + llinitparam.cpp + llkeywords.cpp + lllayoutstack.cpp + lllineeditor.cpp + llmenugl.cpp + llmodaldialog.cpp + llmultifloater.cpp + llmultislider.cpp + llmultisliderctrl.cpp + llnotifications.cpp + llnotificationslistener.cpp + llpanel.cpp + llprogressbar.cpp + llradiogroup.cpp + llresizebar.cpp + llresizehandle.cpp + llresmgr.cpp + llscrollbar.cpp + llscrollcontainer.cpp + llscrollingpanellist.cpp + llscrolllistcell.cpp + llscrolllistcolumn.cpp + llscrolllistctrl.cpp + llscrolllistitem.cpp + llsdparam.cpp + llsearcheditor.cpp + llslider.cpp + llsliderctrl.cpp + llspinctrl.cpp + llstatbar.cpp + llstatgraph.cpp + llstatview.cpp + llstyle.cpp + lltabcontainer.cpp + lltextbox.cpp + lltexteditor.cpp + lltextparser.cpp + lltrans.cpp + llui.cpp + lluicolortable.cpp + lluictrl.cpp + lluictrlfactory.cpp + lluiimage.cpp + lluistring.cpp + llundo.cpp + llviewborder.cpp + llviewmodel.cpp + llview.cpp + llviewquery.cpp + ) + +set(llui_HEADER_FILES + CMakeLists.txt + + llalertdialog.h + llbutton.h + llcallbackmap.h + llcheckboxctrl.h + llclipboard.h + llcombobox.h + llconsole.h + llcontainerview.h + llctrlselectioninterface.h + lldraghandle.h + lleditmenuhandler.h + llf32uictrl.h + llfloater.h + llfloaterreg.h + llflyoutbutton.h + llfocusmgr.h + llfunctorregistry.h + llhtmlhelp.h + lliconctrl.h + llinitparam.h + llkeywords.h + lllayoutstack.h + lllazyvalue.h + lllineeditor.h + llmenugl.h + llmodaldialog.h + llmultifloater.h + llmultisliderctrl.h + llmultislider.h + llnotifications.h + llnotificationslistener.h + llpanel.h + llprogressbar.h + llradiogroup.h + llregistry.h + llresizebar.h + llresizehandle.h + llresmgr.h + llsearcheditor.h + llscrollbar.h + llscrollcontainer.h + llscrollingpanellist.h + llscrolllistcell.h + llscrolllistcolumn.h + llscrolllistctrl.h + llscrolllistitem.h + llsdparam.h + llsliderctrl.h + llslider.h + llspinctrl.h + llstatbar.h + llstatgraph.h + llstatview.h + llstyle.h + lltabcontainer.h + lltextbox.h + lltexteditor.h + lltextparser.h + lltrans.h + lluicolortable.h + lluiconstants.h + lluictrlfactory.h + lluictrl.h + lluifwd.h + llui.h + lluiimage.h + lluistring.h + llundo.h + llviewborder.h + llviewmodel.h + llview.h + llviewquery.h + ) + +set_source_files_properties(${llui_HEADER_FILES} + PROPERTIES HEADER_FILE_ONLY TRUE) + +list(APPEND llui_SOURCE_FILES ${llui_HEADER_FILES}) + +add_library (llui ${llui_SOURCE_FILES}) +# Libraries on which this library depends, needed for Linux builds +# Sort by high-level to low-level +target_link_libraries(llui + llrender + llwindow + llimage + llvfs # ugh, just for LLDir + llxml + llcommon # must be after llimage, llwindow, llrender + llmath + ) diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp index 50fee41029..ec92e57b6e 100644 --- a/indra/llui/llnotifications.cpp +++ b/indra/llui/llnotifications.cpp @@ -1,1501 +1,1504 @@ -/** -* @file llnotifications.cpp -* @brief Non-UI queue manager for keeping a prioritized list of notifications -* -* $LicenseInfo:firstyear=2008&license=viewergpl$ -* -* Copyright (c) 2008-2009, Linden Research, Inc. -* -* Second Life Viewer Source Code -* The source code in this file ("Source Code") is provided by Linden Lab -* to you under the terms of the GNU General Public License, version 2.0 -* ("GPL"), unless you have obtained a separate licensing agreement -* ("Other License"), formally executed by you and Linden Lab. Terms of -* the GPL can be found in doc/GPL-license.txt in this distribution, or -* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 -* -* There are special exceptions to the terms and conditions of the GPL as -* it is applied to this Source Code. View the full text of the exception -* in the file doc/FLOSS-exception.txt in this software distribution, or -* online at -* http://secondlifegrid.net/programs/open_source/licensing/flossexception -* -* By copying, modifying or distributing this software, you acknowledge -* that you have read and understood your obligations described above, -* and agree to abide by those obligations. -* -* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO -* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, -* COMPLETENESS OR PERFORMANCE. -* $/LicenseInfo$ -*/ - -#include "linden_common.h" - -#include "llnotifications.h" - -#include "lluictrl.h" -#include "lluictrlfactory.h" -#include "lldir.h" -#include "llsdserialize.h" -#include "lltrans.h" - -#include -#include - - -const std::string NOTIFICATION_PERSIST_VERSION = "0.93"; - -// local channel for notification history -class LLNotificationHistoryChannel : public LLNotificationChannel -{ - LOG_CLASS(LLNotificationHistoryChannel); -public: - LLNotificationHistoryChannel(const std::string& filename) : - LLNotificationChannel("History", "Visible", &historyFilter, LLNotificationComparators::orderByUUID()), - mFileName(filename) - { - connectChanged(boost::bind(&LLNotificationHistoryChannel::historyHandler, this, _1)); - loadPersistentNotifications(); - } - -private: - bool historyHandler(const LLSD& payload) - { - // we ignore "load" messages, but rewrite the persistence file on any other - std::string sigtype = payload["sigtype"]; - if (sigtype != "load") - { - savePersistentNotifications(); - } - return false; - } - - // The history channel gets all notifications except those that have been cancelled - static bool historyFilter(LLNotificationPtr pNotification) - { - return !pNotification->isCancelled(); - } - - void savePersistentNotifications() - { - llinfos << "Saving open notifications to " << mFileName << llendl; - - llofstream notify_file(mFileName.c_str()); - if (!notify_file.is_open()) - { - llwarns << "Failed to open " << mFileName << llendl; - return; - } - - LLSD output; - output["version"] = NOTIFICATION_PERSIST_VERSION; - LLSD& data = output["data"]; - - for (LLNotificationSet::iterator it = mItems.begin(); it != mItems.end(); ++it) - { - if (!LLNotifications::instance().templateExists((*it)->getName())) continue; - - // only store notifications flagged as persisting - LLNotificationTemplatePtr templatep = LLNotifications::instance().getTemplate((*it)->getName()); - if (!templatep->mPersist) continue; - - data.append((*it)->asLLSD()); - } - - LLPointer formatter = new LLSDXMLFormatter(); - formatter->format(output, notify_file, LLSDFormatter::OPTIONS_PRETTY); - } - - void loadPersistentNotifications() - { - llinfos << "Loading open notifications from " << mFileName << llendl; - - llifstream notify_file(mFileName.c_str()); - if (!notify_file.is_open()) - { - llwarns << "Failed to open " << mFileName << llendl; - return; - } - - LLSD input; - LLPointer parser = new LLSDXMLParser(); - if (parser->parse(notify_file, input, LLSDSerialize::SIZE_UNLIMITED) < 0) - { - llwarns << "Failed to parse open notifications" << llendl; - return; - } - - if (input.isUndefined()) return; - std::string version = input["version"]; - if (version != NOTIFICATION_PERSIST_VERSION) - { - llwarns << "Bad open notifications version: " << version << llendl; - return; - } - LLSD& data = input["data"]; - if (data.isUndefined()) return; - - LLNotifications& instance = LLNotifications::instance(); - for (LLSD::array_const_iterator notification_it = data.beginArray(); - notification_it != data.endArray(); - ++notification_it) - { - instance.add(LLNotificationPtr(new LLNotification(*notification_it))); - } - } - - //virtual - void onDelete(LLNotificationPtr pNotification) - { - // we want to keep deleted notifications in our log - mItems.insert(pNotification); - - return; - } - -private: - std::string mFileName; -}; - -bool filterIgnoredNotifications(LLNotificationPtr notification) -{ - // filter everything if we are to ignore ALL - if(LLNotifications::instance().getIgnoreAllNotifications()) - { - return false; - } - - LLNotificationFormPtr form = notification->getForm(); - // Check to see if the user wants to ignore this alert - if (form->getIgnoreType() != LLNotificationForm::IGNORE_NO) - { - return LLUI::sSettingGroups["ignores"]->getBOOL(notification->getName()); - } - - return true; -} - -bool handleIgnoredNotification(const LLSD& payload) -{ - if (payload["sigtype"].asString() == "add") - { - LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID()); - if (!pNotif) return false; - - LLNotificationFormPtr form = pNotif->getForm(); - LLSD response; - switch(form->getIgnoreType()) - { - case LLNotificationForm::IGNORE_WITH_DEFAULT_RESPONSE: - response = pNotif->getResponseTemplate(LLNotification::WITH_DEFAULT_BUTTON); - break; - case LLNotificationForm::IGNORE_WITH_LAST_RESPONSE: - response = LLUI::sSettingGroups["ignores"]->getLLSD("Default" + pNotif->getName()); - break; - case LLNotificationForm::IGNORE_SHOW_AGAIN: - break; - default: - return false; - } - pNotif->setIgnored(true); - pNotif->respond(response); - return true; // don't process this item any further - } - return false; -} - -namespace LLNotificationFilters -{ - // a sample filter - bool includeEverything(LLNotificationPtr p) - { - return true; - } -}; - -LLNotificationForm::LLNotificationForm() -: mFormData(LLSD::emptyArray()), - mIgnore(IGNORE_NO) -{ -} - - -LLNotificationForm::LLNotificationForm(const std::string& name, const LLXMLNodePtr xml_node) -: mFormData(LLSD::emptyArray()), - mIgnore(IGNORE_NO) -{ - if (!xml_node->hasName("form")) - { - llwarns << "Bad xml node for form: " << xml_node->getName() << llendl; - } - LLXMLNodePtr child = xml_node->getFirstChild(); - while(child) - { - child = LLNotifications::instance().checkForXMLTemplate(child); - - LLSD item_entry; - std::string element_name = child->getName()->mString; - - if (element_name == "ignore" ) - { - bool save_option = false; - child->getAttribute_bool("save_option", save_option); - if (!save_option) - { - mIgnore = IGNORE_WITH_DEFAULT_RESPONSE; - } - else - { - // remember last option chosen by user and automatically respond with that in the future - mIgnore = IGNORE_WITH_LAST_RESPONSE; - LLUI::sSettingGroups["ignores"]->declareLLSD(std::string("Default") + name, "", std::string("Default response for notification " + name)); - } - child->getAttributeString("text", mIgnoreMsg); - BOOL show_notification = TRUE; - LLUI::sSettingGroups["ignores"]->declareBOOL(name, show_notification, "Ignore notification with this name", TRUE); - } - else - { - // flatten xml form entry into single LLSD map with type==name - item_entry["type"] = element_name; - const LLXMLAttribList::iterator attrib_end = child->mAttributes.end(); - for(LLXMLAttribList::iterator attrib_it = child->mAttributes.begin(); - attrib_it != attrib_end; - ++attrib_it) - { - item_entry[std::string(attrib_it->second->getName()->mString)] = attrib_it->second->getValue(); - } - item_entry["value"] = child->getTextContents(); - mFormData.append(item_entry); - } - - child = child->getNextSibling(); - } -} - -LLNotificationForm::LLNotificationForm(const LLSD& sd) -{ - if (sd.isArray()) - { - mFormData = sd; - } - else - { - llwarns << "Invalid form data " << sd << llendl; - mFormData = LLSD::emptyArray(); - } -} - -LLSD LLNotificationForm::asLLSD() const -{ - return mFormData; -} - -LLSD LLNotificationForm::getElement(const std::string& element_name) -{ - for (LLSD::array_const_iterator it = mFormData.beginArray(); - it != mFormData.endArray(); - ++it) - { - if ((*it)["name"].asString() == element_name) return (*it); - } - return LLSD(); -} - - -bool LLNotificationForm::hasElement(const std::string& element_name) -{ - for (LLSD::array_const_iterator it = mFormData.beginArray(); - it != mFormData.endArray(); - ++it) - { - if ((*it)["name"].asString() == element_name) return true; - } - return false; -} - -void LLNotificationForm::addElement(const std::string& type, const std::string& name, const LLSD& value) -{ - LLSD element; - element["type"] = type; - element["name"] = name; - element["text"] = name; - element["value"] = value; - element["index"] = mFormData.size(); - mFormData.append(element); -} - -void LLNotificationForm::append(const LLSD& sub_form) -{ - if (sub_form.isArray()) - { - for (LLSD::array_const_iterator it = sub_form.beginArray(); - it != sub_form.endArray(); - ++it) - { - mFormData.append(*it); - } - } -} - -void LLNotificationForm::formatElements(const LLSD& substitutions) -{ - for (LLSD::array_iterator it = mFormData.beginArray(); - it != mFormData.endArray(); - ++it) - { - // format "text" component of each form element - if ((*it).has("text")) - { - std::string text = (*it)["text"].asString(); - LLStringUtil::format(text, substitutions); - (*it)["text"] = text; - } - if ((*it)["type"].asString() == "text" && (*it).has("value")) - { - std::string value = (*it)["value"].asString(); - LLStringUtil::format(value, substitutions); - (*it)["value"] = value; - } - } -} - -std::string LLNotificationForm::getDefaultOption() -{ - for (LLSD::array_const_iterator it = mFormData.beginArray(); - it != mFormData.endArray(); - ++it) - { - if ((*it)["default"]) return (*it)["name"].asString(); - } - return ""; -} - -LLNotificationTemplate::LLNotificationTemplate() : - mExpireSeconds(0), - mExpireOption(-1), - mURLOption(-1), - mURLOpenExternally(-1), - mUnique(false), - mPriority(NOTIFICATION_PRIORITY_NORMAL) -{ - mForm = LLNotificationFormPtr(new LLNotificationForm()); -} - -LLNotification::LLNotification(const LLNotification::Params& p) : - mTimestamp(p.timestamp), - mSubstitutions(p.substitutions), - mPayload(p.payload), - mExpiresAt(0), - mTemporaryResponder(false), - mRespondedTo(false), - mPriority(p.priority), - mCancelled(false), - mIgnored(false) -{ - if (p.functor.name.isChosen()) - { - mResponseFunctorName = p.functor.name; - } - else if (p.functor.function.isChosen()) - { - mResponseFunctorName = LLUUID::generateNewID().asString(); - LLNotificationFunctorRegistry::instance().registerFunctor(mResponseFunctorName, p.functor.function()); - - mTemporaryResponder = true; - } - - mId.generate(); - init(p.name, p.form_elements); -} - - -LLNotification::LLNotification(const LLSD& sd) : - mTemporaryResponder(false), - mRespondedTo(false), - mCancelled(false), - mIgnored(false) -{ - mId.generate(); - mSubstitutions = sd["substitutions"]; - mPayload = sd["payload"]; - mTimestamp = sd["time"]; - mExpiresAt = sd["expiry"]; - mPriority = (ENotificationPriority)sd["priority"].asInteger(); - mResponseFunctorName = sd["responseFunctor"].asString(); - std::string templatename = sd["name"].asString(); - init(templatename, LLSD()); - // replace form with serialized version - mForm = LLNotificationFormPtr(new LLNotificationForm(sd["form"])); -} - - -LLSD LLNotification::asLLSD() -{ - LLSD output; - output["name"] = mTemplatep->mName; - output["form"] = getForm()->asLLSD(); - output["substitutions"] = mSubstitutions; - output["payload"] = mPayload; - output["time"] = mTimestamp; - output["expiry"] = mExpiresAt; - output["priority"] = (S32)mPriority; - output["responseFunctor"] = mResponseFunctorName; - return output; -} - -void LLNotification::update() -{ - LLNotifications::instance().update(shared_from_this()); -} - -void LLNotification::updateFrom(LLNotificationPtr other) -{ - // can only update from the same notification type - if (mTemplatep != other->mTemplatep) return; - - // NOTE: do NOT change the ID, since it is the key to - // this given instance, just update all the metadata - //mId = other->mId; - - mPayload = other->mPayload; - mSubstitutions = other->mSubstitutions; - mTimestamp = other->mTimestamp; - mExpiresAt = other->mExpiresAt; - mCancelled = other->mCancelled; - mIgnored = other->mIgnored; - mPriority = other->mPriority; - mForm = other->mForm; - mResponseFunctorName = other->mResponseFunctorName; - mRespondedTo = other->mRespondedTo; - mTemporaryResponder = other->mTemporaryResponder; - - update(); -} - -const LLNotificationFormPtr LLNotification::getForm() -{ - return mForm; -} - -void LLNotification::cancel() -{ - mCancelled = true; -} - -LLSD LLNotification::getResponseTemplate(EResponseTemplateType type) -{ - LLSD response = LLSD::emptyMap(); - for (S32 element_idx = 0; - element_idx < mForm->getNumElements(); - ++element_idx) - { - LLSD element = mForm->getElement(element_idx); - if (element.has("name")) - { - response[element["name"].asString()] = element["value"]; - } - - if ((type == WITH_DEFAULT_BUTTON) - && element["default"].asBoolean()) - { - response[element["name"].asString()] = true; - } - } - return response; -} - -//static -S32 LLNotification::getSelectedOption(const LLSD& notification, const LLSD& response) -{ - LLNotificationForm form(notification["form"]); - - for (S32 element_idx = 0; - element_idx < form.getNumElements(); - ++element_idx) - { - LLSD element = form.getElement(element_idx); - - // only look at buttons - if (element["type"].asString() == "button" - && response[element["name"].asString()].asBoolean()) - { - return element["index"].asInteger(); - } - } - - return -1; -} - -//static -std::string LLNotification::getSelectedOptionName(const LLSD& response) -{ - for (LLSD::map_const_iterator response_it = response.beginMap(); - response_it != response.endMap(); - ++response_it) - { - if (response_it->second.isBoolean() && response_it->second.asBoolean()) - { - return response_it->first; - } - } - return ""; -} - - -void LLNotification::respond(const LLSD& response) -{ - mRespondedTo = true; - // look up the functor - LLNotificationFunctorRegistry::ResponseFunctor functor = - LLNotificationFunctorRegistry::instance().getFunctor(mResponseFunctorName); - // and then call it - functor(asLLSD(), response); - - if (mTemporaryResponder) - { - LLNotificationFunctorRegistry::instance().unregisterFunctor(mResponseFunctorName); - mResponseFunctorName = ""; - mTemporaryResponder = false; - } - - if (mForm->getIgnoreType() != LLNotificationForm::IGNORE_NO) - { - BOOL show_notification = mIgnored ? FALSE : TRUE; - LLUI::sSettingGroups["ignores"]->setBOOL(getName(), show_notification); - if (mIgnored && mForm->getIgnoreType() == LLNotificationForm::IGNORE_WITH_LAST_RESPONSE) - { - LLUI::sSettingGroups["ignores"]->setLLSD("Default" + getName(), response); - } - } - - update(); -} - -void LLNotification::setIgnored(bool ignore) -{ - mIgnored = ignore; -} - -void LLNotification::setResponseFunctor(std::string const &responseFunctorName) -{ - if (mTemporaryResponder) - // get rid of the old one - LLNotificationFunctorRegistry::instance().unregisterFunctor(mResponseFunctorName); - mResponseFunctorName = responseFunctorName; - mTemporaryResponder = false; -} - -bool LLNotification::payloadContainsAll(const std::vector& required_fields) const -{ - for(std::vector::const_iterator required_fields_it = required_fields.begin(); - required_fields_it != required_fields.end(); - required_fields_it++) - { - std::string required_field_name = *required_fields_it; - if( ! getPayload().has(required_field_name)) - { - return false; // a required field was not found - } - } - return true; // all required fields were found -} - -bool LLNotification::isEquivalentTo(LLNotificationPtr that) const -{ - if (this->mTemplatep->mName != that->mTemplatep->mName) - { - return false; // must have the same template name or forget it - } - if (this->mTemplatep->mUnique) - { - // highlander bit sez there can only be one of these - return - this->payloadContainsAll(that->mTemplatep->mUniqueContext) && - that->payloadContainsAll(this->mTemplatep->mUniqueContext); - } - return false; -} - -void LLNotification::init(const std::string& template_name, const LLSD& form_elements) -{ - mTemplatep = LLNotifications::instance().getTemplate(template_name); - if (!mTemplatep) return; - - // add default substitutions - const LLStringUtil::format_map_t& default_args = LLTrans::getDefaultArgs(); - for (LLStringUtil::format_map_t::const_iterator iter = default_args.begin(); - iter != default_args.end(); ++iter) - { - mSubstitutions[iter->first] = iter->second; - } - mSubstitutions["_URL"] = getURL(); - mSubstitutions["_NAME"] = template_name; - // TODO: something like this so that a missing alert is sensible: - //mSubstitutions["_ARGS"] = get_all_arguments_as_text(mSubstitutions); - - mForm = LLNotificationFormPtr(new LLNotificationForm(*mTemplatep->mForm)); - mForm->append(form_elements); - - // apply substitution to form labels - mForm->formatElements(mSubstitutions); - - LLDate rightnow = LLDate::now(); - if (mTemplatep->mExpireSeconds) - { - mExpiresAt = LLDate(rightnow.secondsSinceEpoch() + mTemplatep->mExpireSeconds); - } - - if (mPriority == NOTIFICATION_PRIORITY_UNSPECIFIED) - { - mPriority = mTemplatep->mPriority; - } -} - -std::string LLNotification::summarize() const -{ - std::string s = "Notification("; - s += getName(); - s += ") : "; - s += mTemplatep ? mTemplatep->mMessage : ""; - // should also include timestamp and expiration time (but probably not payload) - return s; -} - -std::string LLNotification::getMessage() const -{ - // all our callers cache this result, so it gives us more flexibility - // to do the substitution at call time rather than attempting to - // cache it in the notification - if (!mTemplatep) - return std::string(); - - std::string message = mTemplatep->mMessage; - LLStringUtil::format(message, mSubstitutions); - return message; -} - -std::string LLNotification::getLabel() const -{ - std::string label = mTemplatep->mLabel; - LLStringUtil::format(label, mSubstitutions); - return (mTemplatep ? label : ""); -} - -std::string LLNotification::getURL() const -{ - if (!mTemplatep) - return std::string(); - std::string url = mTemplatep->mURL; - LLStringUtil::format(url, mSubstitutions); - return (mTemplatep ? url : ""); -} - -// ========================================================= -// LLNotificationChannel implementation -// --- -LLBoundListener LLNotificationChannelBase::connectChangedImpl(const LLEventListener& slot) -{ - // when someone wants to connect to a channel, we first throw them - // all of the notifications that are already in the channel - // we use a special signal called "load" in case the channel wants to care - // only about new notifications - for (LLNotificationSet::iterator it = mItems.begin(); it != mItems.end(); ++it) - { - slot(LLSD().insert("sigtype", "load").insert("id", (*it)->id())); - } - // and then connect the signal so that all future notifications will also be - // forwarded. - return mChanged.connect(slot); -} - -LLBoundListener LLNotificationChannelBase::connectPassedFilterImpl(const LLEventListener& slot) -{ - // these two filters only fire for notifications added after the current one, because - // they don't participate in the hierarchy. - return mPassedFilter.connect(slot); -} - -LLBoundListener LLNotificationChannelBase::connectFailedFilterImpl(const LLEventListener& slot) -{ - return mFailedFilter.connect(slot); -} - -// external call, conforms to our standard signature -bool LLNotificationChannelBase::updateItem(const LLSD& payload) -{ - // first check to see if it's in the master list - LLNotificationPtr pNotification = LLNotifications::instance().find(payload["id"]); - if (!pNotification) - return false; // not found - - return updateItem(payload, pNotification); -} - - -//FIX QUIT NOT WORKING - - -// internal call, for use in avoiding lookup -bool LLNotificationChannelBase::updateItem(const LLSD& payload, LLNotificationPtr pNotification) -{ - std::string cmd = payload["sigtype"]; - LLNotificationSet::iterator foundItem = mItems.find(pNotification); - bool wasFound = (foundItem != mItems.end()); - bool passesFilter = mFilter(pNotification); - - // first, we offer the result of the filter test to the simple - // signals for pass/fail. One of these is guaranteed to be called. - // If either signal returns true, the change processing is NOT performed - // (so don't return true unless you know what you're doing!) - bool abortProcessing = false; - if (passesFilter) - { - abortProcessing = mPassedFilter(payload); - } - else - { - abortProcessing = mFailedFilter(payload); - } - - if (abortProcessing) - { - return true; - } - - if (cmd == "load") - { - // should be no reason we'd ever get a load if we already have it - // if passes filter send a load message, else do nothing - assert(!wasFound); - if (passesFilter) - { - // not in our list, add it and say so - mItems.insert(pNotification); - abortProcessing = mChanged(payload); - onLoad(pNotification); - } - } - else if (cmd == "change") - { - // if it passes filter now and was found, we just send a change message - // if it passes filter now and wasn't found, we have to add it - // if it doesn't pass filter and wasn't found, we do nothing - // if it doesn't pass filter and was found, we need to delete it - if (passesFilter) - { - if (wasFound) - { - // it already existed, so this is a change - // since it changed in place, all we have to do is resend the signal - abortProcessing = mChanged(payload); - onChange(pNotification); - } - else - { - // not in our list, add it and say so - mItems.insert(pNotification); - // our payload is const, so make a copy before changing it - LLSD newpayload = payload; - newpayload["sigtype"] = "add"; - abortProcessing = mChanged(newpayload); - onChange(pNotification); - } - } - else - { - if (wasFound) - { - // it already existed, so this is a delete - mItems.erase(pNotification); - // our payload is const, so make a copy before changing it - LLSD newpayload = payload; - newpayload["sigtype"] = "delete"; - abortProcessing = mChanged(newpayload); - onChange(pNotification); - } - // didn't pass, not on our list, do nothing - } - } - else if (cmd == "add") - { - // should be no reason we'd ever get an add if we already have it - // if passes filter send an add message, else do nothing - assert(!wasFound); - if (passesFilter) - { - // not in our list, add it and say so - mItems.insert(pNotification); - abortProcessing = mChanged(payload); - onAdd(pNotification); - } - } - else if (cmd == "delete") - { - // if we have it in our list, pass on the delete, then delete it, else do nothing - if (wasFound) - { - abortProcessing = mChanged(payload); - mItems.erase(pNotification); - onDelete(pNotification); - } - } - return abortProcessing; -} - -/* static */ -LLNotificationChannelPtr LLNotificationChannel::buildChannel(const std::string& name, - const std::string& parent, - LLNotificationFilter filter, - LLNotificationComparator comparator) -{ - // note: this is not a leak; notifications are self-registering. - // This factory helps to prevent excess deletions by making sure all smart - // pointers to notification channels come from the same source - new LLNotificationChannel(name, parent, filter, comparator); - return LLNotifications::instance().getChannel(name); -} - - -LLNotificationChannel::LLNotificationChannel(const std::string& name, - const std::string& parent, - LLNotificationFilter filter, - LLNotificationComparator comparator) : -LLNotificationChannelBase(filter, comparator), -mName(name), -mParent(parent) -{ - // store myself in the channel map - LLNotifications::instance().addChannel(LLNotificationChannelPtr(this)); - // bind to notification broadcast - if (parent.empty()) - { - LLNotifications::instance().connectChanged( - boost::bind(&LLNotificationChannelBase::updateItem, this, _1)); - } - else - { - LLNotificationChannelPtr p = LLNotifications::instance().getChannel(parent); - p->connectChanged(boost::bind(&LLNotificationChannelBase::updateItem, this, _1)); - } -} - - -void LLNotificationChannel::setComparator(LLNotificationComparator comparator) -{ - mComparator = comparator; - LLNotificationSet s2(mComparator); - s2.insert(mItems.begin(), mItems.end()); - mItems.swap(s2); - - // notify clients that we've been resorted - mChanged(LLSD().insert("sigtype", "sort")); -} - -bool LLNotificationChannel::isEmpty() const -{ - return mItems.empty(); -} - -LLNotificationChannel::Iterator LLNotificationChannel::begin() -{ - return mItems.begin(); -} - -LLNotificationChannel::Iterator LLNotificationChannel::end() -{ - return mItems.end(); -} - -std::string LLNotificationChannel::summarize() -{ - std::string s("Channel '"); - s += mName; - s += "'\n "; - for (LLNotificationChannel::Iterator it = begin(); it != end(); ++it) - { - s += (*it)->summarize(); - s += "\n "; - } - return s; -} - - -// --- -// END OF LLNotificationChannel implementation -// ========================================================= - - -// ========================================================= -// LLNotifications implementation -// --- -LLNotifications::LLNotifications() : LLNotificationChannelBase(LLNotificationFilters::includeEverything, - LLNotificationComparators::orderByUUID()), - mIgnoreAllNotifications(false) -{ - LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Notification.Show", boost::bind(&LLNotifications::addFromCallback, this, _2)); -} - - -// The expiration channel gets all notifications that are cancelled -bool LLNotifications::expirationFilter(LLNotificationPtr pNotification) -{ - return pNotification->isCancelled() || pNotification->isRespondedTo(); -} - -bool LLNotifications::expirationHandler(const LLSD& payload) -{ - if (payload["sigtype"].asString() != "delete") - { - // anything added to this channel actually should be deleted from the master - cancel(find(payload["id"])); - return true; // don't process this item any further - } - return false; -} - -bool LLNotifications::uniqueFilter(LLNotificationPtr pNotif) -{ - if (!pNotif->hasUniquenessConstraints()) - { - return true; - } - - // checks against existing unique notifications - for (LLNotificationMap::iterator existing_it = mUniqueNotifications.find(pNotif->getName()); - existing_it != mUniqueNotifications.end(); - ++existing_it) - { - LLNotificationPtr existing_notification = existing_it->second; - if (pNotif != existing_notification - && pNotif->isEquivalentTo(existing_notification)) - { - return false; - } - } - - return true; -} - -bool LLNotifications::uniqueHandler(const LLSD& payload) -{ - LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID()); - if (pNotif && pNotif->hasUniquenessConstraints()) - { - if (payload["sigtype"].asString() == "add") - { - // not a duplicate according to uniqueness criteria, so we keep it - // and store it for future uniqueness checks - mUniqueNotifications.insert(std::make_pair(pNotif->getName(), pNotif)); - } - else if (payload["sigtype"].asString() == "delete") - { - mUniqueNotifications.erase(pNotif->getName()); - } - } - - return false; -} - -bool LLNotifications::failedUniquenessTest(const LLSD& payload) -{ - LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID()); - - if (!pNotif || !pNotif->hasUniquenessConstraints()) - { - return false; - } - - // checks against existing unique notifications - for (LLNotificationMap::iterator existing_it = mUniqueNotifications.find(pNotif->getName()); - existing_it != mUniqueNotifications.end(); - ++existing_it) - { - LLNotificationPtr existing_notification = existing_it->second; - if (pNotif != existing_notification - && pNotif->isEquivalentTo(existing_notification)) - { - // copy notification instance data over to oldest instance - // of this unique notification and update it - existing_notification->updateFrom(pNotif); - // then delete the new one - pNotif->cancel(); - } - } - - return false; -} - - -void LLNotifications::addChannel(LLNotificationChannelPtr pChan) -{ - mChannels[pChan->getName()] = pChan; -} - -LLNotificationChannelPtr LLNotifications::getChannel(const std::string& channelName) -{ - ChannelMap::iterator p = mChannels.find(channelName); - if(p == mChannels.end()) - { - llerrs << "Did not find channel named " << channelName << llendl; - } - return p->second; -} - - -// this function is called once at construction time, after the object is constructed. -void LLNotifications::initSingleton() -{ - loadTemplates(); - createDefaultChannels(); -} - -void LLNotifications::createDefaultChannels() -{ - // now construct the various channels AFTER loading the notifications, - // because the history channel is going to rewrite the stored notifications file - LLNotificationChannel::buildChannel("Expiration", "", - boost::bind(&LLNotifications::expirationFilter, this, _1)); - LLNotificationChannel::buildChannel("Unexpired", "", - !boost::bind(&LLNotifications::expirationFilter, this, _1)); // use negated bind - LLNotificationChannel::buildChannel("Unique", "Unexpired", - boost::bind(&LLNotifications::uniqueFilter, this, _1)); - LLNotificationChannel::buildChannel("Ignore", "Unique", - filterIgnoredNotifications); - LLNotificationChannel::buildChannel("Visible", "Ignore", - &LLNotificationFilters::includeEverything); - - // create special history channel - //std::string notifications_log_file = gDirUtilp->getExpandedFilename ( LL_PATH_PER_SL_ACCOUNT, "open_notifications.xml" ); - // use ^^^ when done debugging notifications serialization - std::string notifications_log_file = gDirUtilp->getExpandedFilename ( LL_PATH_USER_SETTINGS, "open_notifications.xml" ); - // this isn't a leak, don't worry about the empty "new" - new LLNotificationHistoryChannel(notifications_log_file); - - // connect action methods to these channels - LLNotifications::instance().getChannel("Expiration")-> - connectChanged(boost::bind(&LLNotifications::expirationHandler, this, _1)); - LLNotifications::instance().getChannel("Unique")-> - connectChanged(boost::bind(&LLNotifications::uniqueHandler, this, _1)); - LLNotifications::instance().getChannel("Unique")-> - connectFailedFilter(boost::bind(&LLNotifications::failedUniquenessTest, this, _1)); - LLNotifications::instance().getChannel("Ignore")-> - connectFailedFilter(&handleIgnoredNotification); -} - -bool LLNotifications::addTemplate(const std::string &name, - LLNotificationTemplatePtr theTemplate) -{ - if (mTemplates.count(name)) - { - llwarns << "LLNotifications -- attempted to add template '" << name << "' twice." << llendl; - return false; - } - mTemplates[name] = theTemplate; - return true; -} - -LLNotificationTemplatePtr LLNotifications::getTemplate(const std::string& name) -{ - if (mTemplates.count(name)) - { - return mTemplates[name]; - } - else - { - return mTemplates["MissingAlert"]; - } -} - -bool LLNotifications::templateExists(const std::string& name) -{ - return (mTemplates.count(name) != 0); -} - -void LLNotifications::clearTemplates() -{ - mTemplates.clear(); -} - -void LLNotifications::forceResponse(const LLNotification::Params& params, S32 option) -{ - LLNotificationPtr temp_notify(new LLNotification(params)); - LLSD response = temp_notify->getResponseTemplate(); - LLSD selected_item = temp_notify->getForm()->getElement(option); - - if (selected_item.isUndefined()) - { - llwarns << "Invalid option" << option << " for notification " << (std::string)params.name << llendl; - return; - } - response[selected_item["name"].asString()] = true; - - temp_notify->respond(response); -} - -LLNotifications::TemplateNames LLNotifications::getTemplateNames() const -{ - TemplateNames names; - for (TemplateMap::const_iterator it = mTemplates.begin(); it != mTemplates.end(); ++it) - { - names.push_back(it->first); - } - return names; -} - -typedef std::map StringMap; -void replaceSubstitutionStrings(LLXMLNodePtr node, StringMap& replacements) -{ - //llwarns << "replaceSubstitutionStrings" << llendl; - // walk the list of attributes looking for replacements - for (LLXMLAttribList::iterator it=node->mAttributes.begin(); - it != node->mAttributes.end(); ++it) - { - std::string value = it->second->getValue(); - if (value[0] == '$') - { - value.erase(0, 1); // trim off the $ - std::string replacement; - StringMap::const_iterator found = replacements.find(value); - if (found != replacements.end()) - { - replacement = found->second; - //llwarns << "replaceSubstituionStrings: value: " << value << " repl: " << replacement << llendl; - - it->second->setValue(replacement); - } - else - { - llwarns << "replaceSubstituionStrings FAILURE: value: " << value << " repl: " << replacement << llendl; - } - } - } - - // now walk the list of children and call this recursively. - for (LLXMLNodePtr child = node->getFirstChild(); - child.notNull(); child = child->getNextSibling()) - { - replaceSubstitutionStrings(child, replacements); - } -} - -// private to this file -// returns true if the template request was invalid and there's nothing else we -// can do with this node, false if you should keep processing (it may have -// replaced the contents of the node referred to) -LLXMLNodePtr LLNotifications::checkForXMLTemplate(LLXMLNodePtr item) -{ - if (item->hasName("usetemplate")) - { - std::string replacementName; - if (item->getAttributeString("name", replacementName)) - { - StringMap replacements; - for (LLXMLAttribList::const_iterator it=item->mAttributes.begin(); - it != item->mAttributes.end(); ++it) - { - replacements[it->second->getName()->mString] = it->second->getValue(); - } - if (mXmlTemplates.count(replacementName)) - { - item=LLXMLNode::replaceNode(item, mXmlTemplates[replacementName]); - - // walk the nodes looking for $(substitution) here and replace - replaceSubstitutionStrings(item, replacements); - } - else - { - llwarns << "XML template lookup failure on '" << replacementName << "' " << llendl; - } - } - } - return item; -} - -bool LLNotifications::loadTemplates() -{ - const std::string xml_filename = "notifications.xml"; - LLXMLNodePtr root; - - BOOL success = LLUICtrlFactory::getLayeredXMLNode(xml_filename, root); - - if (!success || root.isNull() || !root->hasName( "notifications" )) - { - llerrs << "Problem reading UI Notifications file: " << xml_filename << llendl; - return false; - } - - clearTemplates(); - - for (LLXMLNodePtr item = root->getFirstChild(); - item.notNull(); item = item->getNextSibling()) - { - // we do this FIRST so that item can be changed if we - // encounter a usetemplate -- we just replace the - // current xml node and keep processing - item = checkForXMLTemplate(item); - - if (item->hasName("global")) - { - std::string global_name; - if (item->getAttributeString("name", global_name)) - { - mGlobalStrings[global_name] = item->getTextContents(); - } - continue; - } - - if (item->hasName("template")) - { - // store an xml template; templates must have a single node (can contain - // other nodes) - std::string name; - item->getAttributeString("name", name); - LLXMLNodePtr ptr = item->getFirstChild(); - mXmlTemplates[name] = ptr; - continue; - } - - if (!item->hasName("notification")) - { - llwarns << "Unexpected entity " << item->getName()->mString << - " found in " << xml_filename << llendl; - continue; - } - - // now we know we have a notification entry, so let's build it - LLNotificationTemplatePtr pTemplate(new LLNotificationTemplate()); - - if (!item->getAttributeString("name", pTemplate->mName)) - { - llwarns << "Unable to parse notification with no name" << llendl; - continue; - } - - //llinfos << "Parsing " << pTemplate->mName << llendl; - - pTemplate->mMessage = item->getTextContents(); - pTemplate->mDefaultFunctor = pTemplate->mName; - item->getAttributeString("type", pTemplate->mType); - item->getAttributeString("icon", pTemplate->mIcon); - item->getAttributeString("label", pTemplate->mLabel); - item->getAttributeU32("duration", pTemplate->mExpireSeconds); - item->getAttributeU32("expireOption", pTemplate->mExpireOption); - - std::string priority; - item->getAttributeString("priority", priority); - pTemplate->mPriority = NOTIFICATION_PRIORITY_NORMAL; - if (!priority.empty()) - { - if (priority == "low") pTemplate->mPriority = NOTIFICATION_PRIORITY_LOW; - if (priority == "normal") pTemplate->mPriority = NOTIFICATION_PRIORITY_NORMAL; - if (priority == "high") pTemplate->mPriority = NOTIFICATION_PRIORITY_HIGH; - if (priority == "critical") pTemplate->mPriority = NOTIFICATION_PRIORITY_CRITICAL; - } - - item->getAttributeString("functor", pTemplate->mDefaultFunctor); - - BOOL persist = false; - item->getAttributeBOOL("persist", persist); - pTemplate->mPersist = persist; - - std::string sound; - item->getAttributeString("sound", sound); - if (!sound.empty()) - { - // test for bad sound effect name / missing effect - if (LLUI::sSettingGroups["config"]->controlExists(sound)) - { - pTemplate->mSoundEffect = - LLUUID(LLUI::sSettingGroups["config"]->getString(sound)); - } - else - { - llwarns << "Unknown sound effect control name " << sound - << llendl; - } - } - - for (LLXMLNodePtr child = item->getFirstChild(); - !child.isNull(); child = child->getNextSibling()) - { - child = checkForXMLTemplate(child); - - // - if (child->hasName("url")) - { - pTemplate->mURL = child->getTextContents(); - child->getAttributeU32("option", pTemplate->mURLOption); - child->getAttributeU32("openexternally", pTemplate->mURLOpenExternally); - } - - if (child->hasName("unique")) - { - pTemplate->mUnique = true; - for (LLXMLNodePtr formitem = child->getFirstChild(); - !formitem.isNull(); formitem = formitem->getNextSibling()) - { - if (formitem->hasName("context")) - { - std::string key; - formitem->getAttributeString("key", key); - pTemplate->mUniqueContext.push_back(key); - //llwarns << "adding " << key << " to unique context" << llendl; - } - else - { - llwarns << "'unique' has unrecognized subelement " - << formitem->getName()->mString << llendl; - } - } - } - - //
- if (child->hasName("form")) - { - pTemplate->mForm = LLNotificationFormPtr(new LLNotificationForm(pTemplate->mName, child)); - } - } - addTemplate(pTemplate->mName, pTemplate); - } - - //std::ostringstream ostream; - //root->writeToOstream(ostream, "\n "); - //llwarns << ostream.str() << llendl; - - return true; -} - -// Add a simple notification (from XUI) -void LLNotifications::addFromCallback(const LLSD& name) -{ - add(LLNotification::Params().name(name.asString())); -} - -// we provide a couple of simple add notification functions so that it's reasonable to create notifications in one line -LLNotificationPtr LLNotifications::add(const std::string& name, - const LLSD& substitutions, - const LLSD& payload) -{ - LLNotification::Params::Functor functor_p; - functor_p.name = name; - return add(LLNotification::Params().name(name).substitutions(substitutions).payload(payload).functor(functor_p)); -} - -LLNotificationPtr LLNotifications::add(const std::string& name, - const LLSD& substitutions, - const LLSD& payload, - const std::string& functor_name) -{ - LLNotification::Params::Functor functor_p; - functor_p.name = functor_name; - return add(LLNotification::Params().name(name).substitutions(substitutions).payload(payload).functor(functor_p)); -} - -LLNotificationPtr LLNotifications::add(const std::string& name, - const LLSD& substitutions, - const LLSD& payload, - LLNotificationFunctorRegistry::ResponseFunctor functor) -{ - LLNotification::Params::Functor functor_p; - functor_p.function = functor; - return add(LLNotification::Params().name(name).substitutions(substitutions).payload(payload).functor(functor_p)); -} - -// generalized add function that takes a parameter block object for more complex instantiations -LLNotificationPtr LLNotifications::add(const LLNotification::Params& p) -{ - LLNotificationPtr pNotif(new LLNotification(p)); - add(pNotif); - return pNotif; -} - - -void LLNotifications::add(const LLNotificationPtr pNotif) -{ - // first see if we already have it -- if so, that's a problem - LLNotificationSet::iterator it=mItems.find(pNotif); - if (it != mItems.end()) - { - llerrs << "Notification added a second time to the master notification channel." << llendl; - } - - updateItem(LLSD().insert("sigtype", "add").insert("id", pNotif->id()), pNotif); -} - -void LLNotifications::cancel(LLNotificationPtr pNotif) -{ - LLNotificationSet::iterator it=mItems.find(pNotif); - if (it == mItems.end()) - { - llerrs << "Attempted to delete nonexistent notification " << pNotif->getName() << llendl; - } - updateItem(LLSD().insert("sigtype", "delete").insert("id", pNotif->id()), pNotif); - pNotif->cancel(); -} - -void LLNotifications::update(const LLNotificationPtr pNotif) -{ - LLNotificationSet::iterator it=mItems.find(pNotif); - if (it != mItems.end()) - { - updateItem(LLSD().insert("sigtype", "change").insert("id", pNotif->id()), pNotif); - } -} - - -LLNotificationPtr LLNotifications::find(LLUUID uuid) -{ - LLNotificationPtr target = LLNotificationPtr(new LLNotification(uuid)); - LLNotificationSet::iterator it=mItems.find(target); - if (it == mItems.end()) - { - llwarns << "Tried to dereference uuid '" << uuid << "' as a notification key but didn't find it." << llendl; - return LLNotificationPtr((LLNotification*)NULL); - } - else - { - return *it; - } -} - -void LLNotifications::forEachNotification(NotificationProcess process) -{ - std::for_each(mItems.begin(), mItems.end(), process); -} - -std::string LLNotifications::getGlobalString(const std::string& key) const -{ - GlobalStringMap::const_iterator it = mGlobalStrings.find(key); - if (it != mGlobalStrings.end()) - { - return it->second; - } - else - { - // if we don't have the key as a global, return the key itself so that the error - // is self-diagnosing. - return key; - } -} - -void LLNotifications::setIgnoreAllNotifications(bool setting) -{ - mIgnoreAllNotifications = setting; -} -bool LLNotifications::getIgnoreAllNotifications() -{ - return mIgnoreAllNotifications; -} - -// --- -// END OF LLNotifications implementation -// ========================================================= - -std::ostream& operator<<(std::ostream& s, const LLNotification& notification) -{ - s << notification.summarize(); - return s; -} - +/** +* @file llnotifications.cpp +* @brief Non-UI queue manager for keeping a prioritized list of notifications +* +* $LicenseInfo:firstyear=2008&license=viewergpl$ +* +* Copyright (c) 2008-2009, Linden Research, Inc. +* +* Second Life Viewer Source Code +* The source code in this file ("Source Code") is provided by Linden Lab +* to you under the terms of the GNU General Public License, version 2.0 +* ("GPL"), unless you have obtained a separate licensing agreement +* ("Other License"), formally executed by you and Linden Lab. Terms of +* the GPL can be found in doc/GPL-license.txt in this distribution, or +* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 +* +* There are special exceptions to the terms and conditions of the GPL as +* it is applied to this Source Code. View the full text of the exception +* in the file doc/FLOSS-exception.txt in this software distribution, or +* online at +* http://secondlifegrid.net/programs/open_source/licensing/flossexception +* +* By copying, modifying or distributing this software, you acknowledge +* that you have read and understood your obligations described above, +* and agree to abide by those obligations. +* +* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO +* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, +* COMPLETENESS OR PERFORMANCE. +* $/LicenseInfo$ +*/ + +#include "linden_common.h" + +#include "llnotifications.h" + +#include "lluictrl.h" +#include "lluictrlfactory.h" +#include "lldir.h" +#include "llsdserialize.h" +#include "lltrans.h" +#include "llnotificationslistener.h" + +#include +#include + + +const std::string NOTIFICATION_PERSIST_VERSION = "0.93"; + +// local channel for notification history +class LLNotificationHistoryChannel : public LLNotificationChannel +{ + LOG_CLASS(LLNotificationHistoryChannel); +public: + LLNotificationHistoryChannel(const std::string& filename) : + LLNotificationChannel("History", "Visible", &historyFilter, LLNotificationComparators::orderByUUID()), + mFileName(filename) + { + connectChanged(boost::bind(&LLNotificationHistoryChannel::historyHandler, this, _1)); + loadPersistentNotifications(); + } + +private: + bool historyHandler(const LLSD& payload) + { + // we ignore "load" messages, but rewrite the persistence file on any other + std::string sigtype = payload["sigtype"]; + if (sigtype != "load") + { + savePersistentNotifications(); + } + return false; + } + + // The history channel gets all notifications except those that have been cancelled + static bool historyFilter(LLNotificationPtr pNotification) + { + return !pNotification->isCancelled(); + } + + void savePersistentNotifications() + { + llinfos << "Saving open notifications to " << mFileName << llendl; + + llofstream notify_file(mFileName.c_str()); + if (!notify_file.is_open()) + { + llwarns << "Failed to open " << mFileName << llendl; + return; + } + + LLSD output; + output["version"] = NOTIFICATION_PERSIST_VERSION; + LLSD& data = output["data"]; + + for (LLNotificationSet::iterator it = mItems.begin(); it != mItems.end(); ++it) + { + if (!LLNotifications::instance().templateExists((*it)->getName())) continue; + + // only store notifications flagged as persisting + LLNotificationTemplatePtr templatep = LLNotifications::instance().getTemplate((*it)->getName()); + if (!templatep->mPersist) continue; + + data.append((*it)->asLLSD()); + } + + LLPointer formatter = new LLSDXMLFormatter(); + formatter->format(output, notify_file, LLSDFormatter::OPTIONS_PRETTY); + } + + void loadPersistentNotifications() + { + llinfos << "Loading open notifications from " << mFileName << llendl; + + llifstream notify_file(mFileName.c_str()); + if (!notify_file.is_open()) + { + llwarns << "Failed to open " << mFileName << llendl; + return; + } + + LLSD input; + LLPointer parser = new LLSDXMLParser(); + if (parser->parse(notify_file, input, LLSDSerialize::SIZE_UNLIMITED) < 0) + { + llwarns << "Failed to parse open notifications" << llendl; + return; + } + + if (input.isUndefined()) return; + std::string version = input["version"]; + if (version != NOTIFICATION_PERSIST_VERSION) + { + llwarns << "Bad open notifications version: " << version << llendl; + return; + } + LLSD& data = input["data"]; + if (data.isUndefined()) return; + + LLNotifications& instance = LLNotifications::instance(); + for (LLSD::array_const_iterator notification_it = data.beginArray(); + notification_it != data.endArray(); + ++notification_it) + { + instance.add(LLNotificationPtr(new LLNotification(*notification_it))); + } + } + + //virtual + void onDelete(LLNotificationPtr pNotification) + { + // we want to keep deleted notifications in our log + mItems.insert(pNotification); + + return; + } + +private: + std::string mFileName; +}; + +bool filterIgnoredNotifications(LLNotificationPtr notification) +{ + // filter everything if we are to ignore ALL + if(LLNotifications::instance().getIgnoreAllNotifications()) + { + return false; + } + + LLNotificationFormPtr form = notification->getForm(); + // Check to see if the user wants to ignore this alert + if (form->getIgnoreType() != LLNotificationForm::IGNORE_NO) + { + return LLUI::sSettingGroups["ignores"]->getBOOL(notification->getName()); + } + + return true; +} + +bool handleIgnoredNotification(const LLSD& payload) +{ + if (payload["sigtype"].asString() == "add") + { + LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID()); + if (!pNotif) return false; + + LLNotificationFormPtr form = pNotif->getForm(); + LLSD response; + switch(form->getIgnoreType()) + { + case LLNotificationForm::IGNORE_WITH_DEFAULT_RESPONSE: + response = pNotif->getResponseTemplate(LLNotification::WITH_DEFAULT_BUTTON); + break; + case LLNotificationForm::IGNORE_WITH_LAST_RESPONSE: + response = LLUI::sSettingGroups["ignores"]->getLLSD("Default" + pNotif->getName()); + break; + case LLNotificationForm::IGNORE_SHOW_AGAIN: + break; + default: + return false; + } + pNotif->setIgnored(true); + pNotif->respond(response); + return true; // don't process this item any further + } + return false; +} + +namespace LLNotificationFilters +{ + // a sample filter + bool includeEverything(LLNotificationPtr p) + { + return true; + } +}; + +LLNotificationForm::LLNotificationForm() +: mFormData(LLSD::emptyArray()), + mIgnore(IGNORE_NO) +{ +} + + +LLNotificationForm::LLNotificationForm(const std::string& name, const LLXMLNodePtr xml_node) +: mFormData(LLSD::emptyArray()), + mIgnore(IGNORE_NO) +{ + if (!xml_node->hasName("form")) + { + llwarns << "Bad xml node for form: " << xml_node->getName() << llendl; + } + LLXMLNodePtr child = xml_node->getFirstChild(); + while(child) + { + child = LLNotifications::instance().checkForXMLTemplate(child); + + LLSD item_entry; + std::string element_name = child->getName()->mString; + + if (element_name == "ignore" ) + { + bool save_option = false; + child->getAttribute_bool("save_option", save_option); + if (!save_option) + { + mIgnore = IGNORE_WITH_DEFAULT_RESPONSE; + } + else + { + // remember last option chosen by user and automatically respond with that in the future + mIgnore = IGNORE_WITH_LAST_RESPONSE; + LLUI::sSettingGroups["ignores"]->declareLLSD(std::string("Default") + name, "", std::string("Default response for notification " + name)); + } + child->getAttributeString("text", mIgnoreMsg); + BOOL show_notification = TRUE; + LLUI::sSettingGroups["ignores"]->declareBOOL(name, show_notification, "Ignore notification with this name", TRUE); + } + else + { + // flatten xml form entry into single LLSD map with type==name + item_entry["type"] = element_name; + const LLXMLAttribList::iterator attrib_end = child->mAttributes.end(); + for(LLXMLAttribList::iterator attrib_it = child->mAttributes.begin(); + attrib_it != attrib_end; + ++attrib_it) + { + item_entry[std::string(attrib_it->second->getName()->mString)] = attrib_it->second->getValue(); + } + item_entry["value"] = child->getTextContents(); + mFormData.append(item_entry); + } + + child = child->getNextSibling(); + } +} + +LLNotificationForm::LLNotificationForm(const LLSD& sd) +{ + if (sd.isArray()) + { + mFormData = sd; + } + else + { + llwarns << "Invalid form data " << sd << llendl; + mFormData = LLSD::emptyArray(); + } +} + +LLSD LLNotificationForm::asLLSD() const +{ + return mFormData; +} + +LLSD LLNotificationForm::getElement(const std::string& element_name) +{ + for (LLSD::array_const_iterator it = mFormData.beginArray(); + it != mFormData.endArray(); + ++it) + { + if ((*it)["name"].asString() == element_name) return (*it); + } + return LLSD(); +} + + +bool LLNotificationForm::hasElement(const std::string& element_name) +{ + for (LLSD::array_const_iterator it = mFormData.beginArray(); + it != mFormData.endArray(); + ++it) + { + if ((*it)["name"].asString() == element_name) return true; + } + return false; +} + +void LLNotificationForm::addElement(const std::string& type, const std::string& name, const LLSD& value) +{ + LLSD element; + element["type"] = type; + element["name"] = name; + element["text"] = name; + element["value"] = value; + element["index"] = mFormData.size(); + mFormData.append(element); +} + +void LLNotificationForm::append(const LLSD& sub_form) +{ + if (sub_form.isArray()) + { + for (LLSD::array_const_iterator it = sub_form.beginArray(); + it != sub_form.endArray(); + ++it) + { + mFormData.append(*it); + } + } +} + +void LLNotificationForm::formatElements(const LLSD& substitutions) +{ + for (LLSD::array_iterator it = mFormData.beginArray(); + it != mFormData.endArray(); + ++it) + { + // format "text" component of each form element + if ((*it).has("text")) + { + std::string text = (*it)["text"].asString(); + LLStringUtil::format(text, substitutions); + (*it)["text"] = text; + } + if ((*it)["type"].asString() == "text" && (*it).has("value")) + { + std::string value = (*it)["value"].asString(); + LLStringUtil::format(value, substitutions); + (*it)["value"] = value; + } + } +} + +std::string LLNotificationForm::getDefaultOption() +{ + for (LLSD::array_const_iterator it = mFormData.beginArray(); + it != mFormData.endArray(); + ++it) + { + if ((*it)["default"]) return (*it)["name"].asString(); + } + return ""; +} + +LLNotificationTemplate::LLNotificationTemplate() : + mExpireSeconds(0), + mExpireOption(-1), + mURLOption(-1), + mURLOpenExternally(-1), + mUnique(false), + mPriority(NOTIFICATION_PRIORITY_NORMAL) +{ + mForm = LLNotificationFormPtr(new LLNotificationForm()); +} + +LLNotification::LLNotification(const LLNotification::Params& p) : + mTimestamp(p.timestamp), + mSubstitutions(p.substitutions), + mPayload(p.payload), + mExpiresAt(0), + mTemporaryResponder(false), + mRespondedTo(false), + mPriority(p.priority), + mCancelled(false), + mIgnored(false) +{ + if (p.functor.name.isChosen()) + { + mResponseFunctorName = p.functor.name; + } + else if (p.functor.function.isChosen()) + { + mResponseFunctorName = LLUUID::generateNewID().asString(); + LLNotificationFunctorRegistry::instance().registerFunctor(mResponseFunctorName, p.functor.function()); + + mTemporaryResponder = true; + } + + mId.generate(); + init(p.name, p.form_elements); +} + + +LLNotification::LLNotification(const LLSD& sd) : + mTemporaryResponder(false), + mRespondedTo(false), + mCancelled(false), + mIgnored(false) +{ + mId.generate(); + mSubstitutions = sd["substitutions"]; + mPayload = sd["payload"]; + mTimestamp = sd["time"]; + mExpiresAt = sd["expiry"]; + mPriority = (ENotificationPriority)sd["priority"].asInteger(); + mResponseFunctorName = sd["responseFunctor"].asString(); + std::string templatename = sd["name"].asString(); + init(templatename, LLSD()); + // replace form with serialized version + mForm = LLNotificationFormPtr(new LLNotificationForm(sd["form"])); +} + + +LLSD LLNotification::asLLSD() +{ + LLSD output; + output["name"] = mTemplatep->mName; + output["form"] = getForm()->asLLSD(); + output["substitutions"] = mSubstitutions; + output["payload"] = mPayload; + output["time"] = mTimestamp; + output["expiry"] = mExpiresAt; + output["priority"] = (S32)mPriority; + output["responseFunctor"] = mResponseFunctorName; + return output; +} + +void LLNotification::update() +{ + LLNotifications::instance().update(shared_from_this()); +} + +void LLNotification::updateFrom(LLNotificationPtr other) +{ + // can only update from the same notification type + if (mTemplatep != other->mTemplatep) return; + + // NOTE: do NOT change the ID, since it is the key to + // this given instance, just update all the metadata + //mId = other->mId; + + mPayload = other->mPayload; + mSubstitutions = other->mSubstitutions; + mTimestamp = other->mTimestamp; + mExpiresAt = other->mExpiresAt; + mCancelled = other->mCancelled; + mIgnored = other->mIgnored; + mPriority = other->mPriority; + mForm = other->mForm; + mResponseFunctorName = other->mResponseFunctorName; + mRespondedTo = other->mRespondedTo; + mTemporaryResponder = other->mTemporaryResponder; + + update(); +} + +const LLNotificationFormPtr LLNotification::getForm() +{ + return mForm; +} + +void LLNotification::cancel() +{ + mCancelled = true; +} + +LLSD LLNotification::getResponseTemplate(EResponseTemplateType type) +{ + LLSD response = LLSD::emptyMap(); + for (S32 element_idx = 0; + element_idx < mForm->getNumElements(); + ++element_idx) + { + LLSD element = mForm->getElement(element_idx); + if (element.has("name")) + { + response[element["name"].asString()] = element["value"]; + } + + if ((type == WITH_DEFAULT_BUTTON) + && element["default"].asBoolean()) + { + response[element["name"].asString()] = true; + } + } + return response; +} + +//static +S32 LLNotification::getSelectedOption(const LLSD& notification, const LLSD& response) +{ + LLNotificationForm form(notification["form"]); + + for (S32 element_idx = 0; + element_idx < form.getNumElements(); + ++element_idx) + { + LLSD element = form.getElement(element_idx); + + // only look at buttons + if (element["type"].asString() == "button" + && response[element["name"].asString()].asBoolean()) + { + return element["index"].asInteger(); + } + } + + return -1; +} + +//static +std::string LLNotification::getSelectedOptionName(const LLSD& response) +{ + for (LLSD::map_const_iterator response_it = response.beginMap(); + response_it != response.endMap(); + ++response_it) + { + if (response_it->second.isBoolean() && response_it->second.asBoolean()) + { + return response_it->first; + } + } + return ""; +} + + +void LLNotification::respond(const LLSD& response) +{ + mRespondedTo = true; + // look up the functor + LLNotificationFunctorRegistry::ResponseFunctor functor = + LLNotificationFunctorRegistry::instance().getFunctor(mResponseFunctorName); + // and then call it + functor(asLLSD(), response); + + if (mTemporaryResponder) + { + LLNotificationFunctorRegistry::instance().unregisterFunctor(mResponseFunctorName); + mResponseFunctorName = ""; + mTemporaryResponder = false; + } + + if (mForm->getIgnoreType() != LLNotificationForm::IGNORE_NO) + { + BOOL show_notification = mIgnored ? FALSE : TRUE; + LLUI::sSettingGroups["ignores"]->setBOOL(getName(), show_notification); + if (mIgnored && mForm->getIgnoreType() == LLNotificationForm::IGNORE_WITH_LAST_RESPONSE) + { + LLUI::sSettingGroups["ignores"]->setLLSD("Default" + getName(), response); + } + } + + update(); +} + +void LLNotification::setIgnored(bool ignore) +{ + mIgnored = ignore; +} + +void LLNotification::setResponseFunctor(std::string const &responseFunctorName) +{ + if (mTemporaryResponder) + // get rid of the old one + LLNotificationFunctorRegistry::instance().unregisterFunctor(mResponseFunctorName); + mResponseFunctorName = responseFunctorName; + mTemporaryResponder = false; +} + +bool LLNotification::payloadContainsAll(const std::vector& required_fields) const +{ + for(std::vector::const_iterator required_fields_it = required_fields.begin(); + required_fields_it != required_fields.end(); + required_fields_it++) + { + std::string required_field_name = *required_fields_it; + if( ! getPayload().has(required_field_name)) + { + return false; // a required field was not found + } + } + return true; // all required fields were found +} + +bool LLNotification::isEquivalentTo(LLNotificationPtr that) const +{ + if (this->mTemplatep->mName != that->mTemplatep->mName) + { + return false; // must have the same template name or forget it + } + if (this->mTemplatep->mUnique) + { + // highlander bit sez there can only be one of these + return + this->payloadContainsAll(that->mTemplatep->mUniqueContext) && + that->payloadContainsAll(this->mTemplatep->mUniqueContext); + } + return false; +} + +void LLNotification::init(const std::string& template_name, const LLSD& form_elements) +{ + mTemplatep = LLNotifications::instance().getTemplate(template_name); + if (!mTemplatep) return; + + // add default substitutions + const LLStringUtil::format_map_t& default_args = LLTrans::getDefaultArgs(); + for (LLStringUtil::format_map_t::const_iterator iter = default_args.begin(); + iter != default_args.end(); ++iter) + { + mSubstitutions[iter->first] = iter->second; + } + mSubstitutions["_URL"] = getURL(); + mSubstitutions["_NAME"] = template_name; + // TODO: something like this so that a missing alert is sensible: + //mSubstitutions["_ARGS"] = get_all_arguments_as_text(mSubstitutions); + + mForm = LLNotificationFormPtr(new LLNotificationForm(*mTemplatep->mForm)); + mForm->append(form_elements); + + // apply substitution to form labels + mForm->formatElements(mSubstitutions); + + LLDate rightnow = LLDate::now(); + if (mTemplatep->mExpireSeconds) + { + mExpiresAt = LLDate(rightnow.secondsSinceEpoch() + mTemplatep->mExpireSeconds); + } + + if (mPriority == NOTIFICATION_PRIORITY_UNSPECIFIED) + { + mPriority = mTemplatep->mPriority; + } +} + +std::string LLNotification::summarize() const +{ + std::string s = "Notification("; + s += getName(); + s += ") : "; + s += mTemplatep ? mTemplatep->mMessage : ""; + // should also include timestamp and expiration time (but probably not payload) + return s; +} + +std::string LLNotification::getMessage() const +{ + // all our callers cache this result, so it gives us more flexibility + // to do the substitution at call time rather than attempting to + // cache it in the notification + if (!mTemplatep) + return std::string(); + + std::string message = mTemplatep->mMessage; + LLStringUtil::format(message, mSubstitutions); + return message; +} + +std::string LLNotification::getLabel() const +{ + std::string label = mTemplatep->mLabel; + LLStringUtil::format(label, mSubstitutions); + return (mTemplatep ? label : ""); +} + +std::string LLNotification::getURL() const +{ + if (!mTemplatep) + return std::string(); + std::string url = mTemplatep->mURL; + LLStringUtil::format(url, mSubstitutions); + return (mTemplatep ? url : ""); +} + +// ========================================================= +// LLNotificationChannel implementation +// --- +LLBoundListener LLNotificationChannelBase::connectChangedImpl(const LLEventListener& slot) +{ + // when someone wants to connect to a channel, we first throw them + // all of the notifications that are already in the channel + // we use a special signal called "load" in case the channel wants to care + // only about new notifications + for (LLNotificationSet::iterator it = mItems.begin(); it != mItems.end(); ++it) + { + slot(LLSD().insert("sigtype", "load").insert("id", (*it)->id())); + } + // and then connect the signal so that all future notifications will also be + // forwarded. + return mChanged.connect(slot); +} + +LLBoundListener LLNotificationChannelBase::connectPassedFilterImpl(const LLEventListener& slot) +{ + // these two filters only fire for notifications added after the current one, because + // they don't participate in the hierarchy. + return mPassedFilter.connect(slot); +} + +LLBoundListener LLNotificationChannelBase::connectFailedFilterImpl(const LLEventListener& slot) +{ + return mFailedFilter.connect(slot); +} + +// external call, conforms to our standard signature +bool LLNotificationChannelBase::updateItem(const LLSD& payload) +{ + // first check to see if it's in the master list + LLNotificationPtr pNotification = LLNotifications::instance().find(payload["id"]); + if (!pNotification) + return false; // not found + + return updateItem(payload, pNotification); +} + + +//FIX QUIT NOT WORKING + + +// internal call, for use in avoiding lookup +bool LLNotificationChannelBase::updateItem(const LLSD& payload, LLNotificationPtr pNotification) +{ + std::string cmd = payload["sigtype"]; + LLNotificationSet::iterator foundItem = mItems.find(pNotification); + bool wasFound = (foundItem != mItems.end()); + bool passesFilter = mFilter(pNotification); + + // first, we offer the result of the filter test to the simple + // signals for pass/fail. One of these is guaranteed to be called. + // If either signal returns true, the change processing is NOT performed + // (so don't return true unless you know what you're doing!) + bool abortProcessing = false; + if (passesFilter) + { + abortProcessing = mPassedFilter(payload); + } + else + { + abortProcessing = mFailedFilter(payload); + } + + if (abortProcessing) + { + return true; + } + + if (cmd == "load") + { + // should be no reason we'd ever get a load if we already have it + // if passes filter send a load message, else do nothing + assert(!wasFound); + if (passesFilter) + { + // not in our list, add it and say so + mItems.insert(pNotification); + abortProcessing = mChanged(payload); + onLoad(pNotification); + } + } + else if (cmd == "change") + { + // if it passes filter now and was found, we just send a change message + // if it passes filter now and wasn't found, we have to add it + // if it doesn't pass filter and wasn't found, we do nothing + // if it doesn't pass filter and was found, we need to delete it + if (passesFilter) + { + if (wasFound) + { + // it already existed, so this is a change + // since it changed in place, all we have to do is resend the signal + abortProcessing = mChanged(payload); + onChange(pNotification); + } + else + { + // not in our list, add it and say so + mItems.insert(pNotification); + // our payload is const, so make a copy before changing it + LLSD newpayload = payload; + newpayload["sigtype"] = "add"; + abortProcessing = mChanged(newpayload); + onChange(pNotification); + } + } + else + { + if (wasFound) + { + // it already existed, so this is a delete + mItems.erase(pNotification); + // our payload is const, so make a copy before changing it + LLSD newpayload = payload; + newpayload["sigtype"] = "delete"; + abortProcessing = mChanged(newpayload); + onChange(pNotification); + } + // didn't pass, not on our list, do nothing + } + } + else if (cmd == "add") + { + // should be no reason we'd ever get an add if we already have it + // if passes filter send an add message, else do nothing + assert(!wasFound); + if (passesFilter) + { + // not in our list, add it and say so + mItems.insert(pNotification); + abortProcessing = mChanged(payload); + onAdd(pNotification); + } + } + else if (cmd == "delete") + { + // if we have it in our list, pass on the delete, then delete it, else do nothing + if (wasFound) + { + abortProcessing = mChanged(payload); + mItems.erase(pNotification); + onDelete(pNotification); + } + } + return abortProcessing; +} + +/* static */ +LLNotificationChannelPtr LLNotificationChannel::buildChannel(const std::string& name, + const std::string& parent, + LLNotificationFilter filter, + LLNotificationComparator comparator) +{ + // note: this is not a leak; notifications are self-registering. + // This factory helps to prevent excess deletions by making sure all smart + // pointers to notification channels come from the same source + new LLNotificationChannel(name, parent, filter, comparator); + return LLNotifications::instance().getChannel(name); +} + + +LLNotificationChannel::LLNotificationChannel(const std::string& name, + const std::string& parent, + LLNotificationFilter filter, + LLNotificationComparator comparator) : +LLNotificationChannelBase(filter, comparator), +mName(name), +mParent(parent) +{ + // store myself in the channel map + LLNotifications::instance().addChannel(LLNotificationChannelPtr(this)); + // bind to notification broadcast + if (parent.empty()) + { + LLNotifications::instance().connectChanged( + boost::bind(&LLNotificationChannelBase::updateItem, this, _1)); + } + else + { + LLNotificationChannelPtr p = LLNotifications::instance().getChannel(parent); + p->connectChanged(boost::bind(&LLNotificationChannelBase::updateItem, this, _1)); + } +} + + +void LLNotificationChannel::setComparator(LLNotificationComparator comparator) +{ + mComparator = comparator; + LLNotificationSet s2(mComparator); + s2.insert(mItems.begin(), mItems.end()); + mItems.swap(s2); + + // notify clients that we've been resorted + mChanged(LLSD().insert("sigtype", "sort")); +} + +bool LLNotificationChannel::isEmpty() const +{ + return mItems.empty(); +} + +LLNotificationChannel::Iterator LLNotificationChannel::begin() +{ + return mItems.begin(); +} + +LLNotificationChannel::Iterator LLNotificationChannel::end() +{ + return mItems.end(); +} + +std::string LLNotificationChannel::summarize() +{ + std::string s("Channel '"); + s += mName; + s += "'\n "; + for (LLNotificationChannel::Iterator it = begin(); it != end(); ++it) + { + s += (*it)->summarize(); + s += "\n "; + } + return s; +} + + +// --- +// END OF LLNotificationChannel implementation +// ========================================================= + + +// ========================================================= +// LLNotifications implementation +// --- +LLNotifications::LLNotifications() : LLNotificationChannelBase(LLNotificationFilters::includeEverything, + LLNotificationComparators::orderByUUID()), + mIgnoreAllNotifications(false) +{ + LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Notification.Show", boost::bind(&LLNotifications::addFromCallback, this, _2)); + + mListener.reset(new LLNotificationsListener(*this)); +} + + +// The expiration channel gets all notifications that are cancelled +bool LLNotifications::expirationFilter(LLNotificationPtr pNotification) +{ + return pNotification->isCancelled() || pNotification->isRespondedTo(); +} + +bool LLNotifications::expirationHandler(const LLSD& payload) +{ + if (payload["sigtype"].asString() != "delete") + { + // anything added to this channel actually should be deleted from the master + cancel(find(payload["id"])); + return true; // don't process this item any further + } + return false; +} + +bool LLNotifications::uniqueFilter(LLNotificationPtr pNotif) +{ + if (!pNotif->hasUniquenessConstraints()) + { + return true; + } + + // checks against existing unique notifications + for (LLNotificationMap::iterator existing_it = mUniqueNotifications.find(pNotif->getName()); + existing_it != mUniqueNotifications.end(); + ++existing_it) + { + LLNotificationPtr existing_notification = existing_it->second; + if (pNotif != existing_notification + && pNotif->isEquivalentTo(existing_notification)) + { + return false; + } + } + + return true; +} + +bool LLNotifications::uniqueHandler(const LLSD& payload) +{ + LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID()); + if (pNotif && pNotif->hasUniquenessConstraints()) + { + if (payload["sigtype"].asString() == "add") + { + // not a duplicate according to uniqueness criteria, so we keep it + // and store it for future uniqueness checks + mUniqueNotifications.insert(std::make_pair(pNotif->getName(), pNotif)); + } + else if (payload["sigtype"].asString() == "delete") + { + mUniqueNotifications.erase(pNotif->getName()); + } + } + + return false; +} + +bool LLNotifications::failedUniquenessTest(const LLSD& payload) +{ + LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID()); + + if (!pNotif || !pNotif->hasUniquenessConstraints()) + { + return false; + } + + // checks against existing unique notifications + for (LLNotificationMap::iterator existing_it = mUniqueNotifications.find(pNotif->getName()); + existing_it != mUniqueNotifications.end(); + ++existing_it) + { + LLNotificationPtr existing_notification = existing_it->second; + if (pNotif != existing_notification + && pNotif->isEquivalentTo(existing_notification)) + { + // copy notification instance data over to oldest instance + // of this unique notification and update it + existing_notification->updateFrom(pNotif); + // then delete the new one + pNotif->cancel(); + } + } + + return false; +} + + +void LLNotifications::addChannel(LLNotificationChannelPtr pChan) +{ + mChannels[pChan->getName()] = pChan; +} + +LLNotificationChannelPtr LLNotifications::getChannel(const std::string& channelName) +{ + ChannelMap::iterator p = mChannels.find(channelName); + if(p == mChannels.end()) + { + llerrs << "Did not find channel named " << channelName << llendl; + } + return p->second; +} + + +// this function is called once at construction time, after the object is constructed. +void LLNotifications::initSingleton() +{ + loadTemplates(); + createDefaultChannels(); +} + +void LLNotifications::createDefaultChannels() +{ + // now construct the various channels AFTER loading the notifications, + // because the history channel is going to rewrite the stored notifications file + LLNotificationChannel::buildChannel("Expiration", "", + boost::bind(&LLNotifications::expirationFilter, this, _1)); + LLNotificationChannel::buildChannel("Unexpired", "", + !boost::bind(&LLNotifications::expirationFilter, this, _1)); // use negated bind + LLNotificationChannel::buildChannel("Unique", "Unexpired", + boost::bind(&LLNotifications::uniqueFilter, this, _1)); + LLNotificationChannel::buildChannel("Ignore", "Unique", + filterIgnoredNotifications); + LLNotificationChannel::buildChannel("Visible", "Ignore", + &LLNotificationFilters::includeEverything); + + // create special history channel + //std::string notifications_log_file = gDirUtilp->getExpandedFilename ( LL_PATH_PER_SL_ACCOUNT, "open_notifications.xml" ); + // use ^^^ when done debugging notifications serialization + std::string notifications_log_file = gDirUtilp->getExpandedFilename ( LL_PATH_USER_SETTINGS, "open_notifications.xml" ); + // this isn't a leak, don't worry about the empty "new" + new LLNotificationHistoryChannel(notifications_log_file); + + // connect action methods to these channels + LLNotifications::instance().getChannel("Expiration")-> + connectChanged(boost::bind(&LLNotifications::expirationHandler, this, _1)); + LLNotifications::instance().getChannel("Unique")-> + connectChanged(boost::bind(&LLNotifications::uniqueHandler, this, _1)); + LLNotifications::instance().getChannel("Unique")-> + connectFailedFilter(boost::bind(&LLNotifications::failedUniquenessTest, this, _1)); + LLNotifications::instance().getChannel("Ignore")-> + connectFailedFilter(&handleIgnoredNotification); +} + +bool LLNotifications::addTemplate(const std::string &name, + LLNotificationTemplatePtr theTemplate) +{ + if (mTemplates.count(name)) + { + llwarns << "LLNotifications -- attempted to add template '" << name << "' twice." << llendl; + return false; + } + mTemplates[name] = theTemplate; + return true; +} + +LLNotificationTemplatePtr LLNotifications::getTemplate(const std::string& name) +{ + if (mTemplates.count(name)) + { + return mTemplates[name]; + } + else + { + return mTemplates["MissingAlert"]; + } +} + +bool LLNotifications::templateExists(const std::string& name) +{ + return (mTemplates.count(name) != 0); +} + +void LLNotifications::clearTemplates() +{ + mTemplates.clear(); +} + +void LLNotifications::forceResponse(const LLNotification::Params& params, S32 option) +{ + LLNotificationPtr temp_notify(new LLNotification(params)); + LLSD response = temp_notify->getResponseTemplate(); + LLSD selected_item = temp_notify->getForm()->getElement(option); + + if (selected_item.isUndefined()) + { + llwarns << "Invalid option" << option << " for notification " << (std::string)params.name << llendl; + return; + } + response[selected_item["name"].asString()] = true; + + temp_notify->respond(response); +} + +LLNotifications::TemplateNames LLNotifications::getTemplateNames() const +{ + TemplateNames names; + for (TemplateMap::const_iterator it = mTemplates.begin(); it != mTemplates.end(); ++it) + { + names.push_back(it->first); + } + return names; +} + +typedef std::map StringMap; +void replaceSubstitutionStrings(LLXMLNodePtr node, StringMap& replacements) +{ + //llwarns << "replaceSubstitutionStrings" << llendl; + // walk the list of attributes looking for replacements + for (LLXMLAttribList::iterator it=node->mAttributes.begin(); + it != node->mAttributes.end(); ++it) + { + std::string value = it->second->getValue(); + if (value[0] == '$') + { + value.erase(0, 1); // trim off the $ + std::string replacement; + StringMap::const_iterator found = replacements.find(value); + if (found != replacements.end()) + { + replacement = found->second; + //llwarns << "replaceSubstituionStrings: value: " << value << " repl: " << replacement << llendl; + + it->second->setValue(replacement); + } + else + { + llwarns << "replaceSubstituionStrings FAILURE: value: " << value << " repl: " << replacement << llendl; + } + } + } + + // now walk the list of children and call this recursively. + for (LLXMLNodePtr child = node->getFirstChild(); + child.notNull(); child = child->getNextSibling()) + { + replaceSubstitutionStrings(child, replacements); + } +} + +// private to this file +// returns true if the template request was invalid and there's nothing else we +// can do with this node, false if you should keep processing (it may have +// replaced the contents of the node referred to) +LLXMLNodePtr LLNotifications::checkForXMLTemplate(LLXMLNodePtr item) +{ + if (item->hasName("usetemplate")) + { + std::string replacementName; + if (item->getAttributeString("name", replacementName)) + { + StringMap replacements; + for (LLXMLAttribList::const_iterator it=item->mAttributes.begin(); + it != item->mAttributes.end(); ++it) + { + replacements[it->second->getName()->mString] = it->second->getValue(); + } + if (mXmlTemplates.count(replacementName)) + { + item=LLXMLNode::replaceNode(item, mXmlTemplates[replacementName]); + + // walk the nodes looking for $(substitution) here and replace + replaceSubstitutionStrings(item, replacements); + } + else + { + llwarns << "XML template lookup failure on '" << replacementName << "' " << llendl; + } + } + } + return item; +} + +bool LLNotifications::loadTemplates() +{ + const std::string xml_filename = "notifications.xml"; + LLXMLNodePtr root; + + BOOL success = LLUICtrlFactory::getLayeredXMLNode(xml_filename, root); + + if (!success || root.isNull() || !root->hasName( "notifications" )) + { + llerrs << "Problem reading UI Notifications file: " << xml_filename << llendl; + return false; + } + + clearTemplates(); + + for (LLXMLNodePtr item = root->getFirstChild(); + item.notNull(); item = item->getNextSibling()) + { + // we do this FIRST so that item can be changed if we + // encounter a usetemplate -- we just replace the + // current xml node and keep processing + item = checkForXMLTemplate(item); + + if (item->hasName("global")) + { + std::string global_name; + if (item->getAttributeString("name", global_name)) + { + mGlobalStrings[global_name] = item->getTextContents(); + } + continue; + } + + if (item->hasName("template")) + { + // store an xml template; templates must have a single node (can contain + // other nodes) + std::string name; + item->getAttributeString("name", name); + LLXMLNodePtr ptr = item->getFirstChild(); + mXmlTemplates[name] = ptr; + continue; + } + + if (!item->hasName("notification")) + { + llwarns << "Unexpected entity " << item->getName()->mString << + " found in " << xml_filename << llendl; + continue; + } + + // now we know we have a notification entry, so let's build it + LLNotificationTemplatePtr pTemplate(new LLNotificationTemplate()); + + if (!item->getAttributeString("name", pTemplate->mName)) + { + llwarns << "Unable to parse notification with no name" << llendl; + continue; + } + + //llinfos << "Parsing " << pTemplate->mName << llendl; + + pTemplate->mMessage = item->getTextContents(); + pTemplate->mDefaultFunctor = pTemplate->mName; + item->getAttributeString("type", pTemplate->mType); + item->getAttributeString("icon", pTemplate->mIcon); + item->getAttributeString("label", pTemplate->mLabel); + item->getAttributeU32("duration", pTemplate->mExpireSeconds); + item->getAttributeU32("expireOption", pTemplate->mExpireOption); + + std::string priority; + item->getAttributeString("priority", priority); + pTemplate->mPriority = NOTIFICATION_PRIORITY_NORMAL; + if (!priority.empty()) + { + if (priority == "low") pTemplate->mPriority = NOTIFICATION_PRIORITY_LOW; + if (priority == "normal") pTemplate->mPriority = NOTIFICATION_PRIORITY_NORMAL; + if (priority == "high") pTemplate->mPriority = NOTIFICATION_PRIORITY_HIGH; + if (priority == "critical") pTemplate->mPriority = NOTIFICATION_PRIORITY_CRITICAL; + } + + item->getAttributeString("functor", pTemplate->mDefaultFunctor); + + BOOL persist = false; + item->getAttributeBOOL("persist", persist); + pTemplate->mPersist = persist; + + std::string sound; + item->getAttributeString("sound", sound); + if (!sound.empty()) + { + // test for bad sound effect name / missing effect + if (LLUI::sSettingGroups["config"]->controlExists(sound)) + { + pTemplate->mSoundEffect = + LLUUID(LLUI::sSettingGroups["config"]->getString(sound)); + } + else + { + llwarns << "Unknown sound effect control name " << sound + << llendl; + } + } + + for (LLXMLNodePtr child = item->getFirstChild(); + !child.isNull(); child = child->getNextSibling()) + { + child = checkForXMLTemplate(child); + + // + if (child->hasName("url")) + { + pTemplate->mURL = child->getTextContents(); + child->getAttributeU32("option", pTemplate->mURLOption); + child->getAttributeU32("openexternally", pTemplate->mURLOpenExternally); + } + + if (child->hasName("unique")) + { + pTemplate->mUnique = true; + for (LLXMLNodePtr formitem = child->getFirstChild(); + !formitem.isNull(); formitem = formitem->getNextSibling()) + { + if (formitem->hasName("context")) + { + std::string key; + formitem->getAttributeString("key", key); + pTemplate->mUniqueContext.push_back(key); + //llwarns << "adding " << key << " to unique context" << llendl; + } + else + { + llwarns << "'unique' has unrecognized subelement " + << formitem->getName()->mString << llendl; + } + } + } + + // + if (child->hasName("form")) + { + pTemplate->mForm = LLNotificationFormPtr(new LLNotificationForm(pTemplate->mName, child)); + } + } + addTemplate(pTemplate->mName, pTemplate); + } + + //std::ostringstream ostream; + //root->writeToOstream(ostream, "\n "); + //llwarns << ostream.str() << llendl; + + return true; +} + +// Add a simple notification (from XUI) +void LLNotifications::addFromCallback(const LLSD& name) +{ + add(LLNotification::Params().name(name.asString())); +} + +// we provide a couple of simple add notification functions so that it's reasonable to create notifications in one line +LLNotificationPtr LLNotifications::add(const std::string& name, + const LLSD& substitutions, + const LLSD& payload) +{ + LLNotification::Params::Functor functor_p; + functor_p.name = name; + return add(LLNotification::Params().name(name).substitutions(substitutions).payload(payload).functor(functor_p)); +} + +LLNotificationPtr LLNotifications::add(const std::string& name, + const LLSD& substitutions, + const LLSD& payload, + const std::string& functor_name) +{ + LLNotification::Params::Functor functor_p; + functor_p.name = functor_name; + return add(LLNotification::Params().name(name).substitutions(substitutions).payload(payload).functor(functor_p)); +} + +LLNotificationPtr LLNotifications::add(const std::string& name, + const LLSD& substitutions, + const LLSD& payload, + LLNotificationFunctorRegistry::ResponseFunctor functor) +{ + LLNotification::Params::Functor functor_p; + functor_p.function = functor; + return add(LLNotification::Params().name(name).substitutions(substitutions).payload(payload).functor(functor_p)); +} + +// generalized add function that takes a parameter block object for more complex instantiations +LLNotificationPtr LLNotifications::add(const LLNotification::Params& p) +{ + LLNotificationPtr pNotif(new LLNotification(p)); + add(pNotif); + return pNotif; +} + + +void LLNotifications::add(const LLNotificationPtr pNotif) +{ + // first see if we already have it -- if so, that's a problem + LLNotificationSet::iterator it=mItems.find(pNotif); + if (it != mItems.end()) + { + llerrs << "Notification added a second time to the master notification channel." << llendl; + } + + updateItem(LLSD().insert("sigtype", "add").insert("id", pNotif->id()), pNotif); +} + +void LLNotifications::cancel(LLNotificationPtr pNotif) +{ + LLNotificationSet::iterator it=mItems.find(pNotif); + if (it == mItems.end()) + { + llerrs << "Attempted to delete nonexistent notification " << pNotif->getName() << llendl; + } + updateItem(LLSD().insert("sigtype", "delete").insert("id", pNotif->id()), pNotif); + pNotif->cancel(); +} + +void LLNotifications::update(const LLNotificationPtr pNotif) +{ + LLNotificationSet::iterator it=mItems.find(pNotif); + if (it != mItems.end()) + { + updateItem(LLSD().insert("sigtype", "change").insert("id", pNotif->id()), pNotif); + } +} + + +LLNotificationPtr LLNotifications::find(LLUUID uuid) +{ + LLNotificationPtr target = LLNotificationPtr(new LLNotification(uuid)); + LLNotificationSet::iterator it=mItems.find(target); + if (it == mItems.end()) + { + llwarns << "Tried to dereference uuid '" << uuid << "' as a notification key but didn't find it." << llendl; + return LLNotificationPtr((LLNotification*)NULL); + } + else + { + return *it; + } +} + +void LLNotifications::forEachNotification(NotificationProcess process) +{ + std::for_each(mItems.begin(), mItems.end(), process); +} + +std::string LLNotifications::getGlobalString(const std::string& key) const +{ + GlobalStringMap::const_iterator it = mGlobalStrings.find(key); + if (it != mGlobalStrings.end()) + { + return it->second; + } + else + { + // if we don't have the key as a global, return the key itself so that the error + // is self-diagnosing. + return key; + } +} + +void LLNotifications::setIgnoreAllNotifications(bool setting) +{ + mIgnoreAllNotifications = setting; +} +bool LLNotifications::getIgnoreAllNotifications() +{ + return mIgnoreAllNotifications; +} + +// --- +// END OF LLNotifications implementation +// ========================================================= + +std::ostream& operator<<(std::ostream& s, const LLNotification& notification) +{ + s << notification.summarize(); + return s; +} + diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h index 512886790c..971d11db97 100644 --- a/indra/llui/llnotifications.h +++ b/indra/llui/llnotifications.h @@ -1,910 +1,913 @@ -/** -* @file llnotifications.h -* @brief Non-UI manager and support for keeping a prioritized list of notifications -* @author Q (with assistance from Richard and Coco) -* -* $LicenseInfo:firstyear=2008&license=viewergpl$ -* -* Copyright (c) 2008-2009, Linden Research, Inc. -* -* Second Life Viewer Source Code -* The source code in this file ("Source Code") is provided by Linden Lab -* to you under the terms of the GNU General Public License, version 2.0 -* ("GPL"), unless you have obtained a separate licensing agreement -* ("Other License"), formally executed by you and Linden Lab. Terms of -* the GPL can be found in doc/GPL-license.txt in this distribution, or -* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 -* -* There are special exceptions to the terms and conditions of the GPL as -* it is applied to this Source Code. View the full text of the exception -* in the file doc/FLOSS-exception.txt in this software distribution, or -* online at -* http://secondlifegrid.net/programs/open_source/licensing/flossexception -* -* By copying, modifying or distributing this software, you acknowledge -* that you have read and understood your obligations described above, -* and agree to abide by those obligations. -* -* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO -* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, -* COMPLETENESS OR PERFORMANCE. -* $/LicenseInfo$ -*/ - -#ifndef LL_LLNOTIFICATIONS_H -#define LL_LLNOTIFICATIONS_H - -/** - * This system is intended to provide a singleton mechanism for adding - * notifications to one of an arbitrary set of event channels. - * - * Controlling JIRA: DEV-9061 - * - * Every notification has (see code for full list): - * - a textual name, which is used to look up its template in the XML files - * - a payload, which is a block of LLSD - * - a channel, which is normally extracted from the XML files but - * can be overridden. - * - a timestamp, used to order the notifications - * - expiration time -- if nonzero, specifies a time after which the - * notification will no longer be valid. - * - a callback name and a couple of status bits related to callbacks (see below) - * - * There is a management class called LLNotifications, which is an LLSingleton. - * The class maintains a collection of all of the notifications received - * or processed during this session, and also manages the persistence - * of those notifications that must be persisted. - * - * We also have Channels. A channel is a view on a collection of notifications; - * The collection is defined by a filter function that controls which - * notifications are in the channel, and its ordering is controlled by - * a comparator. - * - * There is a hierarchy of channels; notifications flow down from - * the management class (LLNotifications, which itself inherits from - * The channel base class) to the individual channels. - * Any change to notifications (add, delete, modify) is - * automatically propagated through the channel hierarchy. - * - * We provide methods for adding a new notification, for removing - * one, and for managing channels. Channels are relatively cheap to construct - * and maintain, so in general, human interfaces should use channels to - * select and manage their lists of notifications. - * - * We also maintain a collection of templates that are loaded from the - * XML file of template translations. The system supports substitution - * of named variables from the payload into the XML file. - * - * By default, only the "unknown message" template is built into the system. - * It is not an error to add a notification that's not found in the - * template system, but it is logged. - * - */ - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -// we want to minimize external dependencies, but this one is important -#include "llsd.h" - -// and we need this to manage the notification callbacks -#include "llevents.h" -#include "llfunctorregistry.h" -#include "llui.h" -#include "llmemory.h" - -class LLNotification; -typedef boost::shared_ptr LLNotificationPtr; - - -typedef enum e_notification_priority -{ - NOTIFICATION_PRIORITY_UNSPECIFIED, - NOTIFICATION_PRIORITY_LOW, - NOTIFICATION_PRIORITY_NORMAL, - NOTIFICATION_PRIORITY_HIGH, - NOTIFICATION_PRIORITY_CRITICAL -} ENotificationPriority; - -typedef boost::function LLNotificationResponder; - -typedef LLFunctorRegistry LLNotificationFunctorRegistry; -typedef LLFunctorRegistration LLNotificationFunctorRegistration; - -// context data that can be looked up via a notification's payload by the display logic -// derive from this class to implement specific contexts -class LLNotificationContext : public LLInstanceTracker -{ -public: - LLNotificationContext() : LLInstanceTracker(LLUUID::generateNewID()) - { - } - - virtual ~LLNotificationContext() {} - - LLSD asLLSD() const - { - return getKey(); - } - -private: - -}; - -// Contains notification form data, such as buttons and text fields along with -// manipulator functions -class LLNotificationForm -{ - LOG_CLASS(LLNotificationForm); - -public: - typedef enum e_ignore_type - { - IGNORE_NO, - IGNORE_WITH_DEFAULT_RESPONSE, - IGNORE_WITH_LAST_RESPONSE, - IGNORE_SHOW_AGAIN - } EIgnoreType; - - LLNotificationForm(); - LLNotificationForm(const LLSD& sd); - LLNotificationForm(const std::string& name, const LLXMLNodePtr xml_node); - - LLSD asLLSD() const; - - S32 getNumElements() { return mFormData.size(); } - LLSD getElement(S32 index) { return mFormData.get(index); } - LLSD getElement(const std::string& element_name); - bool hasElement(const std::string& element_name); - void addElement(const std::string& type, const std::string& name, const LLSD& value = LLSD()); - void formatElements(const LLSD& substitutions); - // appends form elements from another form serialized as LLSD - void append(const LLSD& sub_form); - std::string getDefaultOption(); - - EIgnoreType getIgnoreType() { return mIgnore; } - std::string getIgnoreMessage() { return mIgnoreMsg; } - -private: - LLSD mFormData; - EIgnoreType mIgnore; - std::string mIgnoreMsg; -}; - -typedef boost::shared_ptr LLNotificationFormPtr; - -// This is the class of object read from the XML file (notifications.xml, -// from the appropriate local language directory). -struct LLNotificationTemplate -{ - LLNotificationTemplate(); - // the name of the notification -- the key used to identify it - // Ideally, the key should follow variable naming rules - // (no spaces or punctuation). - std::string mName; - // The type of the notification - // used to control which queue it's stored in - std::string mType; - // The text used to display the notification. Replaceable parameters - // are enclosed in square brackets like this []. - std::string mMessage; - // The label for the notification; used for - // certain classes of notification (those with a window and a window title). - // Also used when a notification pops up underneath the current one. - // Replaceable parameters can be used in the label. - std::string mLabel; - // The name of the icon image. This should include an extension. - std::string mIcon; - // This is the Highlander bit -- "There Can Be Only One" - // An outstanding notification with this bit set - // is updated by an incoming notification with the same name, - // rather than creating a new entry in the queue. - // (used for things like progress indications, or repeating warnings - // like "the grid is going down in N minutes") - bool mUnique; - // if we want to be unique only if a certain part of the payload is constant - // specify the field names for the payload. The notification will only be - // combined if all of the fields named in the context are identical in the - // new and the old notification; otherwise, the notification will be - // duplicated. This is to support suppressing duplicate offers from the same - // sender but still differentiating different offers. Example: Invitation to - // conference chat. - std::vector mUniqueContext; - // If this notification expires automatically, this value will be - // nonzero, and indicates the number of seconds for which the notification - // will be valid (a teleport offer, for example, might be valid for - // 300 seconds). - U32 mExpireSeconds; - // if the offer expires, one of the options is chosen automatically - // based on its "value" parameter. This controls which one. - // If expireSeconds is specified, expireOption should also be specified. - U32 mExpireOption; - // if the notification contains a url, it's stored here (and replaced - // into the message where [_URL] is found) - std::string mURL; - // if there's a URL in the message, this controls which option visits - // that URL. Obsolete this and eliminate the buttons for affected - // messages when we allow clickable URLs in the UI - U32 mURLOption; - - U32 mURLOpenExternally; - //This is a flag that tells if the url needs to open externally dispite - //what the user setting is. - - // does this notification persist across sessions? if so, it will be - // serialized to disk on first receipt and read on startup - bool mPersist; - // This is the name of the default functor, if present, to be - // used for the notification's callback. It is optional, and used only if - // the notification is constructed without an identified functor. - std::string mDefaultFunctor; - // The form data associated with a given notification (buttons, text boxes, etc) - LLNotificationFormPtr mForm; - // default priority for notifications of this type - ENotificationPriority mPriority; - // UUID of the audio file to be played when this notification arrives - // this is loaded as a name, but looked up to get the UUID upon template load. - // If null, it wasn't specified. - LLUUID mSoundEffect; -}; - -// we want to keep a map of these by name, and it's best to manage them -// with smart pointers -typedef boost::shared_ptr LLNotificationTemplatePtr; - -/** - * @class LLNotification - * @brief The object that expresses the details of a notification - * - * We make this noncopyable because - * we want to manage these through LLNotificationPtr, and only - * ever create one instance of any given notification. - * - * The enable_shared_from_this flag ensures that if we construct - * a smart pointer from a notification, we'll always get the same - * shared pointer. - */ -class LLNotification : - boost::noncopyable, - public boost::enable_shared_from_this -{ -LOG_CLASS(LLNotification); -friend class LLNotifications; - -public: - // parameter object used to instantiate a new notification - struct Params : public LLInitParam::Block - { - friend class LLNotification; - - Mandatory name; - - // optional - Optional substitutions; - Optional payload; - Optional priority; - Optional form_elements; - Optional timestamp; - Optional context; - - struct Functor : public LLInitParam::Choice - { - Alternative name; - Alternative function; - - Functor() - : name("functor_name"), - function("functor") - {} - }; - Optional functor; - - Params() - : name("name"), - priority("priority", NOTIFICATION_PRIORITY_UNSPECIFIED), - timestamp("time_stamp") - { - timestamp = LLDate::now(); - } - - Params(const std::string& _name) - : name("name"), - priority("priority", NOTIFICATION_PRIORITY_UNSPECIFIED), - timestamp("time_stamp") - { - functor.name = _name; - name = _name; - timestamp = LLDate::now(); - } - }; - -private: - - LLUUID mId; - LLSD mPayload; - LLSD mSubstitutions; - LLDate mTimestamp; - LLDate mExpiresAt; - bool mCancelled; - bool mRespondedTo; // once the notification has been responded to, this becomes true - bool mIgnored; - ENotificationPriority mPriority; - LLNotificationFormPtr mForm; - - // a reference to the template - LLNotificationTemplatePtr mTemplatep; - - /* - We want to be able to store and reload notifications so that they can survive - a shutdown/restart of the client. So we can't simply pass in callbacks; - we have to specify a callback mechanism that can be used by name rather than - by some arbitrary pointer -- and then people have to initialize callbacks - in some useful location. So we use LLNotificationFunctorRegistry to manage them. - */ - std::string mResponseFunctorName; - - /* - In cases where we want to specify an explict, non-persisted callback, - we store that in the callback registry under a dynamically generated - key, and store the key in the notification, so we can still look it up - using the same mechanism. - */ - bool mTemporaryResponder; - - void init(const std::string& template_name, const LLSD& form_elements); - - LLNotification(const Params& p); - - // this is just for making it easy to look things up in a set organized by UUID -- DON'T USE IT - // for anything real! - LLNotification(LLUUID uuid) : mId(uuid) {} - - void cancel(); - - bool payloadContainsAll(const std::vector& required_fields) const; - -public: - - // constructor from a saved notification - LLNotification(const LLSD& sd); - - void setResponseFunctor(std::string const &responseFunctorName); - - typedef enum e_response_template_type - { - WITHOUT_DEFAULT_BUTTON, - WITH_DEFAULT_BUTTON - } EResponseTemplateType; - - // return response LLSD filled in with default form contents and (optionally) the default button selected - LLSD getResponseTemplate(EResponseTemplateType type = WITHOUT_DEFAULT_BUTTON); - - // returns index of first button with value==TRUE - // usually this the button the user clicked on - // returns -1 if no button clicked (e.g. form has not been displayed) - static S32 getSelectedOption(const LLSD& notification, const LLSD& response); - // returns name of first button with value==TRUE - static std::string getSelectedOptionName(const LLSD& notification); - - // after someone responds to a notification (usually by clicking a button, - // but sometimes by filling out a little form and THEN clicking a button), - // the result of the response (the name and value of the button clicked, - // plus any other data) should be packaged up as LLSD, then passed as a - // parameter to the notification's respond() method here. This will look up - // and call the appropriate responder. - // - // response is notification serialized as LLSD: - // ["name"] = notification name - // ["form"] = LLSD tree that includes form description and any prefilled form data - // ["response"] = form data filled in by user - // (including, but not limited to which button they clicked on) - // ["payload"] = transaction specific data, such as ["source_id"] (originator of notification), - // ["item_id"] (attached inventory item), etc. - // ["substitutions"] = string substitutions used to generate notification message - // from the template - // ["time"] = time at which notification was generated; - // ["expiry"] = time at which notification expires; - // ["responseFunctor"] = name of registered functor that handles responses to notification; - LLSD asLLSD(); - - void respond(const LLSD& sd); - - void setIgnored(bool ignore); - - bool isCancelled() const - { - return mCancelled; - } - - bool isRespondedTo() const - { - return mRespondedTo; - } - - bool isIgnored() const - { - return mIgnored; - } - - const std::string& getName() const - { - return mTemplatep->mName; - } - - const LLUUID& id() const - { - return mId; - } - - const LLSD& getPayload() const - { - return mPayload; - } - - const LLSD& getSubstitutions() const - { - return mSubstitutions; - } - - const LLDate& getDate() const - { - return mTimestamp; - } - - std::string getType() const - { - return (mTemplatep ? mTemplatep->mType : ""); - } - - std::string getMessage() const; - std::string getLabel() const; - - std::string getURL() const; -// { -// return (mTemplatep ? mTemplatep->mURL : ""); -// } - - S32 getURLOption() const - { - return (mTemplatep ? mTemplatep->mURLOption : -1); - } - - S32 getURLOpenExternally() const - { - return(mTemplatep? mTemplatep->mURLOpenExternally : -1); - } - - const LLNotificationFormPtr getForm(); - - const LLDate getExpiration() const - { - return mExpiresAt; - } - - ENotificationPriority getPriority() const - { - return mPriority; - } - - const LLUUID getID() const - { - return mId; - } - - // comparing two notifications normally means comparing them by UUID (so we can look them - // up quickly this way) - bool operator<(const LLNotification& rhs) const - { - return mId < rhs.mId; - } - - bool operator==(const LLNotification& rhs) const - { - return mId == rhs.mId; - } - - bool operator!=(const LLNotification& rhs) const - { - return !operator==(rhs); - } - - bool isSameObjectAs(const LLNotification* rhs) const - { - return this == rhs; - } - - // this object has been updated, so tell all our clients - void update(); - - void updateFrom(LLNotificationPtr other); - - // A fuzzy equals comparator. - // true only if both notifications have the same template and - // 1) flagged as unique (there can be only one of these) OR - // 2) all required payload fields of each also exist in the other. - bool isEquivalentTo(LLNotificationPtr that) const; - - // if the current time is greater than the expiration, the notification is expired - bool isExpired() const - { - if (mExpiresAt.secondsSinceEpoch() == 0) - { - return false; - } - - LLDate rightnow = LLDate::now(); - return rightnow > mExpiresAt; - } - - std::string summarize() const; - - bool hasUniquenessConstraints() const { return (mTemplatep ? mTemplatep->mUnique : false);} - - virtual ~LLNotification() {} -}; - -std::ostream& operator<<(std::ostream& s, const LLNotification& notification); - -namespace LLNotificationFilters -{ - // a sample filter - bool includeEverything(LLNotificationPtr p); - - typedef enum e_comparison - { - EQUAL, - LESS, - GREATER, - LESS_EQUAL, - GREATER_EQUAL - } EComparison; - - // generic filter functor that takes method or member variable reference - template - struct filterBy - { - typedef boost::function field_t; - typedef typename boost::remove_reference::type value_t; - - filterBy(field_t field, value_t value, EComparison comparison = EQUAL) - : mField(field), - mFilterValue(value), - mComparison(comparison) - { - } - - bool operator()(LLNotificationPtr p) - { - switch(mComparison) - { - case EQUAL: - return mField(p) == mFilterValue; - case LESS: - return mField(p) < mFilterValue; - case GREATER: - return mField(p) > mFilterValue; - case LESS_EQUAL: - return mField(p) <= mFilterValue; - case GREATER_EQUAL: - return mField(p) >= mFilterValue; - default: - return false; - } - } - - field_t mField; - value_t mFilterValue; - EComparison mComparison; - }; -}; - -namespace LLNotificationComparators -{ - typedef enum e_direction { ORDER_DECREASING, ORDER_INCREASING } EDirection; - - // generic order functor that takes method or member variable reference - template - struct orderBy - { - typedef boost::function field_t; - orderBy(field_t field, EDirection = ORDER_INCREASING) : mField(field) {} - bool operator()(LLNotificationPtr lhs, LLNotificationPtr rhs) - { - if (mDirection == ORDER_DECREASING) - { - return mField(lhs) > mField(rhs); - } - else - { - return mField(lhs) < mField(rhs); - } - } - - field_t mField; - EDirection mDirection; - }; - - struct orderByUUID : public orderBy - { - orderByUUID(EDirection direction = ORDER_INCREASING) : orderBy(&LLNotification::id, direction) {} - }; - - struct orderByDate : public orderBy - { - orderByDate(EDirection direction = ORDER_INCREASING) : orderBy(&LLNotification::getDate, direction) {} - }; -}; - -typedef boost::function LLNotificationFilter; -typedef boost::function LLNotificationComparator; -typedef std::set LLNotificationSet; -typedef std::multimap LLNotificationMap; - -// ======================================================== -// Abstract base class (interface) for a channel; also used for the master container. -// This lets us arrange channels into a call hierarchy. - -// We maintain a heirarchy of notification channels; events are always started at the top -// and propagated through the hierarchy only if they pass a filter. -// Any channel can be created with a parent. A null parent (empty string) means it's -// tied to the root of the tree (the LLNotifications class itself). -// The default hierarchy looks like this: -// -// LLNotifications --+-- Expiration --+-- Mute --+-- Ignore --+-- Visible --+-- History -// +-- Alerts -// +-- Notifications -// -// In general, new channels that want to only see notifications that pass through -// all of the built-in tests should attach to the "Visible" channel -// -class LLNotificationChannelBase : - public LLEventTrackable -{ - LOG_CLASS(LLNotificationChannelBase); -public: - LLNotificationChannelBase(LLNotificationFilter filter, LLNotificationComparator comp) : - mFilter(filter), mItems(comp) - {} - virtual ~LLNotificationChannelBase() {} - // you can also connect to a Channel, so you can be notified of - // changes to this channel - template - LLBoundListener connectChanged(const LISTENER& slot) - { - // Examine slot to see if it binds an LLEventTrackable subclass, or a - // boost::shared_ptr to something, or a boost::weak_ptr to something. - // Call this->connectChangedImpl() to actually connect it. - return LLEventDetail::visit_and_connect(slot, - boost::bind(&LLNotificationChannelBase::connectChangedImpl, - this, - _1)); - } - template - LLBoundListener connectPassedFilter(const LISTENER& slot) - { - // see comments in connectChanged() - return LLEventDetail::visit_and_connect(slot, - boost::bind(&LLNotificationChannelBase::connectPassedFilterImpl, - this, - _1)); - } - template - LLBoundListener connectFailedFilter(const LISTENER& slot) - { - // see comments in connectChanged() - return LLEventDetail::visit_and_connect(slot, - boost::bind(&LLNotificationChannelBase::connectFailedFilterImpl, - this, - _1)); - } - - // use this when items change or to add a new one - bool updateItem(const LLSD& payload); - const LLNotificationFilter& getFilter() { return mFilter; } - -protected: - LLBoundListener connectChangedImpl(const LLEventListener& slot); - LLBoundListener connectPassedFilterImpl(const LLEventListener& slot); - LLBoundListener connectFailedFilterImpl(const LLEventListener& slot); - - LLNotificationSet mItems; - LLStandardSignal mChanged; - LLStandardSignal mPassedFilter; - LLStandardSignal mFailedFilter; - - // these are action methods that subclasses can override to take action - // on specific types of changes; the management of the mItems list is - // still handled by the generic handler. - virtual void onLoad(LLNotificationPtr p) {} - virtual void onAdd(LLNotificationPtr p) {} - virtual void onDelete(LLNotificationPtr p) {} - virtual void onChange(LLNotificationPtr p) {} - - bool updateItem(const LLSD& payload, LLNotificationPtr pNotification); - LLNotificationFilter mFilter; -}; - -// The type of the pointers that we're going to manage in the NotificationQueue system -// Because LLNotifications is a singleton, we don't actually expect to ever -// destroy it, but if it becomes necessary to do so, the shared_ptr model -// will ensure that we don't leak resources. -class LLNotificationChannel; -typedef boost::shared_ptr LLNotificationChannelPtr; - -// manages a list of notifications -// Note that if this is ever copied around, we might find ourselves with multiple copies -// of a queue with notifications being added to different nonequivalent copies. So we -// make it inherit from boost::noncopyable, and then create a map of shared_ptr to manage it. -// -// NOTE: LLNotificationChannel is self-registering. The *correct* way to create one is to -// do something like: -// LLNotificationChannel::buildChannel("name", "parent"...); -// This returns an LLNotificationChannelPtr, which you can store, or -// you can then retrieve the channel by using the registry: -// LLNotifications::instance().getChannel("name")... -// -class LLNotificationChannel : - boost::noncopyable, - public LLNotificationChannelBase -{ - LOG_CLASS(LLNotificationChannel); - -public: - virtual ~LLNotificationChannel() {} - typedef LLNotificationSet::iterator Iterator; - - std::string getName() const { return mName; } - std::string getParentChannelName() { return mParent; } - - bool isEmpty() const; - - Iterator begin(); - Iterator end(); - - // Channels have a comparator to control sort order; - // the default sorts by arrival date - void setComparator(LLNotificationComparator comparator); - - std::string summarize(); - - // factory method for constructing these channels; since they're self-registering, - // we want to make sure that you can't use new to make them - static LLNotificationChannelPtr buildChannel(const std::string& name, const std::string& parent, - LLNotificationFilter filter=LLNotificationFilters::includeEverything, - LLNotificationComparator comparator=LLNotificationComparators::orderByUUID()); - -protected: - // Notification Channels have a filter, which determines which notifications - // will be added to this channel. - // Channel filters cannot change. - // Channels have a protected constructor so you can't make smart pointers that don't - // come from our internal reference; call NotificationChannel::build(args) - LLNotificationChannel(const std::string& name, const std::string& parent, - LLNotificationFilter filter, LLNotificationComparator comparator); - -private: - std::string mName; - std::string mParent; - LLNotificationComparator mComparator; -}; - - - -class LLNotifications : - public LLSingleton, - public LLNotificationChannelBase -{ - LOG_CLASS(LLNotifications); - - friend class LLSingleton; -public: - // load notification descriptions from file; - // OK to call more than once because it will reload - bool loadTemplates(); - LLXMLNodePtr checkForXMLTemplate(LLXMLNodePtr item); - - // Add a simple notification (from XUI) - void addFromCallback(const LLSD& name); - - // we provide a collection of simple add notification functions so that it's reasonable to create notifications in one line - LLNotificationPtr add(const std::string& name, - const LLSD& substitutions = LLSD(), - const LLSD& payload = LLSD()); - LLNotificationPtr add(const std::string& name, - const LLSD& substitutions, - const LLSD& payload, - const std::string& functor_name); - LLNotificationPtr add(const std::string& name, - const LLSD& substitutions, - const LLSD& payload, - LLNotificationFunctorRegistry::ResponseFunctor functor); - LLNotificationPtr add(const LLNotification::Params& p); - - void add(const LLNotificationPtr pNotif); - void cancel(LLNotificationPtr pNotif); - void update(const LLNotificationPtr pNotif); - - LLNotificationPtr find(LLUUID uuid); - - typedef boost::function NotificationProcess; - - void forEachNotification(NotificationProcess process); - - // This is all stuff for managing the templates - // take your template out - LLNotificationTemplatePtr getTemplate(const std::string& name); - - // get the whole collection - typedef std::vector TemplateNames; - TemplateNames getTemplateNames() const; // returns a list of notification names - - typedef std::map TemplateMap; - - TemplateMap::const_iterator templatesBegin() { return mTemplates.begin(); } - TemplateMap::const_iterator templatesEnd() { return mTemplates.end(); } - - // test for existence - bool templateExists(const std::string& name); - // useful if you're reloading the file - void clearTemplates(); // erase all templates - - void forceResponse(const LLNotification::Params& params, S32 option); - - void createDefaultChannels(); - - typedef std::map ChannelMap; - ChannelMap mChannels; - - void addChannel(LLNotificationChannelPtr pChan); - LLNotificationChannelPtr getChannel(const std::string& channelName); - - std::string getGlobalString(const std::string& key) const; - - void setIgnoreAllNotifications(bool ignore); - bool getIgnoreAllNotifications(); - -private: - // we're a singleton, so we don't have a public constructor - LLNotifications(); - /*virtual*/ void initSingleton(); - - void loadPersistentNotifications(); - - bool expirationFilter(LLNotificationPtr pNotification); - bool expirationHandler(const LLSD& payload); - bool uniqueFilter(LLNotificationPtr pNotification); - bool uniqueHandler(const LLSD& payload); - bool failedUniquenessTest(const LLSD& payload); - LLNotificationChannelPtr pHistoryChannel; - LLNotificationChannelPtr pExpirationChannel; - - // put your template in - bool addTemplate(const std::string& name, LLNotificationTemplatePtr theTemplate); - TemplateMap mTemplates; - - std::string mFileName; - - typedef std::map XMLTemplateMap; - XMLTemplateMap mXmlTemplates; - - LLNotificationMap mUniqueNotifications; - - typedef std::map GlobalStringMap; - GlobalStringMap mGlobalStrings; - - bool mIgnoreAllNotifications; -}; - - -#endif//LL_LLNOTIFICATIONS_H - +/** +* @file llnotifications.h +* @brief Non-UI manager and support for keeping a prioritized list of notifications +* @author Q (with assistance from Richard and Coco) +* +* $LicenseInfo:firstyear=2008&license=viewergpl$ +* +* Copyright (c) 2008-2009, Linden Research, Inc. +* +* Second Life Viewer Source Code +* The source code in this file ("Source Code") is provided by Linden Lab +* to you under the terms of the GNU General Public License, version 2.0 +* ("GPL"), unless you have obtained a separate licensing agreement +* ("Other License"), formally executed by you and Linden Lab. Terms of +* the GPL can be found in doc/GPL-license.txt in this distribution, or +* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 +* +* There are special exceptions to the terms and conditions of the GPL as +* it is applied to this Source Code. View the full text of the exception +* in the file doc/FLOSS-exception.txt in this software distribution, or +* online at +* http://secondlifegrid.net/programs/open_source/licensing/flossexception +* +* By copying, modifying or distributing this software, you acknowledge +* that you have read and understood your obligations described above, +* and agree to abide by those obligations. +* +* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO +* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, +* COMPLETENESS OR PERFORMANCE. +* $/LicenseInfo$ +*/ + +#ifndef LL_LLNOTIFICATIONS_H +#define LL_LLNOTIFICATIONS_H + +/** + * This system is intended to provide a singleton mechanism for adding + * notifications to one of an arbitrary set of event channels. + * + * Controlling JIRA: DEV-9061 + * + * Every notification has (see code for full list): + * - a textual name, which is used to look up its template in the XML files + * - a payload, which is a block of LLSD + * - a channel, which is normally extracted from the XML files but + * can be overridden. + * - a timestamp, used to order the notifications + * - expiration time -- if nonzero, specifies a time after which the + * notification will no longer be valid. + * - a callback name and a couple of status bits related to callbacks (see below) + * + * There is a management class called LLNotifications, which is an LLSingleton. + * The class maintains a collection of all of the notifications received + * or processed during this session, and also manages the persistence + * of those notifications that must be persisted. + * + * We also have Channels. A channel is a view on a collection of notifications; + * The collection is defined by a filter function that controls which + * notifications are in the channel, and its ordering is controlled by + * a comparator. + * + * There is a hierarchy of channels; notifications flow down from + * the management class (LLNotifications, which itself inherits from + * The channel base class) to the individual channels. + * Any change to notifications (add, delete, modify) is + * automatically propagated through the channel hierarchy. + * + * We provide methods for adding a new notification, for removing + * one, and for managing channels. Channels are relatively cheap to construct + * and maintain, so in general, human interfaces should use channels to + * select and manage their lists of notifications. + * + * We also maintain a collection of templates that are loaded from the + * XML file of template translations. The system supports substitution + * of named variables from the payload into the XML file. + * + * By default, only the "unknown message" template is built into the system. + * It is not an error to add a notification that's not found in the + * template system, but it is logged. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +// we want to minimize external dependencies, but this one is important +#include "llsd.h" + +// and we need this to manage the notification callbacks +#include "llevents.h" +#include "llfunctorregistry.h" +#include "llui.h" +#include "llmemory.h" + +class LLNotification; +typedef boost::shared_ptr LLNotificationPtr; + + +typedef enum e_notification_priority +{ + NOTIFICATION_PRIORITY_UNSPECIFIED, + NOTIFICATION_PRIORITY_LOW, + NOTIFICATION_PRIORITY_NORMAL, + NOTIFICATION_PRIORITY_HIGH, + NOTIFICATION_PRIORITY_CRITICAL +} ENotificationPriority; + +typedef boost::function LLNotificationResponder; + +typedef LLFunctorRegistry LLNotificationFunctorRegistry; +typedef LLFunctorRegistration LLNotificationFunctorRegistration; + +// context data that can be looked up via a notification's payload by the display logic +// derive from this class to implement specific contexts +class LLNotificationContext : public LLInstanceTracker +{ +public: + LLNotificationContext() : LLInstanceTracker(LLUUID::generateNewID()) + { + } + + virtual ~LLNotificationContext() {} + + LLSD asLLSD() const + { + return getKey(); + } + +private: + +}; + +// Contains notification form data, such as buttons and text fields along with +// manipulator functions +class LLNotificationForm +{ + LOG_CLASS(LLNotificationForm); + +public: + typedef enum e_ignore_type + { + IGNORE_NO, + IGNORE_WITH_DEFAULT_RESPONSE, + IGNORE_WITH_LAST_RESPONSE, + IGNORE_SHOW_AGAIN + } EIgnoreType; + + LLNotificationForm(); + LLNotificationForm(const LLSD& sd); + LLNotificationForm(const std::string& name, const LLXMLNodePtr xml_node); + + LLSD asLLSD() const; + + S32 getNumElements() { return mFormData.size(); } + LLSD getElement(S32 index) { return mFormData.get(index); } + LLSD getElement(const std::string& element_name); + bool hasElement(const std::string& element_name); + void addElement(const std::string& type, const std::string& name, const LLSD& value = LLSD()); + void formatElements(const LLSD& substitutions); + // appends form elements from another form serialized as LLSD + void append(const LLSD& sub_form); + std::string getDefaultOption(); + + EIgnoreType getIgnoreType() { return mIgnore; } + std::string getIgnoreMessage() { return mIgnoreMsg; } + +private: + LLSD mFormData; + EIgnoreType mIgnore; + std::string mIgnoreMsg; +}; + +typedef boost::shared_ptr LLNotificationFormPtr; + +// This is the class of object read from the XML file (notifications.xml, +// from the appropriate local language directory). +struct LLNotificationTemplate +{ + LLNotificationTemplate(); + // the name of the notification -- the key used to identify it + // Ideally, the key should follow variable naming rules + // (no spaces or punctuation). + std::string mName; + // The type of the notification + // used to control which queue it's stored in + std::string mType; + // The text used to display the notification. Replaceable parameters + // are enclosed in square brackets like this []. + std::string mMessage; + // The label for the notification; used for + // certain classes of notification (those with a window and a window title). + // Also used when a notification pops up underneath the current one. + // Replaceable parameters can be used in the label. + std::string mLabel; + // The name of the icon image. This should include an extension. + std::string mIcon; + // This is the Highlander bit -- "There Can Be Only One" + // An outstanding notification with this bit set + // is updated by an incoming notification with the same name, + // rather than creating a new entry in the queue. + // (used for things like progress indications, or repeating warnings + // like "the grid is going down in N minutes") + bool mUnique; + // if we want to be unique only if a certain part of the payload is constant + // specify the field names for the payload. The notification will only be + // combined if all of the fields named in the context are identical in the + // new and the old notification; otherwise, the notification will be + // duplicated. This is to support suppressing duplicate offers from the same + // sender but still differentiating different offers. Example: Invitation to + // conference chat. + std::vector mUniqueContext; + // If this notification expires automatically, this value will be + // nonzero, and indicates the number of seconds for which the notification + // will be valid (a teleport offer, for example, might be valid for + // 300 seconds). + U32 mExpireSeconds; + // if the offer expires, one of the options is chosen automatically + // based on its "value" parameter. This controls which one. + // If expireSeconds is specified, expireOption should also be specified. + U32 mExpireOption; + // if the notification contains a url, it's stored here (and replaced + // into the message where [_URL] is found) + std::string mURL; + // if there's a URL in the message, this controls which option visits + // that URL. Obsolete this and eliminate the buttons for affected + // messages when we allow clickable URLs in the UI + U32 mURLOption; + + U32 mURLOpenExternally; + //This is a flag that tells if the url needs to open externally dispite + //what the user setting is. + + // does this notification persist across sessions? if so, it will be + // serialized to disk on first receipt and read on startup + bool mPersist; + // This is the name of the default functor, if present, to be + // used for the notification's callback. It is optional, and used only if + // the notification is constructed without an identified functor. + std::string mDefaultFunctor; + // The form data associated with a given notification (buttons, text boxes, etc) + LLNotificationFormPtr mForm; + // default priority for notifications of this type + ENotificationPriority mPriority; + // UUID of the audio file to be played when this notification arrives + // this is loaded as a name, but looked up to get the UUID upon template load. + // If null, it wasn't specified. + LLUUID mSoundEffect; +}; + +// we want to keep a map of these by name, and it's best to manage them +// with smart pointers +typedef boost::shared_ptr LLNotificationTemplatePtr; + +/** + * @class LLNotification + * @brief The object that expresses the details of a notification + * + * We make this noncopyable because + * we want to manage these through LLNotificationPtr, and only + * ever create one instance of any given notification. + * + * The enable_shared_from_this flag ensures that if we construct + * a smart pointer from a notification, we'll always get the same + * shared pointer. + */ +class LLNotification : + boost::noncopyable, + public boost::enable_shared_from_this +{ +LOG_CLASS(LLNotification); +friend class LLNotifications; + +public: + // parameter object used to instantiate a new notification + struct Params : public LLInitParam::Block + { + friend class LLNotification; + + Mandatory name; + + // optional + Optional substitutions; + Optional payload; + Optional priority; + Optional form_elements; + Optional timestamp; + Optional context; + + struct Functor : public LLInitParam::Choice + { + Alternative name; + Alternative function; + + Functor() + : name("functor_name"), + function("functor") + {} + }; + Optional functor; + + Params() + : name("name"), + priority("priority", NOTIFICATION_PRIORITY_UNSPECIFIED), + timestamp("time_stamp") + { + timestamp = LLDate::now(); + } + + Params(const std::string& _name) + : name("name"), + priority("priority", NOTIFICATION_PRIORITY_UNSPECIFIED), + timestamp("time_stamp") + { + functor.name = _name; + name = _name; + timestamp = LLDate::now(); + } + }; + +private: + + LLUUID mId; + LLSD mPayload; + LLSD mSubstitutions; + LLDate mTimestamp; + LLDate mExpiresAt; + bool mCancelled; + bool mRespondedTo; // once the notification has been responded to, this becomes true + bool mIgnored; + ENotificationPriority mPriority; + LLNotificationFormPtr mForm; + + // a reference to the template + LLNotificationTemplatePtr mTemplatep; + + /* + We want to be able to store and reload notifications so that they can survive + a shutdown/restart of the client. So we can't simply pass in callbacks; + we have to specify a callback mechanism that can be used by name rather than + by some arbitrary pointer -- and then people have to initialize callbacks + in some useful location. So we use LLNotificationFunctorRegistry to manage them. + */ + std::string mResponseFunctorName; + + /* + In cases where we want to specify an explict, non-persisted callback, + we store that in the callback registry under a dynamically generated + key, and store the key in the notification, so we can still look it up + using the same mechanism. + */ + bool mTemporaryResponder; + + void init(const std::string& template_name, const LLSD& form_elements); + + LLNotification(const Params& p); + + // this is just for making it easy to look things up in a set organized by UUID -- DON'T USE IT + // for anything real! + LLNotification(LLUUID uuid) : mId(uuid) {} + + void cancel(); + + bool payloadContainsAll(const std::vector& required_fields) const; + +public: + + // constructor from a saved notification + LLNotification(const LLSD& sd); + + void setResponseFunctor(std::string const &responseFunctorName); + + typedef enum e_response_template_type + { + WITHOUT_DEFAULT_BUTTON, + WITH_DEFAULT_BUTTON + } EResponseTemplateType; + + // return response LLSD filled in with default form contents and (optionally) the default button selected + LLSD getResponseTemplate(EResponseTemplateType type = WITHOUT_DEFAULT_BUTTON); + + // returns index of first button with value==TRUE + // usually this the button the user clicked on + // returns -1 if no button clicked (e.g. form has not been displayed) + static S32 getSelectedOption(const LLSD& notification, const LLSD& response); + // returns name of first button with value==TRUE + static std::string getSelectedOptionName(const LLSD& notification); + + // after someone responds to a notification (usually by clicking a button, + // but sometimes by filling out a little form and THEN clicking a button), + // the result of the response (the name and value of the button clicked, + // plus any other data) should be packaged up as LLSD, then passed as a + // parameter to the notification's respond() method here. This will look up + // and call the appropriate responder. + // + // response is notification serialized as LLSD: + // ["name"] = notification name + // ["form"] = LLSD tree that includes form description and any prefilled form data + // ["response"] = form data filled in by user + // (including, but not limited to which button they clicked on) + // ["payload"] = transaction specific data, such as ["source_id"] (originator of notification), + // ["item_id"] (attached inventory item), etc. + // ["substitutions"] = string substitutions used to generate notification message + // from the template + // ["time"] = time at which notification was generated; + // ["expiry"] = time at which notification expires; + // ["responseFunctor"] = name of registered functor that handles responses to notification; + LLSD asLLSD(); + + void respond(const LLSD& sd); + + void setIgnored(bool ignore); + + bool isCancelled() const + { + return mCancelled; + } + + bool isRespondedTo() const + { + return mRespondedTo; + } + + bool isIgnored() const + { + return mIgnored; + } + + const std::string& getName() const + { + return mTemplatep->mName; + } + + const LLUUID& id() const + { + return mId; + } + + const LLSD& getPayload() const + { + return mPayload; + } + + const LLSD& getSubstitutions() const + { + return mSubstitutions; + } + + const LLDate& getDate() const + { + return mTimestamp; + } + + std::string getType() const + { + return (mTemplatep ? mTemplatep->mType : ""); + } + + std::string getMessage() const; + std::string getLabel() const; + + std::string getURL() const; +// { +// return (mTemplatep ? mTemplatep->mURL : ""); +// } + + S32 getURLOption() const + { + return (mTemplatep ? mTemplatep->mURLOption : -1); + } + + S32 getURLOpenExternally() const + { + return(mTemplatep? mTemplatep->mURLOpenExternally : -1); + } + + const LLNotificationFormPtr getForm(); + + const LLDate getExpiration() const + { + return mExpiresAt; + } + + ENotificationPriority getPriority() const + { + return mPriority; + } + + const LLUUID getID() const + { + return mId; + } + + // comparing two notifications normally means comparing them by UUID (so we can look them + // up quickly this way) + bool operator<(const LLNotification& rhs) const + { + return mId < rhs.mId; + } + + bool operator==(const LLNotification& rhs) const + { + return mId == rhs.mId; + } + + bool operator!=(const LLNotification& rhs) const + { + return !operator==(rhs); + } + + bool isSameObjectAs(const LLNotification* rhs) const + { + return this == rhs; + } + + // this object has been updated, so tell all our clients + void update(); + + void updateFrom(LLNotificationPtr other); + + // A fuzzy equals comparator. + // true only if both notifications have the same template and + // 1) flagged as unique (there can be only one of these) OR + // 2) all required payload fields of each also exist in the other. + bool isEquivalentTo(LLNotificationPtr that) const; + + // if the current time is greater than the expiration, the notification is expired + bool isExpired() const + { + if (mExpiresAt.secondsSinceEpoch() == 0) + { + return false; + } + + LLDate rightnow = LLDate::now(); + return rightnow > mExpiresAt; + } + + std::string summarize() const; + + bool hasUniquenessConstraints() const { return (mTemplatep ? mTemplatep->mUnique : false);} + + virtual ~LLNotification() {} +}; + +std::ostream& operator<<(std::ostream& s, const LLNotification& notification); + +namespace LLNotificationFilters +{ + // a sample filter + bool includeEverything(LLNotificationPtr p); + + typedef enum e_comparison + { + EQUAL, + LESS, + GREATER, + LESS_EQUAL, + GREATER_EQUAL + } EComparison; + + // generic filter functor that takes method or member variable reference + template + struct filterBy + { + typedef boost::function field_t; + typedef typename boost::remove_reference::type value_t; + + filterBy(field_t field, value_t value, EComparison comparison = EQUAL) + : mField(field), + mFilterValue(value), + mComparison(comparison) + { + } + + bool operator()(LLNotificationPtr p) + { + switch(mComparison) + { + case EQUAL: + return mField(p) == mFilterValue; + case LESS: + return mField(p) < mFilterValue; + case GREATER: + return mField(p) > mFilterValue; + case LESS_EQUAL: + return mField(p) <= mFilterValue; + case GREATER_EQUAL: + return mField(p) >= mFilterValue; + default: + return false; + } + } + + field_t mField; + value_t mFilterValue; + EComparison mComparison; + }; +}; + +namespace LLNotificationComparators +{ + typedef enum e_direction { ORDER_DECREASING, ORDER_INCREASING } EDirection; + + // generic order functor that takes method or member variable reference + template + struct orderBy + { + typedef boost::function field_t; + orderBy(field_t field, EDirection = ORDER_INCREASING) : mField(field) {} + bool operator()(LLNotificationPtr lhs, LLNotificationPtr rhs) + { + if (mDirection == ORDER_DECREASING) + { + return mField(lhs) > mField(rhs); + } + else + { + return mField(lhs) < mField(rhs); + } + } + + field_t mField; + EDirection mDirection; + }; + + struct orderByUUID : public orderBy + { + orderByUUID(EDirection direction = ORDER_INCREASING) : orderBy(&LLNotification::id, direction) {} + }; + + struct orderByDate : public orderBy + { + orderByDate(EDirection direction = ORDER_INCREASING) : orderBy(&LLNotification::getDate, direction) {} + }; +}; + +typedef boost::function LLNotificationFilter; +typedef boost::function LLNotificationComparator; +typedef std::set LLNotificationSet; +typedef std::multimap LLNotificationMap; + +// ======================================================== +// Abstract base class (interface) for a channel; also used for the master container. +// This lets us arrange channels into a call hierarchy. + +// We maintain a heirarchy of notification channels; events are always started at the top +// and propagated through the hierarchy only if they pass a filter. +// Any channel can be created with a parent. A null parent (empty string) means it's +// tied to the root of the tree (the LLNotifications class itself). +// The default hierarchy looks like this: +// +// LLNotifications --+-- Expiration --+-- Mute --+-- Ignore --+-- Visible --+-- History +// +-- Alerts +// +-- Notifications +// +// In general, new channels that want to only see notifications that pass through +// all of the built-in tests should attach to the "Visible" channel +// +class LLNotificationChannelBase : + public LLEventTrackable +{ + LOG_CLASS(LLNotificationChannelBase); +public: + LLNotificationChannelBase(LLNotificationFilter filter, LLNotificationComparator comp) : + mFilter(filter), mItems(comp) + {} + virtual ~LLNotificationChannelBase() {} + // you can also connect to a Channel, so you can be notified of + // changes to this channel + template + LLBoundListener connectChanged(const LISTENER& slot) + { + // Examine slot to see if it binds an LLEventTrackable subclass, or a + // boost::shared_ptr to something, or a boost::weak_ptr to something. + // Call this->connectChangedImpl() to actually connect it. + return LLEventDetail::visit_and_connect(slot, + boost::bind(&LLNotificationChannelBase::connectChangedImpl, + this, + _1)); + } + template + LLBoundListener connectPassedFilter(const LISTENER& slot) + { + // see comments in connectChanged() + return LLEventDetail::visit_and_connect(slot, + boost::bind(&LLNotificationChannelBase::connectPassedFilterImpl, + this, + _1)); + } + template + LLBoundListener connectFailedFilter(const LISTENER& slot) + { + // see comments in connectChanged() + return LLEventDetail::visit_and_connect(slot, + boost::bind(&LLNotificationChannelBase::connectFailedFilterImpl, + this, + _1)); + } + + // use this when items change or to add a new one + bool updateItem(const LLSD& payload); + const LLNotificationFilter& getFilter() { return mFilter; } + +protected: + LLBoundListener connectChangedImpl(const LLEventListener& slot); + LLBoundListener connectPassedFilterImpl(const LLEventListener& slot); + LLBoundListener connectFailedFilterImpl(const LLEventListener& slot); + + LLNotificationSet mItems; + LLStandardSignal mChanged; + LLStandardSignal mPassedFilter; + LLStandardSignal mFailedFilter; + + // these are action methods that subclasses can override to take action + // on specific types of changes; the management of the mItems list is + // still handled by the generic handler. + virtual void onLoad(LLNotificationPtr p) {} + virtual void onAdd(LLNotificationPtr p) {} + virtual void onDelete(LLNotificationPtr p) {} + virtual void onChange(LLNotificationPtr p) {} + + bool updateItem(const LLSD& payload, LLNotificationPtr pNotification); + LLNotificationFilter mFilter; +}; + +// The type of the pointers that we're going to manage in the NotificationQueue system +// Because LLNotifications is a singleton, we don't actually expect to ever +// destroy it, but if it becomes necessary to do so, the shared_ptr model +// will ensure that we don't leak resources. +class LLNotificationChannel; +typedef boost::shared_ptr LLNotificationChannelPtr; + +// manages a list of notifications +// Note that if this is ever copied around, we might find ourselves with multiple copies +// of a queue with notifications being added to different nonequivalent copies. So we +// make it inherit from boost::noncopyable, and then create a map of shared_ptr to manage it. +// +// NOTE: LLNotificationChannel is self-registering. The *correct* way to create one is to +// do something like: +// LLNotificationChannel::buildChannel("name", "parent"...); +// This returns an LLNotificationChannelPtr, which you can store, or +// you can then retrieve the channel by using the registry: +// LLNotifications::instance().getChannel("name")... +// +class LLNotificationChannel : + boost::noncopyable, + public LLNotificationChannelBase +{ + LOG_CLASS(LLNotificationChannel); + +public: + virtual ~LLNotificationChannel() {} + typedef LLNotificationSet::iterator Iterator; + + std::string getName() const { return mName; } + std::string getParentChannelName() { return mParent; } + + bool isEmpty() const; + + Iterator begin(); + Iterator end(); + + // Channels have a comparator to control sort order; + // the default sorts by arrival date + void setComparator(LLNotificationComparator comparator); + + std::string summarize(); + + // factory method for constructing these channels; since they're self-registering, + // we want to make sure that you can't use new to make them + static LLNotificationChannelPtr buildChannel(const std::string& name, const std::string& parent, + LLNotificationFilter filter=LLNotificationFilters::includeEverything, + LLNotificationComparator comparator=LLNotificationComparators::orderByUUID()); + +protected: + // Notification Channels have a filter, which determines which notifications + // will be added to this channel. + // Channel filters cannot change. + // Channels have a protected constructor so you can't make smart pointers that don't + // come from our internal reference; call NotificationChannel::build(args) + LLNotificationChannel(const std::string& name, const std::string& parent, + LLNotificationFilter filter, LLNotificationComparator comparator); + +private: + std::string mName; + std::string mParent; + LLNotificationComparator mComparator; +}; + + +class LLNotificationsListener; + +class LLNotifications : + public LLSingleton, + public LLNotificationChannelBase +{ + LOG_CLASS(LLNotifications); + + friend class LLSingleton; +public: + // load notification descriptions from file; + // OK to call more than once because it will reload + bool loadTemplates(); + LLXMLNodePtr checkForXMLTemplate(LLXMLNodePtr item); + + // Add a simple notification (from XUI) + void addFromCallback(const LLSD& name); + + // we provide a collection of simple add notification functions so that it's reasonable to create notifications in one line + LLNotificationPtr add(const std::string& name, + const LLSD& substitutions = LLSD(), + const LLSD& payload = LLSD()); + LLNotificationPtr add(const std::string& name, + const LLSD& substitutions, + const LLSD& payload, + const std::string& functor_name); + LLNotificationPtr add(const std::string& name, + const LLSD& substitutions, + const LLSD& payload, + LLNotificationFunctorRegistry::ResponseFunctor functor); + LLNotificationPtr add(const LLNotification::Params& p); + + void add(const LLNotificationPtr pNotif); + void cancel(LLNotificationPtr pNotif); + void update(const LLNotificationPtr pNotif); + + LLNotificationPtr find(LLUUID uuid); + + typedef boost::function NotificationProcess; + + void forEachNotification(NotificationProcess process); + + // This is all stuff for managing the templates + // take your template out + LLNotificationTemplatePtr getTemplate(const std::string& name); + + // get the whole collection + typedef std::vector TemplateNames; + TemplateNames getTemplateNames() const; // returns a list of notification names + + typedef std::map TemplateMap; + + TemplateMap::const_iterator templatesBegin() { return mTemplates.begin(); } + TemplateMap::const_iterator templatesEnd() { return mTemplates.end(); } + + // test for existence + bool templateExists(const std::string& name); + // useful if you're reloading the file + void clearTemplates(); // erase all templates + + void forceResponse(const LLNotification::Params& params, S32 option); + + void createDefaultChannels(); + + typedef std::map ChannelMap; + ChannelMap mChannels; + + void addChannel(LLNotificationChannelPtr pChan); + LLNotificationChannelPtr getChannel(const std::string& channelName); + + std::string getGlobalString(const std::string& key) const; + + void setIgnoreAllNotifications(bool ignore); + bool getIgnoreAllNotifications(); + +private: + // we're a singleton, so we don't have a public constructor + LLNotifications(); + /*virtual*/ void initSingleton(); + + void loadPersistentNotifications(); + + bool expirationFilter(LLNotificationPtr pNotification); + bool expirationHandler(const LLSD& payload); + bool uniqueFilter(LLNotificationPtr pNotification); + bool uniqueHandler(const LLSD& payload); + bool failedUniquenessTest(const LLSD& payload); + LLNotificationChannelPtr pHistoryChannel; + LLNotificationChannelPtr pExpirationChannel; + + // put your template in + bool addTemplate(const std::string& name, LLNotificationTemplatePtr theTemplate); + TemplateMap mTemplates; + + std::string mFileName; + + typedef std::map XMLTemplateMap; + XMLTemplateMap mXmlTemplates; + + LLNotificationMap mUniqueNotifications; + + typedef std::map GlobalStringMap; + GlobalStringMap mGlobalStrings; + + bool mIgnoreAllNotifications; + + boost::scoped_ptr mListener; +}; + + +#endif//LL_LLNOTIFICATIONS_H + diff --git a/indra/llui/llnotificationslistener.cpp b/indra/llui/llnotificationslistener.cpp new file mode 100644 index 0000000000..d2d83bd6e3 --- /dev/null +++ b/indra/llui/llnotificationslistener.cpp @@ -0,0 +1,28 @@ +/** + * @file llnotificationslistener.cpp + * @author Brad Kittenbrink + * @date 2009-07-08 + * @brief Implementation for llnotificationslistener. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llnotificationslistener.h" + +#include "llnotifications.h" + +LLNotificationsListener::LLNotificationsListener(LLNotifications & notifications) : + LLDispatchListener("LLNotifications", "op"), + mNotifications(notifications) +{ + add("requestAdd", &LLNotificationsListener::requestAdd); +} + +void LLNotificationsListener::requestAdd(const LLSD& event_data) const +{ + mNotifications.add(event_data["name"], event_data["substitutions"], event_data["payload"]); +} diff --git a/indra/llui/llnotificationslistener.h b/indra/llui/llnotificationslistener.h new file mode 100644 index 0000000000..a163b00550 --- /dev/null +++ b/indra/llui/llnotificationslistener.h @@ -0,0 +1,31 @@ +/** + * @file llnotificationslistener.h + * @author Brad Kittenbrink + * @date 2009-07-08 + * @brief Wrap subset of LLNotifications API in event API for test scripts. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#ifndef LL_LLNOTIFICATIONSLISTENER_H +#define LL_LLNOTIFICATIONSLISTENER_H + +#include "lleventdispatcher.h" + +class LLNotifications; +class LLSD; + +class LLNotificationsListener : public LLDispatchListener +{ +public: + LLNotificationsListener(LLNotifications & notifications); + + void requestAdd(LLSD const & event_data) const; + +private: + LLNotifications & mNotifications; +}; + +#endif // LL_LLNOTIFICATIONSLISTENER_H diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index b14853777d..947f5bdb20 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -1154,6 +1154,8 @@ bool LLAppViewer::mainLoop() bool LLAppViewer::cleanup() { + // *TODO - unload event host module here -brad + //---------------------------------------------- //this test code will be removed after the test //test manual call stack tracer @@ -4132,4 +4134,8 @@ void LLAppViewer::loadEventHostModule(S32 listen_port) const args["listen_port"] = listen_port; ll_plugin_start_func(args); + + args = LLSD(); + args["MESSAGE"] = "EventHost module loaded successfully"; + LLNotifications::instance().add("SystemMessageTip", args); } -- cgit v1.2.3