diff options
author | Kent Quirk <q@lindenlab.com> | 2009-01-05 18:59:12 +0000 |
---|---|---|
committer | Kent Quirk <q@lindenlab.com> | 2009-01-05 18:59:12 +0000 |
commit | 667ca55bad0108c4bdf8f007b89e1a52fc766aad (patch) | |
tree | 7bd62ac8d9af079c3994565f3f200ccc250bbc28 /indra/llui | |
parent | 95f365789f4cebc7bd97ccefd538f14d481a8373 (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.txt | 4 | ||||
-rw-r--r-- | indra/llui/llcombobox.cpp | 32 | ||||
-rw-r--r-- | indra/llui/llcombobox.h | 1 | ||||
-rw-r--r-- | indra/llui/llfloater.cpp | 5 | ||||
-rw-r--r-- | indra/llui/llfloater.h | 24 | ||||
-rw-r--r-- | indra/llui/llfunctorregistry.cpp | 37 | ||||
-rw-r--r-- | indra/llui/llfunctorregistry.h | 145 | ||||
-rw-r--r-- | indra/llui/llnotifications.cpp | 1471 | ||||
-rw-r--r-- | indra/llui/llnotifications.h | 892 | ||||
-rw-r--r-- | indra/llui/llpanel.cpp | 129 | ||||
-rw-r--r-- | indra/llui/llpanel.h | 26 | ||||
-rw-r--r-- | indra/llui/llscrollbar.cpp | 76 | ||||
-rw-r--r-- | indra/llui/llscrollbar.h | 2 | ||||
-rw-r--r-- | indra/llui/llscrolllistctrl.cpp | 342 | ||||
-rw-r--r-- | indra/llui/llscrolllistctrl.h | 115 | ||||
-rw-r--r-- | indra/llui/llui.cpp | 16 | ||||
-rw-r--r-- | indra/llui/llui.h | 237 | ||||
-rw-r--r-- | indra/llui/lluictrl.cpp | 11 | ||||
-rw-r--r-- | indra/llui/lluictrl.h | 2 | ||||
-rw-r--r-- | indra/llui/lluictrlfactory.h | 1 | ||||
-rw-r--r-- | indra/llui/lluistring.cpp | 13 | ||||
-rw-r--r-- | indra/llui/lluistring.h | 6 | ||||
-rw-r--r-- | indra/llui/llview.cpp | 5 | ||||
-rw-r--r-- | indra/llui/llview.h | 7 |
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 |