diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 9feaeb96ff..9167a771da 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -27530,6 +27530,19 @@ Change of this parameter will affect the layout of buttons in notification toast Value 1 + + OmnifilterRuleSetID + + Comment + The id of the current rule set for the Omnifilter. + Persist + 1 + Type + S32 + Value + -1 + + FSAutoOrderIMTabs Comment diff --git a/indra/newview/omnifilter.cpp b/indra/newview/omnifilter.cpp index e9874372bf..02bb4abc03 100644 --- a/indra/newview/omnifilter.cpp +++ b/indra/newview/omnifilter.cpp @@ -29,6 +29,10 @@ #include "llcombobox.h" #include "lllineeditor.h" #include "lltexteditor.h" +// [FIRE-36763] - Add rule sets to Omnifilter +#include "llviewercontrol.h" // Needed for gSavedSettings +#include "llnotificationsutil.h" // Needed for Notifications +// [FIRE-36763] #include "fsscrolllistctrl.h" @@ -78,7 +82,11 @@ OmnifilterEngine::Needle* Omnifilter::getSelectedNeedle() if (needle_name_cell) { const std::string& needle_name = needle_name_cell->getValue().asString(); - if (!needle_name.empty()) + // [FIRE-36763] - Add rule sets to Omnifilter + //if (!needle_name.empty()) + // Only try to access the needle if the name is not empty and it is on the current needle list, otherwise it will cause an error + if (!needle_name.empty() && OmnifilterEngine::getInstance()->getNeedleList().contains(needle_name)) + // [FIRE-36763] { return &OmnifilterEngine::getInstance()->getNeedleList().at(needle_name); } @@ -277,6 +285,237 @@ void Omnifilter::onDownNeedleClicked() } // [FIRE-36649] +// [FIRE-36763] - Add rule sets to Omnifilter +// Callback method for the New Rule Set button when clicked. +// Creates an notification to the user to ask for the name of the new rule set. +void Omnifilter::onNewRuleSetClicked() +{ + LLSD args; + // Default name of the new rule set + args["RULESETNAME"] = "New Rule Set"; + // Display the new rule set notification and if the user presses the OK button, call the new rule set name selected callback method + LLNotificationsUtil::add("OmniFilterNewRuleSet", args, LLSD(), + boost::bind(&Omnifilter::onNewRuleSetNameSelectedCallback, this, _1, _2)); +} + +// Callback method for the Clone Rule Set button when clicked. +// Creates an notification to the user to ask for the name of the clone rule set. +void Omnifilter::onCloneRuleSetClicked() +{ + LLSD args; + // Default name of the cloned rule set + args["RULESETNAME"] = "Cloned Rule Set"; + // Display the new rule set notification and if the user presses the OK button, call the new rule set name selected callback method + LLNotificationsUtil::add("OmniFilterCloneRuleSet", args, LLSD(), + boost::bind(&Omnifilter::onCloneRuleSetNameSelectedCallback, this, _1, _2)); +} + +// Callback method for the Remove Rule Set button when clicked. +// Creates a notification to the user to ask if they are sure they want to remove the specified Rule Set. +void Omnifilter::onRemoveRuleSetClicked() +{ + static OmnifilterEngine* instance = OmnifilterEngine::getInstance(); + // IF the user tries to remove the Default rule set, send an error as that is not allowed. + if (mRuleSetsCmb->getCurrentIndex() == 0) + { + LLNotificationsUtil::add("OmniFilterRemoveRuleSetDefault", LLSD(), LLSD()); + } + // Else prompt the user with a confirmation notification if they really want to remove the + else + { + LLSD args; + args["RULESETNAME"] = instance->getCurrentSelectedRuleSet(); + // Display the remove rule set notification and if the user presses the OK button, call the remove rule set confirm callback method + LLNotificationsUtil::add("OmniFilterRemoveRuleSet", args, LLSD(), + boost::bind(&Omnifilter::onRemoveRuleSetConfirmedCallback, this, _1, _2)); + } +} + +// New Rule Set callback, which does the actual cloning. +void Omnifilter::onNewRuleSetNameSelectedCallback(const LLSD& notification, const LLSD& response) +{ + static OmnifilterEngine* instance = OmnifilterEngine::getInstance(); + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) // YES + { + std::string new_name = response["new_name"].asString(); + // Try to perform the actual creating a new rule set. + S32 return_value = instance->addNewRuleSet(new_name); + + if (return_value == 1) + { + S32 new_rule_index = gSavedSettings.getS32("OmnifilterRuleSetID"); + // Add the new rule set to the Rule Set Drop Down + mRuleSetsCmb->add(new_name, LLSD(new_rule_index)); + // Select the new rule set + mRuleSetsCmb->setCurrentByIndex(new_rule_index); + + // Clear the Rule list + mNeedleListCtrl->clear(); + mNeedleListCtrl->clearRows(); + + // Add the new item. + onAddNeedleClicked(); + + // Save the state of the OmniFilter Window + instance->setDirty(true); + } + // Else the user tried to create a new rule set with a blank name, so show an error. + else if (return_value == -1) + { + LLNotificationsUtil::add("OmniFilterrNewRuleSetBlank", LLSD(), LLSD(), boost::bind(&Omnifilter::onNewRuleSetClicked, this)); + } + // Else the user tried to create a new rule set with a duplicate name, so show an error. + else if (return_value == 0) + { + LLNotificationsUtil::add("OmniFilterNewRuleSetDuplicate", LLSD(), LLSD(), boost::bind(&Omnifilter::onNewRuleSetClicked, this)); + } + } +} + +// Clone Rule Set callback, which does the actual cloning. +void Omnifilter::onCloneRuleSetNameSelectedCallback(const LLSD& notification, const LLSD& response) +{ + static OmnifilterEngine* instance = OmnifilterEngine::getInstance(); + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) // YES + { + std::string new_name = response["new_name"].asString(); + // Try to perform the actual cloning of the rule set. + S32 return_value = instance->addClonedRuleSet(new_name); + // If the cloning succeeded + if (return_value == 1) + { + // Get the index of the added rule set. + S32 new_rule_index = gSavedSettings.getS32("OmnifilterRuleSetID"); + // Add the new rule set to the Rule Set Drop Down + mRuleSetsCmb->add(new_name, LLSD(new_rule_index)); + + // Select the new rule set + mRuleSetsCmb->setCurrentByIndex(new_rule_index); + + // Save the state of the OmniFilter Window + instance->setDirty(true); + } + // Else the user tried to create a cloned rule set with a blank name, so show an error. + else if (return_value == -1) + { + LLNotificationsUtil::add("OmniFilterCloneRuleSetBlank", LLSD(), LLSD(), boost::bind(&Omnifilter::onCloneRuleSetClicked, this)); + } + // Else the user tried to create a cloned rule set with a duplicate name, so show an error. + else if (return_value == 0) + { + LLNotificationsUtil::add("OmniFilterCloneRuleSetDuplicate", LLSD(), LLSD(), boost::bind(&Omnifilter::onCloneRuleSetClicked, this)); + } + } +} + +// Remove Rule Set callback, which does the actual removal. +void Omnifilter::onRemoveRuleSetConfirmedCallback(const LLSD& notification, const LLSD& response) +{ + static OmnifilterEngine* instance = OmnifilterEngine::getInstance(); + // Get the user response + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) // YES + { + std::string tooltip_msg; + // Get the index of the current rule set being removed. + S32 current_index = gSavedSettings.getS32("OmnifilterRuleSetID"); + // Try to perform the actual removal. + S32 return_value = instance->removeCurrentRuleSet(); + // If the removal succeeded + if (return_value == 1) + { + // Remove the rule set from the Rule Set Drop Down + mRuleSetsCmb->remove(current_index); + // Reload the current rule set and switch the UI over to the Default rule set. + reloadRule(); + mRuleSetsCmb->setCurrentByIndex(0); + + // Save the state of the OmniFilter Window + instance->setDirty(true); + } + // Else the user tried to remove the default rule set, so show an error. + else if (return_value == 0) + { + LLNotificationsUtil::add("OmniFilterRemoveRuleSetDefault", LLSD(), LLSD()); + } + // Else the user tried to remove a rule set that index was out of bounds, so show an error. + else if (return_value == -1) + { + LLSD args; + args["OUTOFBOUNDS"] = current_index; + LLNotificationsUtil::add("OmniFilterRemoveRuleSetOutofBounds", args, LLSD()); + } + // Else the user tried to remove a rule set that had an name that does not exist in the map of rule sets, so show an error. + else if (return_value == -2) + { + LLSD args; + args["INVALIDNAME"] = instance->getCurrentSelectedRuleSet(); + LLNotificationsUtil::add("OmniFilterRemoveRuleSetInvalidName", args, LLSD()); + } + } +} + +// Rule Set Drop Down on commit callback method +void Omnifilter::onRuleSetChanged() +{ + static OmnifilterEngine* instance = OmnifilterEngine::getInstance(); + + // Save the current + // Write the current rule set back to the storeage + instance->assignRuleSet(false); + // Store the current combo box index as the Needle Preset Index + gSavedSettings.setS32("OmnifilterRuleSetID", mRuleSetsCmb->getCurrentIndex()); + // Assign and reload the current rule + instance->assignRuleSetNameFromSettings(); + + instance->assignRuleSet(true); + reloadRule(); +} + +// Reloads the full rule sets drop down widget (combobox) +void Omnifilter::reloadRules() +{ + static OmnifilterEngine* instance = OmnifilterEngine::getInstance(); + // Clear the current needles + mRuleSetsCmb->clear(); + S32 current_rule_set_id = gSavedSettings.getS32("OmnifilterRuleSetID"); + for (S32 index = 0; index < instance->getOrderedRuleSetSize(); index++) + { + // Get the name from the ordered list of rule sets. + const std::string needle_rule_set_name(instance->getOrderedRuleSetName(index)); + LLSD value(index); + // And add to the UI element. The value is the value that is passed along with the commit callback method. + mRuleSetsCmb->add(needle_rule_set_name, value); + } + // Finally, select the stored rule set + mRuleSetsCmb->selectByValue(LLSD(current_rule_set_id)); +} + +// Reloads the actual rule set UI elements, including the Rule scroll list and editor panel +void Omnifilter::reloadRule() +{ + // Use static pointer for the instance so that don't have to keep requesting every time this code is touched. + static OmnifilterEngine* instance = OmnifilterEngine::getInstance(); + // Clear the current needles + mNeedleListCtrl->clearRows(); + mNeedleListCtrl->clear(); + + // Loop over the ordered list + for (const auto& needle_name : instance->getOrderedNeedleList()) + { + // Get the current needle and use the addNeedle method to add the the UI. + const auto& needle = instance->getNeedleList()[needle_name]; + addNeedle(needle_name, needle); + } + + mNeedleListCtrl->selectFirstItem(); + onSelectNeedle(); +} + +// [FIRE-36763] + void Omnifilter::onNeedleNameChanged() { const std::string& old_name = mNeedleListCtrl->getSelectedItemLabel(NEEDLE_NAME_COLUMN); @@ -381,6 +620,13 @@ bool Omnifilter::postBuild() mSenderCaseSensitiveCheck = getChild("sender_case"); mSenderMatchTypeCombo = getChild("sender_match_type"); mContentCtrl = getChild("content"); + // [FIRE-36763] - Add rule sets to Omnifilter + // Add the preset controls + mRuleSetsCmb = getChild("cmb_rule_sets"); // Rule Set Drop Down + mNewRuleSetBtn = getChild("btn_rule_set_new"); // New Rule Set + mCloneRuleSetBtn = getChild("btn_rule_set_clone"); // Clone Rule Set + mRemoveRuleSetBtn = getChild("btn_rule_set_remove"); // Remove Rule Set + // [FIRE-36763] mContentCaseSensitiveCheck = getChild("content_case"); mContentMatchTypeCombo = getChild("content_match_type"); mRegionNameCtrl = getChild("region_name"); @@ -412,6 +658,12 @@ bool Omnifilter::postBuild() mFilterLogCtrl->deleteAllItems(); auto& instance = OmnifilterEngine::instance(); + // [FIRE-36763] - Add rule sets to Omnifilter + // Reload the rules set UI elements, uses the settings stored rule set ID + reloadRules(); + instance.assignRuleSetNameFromSettings(); + instance.assignRuleSet(true); + // [FIRE-36763] // [FIRE-36649] - Add reordering to OmniFilter //for (const auto& [needle_name, needle] : instance.getNeedleList()) // Loop over the ordered list @@ -446,6 +698,13 @@ bool Omnifilter::postBuild() mUpNeedleBtn->setCommitCallback(boost::bind(&Omnifilter::onUpNeedleClicked, this)); mDownNeedleBtn->setCommitCallback(boost::bind(&Omnifilter::onDownNeedleClicked, this)); // [FIRE-36649] + // [FIRE-36763] - Add rule sets to Omnifilter + // Add the Rule Set controls + mRuleSetsCmb->setCommitCallback(boost::bind(&Omnifilter::onRuleSetChanged, this)); // Rule Set Drop Down + mNewRuleSetBtn->setCommitCallback(boost::bind(&Omnifilter::onNewRuleSetClicked, this)); // New Rule Set + mCloneRuleSetBtn->setCommitCallback(boost::bind(&Omnifilter::onCloneRuleSetClicked, this)); // Clone Rule Set + mRemoveRuleSetBtn->setCommitCallback(boost::bind(&Omnifilter::onRemoveRuleSetClicked, this)); // Remove Rule Set + // [FIRE-36763] mNeedleNameCtrl->setCommitCallback(boost::bind(&Omnifilter::onNeedleNameChanged, this)); mSenderNameCtrl->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this)); mSenderCaseSensitiveCheck->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this)); diff --git a/indra/newview/omnifilter.h b/indra/newview/omnifilter.h index 7aafa3712b..db9299752b 100644 --- a/indra/newview/omnifilter.h +++ b/indra/newview/omnifilter.h @@ -60,6 +60,17 @@ protected: void onUpNeedleClicked(); void onDownNeedleClicked(); // [FIRE-36649] + // [FIRE-36763] - Add rule sets to Omnifilter + void onNewRuleSetClicked(); + void onCloneRuleSetClicked(); + void onRemoveRuleSetClicked(); + void onNewRuleSetNameSelectedCallback(const LLSD& notification, const LLSD& response); + void onCloneRuleSetNameSelectedCallback(const LLSD& notification, const LLSD& response); + void onRemoveRuleSetConfirmedCallback(const LLSD& notification, const LLSD& response); + void onRuleSetChanged(); + void reloadRules(); + void reloadRule(); + // [FIRE-36763] void onNeedleNameChanged(); void onNeedleCheckboxChanged(LLUICtrl* ctrl); void onOwnerChanged(); @@ -73,6 +84,12 @@ protected: LLButton* mUpNeedleBtn{ nullptr }; LLButton* mDownNeedleBtn{ nullptr }; // [FIRE-36649] + // [FIRE-36763] - Add rule sets to Omnifilter + LLComboBox* mRuleSetsCmb{ nullptr }; + LLButton* mNewRuleSetBtn{ nullptr }; + LLButton* mCloneRuleSetBtn{ nullptr }; + LLButton* mRemoveRuleSetBtn{ nullptr }; + // [FIRE-36763] FSScrollListCtrl* mFilterLogCtrl{ nullptr }; LLPanel* mPanelDetails{ nullptr }; LLLineEditor* mNeedleNameCtrl{ nullptr }; diff --git a/indra/newview/omnifilterengine.cpp b/indra/newview/omnifilterengine.cpp index bdc524049d..87ffb5978e 100644 --- a/indra/newview/omnifilterengine.cpp +++ b/indra/newview/omnifilterengine.cpp @@ -38,13 +38,18 @@ OmnifilterEngine::OmnifilterEngine() : LLSingleton() , LLEventTimer(5.0f) , mDirty(false) + , mCurrentSelectedRuleSet("Default") // [FIRE-36763] - Add rule sets to Omnifilter { mEventTimer.stop(); } OmnifilterEngine::~OmnifilterEngine() { - // delete Xxx; + // [FIRE-36763] - Add rule sets to Omnifilter + // If the user changed a setting and quickly closed the viewer, the changes would not be saved, but + // the changes to the settings.xml would, which would cause issues. So save upon close just in case. + saveNeedles(); + // [FIRE-36763] } void OmnifilterEngine::init() @@ -325,6 +330,363 @@ void OmnifilterEngine::setDirty(bool dirty) } } +// [FIRE-36763] - Add rule sets to Omnifilter +// Get the name from the vector of ordered rule sets at the specified index +std::string_view OmnifilterEngine::getOrderedRuleSetName(const S32 index) const +{ + // If the index is within the range of the vector, return the stored value + if (index >= 0 && index < mOrderedRuleSets.size() && mOrderedRuleSets.size() > 0) + { + return mOrderedRuleSets[index]; + } + + // Return an empty string if not found. + return ""; +} + +// Gets the ordered rule set index by looking up the name of a rule set. +S32 OmnifilterEngine::getOrderedRuleSetIndex(std::string_view lookup_name) +{ + for (S32 index = 0; index < mOrderedNeedles.size(); index++) + { + if (mOrderedNeedles[index] == lookup_name) + { + return index; + } + } + return -1; +} + +// Parses a passed in LLSD as a Map of rule sets, which contain another map, which contains the actual rule set data and an order value +// return false if the current LLSD is an older format, return true when finish importing the data. +bool OmnifilterEngine::importFromLLSD(const LLSD& needle_rule_sets_llsd) +{ + std::string preset_name = "Default"; + // If there is no default Rule Set return false + if (!needle_rule_sets_llsd.has("Default")) + { + return false; + } + // Else there is a map named default, does it contain a variable of "order", if not, return false + else if (!needle_rule_sets_llsd["Default"].has("order")) + { + return false; + } + mOrderedRuleSets.clear(); + // Use the number of rule sets to pre-allocate the ordered rule sets. So can use assignment operators without re-allocating. + mOrderedRuleSets.resize(needle_rule_sets_llsd.size()); + // Loop over the outer Rule Set map + S32 rule_set_index = 0; + for (const auto&[new_rule_set_name, new_rule_set_llsd] : llsd::inMap(needle_rule_sets_llsd)) + { + + // If there is an order tag, then + if (new_rule_set_llsd.has("order")) + { + // Add the rule set name to the ordered list at the order position. + mOrderedRuleSets[new_rule_set_llsd["order"].asInteger()] = new_rule_set_name; + } + else + { + // Else if there is no order tag, then add add the rule set name to the ordered list in the order read in. + // NOTE maps don't store data order added, but optimized order for storage. + mOrderedRuleSets[rule_set_index++] = new_rule_set_name; + } + // Generate a new rule set + rule_set_t new_rule_set = rule_set_t(needle_ordered_list_t(), needle_list_t()); + // Apply the size of the rule set (number of rules) to the ordered rule set set. + // First = Ordered Rule Set Vector, Second = Actual Rule Set Map + new_rule_set.first.resize(new_rule_set_llsd["rule_set"].size()); + // Set the current needles_llsd to the actual rule set stored. + // keeps old code valid. + LLSD needles_llsd = new_rule_set_llsd["rule_set"]; + S32 index = 0; + // Now loop over the rule map stored in the rule set. + for (const auto& [new_needle_name, needle_data] : llsd::inMap(needles_llsd)) + { + Needle new_needle; + // Perform checks on all input values from the data format and skip any that don't exist. + if (needle_data.has("sender_name")) + new_needle.mSenderName = needle_data["sender_name"].asString(); + if (needle_data.has("content")) + new_needle.mContent = needle_data["content"].asString(); + if (needle_data.has("region_name")) + new_needle.mRegionName = needle_data["region_name"].asString(); + if (needle_data.has("chat_replace")) + new_needle.mChatReplace = needle_data["chat_replace"].asString(); + if (needle_data.has("button_reply")) + new_needle.mButtonReply = needle_data["button_reply"].asString(); + if (needle_data.has("textbox_reply")) + new_needle.mTextBoxReply = needle_data["textbox_reply"].asString(); + if (needle_data.has("sender_name_match_type")) + new_needle.mSenderNameMatchType = static_cast(needle_data["sender_name_match_type"].asInteger()); + if (needle_data.has("content_match_type")) + new_needle.mContentMatchType = static_cast(needle_data["content_match_type"].asInteger()); + + if (needle_data.has("types")) + { + LLSD types_llsd = needle_data["types"]; + + for (const auto& needle_type : llsd::inArray(types_llsd)) + { + new_needle.mTypes.insert(static_cast(needle_type.asInteger())); + } + } + if (needle_data.has("enabled")) + new_needle.mEnabled = needle_data["enabled"].asBoolean(); + if (needle_data.has("sender_name_case_insensitive")) + new_needle.mSenderNameCaseInsensitive = needle_data["sender_name_case_insensitive"].asBoolean(); + if (needle_data.has("content_case_insensitive")) + new_needle.mContentCaseInsensitive = needle_data["content_case_insensitive"].asBoolean(); + + if (needle_data.has("enabled")) + { + const std::string owner_id_str = needle_data["owner_id"].asString(); + if (!owner_id_str.empty()) + { + new_needle.mOwnerID.set(owner_id_str); + } + } + // Assign the actuall new rule set to the parsed needle object + new_rule_set.second[new_needle_name] = new_needle; + // Add the loaded needle name to the ordered needle list + // Needles are stored in order added to the map originally so use the + // order value stored to restore the order back to the user + // defined order. + if (needle_data.has("order")) + { + new_rule_set.first[needle_data["order"].asInteger()] = new_needle_name; + } + else + { + new_rule_set.first[index++] = new_needle_name; + } + } + mNeedleRuleSets[new_rule_set_name] = new_rule_set; + } + + return true; +} + +// Exports the current stored orered rules sets to an LLSD object and returns the object created. +LLSD OmnifilterEngine::exportToLLSD() +{ + LLSD output; + // Loop over all the rulesets + S32 rule_set_order = 0; + // Loop over the ordered rule set names + for (const auto& export_rule_set_name : mOrderedRuleSets) + { + LLSD needles_llsd; + // Get the map of needles that have the name of the current ordered rule set + const auto& export_rule_set = mNeedleRuleSets[export_rule_set_name]; + + // [FIRE-36649] - Add reordering to OmniFilter + // Use ordered needle list to get the names of the needles in specified order and not the order added to the map. + // for (const auto& [needle_name, needle] : mNeedles) + S32 order = 0; + for (const auto& needle_name : export_rule_set.first) + { + const Needle &needle = export_rule_set.second.at(needle_name); + // Store the order of the needle + needles_llsd[needle_name]["order"] = order++; + // [FIRE-36649] + needles_llsd[needle_name]["sender_name"] = needle.mSenderName; + needles_llsd[needle_name]["content"] = needle.mContent; + needles_llsd[needle_name]["region_name"] = needle.mRegionName; + needles_llsd[needle_name]["chat_replace"] = needle.mChatReplace; + needles_llsd[needle_name]["button_reply"] = needle.mButtonReply; + needles_llsd[needle_name]["textbox_reply"] = needle.mTextBoxReply; + needles_llsd[needle_name]["sender_name_match_type"] = needle.mSenderNameMatchType; + needles_llsd[needle_name]["content_match_type"] = needle.mContentMatchType; + needles_llsd[needle_name]["owner_id"] = needle.mOwnerID; + + LLSD types_llsd; + for (auto type : needle.mTypes) + { + types_llsd.append(static_cast(type)); + } + + needles_llsd[needle_name]["types"] = types_llsd; + needles_llsd[needle_name]["enabled"] = needle.mEnabled; + needles_llsd[needle_name]["sender_name_case_insensitive"] = needle.mSenderNameCaseInsensitive; + needles_llsd[needle_name]["content_case_insensitive"] = needle.mContentCaseInsensitive; + } + // Store the rule set into the otuput with the rule set store in the LLSD as well as the rule set order value + output[export_rule_set_name]["rule_set"] = needles_llsd; + output[export_rule_set_name]["order"] = rule_set_order++; + } + + // Return the final LLSD which contains all the Rule Sets + return output; +} + +// Assignes a rule set to either fron the internal variable to the stored map of rule sets, or +// copies the current selected rule set from storage to the internal variables. +// This lets us keep all the original design and not require refactoring of code +// in other classes that interact with the Omnifilter. +bool OmnifilterEngine::assignRuleSet(const bool rule_set_to_internal) +{ + // If there is no current selected rule set or the rule set name is not in the list of rule sets, return false + if (mCurrentSelectedRuleSet.empty() || !mNeedleRuleSets.contains(mCurrentSelectedRuleSet)) + { + return false; + } + + // If transfering data from the currently selected rule set to the internal variables + if (rule_set_to_internal) + { + // Assign the needle map and vector of ordered neele names from the currently selected rule set. + mNeedles = mNeedleRuleSets[mCurrentSelectedRuleSet].second; + mOrderedNeedles = mNeedleRuleSets[mCurrentSelectedRuleSet].first; + } + // Else, want to move the data back the from the internal variables to selected data values + else + { + mNeedleRuleSets[mCurrentSelectedRuleSet].second = mNeedles; + mNeedleRuleSets[mCurrentSelectedRuleSet].first = mOrderedNeedles; + } + return true; +} + +// Assigns the current selected rule set string to the value passed in +bool OmnifilterEngine::setCurrentRuleSet(std::string_view rule_set_name) +{ + // If blank, return false as we should not have a blank name. + if (rule_set_name.empty()) + { + return false; + } + // Store the pass in rule set name. + mCurrentSelectedRuleSet = rule_set_name; + + // Apply the rule set to the current variables + assignRuleSet(); + + return true; +} + +// Performs the actual removal of the current rule set. +S32 OmnifilterEngine::removeCurrentRuleSet() +{ + // If the current rule set list contains the currently selceted rule set, then erase it. + if (mNeedleRuleSets.contains(mCurrentSelectedRuleSet)) + { + S32 current_index = gSavedSettings.getS32("OmnifilterRuleSetID"); + // If the current index is greater then the + if (current_index == 0) + { + // Return 0 for unable to remove default + return 0; + } + + // If the index is within range of the needle rule sets, then + if (current_index < mNeedleRuleSets.size() && current_index > 0) + { + // Erase both the the current rule set and the ordered rule set name + mNeedleRuleSets.erase(mCurrentSelectedRuleSet); + mOrderedRuleSets.erase(mOrderedRuleSets.begin() + current_index); + // Reset the selected rule set ID back to 0, let the user pick the one to use next. 0 is also safe as it should never be removed. + gSavedSettings.setS32("OmnifilterRuleSetID", 0); + // Re-assign the current rule set name and reload the rule set from storage to the rule set + assignRuleSetNameFromSettings(); + assignRuleSet(); + // Return 1 indicating that the removal worked correctly. + return 1; + } + // Return -1 as the rule set index out of bounds + return 0; + } + // Return -2 for rule set name does not exist + return -2; +} + +// Add a new rule set based upon the name passed in +S32 OmnifilterEngine::addNewRuleSet(std::string_view new_name) +{ + // If the name is blank, return an error code, to then let the user know with a notification + if (new_name.empty()) + { + return -1; + } + + std::string str_new_name(new_name); + // If the name already exists, return an error to let the user know + if (mNeedleRuleSets.contains(str_new_name)) + { + return 0; + } + + // We want to clear the current rule set and ordered rules as a new rule set has only a single template rule that is disabled. + mNeedles.clear(); + mOrderedNeedles.clear(); + // Set the rule set in the map to a new rule set pair. + mNeedleRuleSets[str_new_name] = rule_set_t(needle_ordered_list_t(), needle_list_t()); + // Update the OmnifilterRuleSetID saved setting to store the location of the new rule set. + // We want to do this to support auto-selecting the new rule set. + gSavedSettings.setS32("OmnifilterRuleSetID", static_cast(mNeedleRuleSets.size() - 1)); + // Set the current selcted rule set to the new rule set + mCurrentSelectedRuleSet = str_new_name; + // Add the rule set name to the list of ordered rule sets + mOrderedRuleSets.push_back(mCurrentSelectedRuleSet); + + return 1; +} + +// Clone the current rule set based upon the name passed in +S32 OmnifilterEngine::addClonedRuleSet(std::string_view new_name) +{ + // If the name is blank, return an error code, to then let the user know with a notification + if (new_name.empty()) + { + return -1; + } + + std::string str_new_name(new_name); + + // If the name already exists, return an error to let the user know + if (mNeedleRuleSets.contains(str_new_name)) + { + return 0; + } + + // This method is different in the new rule set method by not clearing the current rules and ordered rules. + + // Set the rule set in the map to a new rule set pair. + mNeedleRuleSets[str_new_name] = rule_set_t(needle_ordered_list_t(), needle_list_t()); + // Update the OmnifilterRuleSetID saved setting to store the location of the new rule set. + // We want to do this to support auto-selecting the new rule set. + gSavedSettings.setS32("OmnifilterRuleSetID", static_cast(mNeedleRuleSets.size() - 1)); + // Set the current selcted rule set to the new rule set + mCurrentSelectedRuleSet = str_new_name; + // Add the rule set name to the list of ordered rule sets + mOrderedRuleSets.push_back(mCurrentSelectedRuleSet); + + // Copy the internal rule set to the stored rulset + assignRuleSet(false); + + return 1; +} + +// Takes the stored setting OmnifilterRuleSetID and assigns the current +// selected rule set to the ordered rule set at that location. +bool OmnifilterEngine::assignRuleSetNameFromSettings() +{ + // Get hte number of items to loop over + S32 rule_set_id = gSavedSettings.getS32("OmnifilterRuleSetID"); + std::string found_rule_set(getOrderedRuleSetName(rule_set_id)); + + if (found_rule_set.empty()) + { + return false; + } + + mCurrentSelectedRuleSet = found_rule_set; + + return true; +} +// [FIRE-36763] + void OmnifilterEngine::loadNeedles() { if (mNeedlesXMLPath.empty()) @@ -391,6 +753,28 @@ void OmnifilterEngine::loadNeedles() return; } + // [FIRE-36763] - Add rule sets to Omnifilter + static LLCachedControl NeedlePresetIndex(gSavedSettings, "OmnifilterRuleSetID"); + + // If this is not the first time loading, based upon the preset index being set + // to an invalid valid, after this there should always be atleast 1 rule set (default) + if (NeedlePresetIndex > -1) + { + mNeedleRuleSets.clear(); + // If the importing works out correctly, then use the reloaded rulset set name from the settings and assign that rule set. + if (importFromLLSD(needles_llsd)) + { + assignRuleSetNameFromSettings(); + // Assign the rule set + assignRuleSet(); + // Return as we can skip the rest of the old loading. + return; + } + // Otherwise, the import failed as it may be an existing older rule set that has a vector of ordered rules. + // If so, we still want to use the older load code to parse the older form and convert to the newer format. + } + // Load the file with the old code to get the old .xml file data loaded, to then be outputted in the new format + // [FIRE-36763] // [FIRE-36649] - Add reordering to OmniFilter // Clear the vector of filters mOrderedNeedles.clear(); @@ -444,6 +828,19 @@ void OmnifilterEngine::loadNeedles() } // [/FIRE-36649] } + // [FIRE-36649] - Add reordering to OmniFilter + // Create a default from the current version + gSavedSettings.setS32("OmnifilterRuleSetID", 0); + // The first rule set is always called Default. + mCurrentSelectedRuleSet = "Default"; + // Create a new rule set with the already parsed ordered ruules and map of rules. + mNeedleRuleSets["Default"] = rule_set_t(mOrderedNeedles, mNeedles); + mOrderedRuleSets.push_back(mCurrentSelectedRuleSet); + // Save the ruleset to the storage rule set. + assignRuleSet(false); + // Save the changes back to the Omnifilter.xml file. + saveNeedles(); + // [FIRE-36649] } void OmnifilterEngine::saveNeedles() @@ -469,6 +866,12 @@ void OmnifilterEngine::saveNeedles() LLSD needles_llsd; + // [FIRE-36763] - Add rule sets to Omnifilter + // First assign the current rule set back to the currently selected rule set. + assignRuleSet(false); + // Export the needles to LLSD storage + needles_llsd = exportToLLSD(); + /* // [FIRE-36649] - Add reordering to OmniFilter // Use ordered needle list to get the names of the needles in specified order and not the order added to the map. //for (const auto& [needle_name, needle] : mNeedles) @@ -500,6 +903,8 @@ void OmnifilterEngine::saveNeedles() needles_llsd[needle_name]["sender_name_case_insensitive"] = needle.mSenderNameCaseInsensitive; needles_llsd[needle_name]["content_case_insensitive"] = needle.mContentCaseInsensitive; } + */ + // [FIRE-36763] LLSDSerialize::toXML(needles_llsd, file); diff --git a/indra/newview/omnifilterengine.h b/indra/newview/omnifilterengine.h index 5fb2020884..0c2d05f1b3 100644 --- a/indra/newview/omnifilterengine.h +++ b/indra/newview/omnifilterengine.h @@ -125,21 +125,48 @@ class OmnifilterEngine void init(); + // [FIRE-36763] - Add rule sets to Omnifilter + bool setCurrentRuleSet(std::string_view rule_set_name); + S32 removeCurrentRuleSet(); + S32 addNewRuleSet(std::string_view new_name); + S32 addClonedRuleSet(std::string_view new_name); + bool assignRuleSet(const bool rule_set_to_internal = true); + bool assignRuleSetNameFromSettings(); + std::string getCurrentSelectedRuleSet() { return mCurrentSelectedRuleSet; } + needle_ordered_list_t& getOrderedRuleSets() { return mOrderedRuleSets; } + std::string_view getOrderedRuleSetName(const S32 index) const; + S32 getOrderedRuleSetIndex(std::string_view lookup_name); + S32 getOrderedRuleSetSize() { return static_cast(mOrderedRuleSets.size()); } + // [FIRE-36763] typedef boost::signals2::signal log_signal_t; log_signal_t mLogSignal; std::vector> mLog; + // [FIRE-36763] - Add rule sets to Omnifilter + typedef std::pair rule_set_t; + typedef std::map rule_sets_t; + rule_sets_t& getRuleSets() { return mNeedleRuleSets; } + // [FIRE-36763] protected: const Needle* logMatch(const std::string& needle_name, const Needle& needle); bool matchStrings(std::string_view needle_string, std::string_view haystack_string, eMatchType match_type, bool case_insensitive); + // [FIRE-36763] - Add rule sets to Omnifilter + bool importFromLLSD(const LLSD& data); + LLSD exportToLLSD(); + // [FIRE-36763] void loadNeedles(); void saveNeedles(); bool tick() override; protected: + // [FIRE-36763] - Add rule sets to Omnifilter + rule_sets_t mNeedleRuleSets; + std::string mCurrentSelectedRuleSet; + needle_ordered_list_t mOrderedRuleSets; + // [FIRE-36763] needle_list_t mNeedles; // [FIRE-36649] - Add reordering to OmniFilter needle_ordered_list_t mOrderedNeedles; diff --git a/indra/newview/skins/default/xui/en/floater_omnifilter.xml b/indra/newview/skins/default/xui/en/floater_omnifilter.xml index 8d2b013c43..2e96b8befb 100644 --- a/indra/newview/skins/default/xui/en/floater_omnifilter.xml +++ b/indra/newview/skins/default/xui/en/floater_omnifilter.xml @@ -4,7 +4,7 @@ can_resize="true" can_minimize="true" can_close="true" - height="400" + height="425" min_height="348" min_width="668" layout="topleft" @@ -20,12 +20,71 @@ NEW RULE + + + + + +