summaryrefslogtreecommitdiff
path: root/indra/llui
diff options
context:
space:
mode:
authorKent Quirk <q@lindenlab.com>2009-01-05 18:59:12 +0000
committerKent Quirk <q@lindenlab.com>2009-01-05 18:59:12 +0000
commit667ca55bad0108c4bdf8f007b89e1a52fc766aad (patch)
tree7bd62ac8d9af079c3994565f3f200ccc250bbc28 /indra/llui
parent95f365789f4cebc7bd97ccefd538f14d481a8373 (diff)
svn merge -r106715:HEAD svn+ssh://svn.lindenlab.com/svn/linden/branches/q/notifications-merge-r106715 . QAR-1149 -- Final merge of notifications to trunk.
Diffstat (limited to 'indra/llui')
-rw-r--r--indra/llui/CMakeLists.txt4
-rw-r--r--indra/llui/llcombobox.cpp32
-rw-r--r--indra/llui/llcombobox.h1
-rw-r--r--indra/llui/llfloater.cpp5
-rw-r--r--indra/llui/llfloater.h24
-rw-r--r--indra/llui/llfunctorregistry.cpp37
-rw-r--r--indra/llui/llfunctorregistry.h145
-rw-r--r--indra/llui/llnotifications.cpp1471
-rw-r--r--indra/llui/llnotifications.h892
-rw-r--r--indra/llui/llpanel.cpp129
-rw-r--r--indra/llui/llpanel.h26
-rw-r--r--indra/llui/llscrollbar.cpp76
-rw-r--r--indra/llui/llscrollbar.h2
-rw-r--r--indra/llui/llscrolllistctrl.cpp342
-rw-r--r--indra/llui/llscrolllistctrl.h115
-rw-r--r--indra/llui/llui.cpp16
-rw-r--r--indra/llui/llui.h237
-rw-r--r--indra/llui/lluictrl.cpp11
-rw-r--r--indra/llui/lluictrl.h2
-rw-r--r--indra/llui/lluictrlfactory.h1
-rw-r--r--indra/llui/lluistring.cpp13
-rw-r--r--indra/llui/lluistring.h6
-rw-r--r--indra/llui/llview.cpp5
-rw-r--r--indra/llui/llview.h7
24 files changed, 3291 insertions, 308 deletions
diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt
index 897cc4275d..1b4d4e7d54 100644
--- a/indra/llui/CMakeLists.txt
+++ b/indra/llui/CMakeLists.txt
@@ -36,6 +36,7 @@ set(llui_SOURCE_FILES
lleditmenuhandler.cpp
llfloater.cpp
llfocusmgr.cpp
+ llfunctorregistry.cpp
lliconctrl.cpp
llkeywords.cpp
lllineeditor.cpp
@@ -43,6 +44,7 @@ set(llui_SOURCE_FILES
llmodaldialog.cpp
llmultislider.cpp
llmultisliderctrl.cpp
+ llnotifications.cpp
llpanel.cpp
llprogressbar.cpp
llradiogroup.cpp
@@ -86,6 +88,7 @@ set(llui_HEADER_FILES
lleditmenuhandler.h
llfloater.h
llfocusmgr.h
+ llfunctorregistry.h
llhtmlhelp.h
lliconctrl.h
llkeywords.h
@@ -95,6 +98,7 @@ set(llui_HEADER_FILES
llmodaldialog.h
llmultisliderctrl.h
llmultislider.h
+ llnotifications.h
llpanel.h
llprogressbar.h
llradiogroup.h
diff --git a/indra/llui/llcombobox.cpp b/indra/llui/llcombobox.cpp
index df5bcbe752..6edd5a7fe5 100644
--- a/indra/llui/llcombobox.cpp
+++ b/indra/llui/llcombobox.cpp
@@ -72,11 +72,12 @@ LLComboBox::LLComboBox( const std::string& name, const LLRect &rect, const std::
mTextEntryTentative(TRUE),
mListPosition(BELOW),
mPrearrangeCallback( NULL ),
- mTextEntryCallback( NULL )
+ mTextEntryCallback( NULL ),
+ mLabel(label)
{
// Always use text box
// Text label button
- mButton = new LLButton(label,
+ mButton = new LLButton(mLabel,
LLRect(),
LLStringUtil::null,
NULL, this);
@@ -197,7 +198,12 @@ LLView* LLComboBox::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *
}
}
- combo_box->selectFirstItem();
+ // if providing user text entry or descriptive label
+ // don't select an item under the hood
+ if (!combo_box->acceptsTextInput() && combo_box->mLabel.empty())
+ {
+ combo_box->selectFirstItem();
+ }
return combo_box;
}
@@ -259,7 +265,10 @@ LLScrollListItem* LLComboBox::add(const std::string& name, EAddPosition pos, BOO
{
LLScrollListItem* item = mList->addSimpleElement(name, pos);
item->setEnabled(enabled);
- mList->selectFirstItem();
+ if (!mAllowTextEntry && mLabel.empty())
+ {
+ selectFirstItem();
+ }
return item;
}
@@ -268,7 +277,10 @@ LLScrollListItem* LLComboBox::add(const std::string& name, const LLUUID& id, EAd
{
LLScrollListItem* item = mList->addSimpleElement(name, pos, id);
item->setEnabled(enabled);
- mList->selectFirstItem();
+ if (!mAllowTextEntry && mLabel.empty())
+ {
+ selectFirstItem();
+ }
return item;
}
@@ -278,7 +290,10 @@ LLScrollListItem* LLComboBox::add(const std::string& name, void* userdata, EAddP
LLScrollListItem* item = mList->addSimpleElement(name, pos);
item->setEnabled(enabled);
item->setUserdata( userdata );
- mList->selectFirstItem();
+ if (!mAllowTextEntry && mLabel.empty())
+ {
+ selectFirstItem();
+ }
return item;
}
@@ -287,7 +302,10 @@ LLScrollListItem* LLComboBox::add(const std::string& name, LLSD value, EAddPosit
{
LLScrollListItem* item = mList->addSimpleElement(name, pos, value);
item->setEnabled(enabled);
- mList->selectFirstItem();
+ if (!mAllowTextEntry && mLabel.empty())
+ {
+ selectFirstItem();
+ }
return item;
}
diff --git a/indra/llui/llcombobox.h b/indra/llui/llcombobox.h
index 7427a33129..0317ebdae9 100644
--- a/indra/llui/llcombobox.h
+++ b/indra/llui/llcombobox.h
@@ -188,6 +188,7 @@ protected:
LLScrollListCtrl* mList;
EPreferredPosition mListPosition;
LLPointer<LLUIImage> mArrowImage;
+ std::string mLabel;
private:
S32 mButtonPadding;
diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp
index 22260b52cf..dd93684a69 100644
--- a/indra/llui/llfloater.cpp
+++ b/indra/llui/llfloater.cpp
@@ -147,6 +147,7 @@ LLFloater::LLFloater() :
}
mDragHandle = NULL;
mHandle.bind(this);
+ mNotificationContext = new LLFloaterNotificationContext(getHandle());
}
LLFloater::LLFloater(const std::string& name)
@@ -220,6 +221,7 @@ void LLFloater::initFloater(const std::string& title,
BOOL drag_on_left, BOOL minimizable, BOOL close_btn)
{
mHandle.bind(this);
+ mNotificationContext = new LLFloaterNotificationContext(getHandle());
// Init function can be called more than once, so clear out old data.
for (S32 i = 0; i < BUTTON_COUNT; i++)
@@ -429,6 +431,9 @@ void LLFloater::initFloater(const std::string& title,
// virtual
LLFloater::~LLFloater()
{
+ delete mNotificationContext;
+ mNotificationContext = NULL;
+
control_map_t::iterator itor;
for (itor = mFloaterControls.begin(); itor != mFloaterControls.end(); ++itor)
{
diff --git a/indra/llui/llfloater.h b/indra/llui/llfloater.h
index b66eba8810..ebf2676960 100644
--- a/indra/llui/llfloater.h
+++ b/indra/llui/llfloater.h
@@ -39,6 +39,7 @@
#include "llpanel.h"
#include "lluuid.h"
#include "lltabcontainer.h"
+#include "llnotifications.h"
#include <set>
class LLDragHandle;
@@ -46,6 +47,7 @@ class LLResizeHandle;
class LLResizeBar;
class LLButton;
class LLMultiFloater;
+class LLFloater;
const S32 LLFLOATER_VPAD = 6;
const S32 LLFLOATER_HPAD = 6;
@@ -70,6 +72,20 @@ const BOOL CLOSE_NO = FALSE;
const BOOL ADJUST_VERTICAL_YES = TRUE;
const BOOL ADJUST_VERTICAL_NO = FALSE;
+// associates a given notification instance with a particular floater
+class LLFloaterNotificationContext :
+ public LLNotificationContext
+{
+public:
+ LLFloaterNotificationContext(LLHandle<LLFloater> floater_handle) :
+ mFloaterHandle(floater_handle)
+ {}
+
+ LLFloater* getFloater() { return mFloaterHandle.get(); }
+private:
+ LLHandle<LLFloater> mFloaterHandle;
+};
+
class LLFloater : public LLPanel
{
@@ -213,6 +229,11 @@ public:
// handle refocusing.
static void closeFocusedFloater();
+ LLNotification::Params contextualNotification(const std::string& name)
+ {
+ return LLNotification::Params(name).context(mNotificationContext);
+ }
+
static void onClickClose(void *userdata);
static void onClickMinimize(void *userdata);
static void onClickTearOff(void *userdata);
@@ -299,7 +320,7 @@ private:
S32 mPreviousMinimizedBottom;
S32 mPreviousMinimizedLeft;
-private:
+ LLFloaterNotificationContext* mNotificationContext;
LLRootHandle<LLFloater> mHandle;
};
@@ -467,7 +488,6 @@ template <class T> class LLFloaterSingleton : public LLUISingleton<T, Visibility
{
};
-
extern LLFloaterView* gFloaterView;
#endif // LL_FLOATER_H
diff --git a/indra/llui/llfunctorregistry.cpp b/indra/llui/llfunctorregistry.cpp
new file mode 100644
index 0000000000..a6ecc6aa18
--- /dev/null
+++ b/indra/llui/llfunctorregistry.cpp
@@ -0,0 +1,37 @@
+/**
+ * @file llfunctorregistry.cpp
+ * @author Kent Quirk
+ * @brief Maintains a registry of named callback functors taking a single LLSD parameter
+ *
+ * $LicenseInfo:firstyear=2008&license=viewergpl$
+ *
+ * Copyright (c) 2008, 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 "llfunctorregistry.h"
+
+// This is a default functor always resident in the system.
+// It's used whenever a functor isn't found in the registry, so that
+// we at least log the data relating to the user response.
diff --git a/indra/llui/llfunctorregistry.h b/indra/llui/llfunctorregistry.h
new file mode 100644
index 0000000000..02bf74a28a
--- /dev/null
+++ b/indra/llui/llfunctorregistry.h
@@ -0,0 +1,145 @@
+/**
+ * @file llfunctorregistry.h
+ * @author Kent Quirk
+ * @brief Maintains a registry of named callback functors taking a single LLSD parameter
+ *
+ * $LicenseInfo:firstyear=2008&license=viewergpl$
+ *
+ * Copyright (c) 2003-2007, 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://secondlife.com/developers/opensource/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://secondlife.com/developers/opensource/flossexception
+ *
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ *
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLFUNCTORREGISTRY_H
+#define LL_LLFUNCTORREGISTRY_H
+
+#include <string>
+#include <map>
+
+#include <boost/function.hpp>
+
+#include "llsd.h"
+#include "llmemory.h"
+
+/**
+ * @class LLFunctorRegistry
+ * @brief Maintains a collection of named functors for remote binding
+ * (mainly for use in callbacks from notifications and other signals)
+ * @see LLNotifications
+ *
+ * This class maintains a collection of named functors in a singleton.
+ * We wanted to be able to persist notifications with their callbacks
+ * across restarts of the viewer; we couldn't store functors that way.
+ * Using this registry, systems that require a functor to be maintained
+ * long term can register it at system startup, and then pass in the
+ * functor by name.
+ */
+
+template <typename FUNCTOR_TYPE>
+class LLFunctorRegistry : public LLSingleton<LLFunctorRegistry<FUNCTOR_TYPE> >
+{
+ friend class LLSingleton<LLFunctorRegistry>;
+ LOG_CLASS(LLFunctorRegistry);
+private:
+ LLFunctorRegistry() : LOGFUNCTOR("LogFunctor"), DONOTHING("DoNothing")
+ {
+ mMap[LOGFUNCTOR] = log_functor;
+ mMap[DONOTHING] = do_nothing;
+ }
+
+public:
+ typedef FUNCTOR_TYPE ResponseFunctor;
+ typedef typename std::map<std::string, FUNCTOR_TYPE> FunctorMap;
+
+ bool registerFunctor(const std::string& name, ResponseFunctor f)
+ {
+ bool retval = true;
+ typename FunctorMap::iterator it = mMap.find(name);
+ if (mMap.count(name) == 0)
+ {
+ mMap[name] = f;
+ }
+ else
+ {
+ llerrs << "attempt to store duplicate name '" << name << "' in LLFunctorRegistry. NOT ADDED." << llendl;
+ retval = false;
+ }
+
+ return retval;
+ }
+
+ bool unregisterFunctor(const std::string& name)
+ {
+ if (mMap.count(name) == 0)
+ {
+ llwarns << "trying to remove '" << name << "' from LLFunctorRegistry but it's not there." << llendl;
+ return false;
+ }
+ mMap.erase(name);
+ return true;
+ }
+
+ FUNCTOR_TYPE getFunctor(const std::string& name)
+ {
+ typename FunctorMap::iterator it = mMap.find(name);
+ if (mMap.count(name) != 0)
+ {
+ return mMap[name];
+ }
+ else
+ {
+ llwarns << "tried to find '" << name << "' in LLFunctorRegistry, but it wasn't there." << llendl;
+ return mMap[LOGFUNCTOR];
+ }
+ }
+
+ const std::string LOGFUNCTOR;
+ const std::string DONOTHING;
+
+private:
+
+ static void log_functor(const LLSD& notification, const LLSD& payload)
+ {
+ llwarns << "log_functor called with payload: " << payload << llendl;
+ }
+
+ static void do_nothing(const LLSD& notification, const LLSD& payload)
+ {
+ // what the sign sez
+ }
+
+ FunctorMap mMap;
+};
+
+template <typename FUNCTOR_TYPE>
+class LLFunctorRegistration
+{
+public:
+ LLFunctorRegistration(const std::string& name, FUNCTOR_TYPE functor)
+ {
+ LLFunctorRegistry<FUNCTOR_TYPE>::instance().registerFunctor(name, functor);
+ }
+};
+
+#endif//LL_LLFUNCTORREGISTRY_H
+
diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp
new file mode 100644
index 0000000000..c2da50b98b
--- /dev/null
+++ b/indra/llui/llnotifications.cpp
@@ -0,0 +1,1471 @@
+/**
+* @file llnotifications.cpp
+* @brief Non-UI queue manager for keeping a prioritized list of notifications
+*
+* $LicenseInfo:firstyear=2008&license=viewergpl$
+*
+* Copyright (c) 2008, 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 "lluictrlfactory.h"
+#include "lldir.h"
+#include "llsdserialize.h"
+
+#include "llnotifications.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),
+ 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)
+{
+ LLNotificationFormPtr form = notification->getForm();
+ // Check to see if the user wants to ignore this alert
+ if (form->getIgnoreType() != LLNotificationForm::IGNORE_NO)
+ {
+ return LLUI::sConfigGroup->getWarning(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::sIgnoresGroup->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::sIgnoresGroup->declareLLSD(std::string("Default") + name, "", std::string("Default response for notification " + name));
+ }
+ child->getAttributeString("text", mIgnoreMsg);
+ LLUI::sIgnoresGroup->addWarning(name);
+ }
+ 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["label"] = 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();
+ text = LLNotification::format(text, substitutions);
+ (*it)["text"] = text;
+ }
+ if ((*it)["type"].asString() == "text" && (*it).has("value"))
+ {
+ std::string value = (*it)["value"].asString();
+ value = LLNotification::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),
+ mUnique(false),
+ mPriority(NOTIFICATION_PRIORITY_NORMAL)
+{
+ mForm = LLNotificationFormPtr(new LLNotificationForm());
+}
+
+LLNotification::LLNotification(const LLNotification::Params& p) :
+ mTimestamp(p.timestamp),
+ mSubstitutions(p.substitutions),
+ mPayload(p.payload),
+ mExpiresAt(0),
+ mResponseFunctorName(p.functor_name),
+ mTemporaryResponder(p.mTemporaryResponder),
+ mRespondedTo(false),
+ mPriority(p.priority),
+ mCancelled(false),
+ mIgnored(false)
+{
+ 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;
+ LLNotificationFunctorRegistry::instance().getFunctor(mResponseFunctorName)(asLLSD(), response);
+ if (mTemporaryResponder)
+ {
+ LLNotificationFunctorRegistry::instance().unregisterFunctor(mResponseFunctorName);
+ mResponseFunctorName = "";
+ mTemporaryResponder = false;
+ }
+
+ if (mForm->getIgnoreType() != LLNotificationForm::IGNORE_NO)
+ {
+ LLUI::sIgnoresGroup->setWarning(getName(), !mIgnored);
+ if (mIgnored && mForm->getIgnoreType() == LLNotificationForm::IGNORE_WITH_LAST_RESPONSE)
+ {
+ LLUI::sIgnoresGroup->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
+ // TODO: change this to read from the translatable strings file!
+ mSubstitutions["SECOND_LIFE"] = "Second Life";
+ 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;
+}
+
+//static
+std::string LLNotification::format(const std::string& s, const LLSD& substitutions)
+{
+ if (!substitutions.isMap())
+ {
+ return s;
+ }
+
+ std::ostringstream output;
+ // match strings like [NAME]
+ const boost::regex key("\\[([0-9_A-Z]+)]");
+
+ std::string::const_iterator start = s.begin();
+ std::string::const_iterator end = s.end();
+ boost::smatch match;
+
+ while (boost::regex_search(start, end, match, key, boost::match_default))
+ {
+ bool found_replacement = false;
+ std::string replacement;
+
+ // see if we have a replacement for the bracketed string (without the brackets)
+ // test first using has() because if we just look up with operator[] we get back an
+ // empty string even if the value is missing. We want to distinguish between
+ // missing replacements and deliberately empty replacement strings.
+ if (substitutions.has(std::string(match[1].first, match[1].second)))
+ {
+ replacement = substitutions[std::string(match[1].first, match[1].second)].asString();
+ found_replacement = true;
+ }
+ // if not, see if there's one WITH brackets
+ else if (substitutions.has(std::string(match[0].first, match[0].second)))
+ {
+ replacement = substitutions[std::string(match[0].first, match[0].second)].asString();
+ found_replacement = true;
+ }
+
+ if (found_replacement)
+ {
+ // found a replacement
+ // "hello world" is output
+ output << std::string(start, match[0].first) << replacement;
+ }
+ else
+ {
+ // we had no replacement, so leave the string we searched for so that it gets noticed by QA
+ // "hello [NAME_NOT_FOUND]" is output
+ output << std::string(start, match[0].second);
+ }
+
+ // update search position
+ start = match[0].second;
+ }
+ // send the remainder of the string (with no further matches for bracketed names)
+ output << std::string(start, end);
+ return output.str();
+}
+
+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();
+ return format(mTemplatep->mMessage, mSubstitutions);
+}
+
+std::string LLNotification::getLabel() const
+{
+ return (mTemplatep ? format(mTemplatep->mLabel, mSubstitutions) : "");
+}
+
+
+
+// =========================================================
+// LLNotificationChannel implementation
+// ---
+void LLNotificationChannelBase::connectChanged(const LLStandardSignal::slot_type& 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.get_slot_function()(LLSD().insert("sigtype", "load").insert("id", (*it)->id()));
+ }
+ // and then connect the signal so that all future notifications will also be
+ // forwarded.
+ mChanged.connect(slot);
+}
+
+void LLNotificationChannelBase::connectPassedFilter(const LLStandardSignal::slot_type& slot)
+{
+ // these two filters only fire for notifications added after the current one, because
+ // they don't participate in the hierarchy.
+ mPassedFilter.connect(slot);
+}
+
+void LLNotificationChannelBase::connectFailedFilter(const LLStandardSignal::slot_type& slot)
+{
+ 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;
+}
+
+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);
+ LLStandardSignal::slot_type f = boost::bind(&LLNotificationChannelBase::updateItem, this, _1);
+ p->connectChanged(f);
+ }
+}
+
+
+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())
+{
+}
+
+
+// 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
+ new LLNotificationChannel("Expiration", "",
+ boost::bind(&LLNotifications::expirationFilter, this, _1));
+ new LLNotificationChannel("Unexpired", "",
+ !boost::bind(&LLNotifications::expirationFilter, this, _1)); // use negated bind
+ new LLNotificationChannel("Unique", "Unexpired",
+ boost::bind(&LLNotifications::uniqueFilter, this, _1));
+ new LLNotificationChannel("Ignore", "Unique",
+ filterIgnoredNotifications);
+ new LLNotificationChannel("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" );
+ new LLNotificationHistoryChannel(notifications_log_file);
+
+ // connect action methods to these channels
+ LLNotifications::instance().getChannel("Expiration")->
+ connectChanged(boost::bind(&LLNotifications::expirationHandler, this, _1));
+ LLNotifications::instance().getChannel("Unique")->
+ connectChanged(boost::bind(&LLNotifications::uniqueHandler, this, _1));
+ LLNotifications::instance().getChannel("Unique")->
+ connectFailedFilter(boost::bind(&LLNotifications::failedUniquenessTest, this, _1));
+ LLNotifications::instance().getChannel("Ignore")->
+ connectFailedFilter(&handleIgnoredNotification);
+}
+
+static std::string sStringSkipNextTime("Skip this dialog next time");
+static std::string sStringAlwaysChoose("Always choose this option");
+
+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())
+ {
+ // TODO: test for bad sound effect name / missing effect
+ pTemplate->mSoundEffect = LLUUID(LLUI::sConfigGroup->getString(sound.c_str()));
+ }
+
+ 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);
+ }
+
+ 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;
+}
+
+// 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)
+{
+ return add(LLNotification::Params(name).substitutions(substitutions).payload(payload));
+}
+
+LLNotificationPtr LLNotifications::add(const std::string& name,
+ const LLSD& substitutions,
+ const LLSD& payload,
+ const std::string& functor_name)
+{
+ return add(LLNotification::Params(name).substitutions(substitutions).payload(payload).functor_name(functor_name));
+}
+
+LLNotificationPtr LLNotifications::add(const std::string& name,
+ const LLSD& substitutions,
+ const LLSD& payload,
+ LLNotificationFunctorRegistry::ResponseFunctor functor)
+{
+ return add(LLNotification::Params(name).substitutions(substitutions).payload(payload).functor(functor));
+}
+
+// generalized add function that takes a parameter block object for more complex instantiations
+LLNotificationPtr LLNotifications::add(const LLNotification::Params& p)
+{
+ LLNotificationPtr pNotif(new LLNotification(p));
+ add(pNotif);
+ return pNotif;
+}
+
+
+void LLNotifications::add(const LLNotificationPtr pNotif)
+{
+ // first see if we already have it -- if so, that's a problem
+ LLNotificationSet::iterator it=mItems.find(pNotif);
+ if (it != mItems.end())
+ {
+ llerrs << "Notification added a second time to the master notification channel." << llendl;
+ }
+
+ updateItem(LLSD().insert("sigtype", "add").insert("id", pNotif->id()), pNotif);
+}
+
+void LLNotifications::cancel(LLNotificationPtr pNotif)
+{
+ LLNotificationSet::iterator it=mItems.find(pNotif);
+ if (it == mItems.end())
+ {
+ llerrs << "Attempted to delete nonexistent notification " << pNotif->getName() << llendl;
+ }
+ updateItem(LLSD().insert("sigtype", "delete").insert("id", pNotif->id()), pNotif);
+ pNotif->cancel();
+}
+
+void LLNotifications::update(const LLNotificationPtr pNotif)
+{
+ LLNotificationSet::iterator it=mItems.find(pNotif);
+ if (it != mItems.end())
+ {
+ updateItem(LLSD().insert("sigtype", "change").insert("id", pNotif->id()), pNotif);
+ }
+}
+
+
+LLNotificationPtr LLNotifications::find(LLUUID uuid)
+{
+ LLNotificationPtr target = LLNotificationPtr(new LLNotification(uuid));
+ LLNotificationSet::iterator it=mItems.find(target);
+ if (it == mItems.end())
+ {
+ llwarns << "Tried to dereference uuid '" << uuid << "' as a notification key but didn't find it." << llendl;
+ return LLNotificationPtr((LLNotification*)NULL);
+ }
+ else
+ {
+ return *it;
+ }
+}
+
+void LLNotifications::forEachNotification(NotificationProcess process)
+{
+ std::for_each(mItems.begin(), mItems.end(), process);
+}
+
+std::string LLNotifications::getGlobalString(const std::string& key) const
+{
+ GlobalStringMap::const_iterator it = mGlobalStrings.find(key);
+ if (it != mGlobalStrings.end())
+ {
+ return it->second;
+ }
+ else
+ {
+ // if we don't have the key as a global, return the key itself so that the error
+ // is self-diagnosing.
+ return key;
+ }
+}
+
+
+// ---
+// 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
new file mode 100644
index 0000000000..2a2003f499
--- /dev/null
+++ b/indra/llui/llnotifications.h
@@ -0,0 +1,892 @@
+/**
+* @file llnotifications.h
+* @brief Non-UI manager and support for keeping a prioritized list of notifications
+* @author Q (with assistance from Richard and Coco)
+*
+* $LicenseInfo:firstyear=2008&license=viewergpl$
+*
+* Copyright (c) 2008, Linden Research, Inc.
+*
+* Second Life Viewer Source Code
+* The source code in this file ("Source Code") is provided by Linden Lab
+* to you under the terms of the GNU General Public License, version 2.0
+* ("GPL"), unless you have obtained a separate licensing agreement
+* ("Other License"), formally executed by you and Linden Lab. Terms of
+* the GPL can be found in doc/GPL-license.txt in this distribution, or
+* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+*
+* There are special exceptions to the terms and conditions of the GPL as
+* it is applied to this Source Code. View the full text of the exception
+* in the file doc/FLOSS-exception.txt in this software distribution, or
+* online at http://secondlifegrid.net/programs/open_source/licensing/flossexception
+*
+* By copying, modifying or distributing this software, you acknowledge
+* that you have read and understood your obligations described above,
+* and agree to abide by those obligations.
+*
+* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+* COMPLETENESS OR PERFORMANCE.
+* $/LicenseInfo$
+*/
+
+#ifndef LL_LLNOTIFICATIONS_H
+#define LL_LLNOTIFICATIONS_H
+
+/**
+ * This system is intended to provide a singleton mechanism for adding
+ * notifications to one of an arbitrary set of event channels.
+ *
+ * Controlling JIRA: DEV-9061
+ *
+ * Every notification has (see code for full list):
+ * - a textual name, which is used to look up its template in the XML files
+ * - a payload, which is a block of LLSD
+ * - a channel, which is normally extracted from the XML files but
+ * can be overridden.
+ * - a timestamp, used to order the notifications
+ * - expiration time -- if nonzero, specifies a time after which the
+ * notification will no longer be valid.
+ * - a callback name and a couple of status bits related to callbacks (see below)
+ *
+ * There is a management class called LLNotifications, which is an LLSingleton.
+ * The class maintains a collection of all of the notifications received
+ * or processed during this session, and also manages the persistence
+ * of those notifications that must be persisted.
+ *
+ * We also have Channels. A channel is a view on a collection of notifications;
+ * The collection is defined by a filter function that controls which
+ * notifications are in the channel, and its ordering is controlled by
+ * a comparator.
+ *
+ * There is a hierarchy of channels; notifications flow down from
+ * the management class (LLNotifications, which itself inherits from
+ * The channel base class) to the individual channels.
+ * Any change to notifications (add, delete, modify) is
+ * automatically propagated through the channel hierarchy.
+ *
+ * We provide methods for adding a new notification, for removing
+ * one, and for managing channels. Channels are relatively cheap to construct
+ * and maintain, so in general, human interfaces should use channels to
+ * select and manage their lists of notifications.
+ *
+ * We also maintain a collection of templates that are loaded from the
+ * XML file of template translations. The system supports substitution
+ * of named variables from the payload into the XML file.
+ *
+ * By default, only the "unknown message" template is built into the system.
+ * It is not an error to add a notification that's not found in the
+ * template system, but it is logged.
+ *
+ */
+
+#include <string>
+#include <list>
+#include <vector>
+#include <map>
+#include <set>
+#include <iomanip>
+#include <sstream>
+
+#include <boost/utility.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/signal.hpp>
+#include <boost/type_traits.hpp>
+
+// we want to minimize external dependencies, but this one is important
+#include "llsd.h"
+
+// and we need this to manage the notification callbacks
+#include "llfunctorregistry.h"
+#include "llui.h"
+
+class LLNotification;
+typedef boost::shared_ptr<LLNotification> LLNotificationPtr;
+
+/*****************************************************************************
+* Signal and handler declarations
+* Using a single handler signature means that we can have a common handler
+* type, rather than needing a distinct one for each different handler.
+*****************************************************************************/
+
+/**
+ * A boost::signals Combiner that stops the first time a handler returns true
+ * We need this because we want to have our handlers return bool, so that
+ * we have the option to cause a handler to stop further processing. The
+ * default handler fails when the signal returns a value but has no slots.
+ */
+struct LLStopWhenHandled
+{
+ typedef bool result_type;
+
+ template<typename InputIterator>
+ result_type operator()(InputIterator first, InputIterator last) const
+ {
+ for (InputIterator si = first; si != last; ++si)
+ {
+ if (*si)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+};
+
+
+typedef enum e_notification_priority
+{
+ NOTIFICATION_PRIORITY_UNSPECIFIED,
+ NOTIFICATION_PRIORITY_LOW,
+ NOTIFICATION_PRIORITY_NORMAL,
+ NOTIFICATION_PRIORITY_HIGH,
+ NOTIFICATION_PRIORITY_CRITICAL
+} ENotificationPriority;
+
+/**
+ * We want to have a standard signature for all signals; this way,
+ * we can easily document a protocol for communicating across
+ * dlls and into scripting languages someday.
+ * we want to return a bool to indicate whether the signal has been
+ * handled and should NOT be passed on to other listeners.
+ * Return true to stop further handling of the signal, and false
+ * to continue.
+ * We take an LLSD because this way the contents of the signal
+ * are independent of the API used to communicate it.
+ * It is const ref because then there's low cost to pass it;
+ * if you only need to inspect it, it's very cheap.
+ */
+
+typedef boost::function<void (const LLSD&, const LLSD&)> LLNotificationResponder;
+
+typedef LLFunctorRegistry<LLNotificationResponder> LLNotificationFunctorRegistry;
+typedef LLFunctorRegistration<LLNotificationResponder> LLNotificationFunctorRegistration;
+
+typedef boost::signal<bool(const LLSD&), LLStopWhenHandled> LLStandardSignal;
+
+// context data that can be looked up via a notification's payload by the display logic
+// derive from this class to implement specific contexts
+class LLNotificationContext : public LLInstanceTracker<LLNotificationContext, LLUUID>
+{
+public:
+ LLNotificationContext() : LLInstanceTracker<LLNotificationContext, LLUUID>(LLUUID::generateNewID())
+ {
+ }
+
+ virtual ~LLNotificationContext() {}
+
+ LLSD asLLSD() const
+ {
+ return getKey();
+ }
+
+private:
+
+};
+
+// Contains notification form data, such as buttons and text fields along with
+// manipulator functions
+class LLNotificationForm
+{
+ LOG_CLASS(LLNotificationForm);
+
+public:
+ typedef enum e_ignore_type
+ {
+ IGNORE_NO,
+ IGNORE_WITH_DEFAULT_RESPONSE,
+ IGNORE_WITH_LAST_RESPONSE,
+ IGNORE_SHOW_AGAIN
+ } EIgnoreType;
+
+ LLNotificationForm();
+ LLNotificationForm(const LLSD& sd);
+ LLNotificationForm(const std::string& name, const LLXMLNodePtr xml_node);
+
+ LLSD asLLSD() const;
+
+ S32 getNumElements() { return mFormData.size(); }
+ LLSD getElement(S32 index) { return mFormData.get(index); }
+ LLSD getElement(const std::string& element_name);
+ bool hasElement(const std::string& element_name);
+ void addElement(const std::string& type, const std::string& name, const LLSD& value = LLSD());
+ void formatElements(const LLSD& substitutions);
+ // appends form elements from another form serialized as LLSD
+ void append(const LLSD& sub_form);
+ std::string getDefaultOption();
+
+ EIgnoreType getIgnoreType() { return mIgnore; }
+ std::string getIgnoreMessage() { return mIgnoreMsg; }
+
+private:
+ LLSD mFormData;
+ EIgnoreType mIgnore;
+ std::string mIgnoreMsg;
+};
+
+typedef boost::shared_ptr<LLNotificationForm> LLNotificationFormPtr;
+
+// This is the class of object read from the XML file (notifications.xml,
+// from the appropriate local language directory).
+struct LLNotificationTemplate
+{
+ LLNotificationTemplate();
+ // the name of the notification -- the key used to identify it
+ // Ideally, the key should follow variable naming rules
+ // (no spaces or punctuation).
+ std::string mName;
+ // The type of the notification
+ // used to control which queue it's stored in
+ std::string mType;
+ // The text used to display the notification. Replaceable parameters
+ // are enclosed in square brackets like this [].
+ std::string mMessage;
+ // The label for the notification; used for
+ // certain classes of notification (those with a window and a window title).
+ // Also used when a notification pops up underneath the current one.
+ // Replaceable parameters can be used in the label.
+ std::string mLabel;
+ // The name of the icon image. This should include an extension.
+ std::string mIcon;
+ // This is the Highlander bit -- "There Can Be Only One"
+ // An outstanding notification with this bit set
+ // is updated by an incoming notification with the same name,
+ // rather than creating a new entry in the queue.
+ // (used for things like progress indications, or repeating warnings
+ // like "the grid is going down in N minutes")
+ bool mUnique;
+ // if we want to be unique only if a certain part of the payload is constant
+ // specify the field names for the payload. The notification will only be
+ // combined if all of the fields named in the context are identical in the
+ // new and the old notification; otherwise, the notification will be
+ // duplicated. This is to support suppressing duplicate offers from the same
+ // sender but still differentiating different offers. Example: Invitation to
+ // conference chat.
+ std::vector<std::string> mUniqueContext;
+ // If this notification expires automatically, this value will be
+ // nonzero, and indicates the number of seconds for which the notification
+ // will be valid (a teleport offer, for example, might be valid for
+ // 300 seconds).
+ U32 mExpireSeconds;
+ // if the offer expires, one of the options is chosen automatically
+ // based on its "value" parameter. This controls which one.
+ // If expireSeconds is specified, expireOption should also be specified.
+ U32 mExpireOption;
+ // if the notification contains a url, it's stored here (and replaced
+ // into the message where [_URL] is found)
+ std::string mURL;
+ // if there's a URL in the message, this controls which option visits
+ // that URL. Obsolete this and eliminate the buttons for affected
+ // messages when we allow clickable URLs in the UI
+ U32 mURLOption;
+ // does this notification persist across sessions? if so, it will be
+ // serialized to disk on first receipt and read on startup
+ bool mPersist;
+ // This is the name of the default functor, if present, to be
+ // used for the notification's callback. It is optional, and used only if
+ // the notification is constructed without an identified functor.
+ std::string mDefaultFunctor;
+ // The form data associated with a given notification (buttons, text boxes, etc)
+ LLNotificationFormPtr mForm;
+ // default priority for notifications of this type
+ ENotificationPriority mPriority;
+ // UUID of the audio file to be played when this notification arrives
+ // this is loaded as a name, but looked up to get the UUID upon template load.
+ // If null, it wasn't specified.
+ LLUUID mSoundEffect;
+};
+
+// we want to keep a map of these by name, and it's best to manage them
+// with smart pointers
+typedef boost::shared_ptr<LLNotificationTemplate> LLNotificationTemplatePtr;
+
+/**
+ * @class LLNotification
+ * @brief The object that expresses the details of a notification
+ *
+ * We make this noncopyable because
+ * we want to manage these through LLNotificationPtr, and only
+ * ever create one instance of any given notification.
+ *
+ * The enable_shared_from_this flag ensures that if we construct
+ * a smart pointer from a notification, we'll always get the same
+ * shared pointer.
+ */
+class LLNotification :
+ boost::noncopyable,
+ public boost::enable_shared_from_this<LLNotification>
+{
+LOG_CLASS(LLNotification);
+friend class LLNotifications;
+
+public:
+ // parameter object used to instantiate a new notification
+ class Params : public LLParamBlock<Params>
+ {
+ friend class LLNotification;
+ public:
+ Params(const std::string& _name)
+ : name(_name),
+ mTemporaryResponder(false),
+ functor_name(_name),
+ priority(NOTIFICATION_PRIORITY_UNSPECIFIED),
+ timestamp(LLDate::now())
+ {
+ }
+
+ // pseudo-param
+ Params& functor(LLNotificationFunctorRegistry::ResponseFunctor f)
+ {
+ functor_name = LLUUID::generateNewID().asString();
+ LLNotificationFunctorRegistry::instance().registerFunctor(functor_name, f);
+
+ mTemporaryResponder = true;
+ return *this;
+ }
+
+ LLMandatoryParam<std::string> name;
+
+ // optional
+ LLOptionalParam<LLSD> substitutions;
+ LLOptionalParam<LLSD> payload;
+ LLOptionalParam<ENotificationPriority> priority;
+ LLOptionalParam<LLSD> form_elements;
+ LLOptionalParam<LLDate> timestamp;
+ LLOptionalParam<LLNotificationContext*> context;
+ LLOptionalParam<std::string> functor_name;
+
+ private:
+ bool mTemporaryResponder;
+ };
+
+private:
+
+ LLUUID mId;
+ LLSD mPayload;
+ LLSD mSubstitutions;
+ LLDate mTimestamp;
+ LLDate mExpiresAt;
+ bool mCancelled;
+ bool mRespondedTo; // once the notification has been responded to, this becomes true
+ bool mIgnored;
+ ENotificationPriority mPriority;
+ LLNotificationFormPtr mForm;
+
+ // a reference to the template
+ LLNotificationTemplatePtr mTemplatep;
+
+ /*
+ We want to be able to store and reload notifications so that they can survive
+ a shutdown/restart of the client. So we can't simply pass in callbacks;
+ we have to specify a callback mechanism that can be used by name rather than
+ by some arbitrary pointer -- and then people have to initialize callbacks
+ in some useful location. So we use LLNotificationFunctorRegistry to manage them.
+ */
+ std::string mResponseFunctorName;
+
+ /*
+ In cases where we want to specify an explict, non-persisted callback,
+ we store that in the callback registry under a dynamically generated
+ key, and store the key in the notification, so we can still look it up
+ using the same mechanism.
+ */
+ bool mTemporaryResponder;
+
+ void init(const std::string& template_name, const LLSD& form_elements);
+
+ LLNotification(const Params& p);
+
+ // this is just for making it easy to look things up in a set organized by UUID -- DON'T USE IT
+ // for anything real!
+ LLNotification(LLUUID uuid) : mId(uuid) {}
+
+ void cancel();
+
+ bool payloadContainsAll(const std::vector<std::string>& required_fields) const;
+
+public:
+
+ // constructor from a saved notification
+ LLNotification(const LLSD& sd);
+
+ // This is a string formatter for substituting into the message directly
+ // from LLSD without going through the hopefully-to-be-obsoleted LLString
+ static std::string format(const std::string& text, const LLSD& substitutions);
+
+ void setResponseFunctor(std::string const &responseFunctorName);
+
+ typedef enum e_response_template_type
+ {
+ WITHOUT_DEFAULT_BUTTON,
+ WITH_DEFAULT_BUTTON
+ } EResponseTemplateType;
+
+ // return response LLSD filled in with default form contents and (optionally) the default button selected
+ LLSD getResponseTemplate(EResponseTemplateType type = WITHOUT_DEFAULT_BUTTON);
+
+ // returns index of first button with value==TRUE
+ // usually this the button the user clicked on
+ // returns -1 if no button clicked (e.g. form has not been displayed)
+ static S32 getSelectedOption(const LLSD& notification, const LLSD& response);
+ // returns name of first button with value==TRUE
+ static std::string getSelectedOptionName(const LLSD& notification);
+
+ // after someone responds to a notification (usually by clicking a button,
+ // but sometimes by filling out a little form and THEN clicking a button),
+ // the result of the response (the name and value of the button clicked,
+ // plus any other data) should be packaged up as LLSD, then passed as a
+ // parameter to the notification's respond() method here. This will look up
+ // and call the appropriate responder.
+ //
+ // response is notification serialized as LLSD:
+ // ["name"] = notification name
+ // ["form"] = LLSD tree that includes form description and any prefilled form data
+ // ["response"] = form data filled in by user
+ // (including, but not limited to which button they clicked on)
+ // ["payload"] = transaction specific data, such as ["source_id"] (originator of notification),
+ // ["item_id"] (attached inventory item), etc.
+ // ["substitutions"] = string substitutions used to generate notification message
+ // from the template
+ // ["time"] = time at which notification was generated;
+ // ["expiry"] = time at which notification expires;
+ // ["responseFunctor"] = name of registered functor that handles responses to notification;
+ LLSD asLLSD();
+
+ void respond(const LLSD& sd);
+
+ void setIgnored(bool ignore);
+
+ bool isCancelled() const
+ {
+ return mCancelled;
+ }
+
+ bool isRespondedTo() const
+ {
+ return mRespondedTo;
+ }
+
+ bool isIgnored() const
+ {
+ return mIgnored;
+ }
+
+ const std::string& getName() const
+ {
+ return mTemplatep->mName;
+ }
+
+ const LLUUID& id() const
+ {
+ return mId;
+ }
+
+ const LLSD& getPayload() const
+ {
+ return mPayload;
+ }
+
+ const LLSD& getSubstitutions() const
+ {
+ return mSubstitutions;
+ }
+
+ const LLDate& getDate() const
+ {
+ return mTimestamp;
+ }
+
+ std::string getType() const
+ {
+ return (mTemplatep ? mTemplatep->mType : "");
+ }
+
+ std::string getMessage() const;
+ std::string getLabel() const;
+
+ std::string getURL() const
+ {
+ return (mTemplatep ? mTemplatep->mURL : "");
+ }
+
+ S32 getURLOption() const
+ {
+ return (mTemplatep ? mTemplatep->mURLOption : -1);
+ }
+
+ const LLNotificationFormPtr getForm();
+
+ const LLDate getExpiration() const
+ {
+ return mExpiresAt;
+ }
+
+ ENotificationPriority getPriority() const
+ {
+ return mPriority;
+ }
+
+ const LLUUID getID() const
+ {
+ return mId;
+ }
+
+ // comparing two notifications normally means comparing them by UUID (so we can look them
+ // up quickly this way)
+ bool operator<(const LLNotification& rhs) const
+ {
+ return mId < rhs.mId;
+ }
+
+ bool operator==(const LLNotification& rhs) const
+ {
+ return mId == rhs.mId;
+ }
+
+ bool operator!=(const LLNotification& rhs) const
+ {
+ return !operator==(rhs);
+ }
+
+ bool isSameObjectAs(const LLNotification* rhs) const
+ {
+ return this == rhs;
+ }
+
+ // this object has been updated, so tell all our clients
+ void update();
+
+ void updateFrom(LLNotificationPtr other);
+
+ // A fuzzy equals comparator.
+ // true only if both notifications have the same template and
+ // 1) flagged as unique (there can be only one of these) OR
+ // 2) all required payload fields of each also exist in the other.
+ bool isEquivalentTo(LLNotificationPtr that) const;
+
+ // if the current time is greater than the expiration, the notification is expired
+ bool isExpired() const
+ {
+ if (mExpiresAt.secondsSinceEpoch() == 0)
+ {
+ return false;
+ }
+
+ LLDate rightnow = LLDate::now();
+ return rightnow > mExpiresAt;
+ }
+
+ std::string summarize() const;
+
+ bool hasUniquenessConstraints() const { return (mTemplatep ? mTemplatep->mUnique : false);}
+
+ virtual ~LLNotification() {}
+};
+
+std::ostream& operator<<(std::ostream& s, const LLNotification& notification);
+
+namespace LLNotificationFilters
+{
+ // a sample filter
+ bool includeEverything(LLNotificationPtr p);
+
+ typedef enum e_comparison
+ {
+ EQUAL,
+ LESS,
+ GREATER,
+ LESS_EQUAL,
+ GREATER_EQUAL
+ } EComparison;
+
+ // generic filter functor that takes method or member variable reference
+ template<typename T>
+ struct filterBy
+ {
+ typedef boost::function<T (LLNotificationPtr)> field_t;
+ typedef typename boost::remove_reference<T>::type value_t;
+
+ filterBy(field_t field, value_t value, EComparison comparison = EQUAL)
+ : mField(field),
+ mFilterValue(value),
+ mComparison(comparison)
+ {
+ }
+
+ bool operator()(LLNotificationPtr p)
+ {
+ switch(mComparison)
+ {
+ case EQUAL:
+ return mField(p) == mFilterValue;
+ case LESS:
+ return mField(p) < mFilterValue;
+ case GREATER:
+ return mField(p) > mFilterValue;
+ case LESS_EQUAL:
+ return mField(p) <= mFilterValue;
+ case GREATER_EQUAL:
+ return mField(p) >= mFilterValue;
+ default:
+ return false;
+ }
+ }
+
+ field_t mField;
+ value_t mFilterValue;
+ EComparison mComparison;
+ };
+};
+
+namespace LLNotificationComparators
+{
+ typedef enum e_direction { ORDER_DECREASING, ORDER_INCREASING } EDirection;
+
+ // generic order functor that takes method or member variable reference
+ template<typename T>
+ struct orderBy
+ {
+ typedef boost::function<T (LLNotificationPtr)> field_t;
+ orderBy(field_t field, EDirection = ORDER_INCREASING) : mField(field) {}
+ bool operator()(LLNotificationPtr lhs, LLNotificationPtr rhs)
+ {
+ if (mDirection == ORDER_DECREASING)
+ {
+ return mField(lhs) > mField(rhs);
+ }
+ else
+ {
+ return mField(lhs) < mField(rhs);
+ }
+ }
+
+ field_t mField;
+ EDirection mDirection;
+ };
+
+ struct orderByUUID : public orderBy<const LLUUID&>
+ {
+ orderByUUID(EDirection direction = ORDER_INCREASING) : orderBy<const LLUUID&>(&LLNotification::id, direction) {}
+ };
+
+ struct orderByDate : public orderBy<const LLDate&>
+ {
+ orderByDate(EDirection direction = ORDER_INCREASING) : orderBy<const LLDate&>(&LLNotification::getDate, direction) {}
+ };
+};
+
+typedef boost::function<bool (LLNotificationPtr)> LLNotificationFilter;
+typedef boost::function<bool (LLNotificationPtr, LLNotificationPtr)> LLNotificationComparator;
+typedef std::set<LLNotificationPtr, LLNotificationComparator> LLNotificationSet;
+typedef std::multimap<std::string, LLNotificationPtr> LLNotificationMap;
+
+// ========================================================
+// Abstract base class (interface) for a channel; also used for the master container.
+// This lets us arrange channels into a call hierarchy.
+
+// We maintain a heirarchy of notification channels; events are always started at the top
+// and propagated through the hierarchy only if they pass a filter.
+// Any channel can be created with a parent. A null parent (empty string) means it's
+// tied to the root of the tree (the LLNotifications class itself).
+// The default hierarchy looks like this:
+//
+// LLNotifications --+-- Expiration --+-- Mute --+-- Ignore --+-- Visible --+-- History
+// +-- Alerts
+// +-- Notifications
+//
+// In general, new channels that want to only see notifications that pass through
+// all of the built-in tests should attach to the "Visible" channel
+//
+class LLNotificationChannelBase :
+ public boost::signals::trackable
+{
+ LOG_CLASS(LLNotificationChannelBase);
+public:
+ LLNotificationChannelBase(LLNotificationFilter filter, LLNotificationComparator comp) :
+ mFilter(filter), mItems(comp)
+ {}
+ virtual ~LLNotificationChannelBase() {}
+ // you can also connect to a Channel, so you can be notified of
+ // changes to this channel
+ virtual void connectChanged(const LLStandardSignal::slot_type& slot);
+ virtual void connectPassedFilter(const LLStandardSignal::slot_type& slot);
+ virtual void connectFailedFilter(const LLStandardSignal::slot_type& slot);
+
+ // use this when items change or to add a new one
+ bool updateItem(const LLSD& payload);
+ const LLNotificationFilter& getFilter() { return mFilter; }
+
+protected:
+ LLNotificationSet mItems;
+ LLStandardSignal mChanged;
+ LLStandardSignal mPassedFilter;
+ LLStandardSignal mFailedFilter;
+
+ // these are action methods that subclasses can override to take action
+ // on specific types of changes; the management of the mItems list is
+ // still handled by the generic handler.
+ virtual void onLoad(LLNotificationPtr p) {}
+ virtual void onAdd(LLNotificationPtr p) {}
+ virtual void onDelete(LLNotificationPtr p) {}
+ virtual void onChange(LLNotificationPtr p) {}
+
+ bool updateItem(const LLSD& payload, LLNotificationPtr pNotification);
+ LLNotificationFilter mFilter;
+};
+
+// manages a list of notifications
+// Note that if this is ever copied around, we might find ourselves with multiple copies
+// of a queue with notifications being added to different nonequivalent copies. So we
+// make it inherit from boost::noncopyable, and then create a map of shared_ptr to manage it.
+//
+// NOTE: LLNotificationChannel is self-registering. The *correct* way to create one is to
+// do something like:
+// new LLNotificationChannel("name", "parent"...);
+// You can then retrieve the channel by using the registry:
+// LLNotifications::instance().getChannel("name")...
+//
+class LLNotificationChannel :
+ boost::noncopyable,
+ public LLNotificationChannelBase
+{
+ LOG_CLASS(LLNotificationChannel);
+
+public:
+ virtual ~LLNotificationChannel() {}
+ // Notification Channels have a filter, which determines which notifications
+ // will be added to this channel.
+ // Channel filters cannot change.
+ LLNotificationChannel(const std::string& name, const std::string& parent,
+ LLNotificationFilter filter=LLNotificationFilters::includeEverything,
+ LLNotificationComparator comparator=LLNotificationComparators::orderByUUID());
+
+ typedef LLNotificationSet::iterator Iterator;
+
+ std::string getName() const { return mName; }
+ std::string getParentChannelName() { return mParent; }
+
+ bool isEmpty() const;
+
+ Iterator begin();
+ Iterator end();
+
+ // Channels have a comparator to control sort order;
+ // the default sorts by arrival date
+ void setComparator(LLNotificationComparator comparator);
+
+ std::string summarize();
+
+private:
+ std::string mName;
+ std::string mParent;
+ LLNotificationComparator mComparator;
+};
+
+
+
+// The type of the pointers that we're going to manage in the NotificationQueue system
+// Because LLNotifications is a singleton, we don't actually expect to ever
+// destroy it, but if it becomes necessary to do so, the shared_ptr model
+// will ensure that we don't leak resources.
+typedef boost::shared_ptr<LLNotificationChannel> LLNotificationChannelPtr;
+
+class LLNotifications :
+ public LLSingleton<LLNotifications>,
+ public LLNotificationChannelBase
+{
+ LOG_CLASS(LLNotifications);
+
+ friend class LLSingleton<LLNotifications>;
+public:
+ // load notification descriptions from file;
+ // OK to call more than once because it will reload
+ bool loadTemplates();
+ LLXMLNodePtr checkForXMLTemplate(LLXMLNodePtr item);
+
+ // we provide a collection of simple add notification functions so that it's reasonable to create notifications in one line
+ LLNotificationPtr add(const std::string& name,
+ const LLSD& substitutions = LLSD(),
+ const LLSD& payload = LLSD());
+ LLNotificationPtr add(const std::string& name,
+ const LLSD& substitutions,
+ const LLSD& payload,
+ const std::string& functor_name);
+ LLNotificationPtr add(const std::string& name,
+ const LLSD& substitutions,
+ const LLSD& payload,
+ LLNotificationFunctorRegistry::ResponseFunctor functor);
+ LLNotificationPtr add(const LLNotification::Params& p);
+
+ void add(const LLNotificationPtr pNotif);
+ void cancel(LLNotificationPtr pNotif);
+ void update(const LLNotificationPtr pNotif);
+
+ LLNotificationPtr find(LLUUID uuid);
+
+ typedef boost::function<void (LLNotificationPtr)> NotificationProcess;
+
+ void forEachNotification(NotificationProcess process);
+
+ // This is all stuff for managing the templates
+ // take your template out
+ LLNotificationTemplatePtr getTemplate(const std::string& name);
+
+ // get the whole collection
+ typedef std::vector<std::string> TemplateNames;
+ TemplateNames getTemplateNames() const; // returns a list of notification names
+
+ typedef std::map<std::string, LLNotificationTemplatePtr> TemplateMap;
+
+ TemplateMap::const_iterator templatesBegin() { return mTemplates.begin(); }
+ TemplateMap::const_iterator templatesEnd() { return mTemplates.end(); }
+
+ // test for existence
+ bool templateExists(const std::string& name);
+ // useful if you're reloading the file
+ void clearTemplates(); // erase all templates
+
+ void forceResponse(const LLNotification::Params& params, S32 option);
+
+ void createDefaultChannels();
+
+ typedef std::map<std::string, LLNotificationChannelPtr> ChannelMap;
+ ChannelMap mChannels;
+
+ void addChannel(LLNotificationChannelPtr pChan);
+ LLNotificationChannelPtr getChannel(const std::string& channelName);
+
+ std::string getGlobalString(const std::string& key) const;
+
+private:
+ // we're a singleton, so we don't have a public constructor
+ LLNotifications();
+ /*virtual*/ void initSingleton();
+
+ void loadPersistentNotifications();
+
+ bool expirationFilter(LLNotificationPtr pNotification);
+ bool expirationHandler(const LLSD& payload);
+ bool uniqueFilter(LLNotificationPtr pNotification);
+ bool uniqueHandler(const LLSD& payload);
+ bool failedUniquenessTest(const LLSD& payload);
+ LLNotificationChannelPtr pHistoryChannel;
+ LLNotificationChannelPtr pExpirationChannel;
+
+ // put your template in
+ bool addTemplate(const std::string& name, LLNotificationTemplatePtr theTemplate);
+ TemplateMap mTemplates;
+
+ std::string mFileName;
+
+ typedef std::map<std::string, LLXMLNodePtr> XMLTemplateMap;
+ XMLTemplateMap mXmlTemplates;
+
+ LLNotificationMap mUniqueNotifications;
+
+ typedef std::map<std::string, std::string> GlobalStringMap;
+ GlobalStringMap mGlobalStrings;
+};
+
+
+#endif//LL_LLNOTIFICATIONS_H
+
diff --git a/indra/llui/llpanel.cpp b/indra/llui/llpanel.cpp
index 22934450e7..2edbbe6abc 100644
--- a/indra/llui/llpanel.cpp
+++ b/indra/llui/llpanel.cpp
@@ -58,8 +58,6 @@
#include "llresizebar.h"
#include "llcriticaldamp.h"
-LLPanel::alert_queue_t LLPanel::sAlertQueue;
-
const S32 RESIZE_BAR_OVERLAP = 1;
const S32 RESIZE_BAR_HEIGHT = 3;
@@ -344,14 +342,14 @@ BOOL LLPanel::checkRequirements()
{
if (!mRequirementsError.empty())
{
- LLStringUtil::format_map_t args;
- args["[COMPONENTS]"] = mRequirementsError;
- args["[FLOATER]"] = getName();
+ LLSD args;
+ args["COMPONENTS"] = mRequirementsError;
+ args["FLOATER"] = getName();
llwarns << getName() << " failed requirements check on: \n"
<< mRequirementsError << llendl;
-
- alertXml(std::string("FailedRequirementsCheck"), args);
+
+ LLNotifications::instance().add(LLNotification::Params("FailedRequirementsCheck").payload(args));
mRequirementsError.clear();
return FALSE;
}
@@ -359,25 +357,6 @@ BOOL LLPanel::checkRequirements()
return TRUE;
}
-//static
-void LLPanel::alertXml(const std::string& label, LLStringUtil::format_map_t args)
-{
- sAlertQueue.push(LLAlertInfo(label,args));
-}
-
-//static
-BOOL LLPanel::nextAlert(LLAlertInfo &alert)
-{
- if (!sAlertQueue.empty())
- {
- alert = sAlertQueue.front();
- sAlertQueue.pop();
- return TRUE;
- }
-
- return FALSE;
-}
-
void LLPanel::setFocus(BOOL b)
{
if( b )
@@ -1039,9 +1018,9 @@ void LLPanel::childDisplayNotFound()
mExpectedMembers.insert(*itor);
}
mNewExpectedMembers.clear();
- LLStringUtil::format_map_t args;
- args["[CONTROLS]"] = msg;
- LLAlertDialog::showXml("FloaterNotFound", args);
+ LLSD args;
+ args["CONTROLS"] = msg;
+ LLNotifications::instance().add("FloaterNotFound", args);
}
void LLPanel::storeRectControl()
@@ -1065,6 +1044,8 @@ struct LLLayoutStack::LLEmbeddedPanel
mAutoResize(auto_resize),
mUserResize(user_resize),
mOrientation(orientation),
+ mCollapsed(FALSE),
+ mCollapseAmt(0.f),
mVisibleAmt(1.f) // default to fully visible
{
LLResizeBar::Side side = (orientation == HORIZONTAL) ? LLResizeBar::RIGHT : LLResizeBar::BOTTOM;
@@ -1095,14 +1076,28 @@ struct LLLayoutStack::LLEmbeddedPanel
mResizeBar = NULL;
}
+ F32 getCollapseFactor()
+ {
+ if (mOrientation == HORIZONTAL)
+ {
+ return mVisibleAmt * clamp_rescale(mCollapseAmt, 0.f, 1.f, 1.f, (F32)mMinWidth / (F32)mPanel->getRect().getWidth());
+ }
+ else
+ {
+ return mVisibleAmt * clamp_rescale(mCollapseAmt, 0.f, 1.f, 1.f, (F32)mMinHeight / (F32)mPanel->getRect().getHeight());
+ }
+ }
+
LLPanel* mPanel;
S32 mMinWidth;
S32 mMinHeight;
BOOL mAutoResize;
BOOL mUserResize;
+ BOOL mCollapsed;
LLResizeBar* mResizeBar;
eLayoutOrientation mOrientation;
F32 mVisibleAmt;
+ F32 mCollapseAmt;
};
static LLRegisterWidget<LLLayoutStack> r2("layout_stack");
@@ -1123,28 +1118,27 @@ LLLayoutStack::~LLLayoutStack()
void LLLayoutStack::draw()
{
updateLayout();
+
+ e_panel_list_t::iterator panel_it;
+ for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it)
{
- e_panel_list_t::iterator panel_it;
- for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it)
+ // clip to layout rectangle, not bounding rectangle
+ LLRect clip_rect = (*panel_it)->mPanel->getRect();
+ // scale clipping rectangle by visible amount
+ if (mOrientation == HORIZONTAL)
{
- // clip to layout rectangle, not bounding rectangle
- LLRect clip_rect = (*panel_it)->mPanel->getRect();
- // scale clipping rectangle by visible amount
- if (mOrientation == HORIZONTAL)
- {
- clip_rect.mRight = clip_rect.mLeft + llround((F32)clip_rect.getWidth() * (*panel_it)->mVisibleAmt);
- }
- else
- {
- clip_rect.mBottom = clip_rect.mTop - llround((F32)clip_rect.getHeight() * (*panel_it)->mVisibleAmt);
- }
+ clip_rect.mRight = clip_rect.mLeft + llround((F32)clip_rect.getWidth() * (*panel_it)->getCollapseFactor());
+ }
+ else
+ {
+ clip_rect.mBottom = clip_rect.mTop - llround((F32)clip_rect.getHeight() * (*panel_it)->getCollapseFactor());
+ }
- LLPanel* panelp = (*panel_it)->mPanel;
+ LLPanel* panelp = (*panel_it)->mPanel;
- LLLocalClipRect clip(clip_rect);
- // only force drawing invisible children if visible amount is non-zero
- drawChild(panelp, 0, 0, !clip_rect.isNull());
- }
+ LLLocalClipRect clip(clip_rect);
+ // only force drawing invisible children if visible amount is non-zero
+ drawChild(panelp, 0, 0, !clip_rect.isNull());
}
}
@@ -1276,8 +1270,13 @@ S32 LLLayoutStack::getDefaultWidth(S32 cur_width)
return cur_width;
}
-void LLLayoutStack::addPanel(LLPanel* panel, S32 min_width, S32 min_height, BOOL auto_resize, BOOL user_resize, S32 index)
+void LLLayoutStack::addPanel(LLPanel* panel, S32 min_width, S32 min_height, BOOL auto_resize, BOOL user_resize, EAnimate animate, S32 index)
{
+ // panel starts off invisible (collapsed)
+ if (animate == ANIMATE)
+ {
+ panel->setVisible(FALSE);
+ }
LLEmbeddedPanel* embedded_panel = new LLEmbeddedPanel(panel, mOrientation, min_width, min_height, auto_resize, user_resize);
mPanels.insert(mPanels.begin() + llclamp(index, 0, (S32)mPanels.size()), embedded_panel);
@@ -1293,6 +1292,11 @@ void LLLayoutStack::addPanel(LLPanel* panel, S32 min_width, S32 min_height, BOOL
sendChildToFront(resize_barp);
}
+ // start expanding panel animation
+ if (animate == ANIMATE)
+ {
+ panel->setVisible(TRUE);
+ }
}
void LLLayoutStack::removePanel(LLPanel* panel)
@@ -1300,6 +1304,14 @@ void LLLayoutStack::removePanel(LLPanel* panel)
removeChild(panel);
}
+void LLLayoutStack::collapsePanel(LLPanel* panel, BOOL collapsed)
+{
+ LLEmbeddedPanel* panel_container = findEmbeddedPanel(panel);
+ if (!panel_container) return;
+
+ panel_container->mCollapsed = collapsed;
+}
+
void LLLayoutStack::updateLayout(BOOL force_resize)
{
calcMinExtents();
@@ -1332,6 +1344,15 @@ void LLLayoutStack::updateLayout(BOOL force_resize)
}
}
+ if ((*panel_it)->mCollapsed)
+ {
+ (*panel_it)->mCollapseAmt = lerp((*panel_it)->mCollapseAmt, 1.f, LLCriticalDamp::getInterpolant(ANIM_CLOSE_TIME));
+ }
+ else
+ {
+ (*panel_it)->mCollapseAmt = lerp((*panel_it)->mCollapseAmt, 0.f, LLCriticalDamp::getInterpolant(ANIM_CLOSE_TIME));
+ }
+
if (mOrientation == HORIZONTAL)
{
// enforce minimize size constraint by default
@@ -1339,7 +1360,7 @@ void LLLayoutStack::updateLayout(BOOL force_resize)
{
panelp->reshape((*panel_it)->mMinWidth, panelp->getRect().getHeight());
}
- total_width += llround(panelp->getRect().getWidth() * (*panel_it)->mVisibleAmt);
+ total_width += llround(panelp->getRect().getWidth() * (*panel_it)->getCollapseFactor());
// want n-1 panel gaps for n panels
if (panel_it != mPanels.begin())
{
@@ -1353,7 +1374,7 @@ void LLLayoutStack::updateLayout(BOOL force_resize)
{
panelp->reshape(panelp->getRect().getWidth(), (*panel_it)->mMinHeight);
}
- total_height += llround(panelp->getRect().getHeight() * (*panel_it)->mVisibleAmt);
+ total_height += llround(panelp->getRect().getHeight() * (*panel_it)->getCollapseFactor());
if (panel_it != mPanels.begin())
{
total_height += mPanelSpacing;
@@ -1367,7 +1388,7 @@ void LLLayoutStack::updateLayout(BOOL force_resize)
for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it)
{
// panels that are not fully visible do not count towards shrink headroom
- if ((*panel_it)->mVisibleAmt < 1.f)
+ if ((*panel_it)->getCollapseFactor() < 1.f)
{
continue;
}
@@ -1431,7 +1452,7 @@ void LLLayoutStack::updateLayout(BOOL force_resize)
S32 delta_size = 0;
// if panel can automatically resize (not animating, and resize flag set)...
- if ((*panel_it)->mVisibleAmt == 1.f
+ if ((*panel_it)->getCollapseFactor() == 1.f
&& (force_resize || (*panel_it)->mAutoResize)
&& !(*panel_it)->mResizeBar->hasMouseCapture())
{
@@ -1515,11 +1536,11 @@ void LLLayoutStack::updateLayout(BOOL force_resize)
if (mOrientation == HORIZONTAL)
{
- cur_x += llround(new_width * (*panel_it)->mVisibleAmt) + mPanelSpacing;
+ cur_x += llround(new_width * (*panel_it)->getCollapseFactor()) + mPanelSpacing;
}
else //VERTICAL
{
- cur_y -= llround(new_height * (*panel_it)->mVisibleAmt) + mPanelSpacing;
+ cur_y -= llround(new_height * (*panel_it)->getCollapseFactor()) + mPanelSpacing;
}
}
diff --git a/indra/llui/llpanel.h b/indra/llui/llpanel.h
index c55eb6bba2..da876667e6 100644
--- a/indra/llui/llpanel.h
+++ b/indra/llui/llpanel.h
@@ -49,23 +49,13 @@ const BOOL BORDER_YES = TRUE;
const BOOL BORDER_NO = FALSE;
-struct LLAlertInfo
-{
- std::string mLabel;
- LLStringUtil::format_map_t mArgs;
-
- LLAlertInfo(std::string label, LLStringUtil::format_map_t args) : mLabel(label), mArgs(args) { }
- LLAlertInfo(){}
-};
-
-
/*
* General purpose concrete view base class.
* Transparent or opaque,
* With or without border,
* Can contain LLUICtrls.
*/
-class LLPanel : public LLUICtrl
+class LLPanel : public LLUICtrl, public boost::signals::trackable
{
public:
@@ -227,8 +217,6 @@ public:
void childNotFound(const std::string& id) const;
void childDisplayNotFound();
- static void alertXml(const std::string& label, LLStringUtil::format_map_t args = LLStringUtil::format_map_t());
- static BOOL nextAlert(LLAlertInfo &alert);
static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory);
protected:
@@ -266,8 +254,6 @@ private:
std::string mRequirementsError;
- typedef std::queue<LLAlertInfo> alert_queue_t;
- static alert_queue_t sAlertQueue;
}; // end class LLPanel
@@ -292,8 +278,16 @@ public:
S32 getMinWidth() const { return mMinWidth; }
S32 getMinHeight() const { return mMinHeight; }
- void addPanel(LLPanel* panel, S32 min_width, S32 min_height, BOOL auto_resize, BOOL user_resize, S32 index = S32_MAX);
+ typedef enum e_animate
+ {
+ NO_ANIMATE,
+ ANIMATE
+ } EAnimate;
+
+ void addPanel(LLPanel* panel, S32 min_width, S32 min_height, BOOL auto_resize, BOOL user_resize, EAnimate animate = NO_ANIMATE, S32 index = S32_MAX);
void removePanel(LLPanel* panel);
+ void collapsePanel(LLPanel* panel, BOOL collapsed = TRUE);
+ S32 getNumPanels() { return mPanels.size(); }
private:
struct LLEmbeddedPanel;
diff --git a/indra/llui/llscrollbar.cpp b/indra/llui/llscrollbar.cpp
index a7163323bd..734b3ab381 100644
--- a/indra/llui/llscrollbar.cpp
+++ b/indra/llui/llscrollbar.cpp
@@ -127,6 +127,8 @@ LLScrollbar::LLScrollbar(
}
line_up_btn->setHeldDownCallback( &LLScrollbar::onLineUpBtnPressed );
line_up_btn->setTabStop(FALSE);
+ line_up_btn->setScaleImage(TRUE);
+
addChild(line_up_btn);
LLButton* line_down_btn = new LLButton(std::string("Line Down"), line_down_rect,
@@ -136,6 +138,7 @@ LLScrollbar::LLScrollbar(
line_down_btn->setFollowsBottom();
line_down_btn->setHeldDownCallback( &LLScrollbar::onLineDownBtnPressed );
line_down_btn->setTabStop(FALSE);
+ line_down_btn->setScaleImage(TRUE);
addChild(line_down_btn);
}
@@ -148,20 +151,29 @@ LLScrollbar::~LLScrollbar()
void LLScrollbar::setDocParams( S32 size, S32 pos )
{
mDocSize = size;
- mDocPos = llclamp( pos, 0, getDocPosMax() );
+ setDocPos(pos);
mDocChanged = TRUE;
updateThumbRect();
}
-void LLScrollbar::setDocPos(S32 pos)
+void LLScrollbar::setDocPos(S32 pos, BOOL update_thumb)
{
+ pos = llclamp(pos, 0, getDocPosMax());
if (pos != mDocPos)
{
- mDocPos = llclamp( pos, 0, getDocPosMax() );
+ mDocPos = pos;
mDocChanged = TRUE;
- updateThumbRect();
+ if( mChangeCallback )
+ {
+ mChangeCallback( mDocPos, this, mCallbackUserData );
+ }
+
+ if( update_thumb )
+ {
+ updateThumbRect();
+ }
}
}
@@ -170,7 +182,7 @@ void LLScrollbar::setDocSize(S32 size)
if (size != mDocSize)
{
mDocSize = size;
- mDocPos = llclamp( mDocPos, 0, getDocPosMax() );
+ setDocPos(mDocPos);
mDocChanged = TRUE;
updateThumbRect();
@@ -182,7 +194,7 @@ void LLScrollbar::setPageSize( S32 page_size )
if (page_size != mPageSize)
{
mPageSize = page_size;
- mDocPos = llclamp( mDocPos, 0, getDocPosMax() );
+ setDocPos(mDocPos);
mDocChanged = TRUE;
updateThumbRect();
@@ -208,9 +220,9 @@ void LLScrollbar::updateThumbRect()
const S32 THUMB_MIN_LENGTH = 16;
S32 window_length = (mOrientation == LLScrollbar::HORIZONTAL) ? getRect().getWidth() : getRect().getHeight();
- S32 thumb_bg_length = window_length - 2 * SCROLLBAR_SIZE;
+ S32 thumb_bg_length = llmax(0, window_length - 2 * SCROLLBAR_SIZE);
S32 visible_lines = llmin( mDocSize, mPageSize );
- S32 thumb_length = mDocSize ? llmax( visible_lines * thumb_bg_length / mDocSize, THUMB_MIN_LENGTH ) : thumb_bg_length;
+ S32 thumb_length = mDocSize ? llmin(llmax( visible_lines * thumb_bg_length / mDocSize, THUMB_MIN_LENGTH), thumb_bg_length) : thumb_bg_length;
S32 variable_lines = mDocSize - visible_lines;
@@ -218,7 +230,7 @@ void LLScrollbar::updateThumbRect()
{
S32 thumb_start_max = thumb_bg_length + SCROLLBAR_SIZE;
S32 thumb_start_min = SCROLLBAR_SIZE + THUMB_MIN_LENGTH;
- S32 thumb_start = variable_lines ? llclamp( thumb_start_max - (mDocPos * (thumb_bg_length - thumb_length)) / variable_lines, thumb_start_min, thumb_start_max ) : thumb_start_max;
+ S32 thumb_start = variable_lines ? llmin( llmax(thumb_start_max - (mDocPos * (thumb_bg_length - thumb_length)) / variable_lines, thumb_start_min), thumb_start_max ) : thumb_start_max;
mThumbRect.mLeft = 0;
mThumbRect.mTop = thumb_start;
@@ -230,7 +242,7 @@ void LLScrollbar::updateThumbRect()
// Horizontal
S32 thumb_start_max = thumb_bg_length + SCROLLBAR_SIZE - thumb_length;
S32 thumb_start_min = SCROLLBAR_SIZE;
- S32 thumb_start = variable_lines ? llclamp( thumb_start_min + (mDocPos * (thumb_bg_length - thumb_length)) / variable_lines, thumb_start_min, thumb_start_max ) : thumb_start_min;
+ S32 thumb_start = variable_lines ? llmin(llmax( thumb_start_min + (mDocPos * (thumb_bg_length - thumb_length)) / variable_lines, thumb_start_min), thumb_start_max ) : thumb_start_min;
mThumbRect.mLeft = thumb_start;
mThumbRect.mTop = SCROLLBAR_SIZE;
@@ -446,7 +458,7 @@ BOOL LLScrollbar::handleMouseUp(S32 x, S32 y, MASK mask)
}
else
{
- // Opaque, so don't just check children
+ // Opaque, so don't just check children
handled = LLView::handleMouseUp( x, y, mask );
}
@@ -455,13 +467,31 @@ BOOL LLScrollbar::handleMouseUp(S32 x, S32 y, MASK mask)
void LLScrollbar::reshape(S32 width, S32 height, BOOL called_from_parent)
{
+ if (width == getRect().getWidth() && height == getRect().getHeight()) return;
LLView::reshape( width, height, called_from_parent );
+ LLButton* up_button = getChild<LLButton>("Line Up");
+ LLButton* down_button = getChild<LLButton>("Line Down");
+
+ if (mOrientation == VERTICAL)
+ {
+ up_button->reshape(up_button->getRect().getWidth(), llmin(getRect().getHeight() / 2, SCROLLBAR_SIZE));
+ down_button->reshape(down_button->getRect().getWidth(), llmin(getRect().getHeight() / 2, SCROLLBAR_SIZE));
+ up_button->setOrigin(up_button->getRect().mLeft, getRect().getHeight() - up_button->getRect().getHeight());
+ }
+ else
+ {
+ up_button->reshape(llmin(getRect().getWidth() / 2, SCROLLBAR_SIZE), up_button->getRect().getHeight());
+ down_button->reshape(llmin(getRect().getWidth() / 2, SCROLLBAR_SIZE), down_button->getRect().getHeight());
+ down_button->setOrigin(getRect().getWidth() - down_button->getRect().getWidth(), down_button->getRect().mBottom);
+ }
updateThumbRect();
}
void LLScrollbar::draw()
{
+ if (!getRect().isValid()) return;
+
S32 local_mouse_x;
S32 local_mouse_y;
LLUI::getCursorPositionLocal(this, &local_mouse_x, &local_mouse_y);
@@ -531,21 +561,7 @@ void LLScrollbar::draw()
void LLScrollbar::changeLine( S32 delta, BOOL update_thumb )
{
- S32 new_pos = llclamp( mDocPos + delta, 0, getDocPosMax() );
- if( new_pos != mDocPos )
- {
- mDocPos = new_pos;
- }
-
- if( mChangeCallback )
- {
- mChangeCallback( mDocPos, this, mCallbackUserData );
- }
-
- if( update_thumb )
- {
- updateThumbRect();
- }
+ setDocPos(mDocPos + delta, update_thumb);
}
void LLScrollbar::setValue(const LLSD& value)
@@ -561,22 +577,22 @@ BOOL LLScrollbar::handleKeyHere(KEY key, MASK mask)
switch( key )
{
case KEY_HOME:
- changeLine( -mDocPos, TRUE );
+ setDocPos( 0 );
handled = TRUE;
break;
case KEY_END:
- changeLine( getDocPosMax() - mDocPos, TRUE );
+ setDocPos( getDocPosMax() );
handled = TRUE;
break;
case KEY_DOWN:
- changeLine( mStepSize, TRUE );
+ setDocPos( getDocPos() + mStepSize );
handled = TRUE;
break;
case KEY_UP:
- changeLine( - mStepSize, TRUE );
+ setDocPos( getDocPos() - mStepSize );
handled = TRUE;
break;
diff --git a/indra/llui/llscrollbar.h b/indra/llui/llscrollbar.h
index d35dc10bd4..5fdacd43f8 100644
--- a/indra/llui/llscrollbar.h
+++ b/indra/llui/llscrollbar.h
@@ -81,7 +81,7 @@ public:
// How many "lines" the "document" has scrolled.
// 0 <= DocPos <= DocSize - DocVisibile
- void setDocPos( S32 pos );
+ void setDocPos( S32 pos, BOOL update_thumb = TRUE );
S32 getDocPos() const { return mDocPos; }
BOOL isAtBeginning();
diff --git a/indra/llui/llscrolllistctrl.cpp b/indra/llui/llscrolllistctrl.cpp
index c0f21ba9e5..ea0c6d4b2f 100644
--- a/indra/llui/llscrolllistctrl.cpp
+++ b/indra/llui/llscrolllistctrl.cpp
@@ -378,6 +378,22 @@ void LLScrollListText::draw(const LLColor4& color, const LLColor4& highlight_col
TRUE);
}
+LLScrollListDate::LLScrollListDate( const LLDate& date, const LLFontGL* font, S32 width, U8 font_style, LLFontGL::HAlign font_alignment, LLColor4& color, BOOL use_color, BOOL visible)
+: LLScrollListText(date.asRFC1123(), font, width, font_style, font_alignment, color, use_color, visible),
+ mDate(date)
+{
+}
+
+void LLScrollListDate::setValue(const LLSD& value)
+{
+ mDate = value.asDate();
+ LLScrollListText::setValue(mDate.asRFC1123());
+}
+
+const LLSD LLScrollListDate::getValue() const
+{
+ return mDate;
+}
LLScrollListItem::~LLScrollListItem()
{
@@ -578,6 +594,7 @@ LLScrollListCtrl::LLScrollListCtrl(const std::string& name, const LLRect& rect,
mSearchColumn(0),
mNumDynamicWidthColumns(0),
mTotalStaticColumnWidth(0),
+ mTotalColumnPadding(0),
mSorted(TRUE),
mDirty(FALSE),
mOriginalSelection(-1),
@@ -627,6 +644,28 @@ LLScrollListCtrl::LLScrollListCtrl(const std::string& name, const LLRect& rect,
mLastSelected = NULL;
}
+S32 LLScrollListCtrl::getSearchColumn()
+{
+ // search for proper search column
+ if (mSearchColumn < 0)
+ {
+ LLScrollListItem* itemp = getFirstData();
+ if (itemp)
+ {
+ for(S32 column = 0; column < getNumColumns(); column++)
+ {
+ LLScrollListCell* cell = itemp->getColumn(column);
+ if (cell && cell->isText())
+ {
+ mSearchColumn = column;
+ break;
+ }
+ }
+ }
+ }
+ return llclamp(mSearchColumn, 0, getNumColumns());
+}
+
LLScrollListCtrl::~LLScrollListCtrl()
{
std::for_each(mItemList.begin(), mItemList.end(), DeletePointer());
@@ -890,8 +929,8 @@ BOOL LLScrollListCtrl::addItem( LLScrollListItem* item, EAddPosition pos, BOOL r
// *TODO: Use bookkeeping to make this an incramental cost with item additions
void LLScrollListCtrl::calcColumnWidths()
{
- const S32 HEADING_TEXT_PADDING = 30;
- const S32 COLUMN_TEXT_PADDING = 20;
+ const S32 HEADING_TEXT_PADDING = 25;
+ const S32 COLUMN_TEXT_PADDING = 10;
mMaxContentWidth = 0;
@@ -904,20 +943,17 @@ void LLScrollListCtrl::calcColumnWidths()
if (!column) continue;
// update column width
- S32 new_width = column->mWidth;
+ S32 new_width = column->getWidth();
if (column->mRelWidth >= 0)
{
new_width = (S32)llround(column->mRelWidth*mItemListRect.getWidth());
}
else if (column->mDynamicWidth)
{
- new_width = (mItemListRect.getWidth() - mTotalStaticColumnWidth) / mNumDynamicWidthColumns;
+ new_width = (mItemListRect.getWidth() - mTotalStaticColumnWidth - mTotalColumnPadding) / mNumDynamicWidthColumns;
}
- if (new_width != column->mWidth)
- {
- column->mWidth = new_width;
- }
+ column->setWidth(new_width);
// update max content width for this column, by looking at all items
column->mMaxContentWidth = column->mHeader ? LLFontGL::sSansSerifSmall->getWidth(column->mLabel) + mColumnPadding + HEADING_TEXT_PADDING : 0;
@@ -971,28 +1007,13 @@ void LLScrollListCtrl::updateColumns()
{
calcColumnWidths();
- // propagate column widths to individual cells
- item_list::iterator iter;
- for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
- {
- LLScrollListItem *itemp = *iter;
- S32 num_cols = itemp->getNumColumns();
- S32 i = 0;
- for (LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i))
- {
- if (i >= (S32)mColumnsIndexed.size()) break;
-
- cell->setWidth(mColumnsIndexed[i]->mWidth);
- }
- }
-
// update column headers
std::vector<LLScrollListColumn*>::iterator column_ordered_it;
S32 left = mItemListRect.mLeft;
LLColumnHeader* last_header = NULL;
for (column_ordered_it = mColumnsIndexed.begin(); column_ordered_it != mColumnsIndexed.end(); ++column_ordered_it)
{
- if ((*column_ordered_it)->mWidth < 0)
+ if ((*column_ordered_it)->getWidth() < 0)
{
// skip hidden columns
continue;
@@ -1001,9 +1022,11 @@ void LLScrollListCtrl::updateColumns()
if (column->mHeader)
{
+ column->mHeader->updateResizeBars();
+
last_header = column->mHeader;
S32 top = mItemListRect.mTop;
- S32 right = left + column->mWidth;
+ S32 right = left + column->getWidth();
if (column->mIndex != (S32)mColumnsIndexed.size()-1)
{
@@ -1021,14 +1044,30 @@ void LLScrollListCtrl::updateColumns()
}
}
- //FIXME: stretch the entire last column if it is resizable (gestures windows shows truncated text in last column)
// expand last column header we encountered to full list width
- if (last_header)
+ if (last_header && last_header->canResize())
{
S32 new_width = llmax(0, mItemListRect.mRight - last_header->getRect().mLeft);
last_header->reshape(new_width, last_header->getRect().getHeight());
last_header->setVisible(mDisplayColumnHeaders && new_width > 0);
+ last_header->getColumn()->setWidth(new_width);
+ }
+
+ // propagate column widths to individual cells
+ item_list::iterator iter;
+ for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
+ {
+ LLScrollListItem *itemp = *iter;
+ S32 num_cols = itemp->getNumColumns();
+ S32 i = 0;
+ for (LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i))
+ {
+ if (i >= (S32)mColumnsIndexed.size()) break;
+
+ cell->setWidth(mColumnsIndexed[i]->getWidth());
+ }
}
+
}
void LLScrollListCtrl::setDisplayHeading(BOOL display)
@@ -1490,7 +1529,7 @@ BOOL LLScrollListCtrl::selectItemByPrefix(const LLWString& target, BOOL case_sen
{
LLScrollListItem* item = *iter;
// Only select enabled items with matching names
- LLScrollListCell* cellp = item->getColumn(mSearchColumn);
+ LLScrollListCell* cellp = item->getColumn(getSearchColumn());
BOOL select = cellp ? item->getEnabled() && ('\0' == cellp->getValue().asString()[0]) : FALSE;
if (select)
{
@@ -1513,7 +1552,7 @@ BOOL LLScrollListCtrl::selectItemByPrefix(const LLWString& target, BOOL case_sen
LLScrollListItem* item = *iter;
// Only select enabled items with matching names
- LLScrollListCell* cellp = item->getColumn(mSearchColumn);
+ LLScrollListCell* cellp = item->getColumn(getSearchColumn());
if (!cellp)
{
continue;
@@ -1743,6 +1782,8 @@ void LLScrollListCtrl::drawItems()
void LLScrollListCtrl::draw()
{
+ LLLocalClipRect clip(getLocalRect());
+
// if user specifies sort, make sure it is maintained
if (needsSorting() && !isSorted())
{
@@ -1816,7 +1857,7 @@ BOOL LLScrollListCtrl::handleToolTip(S32 x, S32 y, std::string& msg, LLRect* sti
S32 rect_left = getColumnOffsetFromIndex(column_index) + mItemListRect.mLeft;
S32 rect_bottom = getRowOffsetFromIndex(getItemIndex(hit_item));
LLRect cell_rect;
- cell_rect.setOriginAndSize(rect_left, rect_bottom, rect_left + columnp->mWidth, mLineHeight);
+ cell_rect.setOriginAndSize(rect_left, rect_bottom, rect_left + columnp->getWidth(), mLineHeight);
// Convert rect local to screen coordinates
localPointToScreen(
cell_rect.mLeft, cell_rect.mBottom,
@@ -2113,7 +2154,7 @@ S32 LLScrollListCtrl::getColumnIndexFromOffset(S32 x)
ordered_columns_t::const_iterator end = mColumnsIndexed.end();
for ( ; iter != end; ++iter)
{
- width = (*iter)->mWidth + mColumnPadding;
+ width = (*iter)->getWidth() + mColumnPadding;
right += width;
if (left <= x && x < right )
{
@@ -2140,7 +2181,7 @@ S32 LLScrollListCtrl::getColumnOffsetFromIndex(S32 index)
{
return column_offset;
}
- column_offset += (*iter)->mWidth + mColumnPadding;
+ column_offset += (*iter)->getWidth() + mColumnPadding;
}
// when running off the end, return the rightmost pixel
@@ -2292,7 +2333,7 @@ BOOL LLScrollListCtrl::handleKeyHere(KEY key,MASK mask )
{
if (getFirstSelected())
{
- LLScrollListCell* cellp = getFirstSelected()->getColumn(mSearchColumn);
+ LLScrollListCell* cellp = getFirstSelected()->getColumn(getSearchColumn());
if (cellp)
{
cellp->highlightText(0, 0);
@@ -2379,7 +2420,7 @@ BOOL LLScrollListCtrl::handleUnicodeCharHere(llwchar uni_char)
{
LLScrollListItem* item = *iter;
- LLScrollListCell* cellp = item->getColumn(mSearchColumn);
+ LLScrollListCell* cellp = item->getColumn(getSearchColumn());
if (cellp)
{
// Only select enabled items with matching first characters
@@ -2446,7 +2487,7 @@ void LLScrollListCtrl::selectItem(LLScrollListItem* itemp, BOOL select_single_it
{
if (mLastSelected)
{
- LLScrollListCell* cellp = mLastSelected->getColumn(mSearchColumn);
+ LLScrollListCell* cellp = mLastSelected->getColumn(getSearchColumn());
if (cellp)
{
cellp->highlightText(0, 0);
@@ -2474,7 +2515,7 @@ void LLScrollListCtrl::deselectItem(LLScrollListItem* itemp)
}
itemp->setSelected(FALSE);
- LLScrollListCell* cellp = itemp->getColumn(mSearchColumn);
+ LLScrollListCell* cellp = itemp->getColumn(getSearchColumn());
if (cellp)
{
cellp->highlightText(0, 0);
@@ -2501,9 +2542,14 @@ struct SameSortColumn
bool operator()(std::pair<S32, BOOL> sort_column) { return sort_column.first == mColumn; }
};
-BOOL LLScrollListCtrl::setSort(S32 column, BOOL ascending)
+BOOL LLScrollListCtrl::setSort(S32 column_idx, BOOL ascending)
{
- sort_column_t new_sort_column(column, ascending);
+ LLScrollListColumn* sort_column = getColumn(column_idx);
+ if (!sort_column) return FALSE;
+
+ sort_column->mSortAscending = ascending;
+
+ sort_column_t new_sort_column(column_idx, ascending);
if (mSortColumns.empty())
{
@@ -2517,7 +2563,7 @@ BOOL LLScrollListCtrl::setSort(S32 column, BOOL ascending)
// remove any existing sort criterion referencing this column
// and add the new one
- mSortColumns.erase(remove_if(mSortColumns.begin(), mSortColumns.end(), SameSortColumn(column)), mSortColumns.end());
+ mSortColumns.erase(remove_if(mSortColumns.begin(), mSortColumns.end(), SameSortColumn(column_idx)), mSortColumns.end());
mSortColumns.push_back(new_sort_column);
// did the sort criteria change?
@@ -2643,6 +2689,12 @@ void LLScrollListCtrl::scrollToShowSelected()
}
}
+void LLScrollListCtrl::updateStaticColumnWidth(LLScrollListColumn* col, S32 new_width)
+{
+ mTotalStaticColumnWidth += llmax(0, new_width) - llmax(0, col->getWidth());
+}
+
+
// virtual
LLXMLNodePtr LLScrollListCtrl::getXML(bool save_children) const
{
@@ -2689,7 +2741,7 @@ LLXMLNodePtr LLScrollListCtrl::getXML(bool save_children) const
child_node->createChild("name", TRUE)->setStringValue(column->mName);
child_node->createChild("label", TRUE)->setStringValue(column->mLabel);
- child_node->createChild("width", TRUE)->setIntValue(column->mWidth);
+ child_node->createChild("width", TRUE)->setIntValue(column->getWidth());
}
return node;
@@ -2813,15 +2865,9 @@ LLView* LLScrollListCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFac
scroll_list->setSearchColumn(search_column);
- if (sort_column >= 0)
- {
- scroll_list->sortByColumnIndex(sort_column, sort_ascending);
- }
-
LLSD columns;
S32 index = 0;
LLXMLNodePtr child;
- S32 total_static = 0;
for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling())
{
if (child->hasName("column"))
@@ -2850,8 +2896,6 @@ LLView* LLScrollListCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFac
std::string tooltip;
child->getAttributeString("tool_tip", tooltip);
- if(!columndynamicwidth) total_static += llmax(0, columnwidth);
-
F32 columnrelwidth = 0.f;
child->getAttributeF32("relwidth", columnrelwidth);
@@ -2872,9 +2916,13 @@ LLView* LLScrollListCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFac
index++;
}
}
- scroll_list->setTotalStaticColumnWidth(total_static);
scroll_list->setColumnHeadings(columns);
+ if (sort_column >= 0)
+ {
+ scroll_list->sortByColumnIndex(sort_column, sort_ascending);
+ }
+
for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling())
{
if (child->hasName("row"))
@@ -3019,22 +3067,26 @@ void LLScrollListCtrl::addColumn(const LLSD& column, EAddPosition pos)
if (mColumns.find(name) == mColumns.end())
{
// Add column
- mColumns[name] = LLScrollListColumn(column);
+ mColumns[name] = LLScrollListColumn(column, this);
LLScrollListColumn* new_column = &mColumns[name];
new_column->mParentCtrl = this;
new_column->mIndex = mColumns.size()-1;
// Add button
- if (new_column->mWidth > 0 || new_column->mRelWidth > 0 || new_column->mDynamicWidth)
+ if (new_column->getWidth() > 0 || new_column->mRelWidth > 0 || new_column->mDynamicWidth)
{
+ if (getNumColumns() > 0)
+ {
+ mTotalColumnPadding += mColumnPadding;
+ }
if (new_column->mRelWidth >= 0)
{
- new_column->mWidth = (S32)llround(new_column->mRelWidth*mItemListRect.getWidth());
+ new_column->setWidth((S32)llround(new_column->mRelWidth*mItemListRect.getWidth()));
}
else if(new_column->mDynamicWidth)
{
mNumDynamicWidthColumns++;
- new_column->mWidth = (mItemListRect.getWidth() - mTotalStaticColumnWidth) / mNumDynamicWidthColumns;
+ new_column->setWidth((mItemListRect.getWidth() - mTotalStaticColumnWidth - mTotalColumnPadding) / mNumDynamicWidthColumns);
}
S32 top = mItemListRect.mTop;
S32 left = mItemListRect.mLeft;
@@ -3043,14 +3095,14 @@ void LLScrollListCtrl::addColumn(const LLSD& column, EAddPosition pos)
for (itor = mColumns.begin(); itor != mColumns.end(); ++itor)
{
if (itor->second.mIndex < new_column->mIndex &&
- itor->second.mWidth > 0)
+ itor->second.getWidth() > 0)
{
- left += itor->second.mWidth + mColumnPadding;
+ left += itor->second.getWidth() + mColumnPadding;
}
}
}
std::string button_name = "btn_" + name;
- S32 right = left+new_column->mWidth;
+ S32 right = left+new_column->getWidth();
if (new_column->mIndex != (S32)mColumns.size()-1)
{
right += mColumnPadding;
@@ -3145,6 +3197,8 @@ void LLScrollListCtrl::clearColumns()
}
mColumns.clear();
mSortColumns.clear();
+ mTotalStaticColumnWidth = 0;
+ mTotalColumnPadding = 0;
}
void LLScrollListCtrl::setColumnLabel(const std::string& column, const std::string& label)
@@ -3244,7 +3298,7 @@ LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& value, EAddPosition p
}
S32 index = columnp->mIndex;
- S32 width = columnp->mWidth;
+ S32 width = columnp->getWidth();
LLFontGL::HAlign font_alignment = columnp->mFontAlignment;
LLColor4 fcolor = LLColor4::black;
@@ -3301,6 +3355,19 @@ LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& value, EAddPosition p
}
new_item->setColumn(index, cell);
}
+ else if (type == "date")
+ {
+ LLScrollListDate* cell = new LLScrollListDate(value.asDate(), font, width, font_style, font_alignment);
+ if (has_color)
+ {
+ cell->setColor(color);
+ }
+ new_item->setColumn(index, cell);
+ if (columnp->mHeader && !value.asString().empty())
+ {
+ columnp->mHeader->setHasResizableElement(TRUE);
+ }
+ }
else
{
LLScrollListText* cell = new LLScrollListText(value.asString(), font, width, font_style, font_alignment, fcolor, TRUE);
@@ -3325,7 +3392,7 @@ LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& value, EAddPosition p
if (new_item->getColumn(column_idx) == NULL)
{
LLScrollListColumn* column_ptr = &column_it->second;
- new_item->setColumn(column_idx, new LLScrollListText(LLStringUtil::null, LLResMgr::getInstance()->getRes( LLFONT_SANSSERIF_SMALL ), column_ptr->mWidth, LLFontGL::NORMAL));
+ new_item->setColumn(column_idx, new LLScrollListText(LLStringUtil::null, LLResMgr::getInstance()->getRes( LLFONT_SANSSERIF_SMALL ), column_ptr->getWidth(), LLFontGL::NORMAL));
}
}
@@ -3469,6 +3536,7 @@ LLColumnHeader::LLColumnHeader(const std::string& label, const LLRect &rect, LLS
mButton->setMouseDownCallback(onMouseDown);
mButton->setCallbackUserData(this);
+ mButton->setToolTip(label);
mAscendingText = std::string("[LOW]...[HIGH](Ascending)"); // *TODO: Translate
mDescendingText = std::string("[HIGH]...[LOW](Descending)"); // *TODO: Translate
@@ -3556,7 +3624,7 @@ void LLColumnHeader::onClick(void* user_data)
LLScrollListCtrl::onClickColumn(column);
- // propage new sort order to sort order list
+ // propagate new sort order to sort order list
headerp->mList->selectNthItem(column->mParentCtrl->getSortAscending() ? 0 : 1);
}
@@ -3646,7 +3714,7 @@ void LLColumnHeader::showList()
text_width = llmax(text_width, LLFontGL::sSansSerifSmall->getWidth(descending_string)) + 10;
text_width = llmax(text_width, getRect().getWidth() - 30);
- mList->getColumn(0)->mWidth = text_width;
+ mList->getColumn(0)->setWidth(text_width);
((LLScrollListText*)mList->getFirstData()->getColumn(0))->setText(ascending_string);
((LLScrollListText*)mList->getLastData()->getColumn(0))->setText(descending_string);
@@ -3688,7 +3756,7 @@ LLView* LLColumnHeader::findSnapEdge(S32& new_edge_val, const LLCoordGL& mouse_d
llassert(snap_edge == SNAP_RIGHT);
// use higher snap threshold for column headers
- threshold = llmin(threshold, 15);
+ threshold = llmin(threshold, 10);
LLRect snap_rect = getSnapRect();
@@ -3727,47 +3795,48 @@ void LLColumnHeader::userSetShape(const LLRect& new_rect)
if (delta_width != 0)
{
- S32 remaining_width = delta_width;
+ S32 remaining_width = -delta_width;
S32 col;
for (col = mColumn->mIndex + 1; col < mColumn->mParentCtrl->getNumColumns(); col++)
{
LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col);
- if (!columnp) break;
+ if (!columnp) continue;
if (columnp->mHeader && columnp->mHeader->canResize())
{
// how many pixels in width can this column afford to give up?
- S32 resize_buffer_amt = llmax(0, columnp->mWidth - MIN_COLUMN_WIDTH);
+ S32 resize_buffer_amt = llmax(0, columnp->getWidth() - MIN_COLUMN_WIDTH);
// user shrinking column, need to add width to other columns
if (delta_width < 0)
{
- if (!columnp->mDynamicWidth && columnp->mWidth > 0)
+ if (/*!columnp->mDynamicWidth && */columnp->getWidth() > 0)
{
// statically sized column, give all remaining width to this column
- columnp->mWidth -= remaining_width;
+ columnp->setWidth(columnp->getWidth() + remaining_width);
if (columnp->mRelWidth > 0.f)
{
- columnp->mRelWidth = (F32)columnp->mWidth / (F32)mColumn->mParentCtrl->getItemListRect().getWidth();
+ columnp->mRelWidth = (F32)columnp->getWidth() / (F32)mColumn->mParentCtrl->getItemListRect().getWidth();
}
+ // all padding went to this widget, we're done
+ break;
}
- break;
}
else
{
// user growing column, need to take width from other columns
- remaining_width -= resize_buffer_amt;
+ remaining_width += resize_buffer_amt;
- if (!columnp->mDynamicWidth && columnp->mWidth > 0)
+ if (/*!columnp->mDynamicWidth && */columnp->getWidth() > 0)
{
- columnp->mWidth -= llmin(columnp->mWidth - MIN_COLUMN_WIDTH, delta_width);
+ columnp->setWidth(columnp->getWidth() - llmin(columnp->getWidth() - MIN_COLUMN_WIDTH, delta_width));
if (columnp->mRelWidth > 0.f)
{
- columnp->mRelWidth = (F32)columnp->mWidth / (F32)mColumn->mParentCtrl->getItemListRect().getWidth();
+ columnp->mRelWidth = (F32)columnp->getWidth() / (F32)mColumn->mParentCtrl->getItemListRect().getWidth();
}
}
- if (remaining_width <= 0)
+ if (remaining_width >= 0)
{
// width sucked up from neighboring columns, done
break;
@@ -3779,14 +3848,14 @@ void LLColumnHeader::userSetShape(const LLRect& new_rect)
// clamp resize amount to maximum that can be absorbed by other columns
if (delta_width > 0)
{
- delta_width -= llmax(remaining_width, 0);
+ delta_width += llmin(remaining_width, 0);
}
// propagate constrained delta_width to new width for this column
new_width = getRect().getWidth() + delta_width - mColumn->mParentCtrl->getColumnPadding();
// use requested width
- mColumn->mWidth = new_width;
+ mColumn->setWidth(new_width);
// update proportional spacing
if (mColumn->mRelWidth > 0.f)
@@ -3804,36 +3873,40 @@ void LLColumnHeader::userSetShape(const LLRect& new_rect)
void LLColumnHeader::setHasResizableElement(BOOL resizable)
{
// for now, dynamically spaced columns can't be resized
- if (mColumn->mDynamicWidth) return;
+// if (mColumn->mDynamicWidth) return;
- if (resizable != mHasResizableElement)
+ if (mHasResizableElement != resizable)
{
+ mColumn->mParentCtrl->dirtyColumns();
mHasResizableElement = resizable;
+ }
+}
- S32 num_resizable_columns = 0;
- S32 col;
- for (col = 0; col < mColumn->mParentCtrl->getNumColumns(); col++)
+void LLColumnHeader::updateResizeBars()
+{
+ S32 num_resizable_columns = 0;
+ S32 col;
+ for (col = 0; col < mColumn->mParentCtrl->getNumColumns(); col++)
+ {
+ LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col);
+ if (columnp->mHeader && columnp->mHeader->canResize())
{
- LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col);
- if (columnp->mHeader && columnp->mHeader->canResize())
- {
- num_resizable_columns++;
- }
+ num_resizable_columns++;
}
+ }
- S32 num_resizers_enabled = 0;
+ S32 num_resizers_enabled = 0;
- // now enable/disable resize handles on resizable columns if we have at least two
- for (col = 0; col < mColumn->mParentCtrl->getNumColumns(); col++)
+ // now enable/disable resize handles on resizable columns if we have at least two
+ for (col = 0; col < mColumn->mParentCtrl->getNumColumns(); col++)
+ {
+ LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col);
+ if (!columnp->mHeader) continue;
+ BOOL enable = num_resizable_columns >= 2 && num_resizers_enabled < (num_resizable_columns - 1) && columnp->mHeader->canResize();
+ columnp->mHeader->enableResizeBar(enable);
+ if (enable)
{
- LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col);
- if (!columnp->mHeader) continue;
- BOOL enable = num_resizable_columns >= 2 && num_resizers_enabled < (num_resizable_columns - 1) && columnp->mHeader->canResize();
- columnp->mHeader->enableResizeBar(enable);
- if (enable)
- {
- num_resizers_enabled++;
- }
+ num_resizers_enabled++;
}
}
}
@@ -3841,7 +3914,7 @@ void LLColumnHeader::setHasResizableElement(BOOL resizable)
void LLColumnHeader::enableResizeBar(BOOL enable)
{
// for now, dynamically spaced columns can't be resized
- if (!mColumn->mDynamicWidth)
+ //if (!mColumn->mDynamicWidth)
{
mResizeBar->setEnabled(enable);
}
@@ -3851,3 +3924,78 @@ BOOL LLColumnHeader::canResize()
{
return getVisible() && (mHasResizableElement || mColumn->mDynamicWidth);
}
+
+void LLScrollListColumn::setWidth(S32 width)
+{
+ if (!mDynamicWidth && mRelWidth <= 0.f)
+ {
+ mParentCtrl->updateStaticColumnWidth(this, width);
+ }
+ mWidth = width;
+}
+
+// Default constructor
+LLScrollListColumn::LLScrollListColumn() :
+ mName(),
+ mSortingColumn(),
+ mSortAscending(TRUE),
+ mLabel(),
+ mWidth(-1),
+ mRelWidth(-1.0),
+ mDynamicWidth(FALSE),
+ mMaxContentWidth(0),
+ mIndex(-1),
+ mParentCtrl(NULL),
+ mHeader(NULL),
+ mFontAlignment(LLFontGL::LEFT)
+{ }
+
+LLScrollListColumn::LLScrollListColumn(const LLSD &sd, LLScrollListCtrl* parent) :
+ mWidth(0),
+ mIndex (-1),
+ mParentCtrl(parent),
+ mHeader(NULL),
+ mMaxContentWidth(0),
+ mDynamicWidth(FALSE),
+ mRelWidth(-1.f)
+{
+ mName = sd.get("name").asString();
+ mSortingColumn = mName;
+ if (sd.has("sort"))
+ {
+ mSortingColumn = sd.get("sort").asString();
+ }
+ mSortAscending = TRUE;
+ if (sd.has("sort_ascending"))
+ {
+ mSortAscending = sd.get("sort_ascending").asBoolean();
+ }
+ mLabel = sd.get("label").asString();
+ if (sd.has("relwidth") && (F32)sd.get("relwidth").asReal() > 0)
+ {
+ mRelWidth = (F32)sd.get("relwidth").asReal();
+ if (mRelWidth < 0) mRelWidth = 0;
+ if (mRelWidth > 1) mRelWidth = 1;
+ mDynamicWidth = FALSE;
+ }
+ else if(sd.has("dynamicwidth") && (BOOL)sd.get("dynamicwidth").asBoolean() == TRUE)
+ {
+ mDynamicWidth = TRUE;
+ mRelWidth = -1;
+ }
+ else
+ {
+
+ setWidth(sd.get("width").asInteger());
+ }
+
+ if (sd.has("halign"))
+ {
+ mFontAlignment = (LLFontGL::HAlign)llclamp(sd.get("halign").asInteger(), (S32)LLFontGL::LEFT, (S32)LLFontGL::HCENTER);
+ }
+ else
+ {
+ mFontAlignment = LLFontGL::LEFT;
+ }
+
+}
diff --git a/indra/llui/llscrolllistctrl.h b/indra/llui/llscrolllistctrl.h
index 0128c01eba..d8ef955042 100644
--- a/indra/llui/llscrolllistctrl.h
+++ b/indra/llui/llscrolllistctrl.h
@@ -47,6 +47,7 @@
#include "llcombobox.h"
#include "llscrollbar.h"
#include "llresizebar.h"
+#include "lldate.h"
/*
* Represents a cell in a scrollable table.
@@ -133,6 +134,18 @@ private:
static U32 sCount;
};
+
+class LLScrollListDate : public LLScrollListText
+{
+public:
+ LLScrollListDate( const LLDate& date, const LLFontGL* font, S32 width=0, U8 font_style = LLFontGL::NORMAL, LLFontGL::HAlign font_alignment = LLFontGL::LEFT, LLColor4& color = LLColor4::black, BOOL use_color = FALSE, BOOL visible = TRUE);
+ virtual void setValue(const LLSD& value);
+ virtual const LLSD getValue() const;
+
+private:
+ LLDate mDate;
+};
+
/*
* Cell displaying an image.
*/
@@ -185,88 +198,11 @@ private:
class LLScrollListColumn
{
public:
- // Default constructor
- LLScrollListColumn() :
- mName(),
- mSortingColumn(),
- mSortAscending(TRUE),
- mLabel(),
- mWidth(-1),
- mRelWidth(-1.0),
- mDynamicWidth(FALSE),
- mMaxContentWidth(0),
- mIndex(-1),
- mParentCtrl(NULL),
- mHeader(NULL),
- mFontAlignment(LLFontGL::LEFT)
- { }
-
- LLScrollListColumn(std::string name, std::string label, S32 width, F32 relwidth) :
- mName(name),
- mSortingColumn(name),
- mSortAscending(TRUE),
- mLabel(label),
- mWidth(width),
- mRelWidth(relwidth),
- mDynamicWidth(FALSE),
- mMaxContentWidth(0),
- mIndex(-1),
- mParentCtrl(NULL),
- mHeader(NULL),
- mFontAlignment(LLFontGL::LEFT)
- { }
-
- LLScrollListColumn(const LLSD &sd)
- {
- mMaxContentWidth = 0;
-
- mName = sd.get("name").asString();
- mSortingColumn = mName;
- if (sd.has("sort"))
- {
- mSortingColumn = sd.get("sort").asString();
- }
- mSortAscending = TRUE;
- if (sd.has("sort_ascending"))
- {
- mSortAscending = sd.get("sort_ascending").asBoolean();
- }
- mLabel = sd.get("label").asString();
- if (sd.has("relwidth") && (F32)sd.get("relwidth").asReal() > 0)
- {
- mRelWidth = (F32)sd.get("relwidth").asReal();
- if (mRelWidth < 0) mRelWidth = 0;
- if (mRelWidth > 1) mRelWidth = 1;
- mDynamicWidth = FALSE;
- mWidth = 0;
- }
- else if(sd.has("dynamicwidth") && (BOOL)sd.get("dynamicwidth").asBoolean() == TRUE)
- {
- mDynamicWidth = TRUE;
- mRelWidth = -1;
- mWidth = 0;
- }
- else
- {
- mWidth = sd.get("width").asInteger();
- mDynamicWidth = FALSE;
- mRelWidth = -1;
- }
-
- if (sd.has("halign"))
- {
- mFontAlignment = (LLFontGL::HAlign)llclamp(sd.get("halign").asInteger(), (S32)LLFontGL::LEFT, (S32)LLFontGL::HCENTER);
- }
- else
- {
- mFontAlignment = LLFontGL::LEFT;
- }
-
- mIndex = -1;
- mParentCtrl = NULL;
- mHeader = NULL;
- mFontAlignment = LLFontGL::LEFT;
- }
+ LLScrollListColumn();
+ LLScrollListColumn(const LLSD &sd, LLScrollListCtrl* parent);
+
+ void setWidth(S32 width);
+ S32 getWidth() const { return mWidth; }
// Public data is fine so long as this remains a simple struct-like data class.
// If it ever gets any smarter than that, these should all become private
@@ -275,7 +211,6 @@ public:
std::string mSortingColumn;
BOOL mSortAscending;
std::string mLabel;
- S32 mWidth;
F32 mRelWidth;
BOOL mDynamicWidth;
S32 mMaxContentWidth;
@@ -283,6 +218,10 @@ public:
LLScrollListCtrl* mParentCtrl;
class LLColumnHeader* mHeader;
LLFontGL::HAlign mFontAlignment;
+
+private:
+ S32 mWidth;
+
};
class LLColumnHeader : public LLComboBox
@@ -301,6 +240,7 @@ public:
void setImage(const std::string &image_name);
LLScrollListColumn* getColumn() { return mColumn; }
void setHasResizableElement(BOOL resizable);
+ void updateResizeBars();
BOOL canResize();
void enableResizeBar(BOOL enable);
std::string getLabel() { return mOrigLabel; }
@@ -551,8 +491,7 @@ public:
virtual S32 getScrollPos() const;
virtual void setScrollPos( S32 pos );
-
- S32 getSearchColumn() { return mSearchColumn; }
+ S32 getSearchColumn();
void setSearchColumn(S32 column) { mSearchColumn = column; }
S32 getColumnIndexFromOffset(S32 x);
S32 getColumnOffsetFromIndex(S32 index);
@@ -613,8 +552,9 @@ public:
virtual void deselect();
virtual BOOL canDeselect() const;
- void setNumDynamicColumns(int num) { mNumDynamicWidthColumns = num; }
- void setTotalStaticColumnWidth(int width) { mTotalStaticColumnWidth = width; }
+ void setNumDynamicColumns(S32 num) { mNumDynamicWidthColumns = num; }
+ void updateStaticColumnWidth(LLScrollListColumn* col, S32 new_width);
+ S32 getTotalStaticColumnWidth() { return mTotalStaticColumnWidth; }
std::string getSortColumnName();
BOOL getSortAscending() { return mSortColumns.empty() ? TRUE : mSortColumns.back().second; }
@@ -719,6 +659,7 @@ private:
S32 mSearchColumn;
S32 mNumDynamicWidthColumns;
S32 mTotalStaticColumnWidth;
+ S32 mTotalColumnPadding;
BOOL mSorted;
diff --git a/indra/llui/llui.cpp b/indra/llui/llui.cpp
index a4b16dc2de..7b2c97af67 100644
--- a/indra/llui/llui.cpp
+++ b/indra/llui/llui.cpp
@@ -30,6 +30,7 @@
*/
// Utilities functions the user interface needs
+
#include "linden_common.h"
#include <string>
@@ -65,6 +66,7 @@ std::map<std::string, std::string> gTranslation;
std::list<std::string> gUntranslated;
LLControlGroup* LLUI::sConfigGroup = NULL;
+LLControlGroup* LLUI::sIgnoresGroup = NULL;
LLControlGroup* LLUI::sColorsGroup = NULL;
LLImageProviderInterface* LLUI::sImageProvider = NULL;
LLUIAudioCallback LLUI::sAudioCallback = NULL;
@@ -90,7 +92,7 @@ void make_ui_sound(const char* namep)
LLUUID uuid(LLUI::sConfigGroup->getString(name));
if (uuid.isNull())
{
- if ("00000000-0000-0000-0000-000000000000" == LLUI::sConfigGroup->getString(name))
+ if (LLUI::sConfigGroup->getString(name) == LLUUID::null.asString())
{
if (LLUI::sConfigGroup->getBOOL("UISndDebugSpamToggle"))
{
@@ -1552,6 +1554,7 @@ bool handleShowXUINamesChanged(const LLSD& newvalue)
}
void LLUI::initClass(LLControlGroup* config,
+ LLControlGroup* ignores,
LLControlGroup* colors,
LLImageProviderInterface* image_provider,
LLUIAudioCallback audio_callback,
@@ -1559,7 +1562,16 @@ void LLUI::initClass(LLControlGroup* config,
const std::string& language)
{
sConfigGroup = config;
+ sIgnoresGroup = ignores;
sColorsGroup = colors;
+
+ if (sConfigGroup == NULL
+ || sIgnoresGroup == NULL
+ || sColorsGroup == NULL)
+ {
+ llerrs << "Failure to initialize configuration groups" << llendl;
+ }
+
sImageProvider = image_provider;
sAudioCallback = audio_callback;
sGLScaleFactor = (scale_factor == NULL) ? LLVector2(1.f, 1.f) : *scale_factor;
@@ -1567,7 +1579,7 @@ void LLUI::initClass(LLControlGroup* config,
LLFontGL::sShadowColor = colors->getColor("ColorDropShadow");
LLUI::sShowXUINames = LLUI::sConfigGroup->getBOOL("ShowXUINames");
- LLUI::sConfigGroup->getControl("ShowXUINames")->getSignal()->connect(boost::bind(&handleShowXUINamesChanged, _1));
+ LLUI::sConfigGroup->getControl("ShowXUINames")->getSignal()->connect(&handleShowXUINamesChanged);
}
void LLUI::cleanupClass()
diff --git a/indra/llui/llui.h b/indra/llui/llui.h
index e2629ee2a4..4366c38740 100644
--- a/indra/llui/llui.h
+++ b/indra/llui/llui.h
@@ -42,6 +42,7 @@
#include "llgl.h" // *TODO: break this dependency
#include <stack>
//#include "llimagegl.h"
+#include <boost/signal.hpp>
// LLUIFactory
#include "llsd.h"
@@ -150,11 +151,13 @@ typedef void (*LLUIAudioCallback)(const LLUUID& uuid);
class LLUI
{
+ LOG_CLASS(LLUI);
public:
//
// Methods
//
static void initClass(LLControlGroup* config,
+ LLControlGroup* ignores,
LLControlGroup* colors,
LLImageProviderInterface* image_provider,
LLUIAudioCallback audio_callback = NULL,
@@ -190,6 +193,7 @@ public:
// Data
//
static LLControlGroup* sConfigGroup;
+ static LLControlGroup* sIgnoresGroup;
static LLControlGroup* sColorsGroup;
static LLImageProviderInterface* sImageProvider;
static LLUIAudioCallback sAudioCallback;
@@ -597,4 +601,237 @@ public:
virtual void cleanUp() = 0;
};
+// This mix-in class adds support for tracking all instances of the specificed class parameter T
+// The (optional) key associates a value of type KEY with a given instance of T, for quick lookup
+// If KEY is not provided, then instances are stored in a simple list
+template<typename T, typename KEY = T*>
+class LLInstanceTracker : boost::noncopyable
+{
+public:
+ typedef typename std::map<KEY, T*>::iterator instance_iter;
+ typedef typename std::map<KEY, T*>::const_iterator instance_const_iter;
+
+ static T* getInstance(KEY k) { instance_iter found = sInstances.find(k); return (found == sInstances.end()) ? NULL : found->second; }
+
+ static instance_iter beginInstances() { return sInstances.begin(); }
+ static instance_iter endInstances() { return sInstances.end(); }
+ static S32 instanceCount() { return sInstances.size(); }
+protected:
+ LLInstanceTracker(KEY key) { add(key); }
+ virtual ~LLInstanceTracker() { remove(); }
+ virtual void setKey(KEY key) { remove(); add(key); }
+ virtual const KEY& getKey() const { return mKey; }
+
+private:
+ void add(KEY key)
+ {
+ mKey = key;
+ sInstances[key] = static_cast<T*>(this);
+ }
+ void remove() { sInstances.erase(mKey); }
+
+private:
+
+ KEY mKey;
+ static std::map<KEY, T*> sInstances;
+};
+
+template<typename T>
+class LLInstanceTracker<T, T*> : boost::noncopyable
+{
+public:
+ typedef typename std::set<T*>::iterator instance_iter;
+ typedef typename std::set<T*>::const_iterator instance_const_iter;
+
+ static instance_iter instancesBegin() { return sInstances.begin(); }
+ static instance_iter instancesEnd() { return sInstances.end(); }
+ static S32 instanceCount() { return sInstances.size(); }
+
+protected:
+ LLInstanceTracker() { sInstances.insert(static_cast<T*>(this)); }
+ virtual ~LLInstanceTracker() { sInstances.erase(static_cast<T*>(this)); }
+
+ static std::set<T*> sInstances;
+};
+
+template <typename T, typename KEY> std::map<KEY, T*> LLInstanceTracker<T, KEY>::sInstances;
+template <typename T> std::set<T*> LLInstanceTracker<T, T*>::sInstances;
+
+class LLCallbackRegistry
+{
+public:
+ typedef boost::signal<void()> callback_signal_t;
+
+ void registerCallback(const callback_signal_t::slot_type& slot)
+ {
+ mCallbacks.connect(slot);
+ }
+
+ void fireCallbacks()
+ {
+ mCallbacks();
+ }
+
+private:
+ callback_signal_t mCallbacks;
+};
+
+class LLInitClassList :
+ public LLCallbackRegistry,
+ public LLSingleton<LLInitClassList>
+{
+ friend class LLSingleton<LLInitClassList>;
+private:
+ LLInitClassList() {}
+};
+
+class LLDestroyClassList :
+ public LLCallbackRegistry,
+ public LLSingleton<LLDestroyClassList>
+{
+ friend class LLSingleton<LLDestroyClassList>;
+private:
+ LLDestroyClassList() {}
+};
+
+template<typename T>
+class LLRegisterWith
+{
+public:
+ LLRegisterWith(boost::function<void ()> func)
+ {
+ T::instance().registerCallback(func);
+ }
+
+ // this avoids a MSVC bug where non-referenced static members are "optimized" away
+ // even if their constructors have side effects
+ void reference()
+ {
+ S32 dummy;
+ dummy = 0;
+ }
+};
+
+template<typename T>
+class LLInitClass
+{
+public:
+ LLInitClass() { sRegister.reference(); }
+
+ static LLRegisterWith<LLInitClassList> sRegister;
+private:
+
+ static void initClass()
+ {
+ llerrs << "No static initClass() method defined for " << typeid(T).name() << llendl;
+ }
+};
+
+template<typename T>
+class LLDestroyClass
+{
+public:
+ LLDestroyClass() { sRegister.reference(); }
+
+ static LLRegisterWith<LLDestroyClassList> sRegister;
+private:
+
+ static void destroyClass()
+ {
+ llerrs << "No static destroyClass() method defined for " << typeid(T).name() << llendl;
+ }
+};
+
+template <typename T> LLRegisterWith<LLInitClassList> LLInitClass<T>::sRegister(&T::initClass);
+template <typename T> LLRegisterWith<LLDestroyClassList> LLDestroyClass<T>::sRegister(&T::destroyClass);
+
+
+template <typename DERIVED>
+class LLParamBlock
+{
+protected:
+ LLParamBlock() { sBlock = (DERIVED*)this; }
+
+ typedef typename boost::add_const<DERIVED>::type Tconst;
+
+ template <typename T>
+ class LLMandatoryParam
+ {
+ public:
+ typedef typename boost::add_const<T>::type T_const;
+
+ LLMandatoryParam(T_const initial_val) : mVal(initial_val), mBlock(sBlock) {}
+ LLMandatoryParam(const LLMandatoryParam<T>& other) : mVal(other.mVal) {}
+
+ DERIVED& operator ()(T_const set_value) { mVal = set_value; return *mBlock; }
+ operator T() const { return mVal; }
+ T operator=(T_const set_value) { mVal = set_value; return mVal; }
+
+ private:
+ T mVal;
+ DERIVED* mBlock;
+ };
+
+ template <typename T>
+ class LLOptionalParam
+ {
+ public:
+ typedef typename boost::add_const<T>::type T_const;
+
+ LLOptionalParam(T_const initial_val) : mVal(initial_val), mBlock(sBlock) {}
+ LLOptionalParam() : mBlock(sBlock) {}
+ LLOptionalParam(const LLOptionalParam<T>& other) : mVal(other.mVal) {}
+
+ DERIVED& operator ()(T_const set_value) { mVal = set_value; return *mBlock; }
+ operator T() const { return mVal; }
+ T operator=(T_const set_value) { mVal = set_value; return mVal; }
+
+ private:
+ T mVal;
+ DERIVED* mBlock;
+ };
+
+ // specialization that requires initialization for reference types
+ template <typename T>
+ class LLOptionalParam <T&>
+ {
+ public:
+ typedef typename boost::add_const<T&>::type T_const;
+
+ LLOptionalParam(T_const initial_val) : mVal(initial_val), mBlock(sBlock) {}
+ LLOptionalParam(const LLOptionalParam<T&>& other) : mVal(other.mVal) {}
+
+ DERIVED& operator ()(T_const set_value) { mVal = set_value; return *mBlock; }
+ operator T&() const { return mVal; }
+ T& operator=(T_const set_value) { mVal = set_value; return mVal; }
+
+ private:
+ T& mVal;
+ DERIVED* mBlock;
+ };
+
+ // specialization that initializes pointer params to NULL
+ template<typename T>
+ class LLOptionalParam<T*>
+ {
+ public:
+ typedef typename boost::add_const<T*>::type T_const;
+
+ LLOptionalParam(T_const initial_val) : mVal(initial_val), mBlock(sBlock) {}
+ LLOptionalParam() : mVal((T*)NULL), mBlock(sBlock) {}
+ LLOptionalParam(const LLOptionalParam<T*>& other) : mVal(other.mVal) {}
+
+ DERIVED& operator ()(T_const set_value) { mVal = set_value; return *mBlock; }
+ operator T*() const { return mVal; }
+ T* operator=(T_const set_value) { mVal = set_value; return mVal; }
+ private:
+ T* mVal;
+ DERIVED* mBlock;
+ };
+
+ static DERIVED* sBlock;
+};
+
+template <typename T> T* LLParamBlock<T>::sBlock = NULL;
+
#endif
diff --git a/indra/llui/lluictrl.cpp b/indra/llui/lluictrl.cpp
index f7419d615b..6e822f7c03 100644
--- a/indra/llui/lluictrl.cpp
+++ b/indra/llui/lluictrl.cpp
@@ -475,10 +475,10 @@ BOOL LLUICtrl::focusPrevItem(BOOL text_fields_only)
return focusPrev(result);
}
-LLUICtrl* LLUICtrl::findRootMostFocusRoot() const
+LLUICtrl* LLUICtrl::findRootMostFocusRoot()
{
- const LLUICtrl* focus_root = NULL;
- const LLUICtrl* next_view = this;
+ LLUICtrl* focus_root = NULL;
+ LLUICtrl* next_view = this;
while(next_view)
{
if (next_view->isFocusRoot())
@@ -487,9 +487,8 @@ LLUICtrl* LLUICtrl::findRootMostFocusRoot() const
}
next_view = next_view->getParentUICtrl();
}
- // since focus_root could be this, need to cast away const to return
- // a non-const result
- return const_cast<LLUICtrl*>(focus_root);
+
+ return focus_root;
}
diff --git a/indra/llui/lluictrl.h b/indra/llui/lluictrl.h
index 4ad9042b9a..7d1f504f79 100644
--- a/indra/llui/lluictrl.h
+++ b/indra/llui/lluictrl.h
@@ -141,7 +141,7 @@ public:
static LLView* fromXML(LLXMLNodePtr node, LLView* parent, class LLUICtrlFactory* factory);
- LLUICtrl* findRootMostFocusRoot() const;
+ LLUICtrl* findRootMostFocusRoot();
class LLTextInputFilter : public LLQueryFilter, public LLSingleton<LLTextInputFilter>
{
diff --git a/indra/llui/lluictrlfactory.h b/indra/llui/lluictrlfactory.h
index 3e22e196b0..dfcea7290e 100644
--- a/indra/llui/lluictrlfactory.h
+++ b/indra/llui/lluictrlfactory.h
@@ -78,6 +78,7 @@ public:
static bool getLayeredXMLNode(const std::string &filename, LLXMLNodePtr& root);
private:
+ bool getLayeredXMLNodeImpl(const std::string &filename, LLXMLNodePtr& root);
typedef std::map<LLHandle<LLPanel>, std::string> built_panel_t;
built_panel_t mBuiltPanels;
diff --git a/indra/llui/lluistring.cpp b/indra/llui/lluistring.cpp
index 0dde20934c..51768ad885 100644
--- a/indra/llui/lluistring.cpp
+++ b/indra/llui/lluistring.cpp
@@ -31,6 +31,7 @@
#include "linden_common.h"
#include "lluistring.h"
+#include "llsd.h"
const LLStringUtil::format_map_t LLUIString::sNullArgs;
@@ -54,6 +55,18 @@ void LLUIString::setArgList(const LLStringUtil::format_map_t& args)
format();
}
+void LLUIString::setArgs(const LLSD& sd)
+{
+ if (!sd.isMap()) return;
+ for(LLSD::map_const_iterator sd_it = sd.beginMap();
+ sd_it != sd.endMap();
+ ++sd_it)
+ {
+ setArg(sd_it->first, sd_it->second.asString());
+ }
+ format();
+}
+
void LLUIString::setArg(const std::string& key, const std::string& replacement)
{
mArgs[key] = replacement;
diff --git a/indra/llui/lluistring.h b/indra/llui/lluistring.h
index 773bc09763..c99aeb50c6 100644
--- a/indra/llui/lluistring.h
+++ b/indra/llui/lluistring.h
@@ -50,9 +50,9 @@
// llinfos << mMessage.getString() << llendl; // outputs "Welcome Steve to Second Life"
// mMessage.setArg("[USERNAME]", "Joe");
// llinfos << mMessage.getString() << llendl; // outputs "Welcome Joe to Second Life"
-// mMessage = "Recepción a la [SECONDLIFE] [USERNAME]"
+// mMessage = "Recepci￳n a la [SECONDLIFE] [USERNAME]"
// mMessage.setArg("[SECONDLIFE]", "Segunda Vida");
-// llinfos << mMessage.getString() << llendl; // outputs "Recepción a la Segunda Vida Joe"
+// llinfos << mMessage.getString() << llendl; // outputs "Recepci￳n a la Segunda Vida Joe"
// Implementation Notes:
// Attempting to have operator[](const std::string& s) return mArgs[s] fails because we have
@@ -71,6 +71,8 @@ public:
LLUIString& operator=(const std::string& s) { assign(s); return *this; }
void setArgList(const LLStringUtil::format_map_t& args);
+ void setArgs(const LLStringUtil::format_map_t& args) { setArgList(args); }
+ void setArgs(const class LLSD& sd);
void setArg(const std::string& key, const std::string& replacement);
const std::string& getString() const { return mResult; }
diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp
index 3e7e59876c..65e9417b8b 100644
--- a/indra/llui/llview.cpp
+++ b/indra/llui/llview.cpp
@@ -1260,7 +1260,7 @@ void LLView::draw()
{
LLView *viewp = *child_iter;
- if (viewp->getVisible() && viewp != focus_view)
+ if (viewp->getVisible() && viewp != focus_view && viewp->getRect().isValid())
{
// Only draw views that are within the root view
localRectToScreen(viewp->getRect(),&screenRect);
@@ -1357,7 +1357,8 @@ void LLView::drawChild(LLView* childp, S32 x_offset, S32 y_offset, BOOL force_dr
{
++sDepth;
- if (childp->getVisible() || force_draw)
+ if ((childp->getVisible() && childp->getRect().isValid())
+ || force_draw)
{
glMatrixMode(GL_MODELVIEW);
LLUI::pushMatrix();
diff --git a/indra/llui/llview.h b/indra/llui/llview.h
index ff7a1afb38..ae0df61599 100644
--- a/indra/llui/llview.h
+++ b/indra/llui/llview.h
@@ -485,7 +485,7 @@ public:
// did we find *something* with that name?
if (child)
{
- llwarns << "Found child named " << name << " but of wrong type" << llendl;
+ llwarns << "Found child named " << name << " but of wrong type " << typeid(child).name() << ", expecting " << typeid(T).name() << llendl;
}
if (create_if_missing)
{
@@ -496,6 +496,11 @@ public:
return result;
}
+ template <class T> T& getChildRef(const std::string& name, BOOL recurse = TRUE) const
+ {
+ return *getChild<T>(name, recurse, TRUE);
+ }
+
virtual LLView* getChildView(const std::string& name, BOOL recurse = TRUE, BOOL create_if_missing = TRUE) const;
template <class T> T* createDummyWidget(const std::string& name) const