summaryrefslogtreecommitdiff
path: root/indra/llui
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llui')
-rw-r--r--indra/llui/CMakeLists.txt456
-rw-r--r--indra/llui/llfloater.cpp36
-rw-r--r--indra/llui/llfloater.h4
-rw-r--r--indra/llui/llfloaterreg.cpp3
-rw-r--r--indra/llui/llfloaterreg.h1
-rw-r--r--indra/llui/llfloaterreglistener.cpp66
-rw-r--r--indra/llui/llfloaterreglistener.h35
-rw-r--r--indra/llui/llfunctorregistry.cpp1
-rw-r--r--indra/llui/llmodaldialog.cpp15
-rw-r--r--indra/llui/llmodaldialog.h3
-rw-r--r--indra/llui/llnotifications.cpp3029
-rw-r--r--indra/llui/llnotifications.h17
-rw-r--r--indra/llui/llnotificationslistener.cpp55
-rw-r--r--indra/llui/llnotificationslistener.h34
-rw-r--r--indra/llui/llslider.cpp2
-rw-r--r--indra/llui/llsliderctrl.cpp1
16 files changed, 1989 insertions, 1769 deletions
diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt
index d9169f57f9..4bd5a83e37 100644
--- a/indra/llui/CMakeLists.txt
+++ b/indra/llui/CMakeLists.txt
@@ -1,226 +1,230 @@
-# -*- cmake -*-
-
-project(llui)
-
-include(00-Common)
-include(LLCommon)
-include(LLImage)
-include(LLMath)
-include(LLMessage)
-include(LLRender)
-include(LLWindow)
-include(LLVFS)
-include(LLXML)
-include(LLXUIXML)
-
-include_directories(
- ${LLCOMMON_INCLUDE_DIRS}
- ${LLIMAGE_INCLUDE_DIRS}
- ${LLMATH_INCLUDE_DIRS}
- ${LLMESSAGE_INCLUDE_DIRS}
- ${LLRENDER_INCLUDE_DIRS}
- ${LLWINDOW_INCLUDE_DIRS}
- ${LLVFS_INCLUDE_DIRS}
- ${LLXML_INCLUDE_DIRS}
- ${LLXUIXML_INCLUDE_DIRS}
- )
-
-set(llui_SOURCE_FILES
- llalertdialog.cpp
- llbutton.cpp
- llcheckboxctrl.cpp
- llclipboard.cpp
- llcombobox.cpp
- llconsole.cpp
- llcontainerview.cpp
- llctrlselectioninterface.cpp
- lldockablefloater.cpp
- lldockcontrol.cpp
- lldraghandle.cpp
- lleditmenuhandler.cpp
- llf32uictrl.cpp
- llfiltereditor.cpp
- llflatlistview.cpp
- llfloater.cpp
- llfloaterreg.cpp
- llflyoutbutton.cpp
- llfocusmgr.cpp
- llfunctorregistry.cpp
- lliconctrl.cpp
- llkeywords.cpp
- lllayoutstack.cpp
- lllineeditor.cpp
- lllocalcliprect.cpp
- llmenubutton.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
- llrngwriter.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
- lltextbase.cpp
- lltextbox.cpp
- lltexteditor.cpp
- lltextparser.cpp
- lltransientfloatermgr.cpp
- lltransutil.cpp
- lltoggleablemenu.cpp
- lltooltip.cpp
- llui.cpp
- lluicolortable.cpp
- lluictrl.cpp
- lluictrlfactory.cpp
- lluiimage.cpp
- lluistring.cpp
- llundo.cpp
- llurlaction.cpp
- llurlentry.cpp
- llurlmatch.cpp
- llurlregistry.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
- lldockablefloater.h
- lldockcontrol.h
- lleditmenuhandler.h
- llf32uictrl.h
- llfiltereditor.h
- llflatlistview.h
- llfloater.h
- llfloaterreg.h
- llflyoutbutton.h
- llfocusmgr.h
- llfunctorregistry.h
- llhandle.h
- llhelp.h
- lliconctrl.h
- llkeywords.h
- lllayoutstack.h
- lllazyvalue.h
- lllineeditor.h
- lllocalcliprect.h
- llmenubutton.h
- llmenugl.h
- llmodaldialog.h
- llmultifloater.h
- llmultisliderctrl.h
- llmultislider.h
- llnotifications.h
- llpanel.h
- llprogressbar.h
- llradiogroup.h
- llresizebar.h
- llresizehandle.h
- llresmgr.h
- llrngwriter.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
- lltextbase.h
- lltextbox.h
- lltexteditor.h
- lltextparser.h
- lltoggleablemenu.h
- lltooltip.h
- lltransientfloatermgr.h
- lltransutil.h
- lluicolortable.h
- lluiconstants.h
- lluictrlfactory.h
- lluictrl.h
- lluifwd.h
- llui.h
- lluiimage.h
- lluistring.h
- llundo.h
- llurlaction.h
- llurlentry.h
- llurlmatch.h
- llurlregistry.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
- ${LLMESSAGE_LIBRARIES}
- ${LLRENDER_LIBRARIES}
- ${LLWINDOW_LIBRARIES}
- ${LLIMAGE_LIBRARIES}
- ${LLVFS_LIBRARIES} # ugh, just for LLDir
- ${LLXUIXML_LIBRARIES}
- ${LLXML_LIBRARIES}
- ${LLMATH_LIBRARIES}
- ${LLCOMMON_LIBRARIES} # must be after llimage, llwindow, llrender
- )
-
-# Add tests
-include(LLAddBuildTest)
-SET(llui_TEST_SOURCE_FILES
- llurlmatch.cpp
- llurlentry.cpp
- )
-LL_ADD_PROJECT_UNIT_TESTS(llui "${llui_TEST_SOURCE_FILES}")
+# -*- cmake -*-
+
+project(llui)
+
+include(00-Common)
+include(LLCommon)
+include(LLImage)
+include(LLMath)
+include(LLMessage)
+include(LLRender)
+include(LLWindow)
+include(LLVFS)
+include(LLXML)
+include(LLXUIXML)
+
+include_directories(
+ ${LLCOMMON_INCLUDE_DIRS}
+ ${LLIMAGE_INCLUDE_DIRS}
+ ${LLMATH_INCLUDE_DIRS}
+ ${LLMESSAGE_INCLUDE_DIRS}
+ ${LLRENDER_INCLUDE_DIRS}
+ ${LLWINDOW_INCLUDE_DIRS}
+ ${LLVFS_INCLUDE_DIRS}
+ ${LLXML_INCLUDE_DIRS}
+ ${LLXUIXML_INCLUDE_DIRS}
+ )
+
+set(llui_SOURCE_FILES
+ llalertdialog.cpp
+ llbutton.cpp
+ llcheckboxctrl.cpp
+ llclipboard.cpp
+ llcombobox.cpp
+ llconsole.cpp
+ llcontainerview.cpp
+ llctrlselectioninterface.cpp
+ lldockablefloater.cpp
+ lldockcontrol.cpp
+ lldraghandle.cpp
+ lleditmenuhandler.cpp
+ llf32uictrl.cpp
+ llfiltereditor.cpp
+ llflatlistview.cpp
+ llfloater.cpp
+ llfloaterreg.cpp
+ llfloaterreglistener.cpp
+ llflyoutbutton.cpp
+ llfocusmgr.cpp
+ llfunctorregistry.cpp
+ lliconctrl.cpp
+ llkeywords.cpp
+ lllayoutstack.cpp
+ lllineeditor.cpp
+ lllocalcliprect.cpp
+ llmenubutton.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
+ llrngwriter.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
+ lltextbase.cpp
+ lltextbox.cpp
+ lltexteditor.cpp
+ lltextparser.cpp
+ lltransientfloatermgr.cpp
+ lltransutil.cpp
+ lltoggleablemenu.cpp
+ lltooltip.cpp
+ llui.cpp
+ lluicolortable.cpp
+ lluictrl.cpp
+ lluictrlfactory.cpp
+ lluiimage.cpp
+ lluistring.cpp
+ llundo.cpp
+ llurlaction.cpp
+ llurlentry.cpp
+ llurlmatch.cpp
+ llurlregistry.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
+ lldockablefloater.h
+ lldockcontrol.h
+ lleditmenuhandler.h
+ llf32uictrl.h
+ llfiltereditor.h
+ llflatlistview.h
+ llfloater.h
+ llfloaterreg.h
+ llfloaterreglistener.h
+ llflyoutbutton.h
+ llfocusmgr.h
+ llfunctorregistry.h
+ llhandle.h
+ llhelp.h
+ lliconctrl.h
+ llkeywords.h
+ lllayoutstack.h
+ lllazyvalue.h
+ lllineeditor.h
+ lllocalcliprect.h
+ llmenubutton.h
+ llmenugl.h
+ llmodaldialog.h
+ llmultifloater.h
+ llmultisliderctrl.h
+ llmultislider.h
+ llnotifications.h
+ llnotificationslistener.h
+ llpanel.h
+ llprogressbar.h
+ llradiogroup.h
+ llresizebar.h
+ llresizehandle.h
+ llresmgr.h
+ llrngwriter.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
+ lltextbase.h
+ lltextbox.h
+ lltexteditor.h
+ lltextparser.h
+ lltoggleablemenu.h
+ lltooltip.h
+ lltransientfloatermgr.h
+ lltransutil.h
+ lluicolortable.h
+ lluiconstants.h
+ lluictrlfactory.h
+ lluictrl.h
+ lluifwd.h
+ llui.h
+ lluiimage.h
+ lluistring.h
+ llundo.h
+ llurlaction.h
+ llurlentry.h
+ llurlmatch.h
+ llurlregistry.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
+ ${LLMESSAGE_LIBRARIES}
+ ${LLRENDER_LIBRARIES}
+ ${LLWINDOW_LIBRARIES}
+ ${LLIMAGE_LIBRARIES}
+ ${LLVFS_LIBRARIES} # ugh, just for LLDir
+ ${LLXUIXML_LIBRARIES}
+ ${LLXML_LIBRARIES}
+ ${LLMATH_LIBRARIES}
+ ${LLCOMMON_LIBRARIES} # must be after llimage, llwindow, llrender
+ )
+
+# Add tests
+include(LLAddBuildTest)
+SET(llui_TEST_SOURCE_FILES
+ llurlmatch.cpp
+ llurlentry.cpp
+ )
+LL_ADD_PROJECT_UNIT_TESTS(llui "${llui_TEST_SOURCE_FILES}")
diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp
index b7a15a2b33..66defbbf0a 100644
--- a/indra/llui/llfloater.cpp
+++ b/indra/llui/llfloater.cpp
@@ -61,6 +61,7 @@
#include "lltrans.h"
#include "llhelp.h"
#include "llmultifloater.h"
+#include "llsdutil.h"
// use this to control "jumping" behavior when Ctrl-Tabbing
const S32 TABBED_FLOATER_OFFSET = 0;
@@ -132,6 +133,16 @@ LLFloater::handle_map_t LLFloater::sFloaterMap;
LLFloaterView* gFloaterView = NULL;
+/*==========================================================================*|
+// DEV-38598: The fundamental problem with this operation is that it can only
+// support a subset of LLSD values. While it's plausible to compare two arrays
+// lexicographically, what strict ordering can you impose on maps?
+// (LLFloaterTOS's current key is an LLSD map.)
+
+// Of course something like this is necessary if you want to build a std::set
+// or std::map with LLSD keys. Fortunately we're getting by with other
+// container types for now.
+
//static
bool LLFloater::KeyCompare::compare(const LLSD& a, const LLSD& b)
{
@@ -159,32 +170,11 @@ bool LLFloater::KeyCompare::compare(const LLSD& a, const LLSD& b)
else
return false; // no valid operation for Binary
}
+|*==========================================================================*/
bool LLFloater::KeyCompare::equate(const LLSD& a, const LLSD& b)
{
- if (a.type() != b.type())
- {
- //llerrs << "Mismatched LLSD types: (" << a << ") mismatches (" << b << ")" << llendl;
- return false;
- }
- else if (a.isUndefined())
- return true;
- else if (a.isInteger())
- return a.asInteger() == b.asInteger();
- else if (a.isReal())
- return a.asReal() == b.asReal();
- else if (a.isString())
- return a.asString() == b.asString();
- else if (a.isUUID())
- return a.asUUID() == b.asUUID();
- else if (a.isDate())
- return a.asDate() == b.asDate();
- else if (a.isURI())
- return a.asString() == b.asString(); // compare URIs as strings
- else if (a.isBoolean())
- return a.asBoolean() == b.asBoolean();
- else
- return false; // no valid operation for Binary
+ return llsd_equals(a, b);
}
//************************************
diff --git a/indra/llui/llfloater.h b/indra/llui/llfloater.h
index 17ffc94014..466d060068 100644
--- a/indra/llui/llfloater.h
+++ b/indra/llui/llfloater.h
@@ -87,12 +87,14 @@ friend class LLMultiFloater;
public:
struct KeyCompare
{
- static bool compare(const LLSD& a, const LLSD& b);
+// static bool compare(const LLSD& a, const LLSD& b);
static bool equate(const LLSD& a, const LLSD& b);
+/*==========================================================================*|
bool operator()(const LLSD& a, const LLSD& b) const
{
return compare(a, b);
}
+|*==========================================================================*/
};
enum EFloaterButtons
diff --git a/indra/llui/llfloaterreg.cpp b/indra/llui/llfloaterreg.cpp
index a63b1b085c..8617ba940e 100644
--- a/indra/llui/llfloaterreg.cpp
+++ b/indra/llui/llfloaterreg.cpp
@@ -36,6 +36,7 @@
#include "llfloater.h"
#include "llmultifloater.h"
+#include "llfloaterreglistener.h"
//*******************************************************
@@ -45,6 +46,8 @@ LLFloaterReg::instance_map_t LLFloaterReg::sInstanceMap;
LLFloaterReg::build_map_t LLFloaterReg::sBuildMap;
std::map<std::string,std::string> LLFloaterReg::sGroupMap;
+static LLFloaterRegListener sFloaterRegListener("LLFloaterReg");
+
//*******************************************************
//static
diff --git a/indra/llui/llfloaterreg.h b/indra/llui/llfloaterreg.h
index 7edac43c96..451bd1dbe3 100644
--- a/indra/llui/llfloaterreg.h
+++ b/indra/llui/llfloaterreg.h
@@ -70,6 +70,7 @@ public:
typedef std::map<std::string, BuildData> build_map_t;
private:
+ friend class LLFloaterRegListener;
static instance_list_t sNullInstanceList;
static instance_map_t sInstanceMap;
static build_map_t sBuildMap;
diff --git a/indra/llui/llfloaterreglistener.cpp b/indra/llui/llfloaterreglistener.cpp
new file mode 100644
index 0000000000..cb8fa6dfda
--- /dev/null
+++ b/indra/llui/llfloaterreglistener.cpp
@@ -0,0 +1,66 @@
+/**
+ * @file llfloaterreglistener.cpp
+ * @author Nat Goodspeed
+ * @date 2009-08-12
+ * @brief Implementation for llfloaterreglistener.
+ *
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
+ * Copyright (c) 2009, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "llfloaterreglistener.h"
+// STL headers
+// std headers
+// external library headers
+// other Linden headers
+#include "llfloaterreg.h"
+
+LLFloaterRegListener::LLFloaterRegListener(const std::string& pumpName):
+ LLDispatchListener(pumpName, "op")
+{
+ add("getBuildMap", &LLFloaterRegListener::getBuildMap, LLSD().insert("reply", LLSD()));
+ LLSD requiredName;
+ requiredName["name"] = LLSD();
+ add("showInstance", &LLFloaterRegListener::showInstance, requiredName);
+ add("hideInstance", &LLFloaterRegListener::hideInstance, requiredName);
+ add("toggleInstance", &LLFloaterRegListener::toggleInstance, requiredName);
+}
+
+void LLFloaterRegListener::getBuildMap(const LLSD& event) const
+{
+ // Honor the "reqid" convention by echoing event["reqid"] in our reply packet.
+ LLReqID reqID(event);
+ LLSD reply(reqID.makeResponse());
+ // Build an LLSD map that mirrors sBuildMap. Since we have no good way to
+ // represent a C++ callable in LLSD, the only part of BuildData we can
+ // store is the filename. For each LLSD map entry, it would be more
+ // extensible to store a nested LLSD map containing a single key "file" --
+ // but we don't bother, simply storing the string filename instead.
+ for (LLFloaterReg::build_map_t::const_iterator mi(LLFloaterReg::sBuildMap.begin()),
+ mend(LLFloaterReg::sBuildMap.end());
+ mi != mend; ++mi)
+ {
+ reply[mi->first] = mi->second.mFile;
+ }
+ // Send the reply to the LLEventPump named in event["reply"].
+ LLEventPumps::instance().obtain(event["reply"]).post(reply);
+}
+
+void LLFloaterRegListener::showInstance(const LLSD& event) const
+{
+ LLFloaterReg::showInstance(event["name"], event["key"], event["focus"]);
+}
+
+void LLFloaterRegListener::hideInstance(const LLSD& event) const
+{
+ LLFloaterReg::hideInstance(event["name"], event["key"]);
+}
+
+void LLFloaterRegListener::toggleInstance(const LLSD& event) const
+{
+ LLFloaterReg::toggleInstance(event["name"], event["key"]);
+}
diff --git a/indra/llui/llfloaterreglistener.h b/indra/llui/llfloaterreglistener.h
new file mode 100644
index 0000000000..58d2c07936
--- /dev/null
+++ b/indra/llui/llfloaterreglistener.h
@@ -0,0 +1,35 @@
+/**
+ * @file llfloaterreglistener.h
+ * @author Nat Goodspeed
+ * @date 2009-08-12
+ * @brief Wrap (subset of) LLFloaterReg API with an event API
+ *
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
+ * Copyright (c) 2009, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLFLOATERREGLISTENER_H)
+#define LL_LLFLOATERREGLISTENER_H
+
+#include "lleventdispatcher.h"
+#include <string>
+
+class LLSD;
+
+/// Event API wrapper for LLFloaterReg
+class LLFloaterRegListener: public LLDispatchListener
+{
+public:
+ /// As all public LLFloaterReg methods are static, there's no point in
+ /// binding an LLFloaterReg instance.
+ LLFloaterRegListener(const std::string& pumpName);
+
+private:
+ void getBuildMap(const LLSD& event) const;
+ void showInstance(const LLSD& event) const;
+ void hideInstance(const LLSD& event) const;
+ void toggleInstance(const LLSD& event) const;
+};
+
+#endif /* ! defined(LL_LLFLOATERREGLISTENER_H) */
diff --git a/indra/llui/llfunctorregistry.cpp b/indra/llui/llfunctorregistry.cpp
index 0c5b1655b1..5f9644f258 100644
--- a/indra/llui/llfunctorregistry.cpp
+++ b/indra/llui/llfunctorregistry.cpp
@@ -31,6 +31,7 @@
* $/LicenseInfo$
**/
+#include "linden_common.h"
#include "llfunctorregistry.h"
// This is a default functor always resident in the system.
diff --git a/indra/llui/llmodaldialog.cpp b/indra/llui/llmodaldialog.cpp
index f77ec5f4c7..387af05935 100644
--- a/indra/llui/llmodaldialog.cpp
+++ b/indra/llui/llmodaldialog.cpp
@@ -297,5 +297,16 @@ void LLModalDialog::onAppFocusGained()
}
}
-
-
+void LLModalDialog::shutdownModals()
+{
+ // This method is only for use during app shutdown. ~LLModalDialog()
+ // checks sModalStack, and if the dialog instance is still there, it
+ // crumps with "Attempt to delete dialog while still in sModalStack!" But
+ // at app shutdown, all bets are off. If the user asks to shut down the
+ // app, we shouldn't have to care WHAT's open. Put differently, if a modal
+ // dialog is so crucial that we can't let the user terminate until s/he
+ // addresses it, we should reject a termination request. The current state
+ // of affairs is that we accept it, but then produce an llerrs popup that
+ // simply makes our software look unreliable.
+ sModalStack.clear();
+}
diff --git a/indra/llui/llmodaldialog.h b/indra/llui/llmodaldialog.h
index 9d716a1880..863572fb5a 100644
--- a/indra/llui/llmodaldialog.h
+++ b/indra/llui/llmodaldialog.h
@@ -73,7 +73,8 @@ public:
static void onAppFocusGained();
static S32 activeCount() { return sModalStack.size(); }
-
+ static void shutdownModals();
+
protected:
void centerOnScreen();
diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp
index 25e2475f59..a68b9cae57 100644
--- a/indra/llui/llnotifications.cpp
+++ b/indra/llui/llnotifications.cpp
@@ -1,1513 +1,1516 @@
-/**
-* @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 <algorithm>
-#include <boost/regex.hpp>
-
-
-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<LLSDFormatter> 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<LLSDParser> 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.time_stamp),
- 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<std::string>& required_fields) const
-{
- for(std::vector<std::string>::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::connectAtFrontChangedImpl(const LLEventListener& slot)
-{
- for (LLNotificationSet::iterator it = mItems.begin(); it != mItems.end(); ++it)
- {
- slot(LLSD().insert("sigtype", "load").insert("id", (*it)->id()));
- }
- return mChanged.connect(slot, boost::signals2::at_front);
-}
-
-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));
- // uniqueHandler slot should be added as first slot of the signal due to
- // usage LLStopWhenHandled combiner in LLStandardSignal
- LLNotifications::instance().getChannel("Unique")->
- connectAtFrontChanged(boost::bind(&LLNotifications::uniqueHandler, this, _1));
-// failedUniquenessTest slot isn't necessary
-// 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<std::string, std::string> 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);
-
- // <url>
- 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;
- }
- }
- }
-
- // <form>
- 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;
- }
- pNotif->cancel();
- updateItem(LLSD().insert("sigtype", "delete").insert("id", pNotif->id()), pNotif);
-}
-
-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 <algorithm>
+#include <boost/regex.hpp>
+
+
+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<LLSDFormatter> 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<LLSDParser> 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.time_stamp),
+ 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<std::string>& required_fields) const
+{
+ for(std::vector<std::string>::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::connectAtFrontChangedImpl(const LLEventListener& slot)
+{
+ for (LLNotificationSet::iterator it = mItems.begin(); it != mItems.end(); ++it)
+ {
+ slot(LLSD().insert("sigtype", "load").insert("id", (*it)->id()));
+ }
+ return mChanged.connect(slot, boost::signals2::at_front);
+}
+
+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));
+ // uniqueHandler slot should be added as first slot of the signal due to
+ // usage LLStopWhenHandled combiner in LLStandardSignal
+ LLNotifications::instance().getChannel("Unique")->
+ connectAtFrontChanged(boost::bind(&LLNotifications::uniqueHandler, this, _1));
+// failedUniquenessTest slot isn't necessary
+// 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<std::string, std::string> 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);
+
+ // <url>
+ 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;
+ }
+ }
+ }
+
+ // <form>
+ 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;
+ }
+ pNotif->cancel();
+ updateItem(LLSD().insert("sigtype", "delete").insert("id", pNotif->id()), pNotif);
+}
+
+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 19895c3293..0335a265b6 100644
--- a/indra/llui/llnotifications.h
+++ b/indra/llui/llnotifications.h
@@ -103,6 +103,7 @@
#include "llpointer.h"
#include "llinitparam.h"
#include "llxmlnode.h"
+#include "llnotificationslistener.h"
class LLNotification;
typedef boost::shared_ptr<LLNotification> LLNotificationPtr;
@@ -813,9 +814,19 @@ private:
LLNotificationComparator mComparator;
};
-
+// An interface class to provide a clean linker seam to the LLNotifications class.
+// Extend this interface as needed for your use of LLNotifications.
+class LLNotificationsInterface
+{
+public:
+ virtual LLNotificationPtr add(const std::string& name,
+ const LLSD& substitutions,
+ const LLSD& payload,
+ LLNotificationFunctorRegistry::ResponseFunctor functor) = 0;
+};
class LLNotifications :
+ public LLNotificationsInterface,
public LLSingleton<LLNotifications>,
public LLNotificationChannelBase
{
@@ -839,7 +850,7 @@ public:
const LLSD& substitutions,
const LLSD& payload,
const std::string& functor_name);
- LLNotificationPtr add(const std::string& name,
+ /* virtual */ LLNotificationPtr add(const std::string& name,
const LLSD& substitutions,
const LLSD& payload,
LLNotificationFunctorRegistry::ResponseFunctor functor);
@@ -918,6 +929,8 @@ private:
GlobalStringMap mGlobalStrings;
bool mIgnoreAllNotifications;
+
+ boost::scoped_ptr<LLNotificationsListener> mListener;
};
diff --git a/indra/llui/llnotificationslistener.cpp b/indra/llui/llnotificationslistener.cpp
new file mode 100644
index 0000000000..75f4d6177d
--- /dev/null
+++ b/indra/llui/llnotificationslistener.cpp
@@ -0,0 +1,55 @@
+/**
+ * @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
+{
+ if(event_data.has("reply"))
+ {
+ mNotifications.add(event_data["name"],
+ event_data["substitutions"],
+ event_data["payload"],
+ boost::bind(&LLNotificationsListener::NotificationResponder,
+ this,
+ event_data["reply"].asString(),
+ _1, _2
+ )
+ );
+ }
+ else
+ {
+ mNotifications.add(event_data["name"],
+ event_data["substitutions"],
+ event_data["payload"]);
+ }
+}
+
+void LLNotificationsListener::NotificationResponder(const std::string& reply_pump,
+ const LLSD& notification,
+ const LLSD& response) const
+{
+ LLSD reponse_event;
+ reponse_event["notification"] = notification;
+ reponse_event["response"] = response;
+ LLEventPumps::getInstance()->obtain(reply_pump).post(reponse_event);
+}
diff --git a/indra/llui/llnotificationslistener.h b/indra/llui/llnotificationslistener.h
new file mode 100644
index 0000000000..6f71a7c781
--- /dev/null
+++ b/indra/llui/llnotificationslistener.h
@@ -0,0 +1,34 @@
+/**
+ * @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:
+ void NotificationResponder(const std::string& replypump,
+ const LLSD& notification,
+ const LLSD& response) const;
+ LLNotifications & mNotifications;
+};
+
+#endif // LL_LLNOTIFICATIONSLISTENER_H
diff --git a/indra/llui/llslider.cpp b/indra/llui/llslider.cpp
index 07b6895378..f86776384a 100644
--- a/indra/llui/llslider.cpp
+++ b/indra/llui/llslider.cpp
@@ -43,6 +43,8 @@
#include "lluictrlfactory.h"
static LLDefaultChildRegistry::Register<LLSlider> r1("slider_bar");
+//FIXME: make this into an unregistered template so that code constructed sliders don't
+// have ambigious template lookup problem
LLSlider::Params::Params()
: track_color("track_color"),
diff --git a/indra/llui/llsliderctrl.cpp b/indra/llui/llsliderctrl.cpp
index 3ecf629082..ed22c0a47f 100644
--- a/indra/llui/llsliderctrl.cpp
+++ b/indra/llui/llsliderctrl.cpp
@@ -396,4 +396,3 @@ void LLSliderCtrl::reportInvalidData()
make_ui_sound("UISndBadKeystroke");
}
-