From 193010e9471685c0efd95d64d225f22dae7197e1 Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Sun, 3 Nov 2013 21:18:06 +0100 Subject: [PATCH] Add THROW_[MF]ALERT[EC] (AIArgs, AIAlert, AIAlertCode, AIAlertPrefix, AIAlertLine) A system to throw errors that allow for easy error reporting to the user by showing a translated pop-up alert box with the error message. The messages use strings.xml for translation and allow the usual replacement args (ie [FILE] is replaced with a filename). The exceptions can be cascaded, by adding more (translated) text when caught and then re-throwing the result. Macros are being used to support adding a function name prefix to a message of the current function that the exception is thrown from. The syntax is: (); to show 'line' (, ); to append 'line' to a caught alert. (, ); to prepend 'line' to a caught alert. where is one of: THROW_ALERT, THROW_MALERT, THROW_FALERT, THROW_FMALERT, THROW_ALERTE, THROW_MALERTE, THROW_FALERTE, THROW_FMALERTE, where M = modal, F = Function name. and where is one of: , AIArgs where is a string literal that will be looked up in strings.xml, and is: (, )[] There are more variations of the macros to throw an arbitrary class (append _CLASS), include an int code (append C) or to store the current errno as code (append E). For example, THROW_MALERTC(code, ...), or THROW_FALERT_CLASS(Foobar, ...), where the ... is the same as for the macros above. Documentation and example usage has been added to aialert.h. --- indra/llcommon/CMakeLists.txt | 2 + indra/llcommon/aialert.cpp | 67 +++++ indra/llcommon/aialert.h | 281 ++++++++++++++++++ indra/llcommon/llstring.h | 4 +- indra/llui/llnotifications.cpp | 27 ++ indra/llui/llnotifications.h | 3 + indra/llui/llnotificationsutil.cpp | 5 + indra/llui/llnotificationsutil.h | 3 + .../skins/default/xui/en-us/notifications.xml | 14 + .../skins/default/xui/en-us/strings.xml | 3 + 10 files changed, 408 insertions(+), 1 deletion(-) create mode 100644 indra/llcommon/aialert.cpp create mode 100644 indra/llcommon/aialert.h diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 2b4c37be0..f2ad6229c 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -17,6 +17,7 @@ include_directories( ) set(llcommon_SOURCE_FILES + aialert.cpp aiframetimer.cpp aithreadid.cpp imageids.cpp @@ -106,6 +107,7 @@ set(llcommon_SOURCE_FILES set(llcommon_HEADER_FILES CMakeLists.txt + aialert.h aiframetimer.h airecursive.h aithreadid.h diff --git a/indra/llcommon/aialert.cpp b/indra/llcommon/aialert.cpp new file mode 100644 index 000000000..44d2c3230 --- /dev/null +++ b/indra/llcommon/aialert.cpp @@ -0,0 +1,67 @@ +/** + * @file aialert.cpp + * + * Copyright (c) 2013, Aleric Inglewood. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * 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. + * + * CHANGELOG + * and additional copyright holders. + * + * 02/11/2013 + * - Initial version, written by Aleric Inglewood @ SL + */ + +#include "aialert.h" + +AIAlert::AIAlert(AIAlertPrefix const& prefix, modal_nt type, + std::string const& xml_desc, AIArgs const& args) : mModal(type) +{ + if (prefix) mLines.push_back(AIAlertLine(prefix)); + mLines.push_back(AIAlertLine(xml_desc, args)); +} + +AIAlert::AIAlert(AIAlertPrefix const& prefix, modal_nt type, + AIAlert const& alert, + std::string const& xml_desc, AIArgs const& args) : mLines(alert.mLines), mModal(type) +{ + if (alert.mModal == modal) mModal = modal; + if (prefix) mLines.push_back(AIAlertLine(prefix, !mLines.empty())); + mLines.push_back(AIAlertLine(xml_desc, args)); +} + +AIAlert::AIAlert(AIAlertPrefix const& prefix, modal_nt type, + std::string const& xml_desc, + AIAlert const& alert) : mLines(alert.mLines), mModal(type) +{ + if (alert.mModal == modal) mModal = modal; + if (!mLines.empty()) { mLines.front().set_newline(); } + mLines.push_front(AIAlertLine(xml_desc)); + if (prefix) mLines.push_front(AIAlertLine(prefix)); +} + +AIAlert::AIAlert(AIAlertPrefix const& prefix, modal_nt type, + std::string const& xml_desc, AIArgs const& args, + AIAlert const& alert) : mLines(alert.mLines), mModal(type) +{ + if (alert.mModal == modal) mModal = modal; + if (!mLines.empty()) { mLines.front().set_newline(); } + mLines.push_front(AIAlertLine(xml_desc, args)); + if (prefix) mLines.push_front(AIAlertLine(prefix)); +} + diff --git a/indra/llcommon/aialert.h b/indra/llcommon/aialert.h new file mode 100644 index 000000000..655a4890d --- /dev/null +++ b/indra/llcommon/aialert.h @@ -0,0 +1,281 @@ +/** + * @file aialert.h + * @brief Declaration of AIArgs, AIAlertPrefix, AIAlertLine, AIAlert and AIAlertCode + * + * Copyright (c) 2013, Aleric Inglewood. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * 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. + * + * CHANGELOG + * and additional copyright holders. + * + * 02/11/2013 + * Initial version, written by Aleric Inglewood @ SL + */ + +#ifndef AI_ALERT +#define AI_ALERT + +#include "llpreprocessor.h" +#include "llstring.h" +#include +#include + +//=================================================================================================================================== +// Facility to throw errors that can easily be converted to an informative pop-up floater for the user. + +// Throw arbitrary class. +#define THROW_ALERT_CLASS(Alert, ...) throw Alert(AIAlertPrefix(), AIAlert::not_modal, __VA_ARGS__) +#define THROW_MALERT_CLASS(Alert, ...) throw Alert(AIAlertPrefix(), AIAlert::modal, __VA_ARGS__) +#define THROW_FALERT_CLASS(Alert, ...) throw Alert(AIAlertPrefix(__PRETTY_FUNCTION__, alert_line_pretty_function_prefix), AIAlert::not_modal, __VA_ARGS__) +#define THROW_FMALERT_CLASS(Alert, ...) throw Alert(AIAlertPrefix(__PRETTY_FUNCTION__, alert_line_pretty_function_prefix), AIAlert::modal, __VA_ARGS__) + +// Shortcut to throw AIAlert. +#define THROW_ALERT(...) THROW_ALERT_CLASS(AIAlert, __VA_ARGS__) +#define THROW_MALERT(...) THROW_MALERT_CLASS(AIAlert, __VA_ARGS__) +#define THROW_FALERT(...) THROW_FALERT_CLASS(AIAlert, __VA_ARGS__) +#define THROW_FMALERT(...) THROW_FMALERT_CLASS(AIAlert, __VA_ARGS__) + +// Shortcut to throw AIAlertCode. +#define THROW_ALERTC(...) THROW_ALERT_CLASS(AIAlertCode, __VA_ARGS__) +#define THROW_MALERTC(...) THROW_MALERT_CLASS(AIAlertCode, __VA_ARGS__) +#define THROW_FALERTC(...) THROW_FALERT_CLASS(AIAlertCode, __VA_ARGS__) +#define THROW_FMALERTC(...) THROW_FMALERT_CLASS(AIAlertCode, __VA_ARGS__) + +// Shortcut to throw AIAlertCode with errno as code. +#define THROW_ALERTE(...) do { int errn = errno; THROW_ALERT_CLASS(AIAlertCode, errn, __VA_ARGS__); } while(0) +#define THROW_MALERTE(...) do { int errn = errno; THROW_MALERT_CLASS(AIAlertCode, errn, __VA_ARGS__); } while(0) +#define THROW_FALERTE(...) do { int errn = errno; THROW_FALERT_CLASS(AIAlertCode, errn, __VA_ARGS__); } while(0) +#define THROW_FMALERTE(...) do { int errn = errno; THROW_FMALERT_CLASS(AIAlertCode, errn, __VA_ARGS__); } while(0) + +// Examples + +#ifdef EXAMPLE_CODE + + //---------------------------------------------------------- + // To show the alert box: + + catch (AIAlert const& alert) + { + LLNotificationsUtil::add(alert); // Optionally pass alert_line_pretty_function_prefix as second parameter to *suppress* that output. + } + + // or, for example + + catch (AIAlertCode const& alert) + { + if (alert.getCode() != EEXIST) + { + LLNotificationsUtil::add(alert, alert_line_pretty_function_prefix); + } + } + //---------------------------------------------------------- + // To throw alerts: + + THROW_ALERT("ExampleKey"); // A) Lookup "ExampleKey" in strings.xml and show translation. + THROW_ALERT("ExampleKey", AIArgs("[FIRST]", first)("[SECOND]", second)(...etc...)); // B) Same as A, but replace [FIRST] with first, [SECOND] with second, etc. + THROW_ALERT("ExampleKey", alert); // C) As A, but followed by a colon and a newline, and then the text of 'alert'. + THROW_ALERT(alert, "ExampleKey"); // D) The text of 'alert', followed by a colon and a newline and then as A. + THROW_ALERT("ExampleKey", AIArgs("[FIRST]", first)("[SECOND]", second), alert); // E) As B, but followed by a colon and a newline, and then the text of 'alert'. + THROW_ALERT(alert, "ExampleKey", AIArgs("[FIRST]", first)("[SECOND]", second)); // F) The text of 'alert', followed by a colon and a newline and then as B. + // where 'alert' is a caught AIAlert object (as above) in a rethrow. + // Prepend ALERT with M and/or F to make the alert box Modal and/or prepend the text with the current function name. + // For example, + THROW_MFALERT("ExampleKey", AIArgs("[FIRST]", first)); // Throw a Modal alert box that is prefixed with the current Function name. + // Append E after ALERT to throw an AIAlertCode class that contains the current errno. + // For example, + THROW_FALERTE("ExampleKey", AIArgs("[FIRST]", first)); // Throw an alert box that is prefixed with the current Function name and pass errno to the catcher. + +#endif // EXAMPLE_CODE + +// +//=================================================================================================================================== + +enum alert_line_type_nt +{ + alert_line_normal = 0, + alert_line_empty_prefix = 1, + alert_line_pretty_function_prefix = 2 + // These must exist of single bits (a mask). +}; + +// An AIAlertPrefix currently comes only in two flavors: +// +// alert_line_empty_prefix : An empty prefix. +// alert_line_pretty_function_prefix : A function name prefix, this is the function from which the alert was thrown. + +class LL_COMMON_API AIAlertPrefix +{ + public: + AIAlertPrefix(void) : mType(alert_line_empty_prefix) { } + AIAlertPrefix(char const* str, alert_line_type_nt type) : mStr(str), mType(type) { } + + operator bool(void) const { return mType != alert_line_empty_prefix; } + alert_line_type_nt type(void) const { return mType; } + std::string const& str(void) const { return mStr; } + + private: + std::string mStr; // Literal text. For example a C++ function name. + alert_line_type_nt mType; // The type of this prefix. +}; + +// A wrapper around LLStringUtil::format_map_t to allow constructing a dictionary +// on one line by doing: +// +// AIArgs("[ARG1]", arg1)("[ARG2]", arg2)("[ARG3]", arg3)... + +class LL_COMMON_API AIArgs +{ + private: + LLStringUtil::format_map_t mArgs; // The underlying replacement map. + + public: + // Construct an empty map. + AIArgs(void) { } + // Construct a map with a single replacement. + AIArgs(char const* key, std::string const& replacement) { mArgs[key] = replacement; } + // Add another replacement. + AIArgs& operator()(char const* key, std::string const& replacement) { mArgs[key] = replacement; return *this; } + // The destructor may not throw. + ~AIArgs() throw() { } + + // Accessor. + LLStringUtil::format_map_t const& operator*() const { return mArgs; } +}; + +// A class that represents one line with its replacements. +// The string mXmlDesc shall be looked up in strings.xml. +// This is not done as part of this class because LLTrans::getString +// is not part of llcommon. + +class LL_COMMON_API AIAlertLine +{ + private: + bool mNewline; // Prepend this line with a newline if set. + std::string mXmlDesc; // The keyword to look up in string.xml. + AIArgs mArgs; // Replacement map. + alert_line_type_nt mType; // The type of this line: alert_line_normal for normal lines, other for prefixes. + + public: + AIAlertLine(std::string const& xml_desc, bool newline = false) : mNewline(newline), mXmlDesc(xml_desc), mType(alert_line_normal) { } + AIAlertLine(std::string const& xml_desc, AIArgs const& args, bool newline = false) : mNewline(newline), mXmlDesc(xml_desc), mArgs(args), mType(alert_line_normal) { } + AIAlertLine(AIAlertPrefix const& prefix, bool newline = false) : mNewline(newline), mXmlDesc("AIPrefix"), mArgs("[PREFIX]", prefix.str()), mType(prefix.type()) { } + // The destructor may not throw. + ~AIAlertLine() throw() { } + + // Prepend a newline before this line. + void set_newline(void) { mNewline = true; } + + // These are to be used like: LLTrans::getString(line.getXmlDesc(), line.args()) and prepend with a \n if prepend_newline() returns true. + std::string getXmlDesc(void) const { return mXmlDesc; } + LLStringUtil::format_map_t const& args(void) const { return *mArgs; } + bool prepend_newline(void) const { return mNewline; } + + // Accessors. + bool suppressed(unsigned int suppress_mask) const { return (suppress_mask & mType) != 0; } + bool is_prefix(void) const { return mType != alert_line_normal; } +}; + +// This class is used to throw an error that will cause +// an alert box to pop up for the user. +// +// An alert box only has text and an OK button. +// The alert box does not give feed back to the program; it is purely informational. + +// The class represents multiple lines, each line is to be translated and catenated, +// separated by newlines, and then written to an alert box. This is not done as part +// of this class because LLTrans::getString and LLNotification is not part of llcommon. +// Instead call LLNotificationUtil::add(AIAlert const&). + +class LL_COMMON_API AIAlert : public std::exception +{ + public: + typedef std::deque lines_type; + enum modal_nt { not_modal, modal }; + + // The destructor may not throw. + ~AIAlert() throw() { } + + // Accessors. + lines_type const& lines(void) const { return mLines; } + bool is_modal(void) const { return mModal == modal; } + + // A string with zero or more replacements. + AIAlert(AIAlertPrefix const& prefix, modal_nt modal, + std::string const& xml_desc, AIArgs const& args = AIArgs()); + + // Same as above bit prepending the message with the text of another alert. + AIAlert(AIAlertPrefix const& prefix, modal_nt modal, + AIAlert const& alert, + std::string const& xml_desc, AIArgs const& args = AIArgs()); + + // Same as above but appending the message with the text of another alert. + // (no args) + AIAlert(AIAlertPrefix const& prefix, modal_nt modal, + std::string const& xml_desc, + AIAlert const& alert); + // (with args) + AIAlert(AIAlertPrefix const& prefix, modal_nt modal, + std::string const& xml_desc, AIArgs const& args, + AIAlert const& alert); + + private: + lines_type mLines; // The lines (or prefixes) of text to be displayed, each consisting on a keyword (to be looked up in strings.xml) and a replacement map. + modal_nt mModal; // If true, make the alert box a modal floater. +}; + +// Same as AIAlert but allows to pass an additional error code. + +class LL_COMMON_API AIAlertCode : public AIAlert +{ + private: + int mCode; + + public: + // The destructor may not throw. + ~AIAlertCode() throw() { } + + // Accessor. + int getCode(void) const { return mCode; } + + // A string with zero or more replacements. + AIAlertCode(AIAlertPrefix const& prefix, modal_nt modal, int code, + std::string const& xml_desc, AIArgs const& args = AIArgs()) : + AIAlert(prefix, modal, xml_desc, args) { } + + // Same as above bit prepending the message with the text of another alert. + AIAlertCode(AIAlertPrefix const& prefix, modal_nt modal, int code, + AIAlert const& alert, + std::string const& xml_desc, AIArgs const& args = AIArgs()) : + AIAlert(prefix, modal, alert, xml_desc, args) { } + + // Same as above but appending the message with the text of another alert. + // (no args) + AIAlertCode(AIAlertPrefix const& prefix, modal_nt modal, int code, + std::string const& xml_desc, + AIAlert const& alert) : + AIAlert(prefix, modal, xml_desc, alert) { } + // (with args) + AIAlertCode(AIAlertPrefix const& prefix, modal_nt modal, int code, + std::string const& xml_desc, AIArgs const& args, + AIAlert const& alert) : + AIAlert(prefix, modal, xml_desc, args, alert) { } + +}; + +#endif // AI_ALERT diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 35d20d2ee..112a25e66 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -231,7 +231,9 @@ public: operator std::string() const { return mString; } bool operator<(const LLFormatMapString& rhs) const { return mString < rhs.mString; } std::size_t length() const { return mString.length(); } - + // The destructor may not throw. + ~LLFormatMapString() throw() { } + private: std::string mString; }; diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp index 41254b0bb..2f60b99fa 100644 --- a/indra/llui/llnotifications.cpp +++ b/indra/llui/llnotifications.cpp @@ -40,6 +40,7 @@ #include "lltrans.h" #include "llnotifications.h" +#include "aialert.h" #include "../newview/hippogridmanager.h" @@ -1479,6 +1480,32 @@ LLNotificationPtr LLNotifications::add(const LLNotification::Params& p) return pNotif; } +LLNotificationPtr LLNotifications::add(AIAlert const& alert, unsigned int suppress_mask) +{ + std::string alert_text; + bool suppress_newlines = false; + bool last_was_prefix = false; + for (AIAlert::lines_type::const_iterator line = alert.lines().begin(); line != alert.lines().end(); ++line) + { + // Even if a line is suppressed, we print its leading newline if requested, but never more than one. + if (!suppress_newlines && line->prepend_newline()) + { + alert_text += '\n'; + suppress_newlines = true; + } + if (!line->suppressed(suppress_mask)) + { + if (last_was_prefix) alert_text += ' '; // The translation system strips off spaces... add them back. + alert_text += LLTrans::getString(line->getXmlDesc(), line->args()); + suppress_newlines = false; + last_was_prefix = line->is_prefix(); + } + } + LLSD substitutions = LLSD::emptyMap(); + substitutions["[PAYLOAD]"] = alert_text; + return add(LLNotification::Params(alert.is_modal() ? "AIAlertModal" : "AIAlert").substitutions(substitutions)); +} + void LLNotifications::add(const LLNotificationPtr pNotif) { diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h index fe9049ee2..4adfabbcb 100644 --- a/indra/llui/llnotifications.h +++ b/indra/llui/llnotifications.h @@ -108,6 +108,8 @@ #include "llnotificationptr.h" #include "llnotificationcontext.h" +class AIAlert; + typedef enum e_notification_priority { NOTIFICATION_PRIORITY_UNSPECIFIED, @@ -737,6 +739,7 @@ public: const LLSD& substitutions, const LLSD& payload, LLNotificationFunctorRegistry::ResponseFunctor functor); + LLNotificationPtr add(AIAlert const& alert, unsigned int suppress_mask); LLNotificationPtr add(const LLNotification::Params& p); void forceResponse(const LLNotification::Params& params, S32 option); diff --git a/indra/llui/llnotificationsutil.cpp b/indra/llui/llnotificationsutil.cpp index 4abc9ca69..13e2b897d 100644 --- a/indra/llui/llnotificationsutil.cpp +++ b/indra/llui/llnotificationsutil.cpp @@ -30,6 +30,11 @@ #include "llsd.h" #include "llxmlnode.h" // apparently needed to call LLNotifications::instance() +LLNotificationPtr LLNotificationsUtil::add(AIAlert const& alert, unsigned int suppress_mask) +{ + return LLNotifications::instance().add(alert, suppress_mask); +} + LLNotificationPtr LLNotificationsUtil::add(const std::string& name) { return LLNotifications::instance().add( diff --git a/indra/llui/llnotificationsutil.h b/indra/llui/llnotificationsutil.h index 4093324d0..586b0d0bc 100644 --- a/indra/llui/llnotificationsutil.h +++ b/indra/llui/llnotificationsutil.h @@ -34,9 +34,12 @@ #include class LLSD; +class AIAlert; namespace LLNotificationsUtil { + LLNotificationPtr add(AIAlert const& alert, unsigned int suppress_mask = 0); // Singu extension. + LLNotificationPtr add(const std::string& name); LLNotificationPtr add(const std::string& name, diff --git a/indra/newview/skins/default/xui/en-us/notifications.xml b/indra/newview/skins/default/xui/en-us/notifications.xml index 1bcef0294..919e2595a 100644 --- a/indra/newview/skins/default/xui/en-us/notifications.xml +++ b/indra/newview/skins/default/xui/en-us/notifications.xml @@ -133,6 +133,20 @@ + +[PAYLOAD] + + + +[PAYLOAD] + + Teleport completed from + + "[PREFIX]: " +