diff options
author | Ansariel <none@none> | 2017-04-27 19:19:11 +0200 |
---|---|---|
committer | Ansariel <none@none> | 2017-04-27 19:19:11 +0200 |
commit | b59ac959fcf2f1f0435f965d2c7279f529f1ce5c (patch) | |
tree | dc3383bbb92181483baae1394c93e7fcda534d4b /indra/newview/llfloaterlinkreplace.cpp | |
parent | d65e6e83821bf134805282e5c2ff66f76c819fe6 (diff) |
MAINT-6519 Contribution, adds feature to replace inventory links
Diffstat (limited to 'indra/newview/llfloaterlinkreplace.cpp')
-rw-r--r-- | indra/newview/llfloaterlinkreplace.cpp | 396 |
1 files changed, 396 insertions, 0 deletions
diff --git a/indra/newview/llfloaterlinkreplace.cpp b/indra/newview/llfloaterlinkreplace.cpp new file mode 100644 index 0000000000..6576af2ea3 --- /dev/null +++ b/indra/newview/llfloaterlinkreplace.cpp @@ -0,0 +1,396 @@ +/** + * @file llfloaterlinkreplace.cpp + * @brief Allows replacing link targets in inventory links + * @author Ansariel Hiller + * + * $LicenseInfo:firstyear=2017&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2017, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterlinkreplace.h" + +#include "llagent.h" +#include "llappearancemgr.h" +#include "lllineeditor.h" +#include "lltextbox.h" +#include "llviewercontrol.h" + +LLFloaterLinkReplace::LLFloaterLinkReplace(const LLSD& key) + : LLFloater(key), + LLEventTimer(gSavedSettings.getF32("LinkReplaceBatchPauseTime")), + mRemainingItems(0), + mSourceUUID(LLUUID::null), + mTargetUUID(LLUUID::null), + mInstance(NULL), + mBatchSize(gSavedSettings.getU32("LinkReplaceBatchSize")) +{ + mEventTimer.stop(); + mInstance = this; +} + +LLFloaterLinkReplace::~LLFloaterLinkReplace() +{ + mInstance = NULL; +} + +BOOL LLFloaterLinkReplace::postBuild() +{ + mStartBtn = getChild<LLButton>("btn_start"); + mStartBtn->setCommitCallback(boost::bind(&LLFloaterLinkReplace::onStartClicked, this)); + + mRefreshBtn = getChild<LLButton>("btn_refresh"); + mRefreshBtn->setCommitCallback(boost::bind(&LLFloaterLinkReplace::checkEnableStart, this)); + + mSourceEditor = getChild<LLInventoryLinkReplaceDropTarget>("source_uuid_editor"); + mTargetEditor = getChild<LLInventoryLinkReplaceDropTarget>("target_uuid_editor"); + + mSourceEditor->setDADCallback(boost::bind(&LLFloaterLinkReplace::onSourceItemDrop, this, _1)); + mTargetEditor->setDADCallback(boost::bind(&LLFloaterLinkReplace::onTargetItemDrop, this, _1)); + + mStatusText = getChild<LLTextBox>("status_text"); + + return TRUE; +} + +void LLFloaterLinkReplace::onOpen(const LLSD& key) +{ + if (key.asUUID().notNull()) + { + LLUUID item_id = key.asUUID(); + LLViewerInventoryItem* item = gInventory.getItem(item_id); + mSourceEditor->setItem(item); + onSourceItemDrop(item->getLinkedUUID()); + } + else + { + checkEnableStart(); + } +} + +void LLFloaterLinkReplace::onSourceItemDrop(const LLUUID& source_item_id) +{ + mSourceUUID = source_item_id; + checkEnableStart(); +} + +void LLFloaterLinkReplace::onTargetItemDrop(const LLUUID& target_item_id) +{ + mTargetUUID = target_item_id; + checkEnableStart(); +} + +void LLFloaterLinkReplace::updateFoundLinks() +{ + LLInventoryModel::item_array_t items; + LLInventoryModel::cat_array_t cat_array; + LLLinkedItemIDMatches is_linked_item_match(mSourceUUID); + gInventory.collectDescendentsIf(gInventory.getRootFolderID(), + cat_array, + items, + LLInventoryModel::INCLUDE_TRASH, + is_linked_item_match); + mRemainingItems = (U32)items.size(); + + LLStringUtil::format_map_t args; + args["NUM"] = llformat("%d", mRemainingItems); + mStatusText->setText(getString("ItemsFound", args)); +} + +void LLFloaterLinkReplace::checkEnableStart() +{ + if (mSourceUUID.notNull() && mTargetUUID.notNull() && mSourceUUID == mTargetUUID) + { + mStatusText->setText(getString("ItemsIdentical")); + } + else if (mSourceUUID.notNull()) + { + updateFoundLinks(); + } + + mStartBtn->setEnabled(mRemainingItems > 0 && mSourceUUID.notNull() && mTargetUUID.notNull() && mSourceUUID != mTargetUUID); +} + +void LLFloaterLinkReplace::onStartClicked() +{ + LL_INFOS() << "Starting inventory link replace" << LL_ENDL; + + if (mSourceUUID.isNull() || mTargetUUID.isNull()) + { + LL_WARNS() << "Cannot replace. Either source or target UUID is null." << LL_ENDL; + return; + } + + if (mSourceUUID == mTargetUUID) + { + LL_WARNS() << "Cannot replace. Source and target are identical." << LL_ENDL; + return; + } + + LLInventoryModel::cat_array_t cat_array; + LLLinkedItemIDMatches is_linked_item_match(mSourceUUID); + gInventory.collectDescendentsIf(gInventory.getRootFolderID(), + cat_array, + mRemainingInventoryItems, + LLInventoryModel::INCLUDE_TRASH, + is_linked_item_match); + LL_INFOS() << "Found " << mRemainingInventoryItems.size() << " inventory links that need to be replaced." << LL_ENDL; + + if (mRemainingInventoryItems.size() > 0) + { + LLViewerInventoryItem* target_item = gInventory.getItem(mTargetUUID); + if (target_item) + { + mRemainingItems = (U32)mRemainingInventoryItems.size(); + + LLStringUtil::format_map_t args; + args["NUM"] = llformat("%d", mRemainingItems); + mStatusText->setText(getString("ItemsRemaining", args)); + + mStartBtn->setEnabled(FALSE); + mRefreshBtn->setEnabled(FALSE); + + mEventTimer.start(); + tick(); + } + else + { + mStatusText->setText(getString("TargetNotFound")); + LL_WARNS() << "Link replace target not found." << LL_ENDL; + } + } +} + +void LLFloaterLinkReplace::linkCreatedCallback(const LLUUID& old_item_id, + const LLUUID& target_item_id, + bool needs_wearable_ordering_update, + bool needs_description_update, + const LLUUID& outfit_folder_id) +{ + LL_DEBUGS() << "Inventory link replace:" << LL_NEWLINE + << " - old_item_id = " << old_item_id.asString() << LL_NEWLINE + << " - target_item_id = " << target_item_id.asString() << LL_NEWLINE + << " - order update = " << (needs_wearable_ordering_update ? "true" : "false") << LL_NEWLINE + << " - description update = " << (needs_description_update ? "true" : "false") << LL_NEWLINE + << " - outfit_folder_id = " << outfit_folder_id.asString() << LL_ENDL; + + // If we are replacing an object, bodypart or gesture link within an outfit folder, + // we need to change the actual description of the link itself. LLAppearanceMgr *should* + // have created COF links that will be used to save the outfit with an empty description. + // Since link_inventory_array() will set the description of the linked item for the link + // itself, this will lead to a dirty outfit state when the outfit with the replaced + // link is worn. So we have to correct this. + if (needs_description_update && outfit_folder_id.notNull()) + { + LLInventoryModel::item_array_t items; + LLInventoryModel::cat_array_t cats; + LLLinkedItemIDMatches is_target_link(target_item_id); + gInventory.collectDescendentsIf(outfit_folder_id, + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + is_target_link); + + for (LLInventoryModel::item_array_t::iterator it = items.begin(); it != items.end(); ++it) + { + LLPointer<LLViewerInventoryItem> item = *it; + + if ((item->getType() == LLAssetType::AT_BODYPART || + item->getType() == LLAssetType::AT_OBJECT || + item->getType() == LLAssetType::AT_GESTURE) + && !item->getActualDescription().empty()) + { + LL_DEBUGS() << "Updating description for " << item->getName() << LL_ENDL; + + LLSD updates; + updates["desc"] = ""; + update_inventory_item(item->getUUID(), updates, LLPointer<LLInventoryCallback>(NULL)); + } + } + } + + LLUUID outfit_update_folder = LLUUID::null; + if (needs_wearable_ordering_update && outfit_folder_id.notNull()) + { + // If a wearable item was involved in the link replace operation and replaced + // a link in an outfit folder, we need to update the clothing ordering information + // *after* the original link has been removed. LLAppearanceMgr abuses the actual link + // description to store the clothing ordering information it. We will have to update + // the clothing ordering information or the outfit will be in dirty state when worn. + outfit_update_folder = outfit_folder_id; + } + + LLPointer<LLInventoryCallback> cb = new LLBoostFuncInventoryCallback(boost::bind(&LLFloaterLinkReplace::itemRemovedCallback, this, outfit_update_folder)); + remove_inventory_object(old_item_id, cb); +} + +void LLFloaterLinkReplace::itemRemovedCallback(const LLUUID& outfit_folder_id) +{ + if (outfit_folder_id.notNull()) + { + LLAppearanceMgr::getInstance()->updateClothingOrderingInfo(outfit_folder_id); + } + + if (mInstance) + { + decreaseOpenItemCount(); + } +} + +void LLFloaterLinkReplace::decreaseOpenItemCount() +{ + mRemainingItems--; + + if (mRemainingItems == 0) + { + mStatusText->setText(getString("ReplaceFinished")); + mStartBtn->setEnabled(TRUE); + mRefreshBtn->setEnabled(TRUE); + mEventTimer.stop(); + LL_INFOS() << "Inventory link replace finished." << LL_ENDL; + } + else + { + LLStringUtil::format_map_t args; + args["NUM"] = llformat("%d", mRemainingItems); + mStatusText->setText(getString("ItemsRemaining", args)); + LL_DEBUGS() << "Inventory link replace: " << mRemainingItems << " links remaining..." << LL_ENDL; + } +} + +BOOL LLFloaterLinkReplace::tick() +{ + LL_DEBUGS() << "Calling tick - remaining items = " << mRemainingInventoryItems.size() << LL_ENDL; + + LLInventoryModel::item_array_t current_batch; + + for (U32 i = 0; i < mBatchSize; ++i) + { + if (!mRemainingInventoryItems.size()) + { + mEventTimer.stop(); + break; + } + + current_batch.push_back(mRemainingInventoryItems.back()); + mRemainingInventoryItems.pop_back(); + } + processBatch(current_batch); + + return FALSE; +} + +void LLFloaterLinkReplace::processBatch(LLInventoryModel::item_array_t items) +{ + const LLViewerInventoryItem* target_item = gInventory.getItem(mTargetUUID); + const LLUUID cof_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT, false); + const LLUUID outfit_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS, false); + + for (LLInventoryModel::item_array_t::iterator it = items.begin(); it != items.end(); ++it) + { + LLPointer<LLInventoryItem> source_item = *it; + + if (source_item->getParentUUID() != cof_folder_id) + { + bool is_outfit_folder = gInventory.isObjectDescendentOf(source_item->getParentUUID(), outfit_folder_id); + // If either the new or old item in the COF is a wearable, we need to update wearable ordering after the link has been replaced + bool needs_wearable_ordering_update = is_outfit_folder && source_item->getType() == LLAssetType::AT_CLOTHING || target_item->getType() == LLAssetType::AT_CLOTHING; + // Other items in the COF need a description update (description of the actual link item must be empty) + bool needs_description_update = is_outfit_folder && target_item->getType() != LLAssetType::AT_CLOTHING; + + LL_DEBUGS() << "is_outfit_folder = " << (is_outfit_folder ? "true" : "false") << LL_NEWLINE + << "needs_wearable_ordering_update = " << (needs_wearable_ordering_update ? "true" : "false") << LL_NEWLINE + << "needs_description_update = " << (needs_description_update ? "true" : "false") << LL_ENDL; + + LLInventoryObject::const_object_list_t obj_array; + obj_array.push_back(LLConstPointer<LLInventoryObject>(target_item)); + LLPointer<LLInventoryCallback> cb = new LLBoostFuncInventoryCallback(boost::bind(&LLFloaterLinkReplace::linkCreatedCallback, + this, + source_item->getUUID(), + target_item->getUUID(), + needs_wearable_ordering_update, + needs_description_update, + (is_outfit_folder ? source_item->getParentUUID() : LLUUID::null) )); + link_inventory_array(source_item->getParentUUID(), obj_array, cb); + } + else + { + decreaseOpenItemCount(); + } + } +} + + +////////////////////////////////////////////////////////////////////////////// +// LLInventoryLinkReplaceDropTarget + +static LLDefaultChildRegistry::Register<LLInventoryLinkReplaceDropTarget> r("inventory_link_replace_drop_target"); + +BOOL LLInventoryLinkReplaceDropTarget::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + LLInventoryItem* item = (LLInventoryItem*)cargo_data; + + if (cargo_type >= DAD_TEXTURE && cargo_type <= DAD_LINK && + item && item->getActualType() != LLAssetType::AT_LINK_FOLDER && item->getType() != LLAssetType::AT_CATEGORY && + ( + LLAssetType::lookupCanLink(item->getType()) || + (item->getType() == LLAssetType::AT_LINK && !gInventory.getObject(item->getLinkedUUID())) // Broken Link! + )) + { + if (drop) + { + setItem(item); + if (!mDADSignal.empty()) + { + mDADSignal(mItemID); + } + } + else + { + *accept = ACCEPT_YES_SINGLE; + } + } + else + { + *accept = ACCEPT_NO; + } + + return TRUE; +} + +void LLInventoryLinkReplaceDropTarget::setItem(LLInventoryItem* item) +{ + if (item) + { + mItemID = item->getLinkedUUID(); + setText(item->getName()); + } + else + { + mItemID.setNull(); + setText(LLStringExplicit("")); + } +} |