summaryrefslogtreecommitdiff
path: root/indra/newview/lloutfitslist.cpp
diff options
context:
space:
mode:
authorAnsariel <ansariel.hiller@phoenixviewer.com>2024-05-22 21:25:21 +0200
committerAndrey Lihatskiy <alihatskiy@productengine.com>2024-05-22 22:40:26 +0300
commite2e37cced861b98de8c1a7c9c0d3a50d2d90e433 (patch)
tree1bb897489ce524986f6196201c10ac0d8861aa5f /indra/newview/lloutfitslist.cpp
parent069ea06848f766466f1a281144c82a0f2bd79f3a (diff)
Fix line endlings
Diffstat (limited to 'indra/newview/lloutfitslist.cpp')
-rw-r--r--indra/newview/lloutfitslist.cpp2738
1 files changed, 1369 insertions, 1369 deletions
diff --git a/indra/newview/lloutfitslist.cpp b/indra/newview/lloutfitslist.cpp
index 665cfba9e6..94b32ceea9 100644
--- a/indra/newview/lloutfitslist.cpp
+++ b/indra/newview/lloutfitslist.cpp
@@ -1,1369 +1,1369 @@
-/**
- * @file lloutfitslist.cpp
- * @brief List of agent's outfits for My Appearance side panel.
- *
- * $LicenseInfo:firstyear=2010&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2010, 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 "lloutfitslist.h"
-
-// llcommon
-#include "llcommonutils.h"
-
-#include "llaccordionctrl.h"
-#include "llaccordionctrltab.h"
-#include "llagentwearables.h"
-#include "llappearancemgr.h"
-#include "llfloaterreg.h"
-#include "llfloatersidepanelcontainer.h"
-#include "llinspecttexture.h"
-#include "llinventoryfunctions.h"
-#include "llinventorymodel.h"
-#include "llmenubutton.h"
-#include "llnotificationsutil.h"
-#include "lloutfitobserver.h"
-#include "lltoggleablemenu.h"
-#include "lltransutil.h"
-#include "llviewermenu.h"
-#include "llvoavatar.h"
-#include "llvoavatarself.h"
-#include "llwearableitemslist.h"
-
-static bool is_tab_header_clicked(LLAccordionCtrlTab* tab, S32 y);
-
-static const LLOutfitTabNameComparator OUTFIT_TAB_NAME_COMPARATOR;
-
-/*virtual*/
-bool LLOutfitTabNameComparator::compare(const LLAccordionCtrlTab* tab1, const LLAccordionCtrlTab* tab2) const
-{
- std::string name1 = tab1->getTitle();
- std::string name2 = tab2->getTitle();
-
- return (LLStringUtil::compareDict(name1, name2) < 0);
-}
-
-struct outfit_accordion_tab_params : public LLInitParam::Block<outfit_accordion_tab_params, LLOutfitAccordionCtrlTab::Params>
-{
- Mandatory<LLWearableItemsList::Params> wearable_list;
-
- outfit_accordion_tab_params()
- : wearable_list("wearable_items_list")
- {}
-};
-
-const outfit_accordion_tab_params& get_accordion_tab_params()
-{
- static outfit_accordion_tab_params tab_params;
- static bool initialized = false;
- if (!initialized)
- {
- initialized = true;
-
- LLXMLNodePtr xmlNode;
- if (LLUICtrlFactory::getLayeredXMLNode("outfit_accordion_tab.xml", xmlNode))
- {
- LLXUIParser parser;
- parser.readXUI(xmlNode, tab_params, "outfit_accordion_tab.xml");
- }
- else
- {
- LL_WARNS() << "Failed to read xml of Outfit's Accordion Tab from outfit_accordion_tab.xml" << LL_ENDL;
- }
- }
-
- return tab_params;
-}
-
-
-static LLPanelInjector<LLOutfitsList> t_outfits_list("outfits_list");
-
-LLOutfitsList::LLOutfitsList()
- : LLOutfitListBase()
- , mAccordion(NULL)
- , mListCommands(NULL)
- , mItemSelected(false)
-{
-}
-
-LLOutfitsList::~LLOutfitsList()
-{
-}
-
-bool LLOutfitsList::postBuild()
-{
- mAccordion = getChild<LLAccordionCtrl>("outfits_accordion");
- mAccordion->setComparator(&OUTFIT_TAB_NAME_COMPARATOR);
-
- return LLOutfitListBase::postBuild();
-}
-
-//virtual
-void LLOutfitsList::onOpen(const LLSD& info)
-{
- if (!mIsInitialized)
- {
- // Start observing changes in Current Outfit category.
- LLOutfitObserver::instance().addCOFChangedCallback(boost::bind(&LLOutfitsList::onCOFChanged, this));
- }
-
- LLOutfitListBase::onOpen(info);
-
- LLAccordionCtrlTab* selected_tab = mAccordion->getSelectedTab();
- if (!selected_tab) return;
-
- // Pass focus to the selected outfit tab.
- selected_tab->showAndFocusHeader();
-}
-
-
-void LLOutfitsList::updateAddedCategory(LLUUID cat_id)
-{
- LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id);
- if (!cat) return;
-
- std::string name = cat->getName();
-
- outfit_accordion_tab_params tab_params(get_accordion_tab_params());
- tab_params.cat_id = cat_id;
- LLOutfitAccordionCtrlTab *tab = LLUICtrlFactory::create<LLOutfitAccordionCtrlTab>(tab_params);
- if (!tab) return;
- LLWearableItemsList* wearable_list = LLUICtrlFactory::create<LLWearableItemsList>(tab_params.wearable_list);
- wearable_list->setShape(tab->getLocalRect());
- tab->addChild(wearable_list);
-
- tab->setName(name);
- tab->setTitle(name);
-
- // *TODO: LLUICtrlFactory::defaultBuilder does not use "display_children" from xml. Should be investigated.
- tab->setDisplayChildren(false);
- mAccordion->addCollapsibleCtrl(tab);
-
- // Start observing the new outfit category.
- LLWearableItemsList* list = tab->getChild<LLWearableItemsList>("wearable_items_list");
- if (!mCategoriesObserver->addCategory(cat_id, boost::bind(&LLWearableItemsList::updateList, list, cat_id)))
- {
- // Remove accordion tab if category could not be added to observer.
- mAccordion->removeCollapsibleCtrl(tab);
-
- // kill removed tab
- tab->die();
- return;
- }
-
- // Map the new tab with outfit category UUID.
- mOutfitsMap.insert(LLOutfitsList::outfits_map_value_t(cat_id, tab));
-
- tab->setRightMouseDownCallback(boost::bind(&LLOutfitListBase::outfitRightClickCallBack, this,
- _1, _2, _3, cat_id));
-
- // Setting tab focus callback to monitor currently selected outfit.
- tab->setFocusReceivedCallback(boost::bind(&LLOutfitListBase::ChangeOutfitSelection, this, list, cat_id));
-
- // Setting callback to reset items selection inside outfit on accordion collapsing and expanding (EXT-7875)
- tab->setDropDownStateChangedCallback(boost::bind(&LLOutfitsList::resetItemSelection, this, list, cat_id));
-
- // force showing list items that don't match current filter(EXT-7158)
- list->setForceShowingUnmatchedItems(true);
-
- // Setting list commit callback to monitor currently selected wearable item.
- list->setCommitCallback(boost::bind(&LLOutfitsList::onListSelectionChange, this, _1));
-
- // Setting list refresh callback to apply filter on list change.
- list->setRefreshCompleteCallback(boost::bind(&LLOutfitsList::onRefreshComplete, this, _1));
-
- list->setRightMouseDownCallback(boost::bind(&LLOutfitsList::onWearableItemsListRightClick, this, _1, _2, _3));
-
- // Fetch the new outfit contents.
- cat->fetch();
-
- // Refresh the list of outfit items after fetch().
- // Further list updates will be triggered by the category observer.
- list->updateList(cat_id);
-
- // If filter is currently applied we store the initial tab state.
- if (!getFilterSubString().empty())
- {
- tab->notifyChildren(LLSD().with("action", "store_state"));
-
- // Setting mForceRefresh flag will make the list refresh its contents
- // even if it is not currently visible. This is required to apply the
- // filter to the newly added list.
- list->setForceRefresh(true);
-
- list->setFilterSubString(getFilterSubString(), false);
- }
-}
-
-void LLOutfitsList::updateRemovedCategory(LLUUID cat_id)
-{
- outfits_map_t::iterator outfits_iter = mOutfitsMap.find(cat_id);
- if (outfits_iter != mOutfitsMap.end())
- {
- const LLUUID& outfit_id = outfits_iter->first;
- LLAccordionCtrlTab* tab = outfits_iter->second;
-
- // An outfit is removed from the list. Do the following:
- // 1. Remove outfit category from observer to stop monitoring its changes.
- mCategoriesObserver->removeCategory(outfit_id);
-
- // 2. Remove the outfit from selection.
- deselectOutfit(outfit_id);
-
- // 3. Remove category UUID to accordion tab mapping.
- mOutfitsMap.erase(outfits_iter);
-
- // 4. Remove outfit tab from accordion.
- mAccordion->removeCollapsibleCtrl(tab);
-
- // kill removed tab
- if (tab != NULL)
- {
- tab->die();
- }
- }
-}
-
-//virtual
-void LLOutfitsList::onHighlightBaseOutfit(LLUUID base_id, LLUUID prev_id)
-{
- if (mOutfitsMap[prev_id])
- {
- mOutfitsMap[prev_id]->setTitleFontStyle("NORMAL");
- mOutfitsMap[prev_id]->setTitleColor(LLUIColorTable::instance().getColor("AccordionHeaderTextColor"));
- }
- if (mOutfitsMap[base_id])
- {
- mOutfitsMap[base_id]->setTitleFontStyle("BOLD");
- mOutfitsMap[base_id]->setTitleColor(LLUIColorTable::instance().getColor("SelectedOutfitTextColor"));
- }
-}
-
-void LLOutfitsList::onListSelectionChange(LLUICtrl* ctrl)
-{
- LLWearableItemsList* list = dynamic_cast<LLWearableItemsList*>(ctrl);
- if (!list) return;
-
- LLViewerInventoryItem *item = gInventory.getItem(list->getSelectedUUID());
- if (!item) return;
-
- ChangeOutfitSelection(list, item->getParentUUID());
-}
-
-void LLOutfitListBase::performAction(std::string action)
-{
- if (mSelectedOutfitUUID.isNull()) return;
-
- LLViewerInventoryCategory* cat = gInventory.getCategory(mSelectedOutfitUUID);
- if (!cat) return;
-
- if ("replaceoutfit" == action)
- {
- LLAppearanceMgr::instance().wearInventoryCategory( cat, false, false );
- }
- else if ("addtooutfit" == action)
- {
- LLAppearanceMgr::instance().wearInventoryCategory( cat, false, true );
- }
- else if ("rename_outfit" == action)
- {
- LLAppearanceMgr::instance().renameOutfit(mSelectedOutfitUUID);
- }
-}
-
-void LLOutfitsList::onSetSelectedOutfitByUUID(const LLUUID& outfit_uuid)
-{
- for (outfits_map_t::iterator iter = mOutfitsMap.begin();
- iter != mOutfitsMap.end();
- ++iter)
- {
- if (outfit_uuid == iter->first)
- {
- LLAccordionCtrlTab* tab = iter->second;
- if (!tab) continue;
-
- LLWearableItemsList* list = dynamic_cast<LLWearableItemsList*>(tab->getAccordionView());
- if (!list) continue;
-
- tab->setFocus(true);
- ChangeOutfitSelection(list, outfit_uuid);
-
- tab->changeOpenClose(false);
- }
- }
-}
-
-// virtual
-bool LLOutfitListBase::isActionEnabled(const LLSD& userdata)
-{
- if (mSelectedOutfitUUID.isNull()) return false;
-
- const std::string command_name = userdata.asString();
- if (command_name == "delete")
- {
- return !hasItemSelected() && LLAppearanceMgr::instance().getCanRemoveOutfit(mSelectedOutfitUUID);
- }
- if (command_name == "rename")
- {
- return get_is_category_renameable(&gInventory, mSelectedOutfitUUID);
- }
- if (command_name == "save_outfit")
- {
- bool outfit_locked = LLAppearanceMgr::getInstance()->isOutfitLocked();
- bool outfit_dirty = LLAppearanceMgr::getInstance()->isOutfitDirty();
- // allow save only if outfit isn't locked and is dirty
- return !outfit_locked && outfit_dirty;
- }
- if (command_name == "wear")
- {
- if (gAgentWearables.isCOFChangeInProgress())
- {
- return false;
- }
-
- if (hasItemSelected())
- {
- return canWearSelected();
- }
-
- // outfit selected
- return LLAppearanceMgr::instance().getCanReplaceCOF(mSelectedOutfitUUID);
- }
- if (command_name == "take_off")
- {
- // Enable "Take Off" if any of selected items can be taken off
- // or the selected outfit contains items that can be taken off.
- return ( hasItemSelected() && canTakeOffSelected() )
- || ( !hasItemSelected() && LLAppearanceMgr::getCanRemoveFromCOF(mSelectedOutfitUUID) );
- }
-
- if (command_name == "wear_add")
- {
- // *TODO: do we ever get here?
- return LLAppearanceMgr::getCanAddToCOF(mSelectedOutfitUUID);
- }
-
- return false;
-}
-
-void LLOutfitsList::getSelectedItemsUUIDs(uuid_vec_t& selected_uuids) const
-{
- // Collect selected items from all selected lists.
- for (wearables_lists_map_t::const_iterator iter = mSelectedListsMap.begin();
- iter != mSelectedListsMap.end();
- ++iter)
- {
- uuid_vec_t uuids;
- (*iter).second->getSelectedUUIDs(uuids);
-
- S32 prev_size = selected_uuids.size();
- selected_uuids.resize(prev_size + uuids.size());
- std::copy(uuids.begin(), uuids.end(), selected_uuids.begin() + prev_size);
- }
-}
-
-void LLOutfitsList::onCollapseAllFolders()
-{
- for (outfits_map_t::iterator iter = mOutfitsMap.begin();
- iter != mOutfitsMap.end();
- ++iter)
- {
- LLAccordionCtrlTab* tab = iter->second;
- if(tab && tab->isExpanded())
- {
- tab->changeOpenClose(true);
- }
- }
-}
-
-void LLOutfitsList::onExpandAllFolders()
-{
- for (outfits_map_t::iterator iter = mOutfitsMap.begin();
- iter != mOutfitsMap.end();
- ++iter)
- {
- LLAccordionCtrlTab* tab = iter->second;
- if(tab && !tab->isExpanded())
- {
- tab->changeOpenClose(false);
- }
- }
-}
-
-bool LLOutfitsList::hasItemSelected()
-{
- return mItemSelected;
-}
-
-//////////////////////////////////////////////////////////////////////////
-// Private methods
-//////////////////////////////////////////////////////////////////////////
-
-void LLOutfitsList::updateChangedCategoryName(LLViewerInventoryCategory *cat, std::string name)
-{
- outfits_map_t::iterator outfits_iter = mOutfitsMap.find(cat->getUUID());
- if (outfits_iter != mOutfitsMap.end())
- {
- // Update tab name with the new category name.
- LLAccordionCtrlTab* tab = outfits_iter->second;
- if (tab)
- {
- tab->setName(name);
- tab->setTitle(name);
- }
- }
-}
-
-void LLOutfitsList::resetItemSelection(LLWearableItemsList* list, const LLUUID& category_id)
-{
- list->resetSelection();
- mItemSelected = false;
- signalSelectionOutfitUUID(category_id);
-}
-
-void LLOutfitsList::onChangeOutfitSelection(LLWearableItemsList* list, const LLUUID& category_id)
-{
- MASK mask = gKeyboard->currentMask(true);
-
- // Reset selection in all previously selected tabs except for the current
- // if new selection is started.
- if (list && !(mask & MASK_CONTROL))
- {
- for (wearables_lists_map_t::iterator iter = mSelectedListsMap.begin();
- iter != mSelectedListsMap.end();
- ++iter)
- {
- LLWearableItemsList* selected_list = (*iter).second;
- if (selected_list != list)
- {
- selected_list->resetSelection();
- }
- }
-
- // Clear current selection.
- mSelectedListsMap.clear();
- }
-
- mItemSelected = list && (list->getSelectedItem() != NULL);
-
- mSelectedListsMap.insert(wearables_lists_map_value_t(category_id, list));
-}
-
-void LLOutfitsList::deselectOutfit(const LLUUID& category_id)
-{
- // Remove selected lists map entry.
- mSelectedListsMap.erase(category_id);
-
- LLOutfitListBase::deselectOutfit(category_id);
-}
-
-void LLOutfitsList::restoreOutfitSelection(LLAccordionCtrlTab* tab, const LLUUID& category_id)
-{
- // Try restoring outfit selection after filtering.
- if (mAccordion->getSelectedTab() == tab)
- {
- signalSelectionOutfitUUID(category_id);
- }
-}
-
-void LLOutfitsList::onRefreshComplete(LLUICtrl* ctrl)
-{
- if (!ctrl || getFilterSubString().empty())
- return;
-
- for (outfits_map_t::iterator
- iter = mOutfitsMap.begin(),
- iter_end = mOutfitsMap.end();
- iter != iter_end; ++iter)
- {
- LLAccordionCtrlTab* tab = iter->second;
- if (!tab) continue;
-
- LLWearableItemsList* list = dynamic_cast<LLWearableItemsList*>(tab->getAccordionView());
- if (list != ctrl) continue;
-
- applyFilterToTab(iter->first, tab, getFilterSubString());
- }
-}
-
-// virtual
-void LLOutfitsList::onFilterSubStringChanged(const std::string& new_string, const std::string& old_string)
-{
- mAccordion->setFilterSubString(new_string);
-
- outfits_map_t::iterator iter = mOutfitsMap.begin(), iter_end = mOutfitsMap.end();
- while (iter != iter_end)
- {
- const LLUUID& category_id = iter->first;
- LLAccordionCtrlTab* tab = iter++->second;
- if (!tab) continue;
-
- LLWearableItemsList* list = dynamic_cast<LLWearableItemsList*>(tab->getAccordionView());
- if (list)
- {
- list->setFilterSubString(new_string, tab->getDisplayChildren());
- }
-
- if (old_string.empty())
- {
- // Store accordion tab state when filter is not empty
- tab->notifyChildren(LLSD().with("action", "store_state"));
- }
-
- if (!new_string.empty())
- {
- applyFilterToTab(category_id, tab, new_string);
- }
- else
- {
- tab->setVisible(true);
-
- // Restore tab title when filter is empty
- tab->setTitle(tab->getTitle());
-
- // Restore accordion state after all those accodrion tab manipulations
- tab->notifyChildren(LLSD().with("action", "restore_state"));
-
- // Try restoring the tab selection.
- restoreOutfitSelection(tab, category_id);
- }
- }
-
- mAccordion->arrange();
-}
-
-void LLOutfitsList::applyFilterToTab(
- const LLUUID& category_id,
- LLAccordionCtrlTab* tab,
- const std::string& filter_substring)
-{
- if (!tab) return;
- LLWearableItemsList* list = dynamic_cast<LLWearableItemsList*>(tab->getAccordionView());
- if (!list) return;
-
- std::string title = tab->getTitle();
- LLStringUtil::toUpper(title);
-
- std::string cur_filter = filter_substring;
- LLStringUtil::toUpper(cur_filter);
-
- tab->setTitle(tab->getTitle(), cur_filter);
-
- if (std::string::npos == title.find(cur_filter))
- {
- // Hide tab if its title doesn't pass filter
- // and it has no matched items
- tab->setVisible(list->hasMatchedItems());
-
- // Remove title highlighting because it might
- // have been previously highlighted by less restrictive filter
- tab->setTitle(tab->getTitle());
-
- // Remove the tab from selection.
- deselectOutfit(category_id);
- }
- else
- {
- // Try restoring the tab selection.
- restoreOutfitSelection(tab, category_id);
- }
-}
-
-bool LLOutfitsList::canWearSelected()
-{
- if (!isAgentAvatarValid())
- {
- return false;
- }
-
- uuid_vec_t selected_items;
- getSelectedItemsUUIDs(selected_items);
- S32 nonreplacable_objects = 0;
-
- for (uuid_vec_t::const_iterator it = selected_items.begin(); it != selected_items.end(); ++it)
- {
- const LLUUID& id = *it;
-
- // Check whether the item is worn.
- if (!get_can_item_be_worn(id))
- {
- return false;
- }
-
- const LLViewerInventoryItem* item = gInventory.getItem(id);
- if (!item)
- {
- return false;
- }
-
- if (item->getType() == LLAssetType::AT_OBJECT)
- {
- nonreplacable_objects++;
- }
- }
-
- // All selected items can be worn. But do we have enough space for them?
- return nonreplacable_objects == 0 || gAgentAvatarp->canAttachMoreObjects(nonreplacable_objects);
-}
-
-void LLOutfitsList::wearSelectedItems()
-{
- uuid_vec_t selected_uuids;
- getSelectedItemsUUIDs(selected_uuids);
-
- if(selected_uuids.empty())
- {
- return;
- }
-
- wear_multiple(selected_uuids, false);
-}
-
-void LLOutfitsList::onWearableItemsListRightClick(LLUICtrl* ctrl, S32 x, S32 y)
-{
- LLWearableItemsList* list = dynamic_cast<LLWearableItemsList*>(ctrl);
- if (!list) return;
-
- uuid_vec_t selected_uuids;
-
- getSelectedItemsUUIDs(selected_uuids);
-
- LLWearableItemsList::ContextMenu::instance().show(list, selected_uuids, x, y);
-}
-
-void LLOutfitsList::onCOFChanged()
-{
- LLInventoryModel::cat_array_t cat_array;
- LLInventoryModel::item_array_t item_array;
-
- // Collect current COF items
- gInventory.collectDescendents(
- LLAppearanceMgr::instance().getCOF(),
- cat_array,
- item_array,
- LLInventoryModel::EXCLUDE_TRASH);
-
- uuid_vec_t vnew;
- uuid_vec_t vadded;
- uuid_vec_t vremoved;
-
- // From gInventory we get the UUIDs of links that are currently in COF.
- // These links UUIDs are not the same UUIDs that we have in each wearable items list.
- // So we collect base items' UUIDs to find them or links that point to them in wearable
- // items lists and update their worn state there.
- LLInventoryModel::item_array_t::const_iterator array_iter = item_array.begin(), array_end = item_array.end();
- while (array_iter < array_end)
- {
- vnew.push_back((*(array_iter++))->getLinkedUUID());
- }
-
- // We need to update only items that were added or removed from COF.
- LLCommonUtils::computeDifference(vnew, mCOFLinkedItems, vadded, vremoved);
-
- // Store the ids of items currently linked from COF.
- mCOFLinkedItems = vnew;
-
- // Append removed ids to added ids because we should update all of them.
- vadded.reserve(vadded.size() + vremoved.size());
- vadded.insert(vadded.end(), vremoved.begin(), vremoved.end());
- vremoved.clear();
-
- outfits_map_t::iterator map_iter = mOutfitsMap.begin(), map_end = mOutfitsMap.end();
- while (map_iter != map_end)
- {
- LLAccordionCtrlTab* tab = (map_iter++)->second;
- if (!tab) continue;
-
- LLWearableItemsList* list = dynamic_cast<LLWearableItemsList*>(tab->getAccordionView());
- if (!list) continue;
-
- // Every list updates the labels of changed items or
- // the links that point to these items.
- list->updateChangedItems(vadded);
- }
-}
-
-void LLOutfitsList::getCurrentCategories(uuid_vec_t& vcur)
-{
- // Creating a vector of currently displayed sub-categories UUIDs.
- for (outfits_map_t::const_iterator iter = mOutfitsMap.begin();
- iter != mOutfitsMap.end();
- iter++)
- {
- vcur.push_back((*iter).first);
- }
-}
-
-
-void LLOutfitsList::sortOutfits()
-{
- mAccordion->sort();
-}
-
-void LLOutfitsList::onOutfitRightClick(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& cat_id)
-{
- LLAccordionCtrlTab* tab = dynamic_cast<LLAccordionCtrlTab*>(ctrl);
- if (mOutfitMenu && is_tab_header_clicked(tab, y) && cat_id.notNull())
- {
- // Focus tab header to trigger tab selection change.
- LLUICtrl* header = tab->findChild<LLUICtrl>("dd_header");
- if (header)
- {
- header->setFocus(true);
- }
-
- uuid_vec_t selected_uuids;
- selected_uuids.push_back(cat_id);
- mOutfitMenu->show(ctrl, selected_uuids, x, y);
- }
-}
-
-LLOutfitListGearMenuBase* LLOutfitsList::createGearMenu()
-{
- return new LLOutfitListGearMenu(this);
-}
-
-
-bool is_tab_header_clicked(LLAccordionCtrlTab* tab, S32 y)
-{
- if(!tab || !tab->getHeaderVisible()) return false;
-
- S32 header_bottom = tab->getLocalRect().getHeight() - tab->getHeaderHeight();
- return y >= header_bottom;
-}
-
-LLOutfitListBase::LLOutfitListBase()
- : LLPanelAppearanceTab()
- , mIsInitialized(false)
-{
- mCategoriesObserver = new LLInventoryCategoriesObserver();
- mOutfitMenu = new LLOutfitContextMenu(this);
- //mGearMenu = createGearMenu();
-}
-
-LLOutfitListBase::~LLOutfitListBase()
-{
- delete mOutfitMenu;
- delete mGearMenu;
-
- if (gInventory.containsObserver(mCategoriesObserver))
- {
- gInventory.removeObserver(mCategoriesObserver);
- }
- delete mCategoriesObserver;
-}
-
-void LLOutfitListBase::onOpen(const LLSD& info)
-{
- if (!mIsInitialized)
- {
- // *TODO: I'm not sure is this check necessary but it never match while developing.
- if (!gInventory.isInventoryUsable())
- return;
-
- const LLUUID outfits = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS);
-
- // *TODO: I'm not sure is this check necessary but it never match while developing.
- LLViewerInventoryCategory* category = gInventory.getCategory(outfits);
- if (!category)
- return;
-
- gInventory.addObserver(mCategoriesObserver);
-
- // Start observing changes in "My Outfits" category.
- mCategoriesObserver->addCategory(outfits,
- boost::bind(&LLOutfitListBase::observerCallback, this, outfits));
-
- //const LLUUID cof = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT);
- // Start observing changes in Current Outfit category.
- //mCategoriesObserver->addCategory(cof, boost::bind(&LLOutfitsList::onCOFChanged, this));
-
- LLOutfitObserver::instance().addBOFChangedCallback(boost::bind(&LLOutfitListBase::highlightBaseOutfit, this));
- LLOutfitObserver::instance().addBOFReplacedCallback(boost::bind(&LLOutfitListBase::highlightBaseOutfit, this));
-
- // Fetch "My Outfits" contents and refresh the list to display
- // initially fetched items. If not all items are fetched now
- // the observer will refresh the list as soon as the new items
- // arrive.
- category->fetch();
- refreshList(outfits);
-
- mIsInitialized = true;
- }
-}
-
-void LLOutfitListBase::observerCallback(const LLUUID& category_id)
-{
- const LLInventoryModel::changed_items_t& changed_items = gInventory.getChangedIDs();
- mChangedItems.insert(changed_items.begin(), changed_items.end());
- refreshList(category_id);
-}
-
-void LLOutfitListBase::refreshList(const LLUUID& category_id)
-{
- bool wasNull = mRefreshListState.CategoryUUID.isNull();
- mRefreshListState.CategoryUUID.setNull();
-
- LLInventoryModel::cat_array_t cat_array;
- LLInventoryModel::item_array_t item_array;
-
- // Collect all sub-categories of a given category.
- LLIsType is_category(LLAssetType::AT_CATEGORY);
- gInventory.collectDescendentsIf(
- category_id,
- cat_array,
- item_array,
- LLInventoryModel::EXCLUDE_TRASH,
- is_category);
-
- // Memorize item names for each UUID
- std::map<LLUUID, std::string> names;
- for (const LLPointer<LLViewerInventoryCategory>& cat : cat_array)
- {
- names.emplace(std::make_pair(cat->getUUID(), cat->getName()));
- }
-
- // Fill added and removed items vectors.
- mRefreshListState.Added.clear();
- mRefreshListState.Removed.clear();
- computeDifference(cat_array, mRefreshListState.Added, mRefreshListState.Removed);
- // Sort added items vector by item name.
- std::sort(mRefreshListState.Added.begin(), mRefreshListState.Added.end(),
- [names](const LLUUID& a, const LLUUID& b)
- {
- return LLStringUtil::compareDict(names.at(a), names.at(b)) < 0;
- });
- // Initialize iterators for added and removed items vectors.
- mRefreshListState.AddedIterator = mRefreshListState.Added.begin();
- mRefreshListState.RemovedIterator = mRefreshListState.Removed.begin();
-
- LL_INFOS() << "added: " << mRefreshListState.Added.size() <<
- ", removed: " << mRefreshListState.Removed.size() <<
- ", changed: " << gInventory.getChangedIDs().size() <<
- LL_ENDL;
-
- mRefreshListState.CategoryUUID = category_id;
- if (wasNull)
- {
- gIdleCallbacks.addFunction(onIdle, this);
- }
-}
-
-// static
-void LLOutfitListBase::onIdle(void* userdata)
-{
- LLOutfitListBase* self = (LLOutfitListBase*)userdata;
-
- self->onIdleRefreshList();
-}
-
-void LLOutfitListBase::onIdleRefreshList()
-{
- if (mRefreshListState.CategoryUUID.isNull())
- return;
-
- const F64 MAX_TIME = 0.05f;
- F64 curent_time = LLTimer::getTotalSeconds();
- const F64 end_time = curent_time + MAX_TIME;
-
- // Handle added tabs.
- while (mRefreshListState.AddedIterator < mRefreshListState.Added.end())
- {
- const LLUUID cat_id = (*mRefreshListState.AddedIterator++);
- updateAddedCategory(cat_id);
-
- curent_time = LLTimer::getTotalSeconds();
- if (curent_time >= end_time)
- return;
- }
- mRefreshListState.Added.clear();
- mRefreshListState.AddedIterator = mRefreshListState.Added.end();
-
- // Handle removed tabs.
- while (mRefreshListState.RemovedIterator < mRefreshListState.Removed.end())
- {
- const LLUUID cat_id = (*mRefreshListState.RemovedIterator++);
- updateRemovedCategory(cat_id);
-
- curent_time = LLTimer::getTotalSeconds();
- if (curent_time >= end_time)
- return;
- }
- mRefreshListState.Removed.clear();
- mRefreshListState.RemovedIterator = mRefreshListState.Removed.end();
-
- // Get changed items from inventory model and update outfit tabs
- // which might have been renamed.
- while (!mChangedItems.empty())
- {
- std::set<LLUUID>::const_iterator items_iter = mChangedItems.begin();
- LLViewerInventoryCategory *cat = gInventory.getCategory(*items_iter);
- mChangedItems.erase(items_iter);
-
- // Links aren't supposed to be allowed here, check only cats
- if (cat)
- {
- std::string name = cat->getName();
- updateChangedCategoryName(cat, name);
- }
-
- curent_time = LLTimer::getTotalSeconds();
- if (curent_time >= end_time)
- return;
- }
-
- sortOutfits();
- highlightBaseOutfit();
-
- gIdleCallbacks.deleteFunction(onIdle, this);
- mRefreshListState.CategoryUUID.setNull();
-
- LL_INFOS() << "done" << LL_ENDL;
-}
-
-void LLOutfitListBase::computeDifference(
- const LLInventoryModel::cat_array_t& vcats,
- uuid_vec_t& vadded,
- uuid_vec_t& vremoved)
-{
- uuid_vec_t vnew;
- // Creating a vector of newly collected sub-categories UUIDs.
- for (LLInventoryModel::cat_array_t::const_iterator iter = vcats.begin();
- iter != vcats.end();
- iter++)
- {
- vnew.push_back((*iter)->getUUID());
- }
-
- uuid_vec_t vcur;
- getCurrentCategories(vcur);
-
- LLCommonUtils::computeDifference(vnew, vcur, vadded, vremoved);
-}
-
-void LLOutfitListBase::sortOutfits()
-{
-}
-
-void LLOutfitListBase::highlightBaseOutfit()
-{
- // id of base outfit
- LLUUID base_id = LLAppearanceMgr::getInstance()->getBaseOutfitUUID();
- if (base_id != mHighlightedOutfitUUID)
- {
- LLUUID prev_id = mHighlightedOutfitUUID;
- mHighlightedOutfitUUID = base_id;
- onHighlightBaseOutfit(base_id, prev_id);
- }
-}
-
-void LLOutfitListBase::removeSelected()
-{
- LLNotificationsUtil::add("DeleteOutfits", LLSD(), LLSD(), boost::bind(&LLOutfitListBase::onOutfitsRemovalConfirmation, this, _1, _2));
-}
-
-void LLOutfitListBase::onOutfitsRemovalConfirmation(const LLSD& notification, const LLSD& response)
-{
- S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
- if (option != 0) return; // canceled
-
- if (mSelectedOutfitUUID.notNull())
- {
- gInventory.removeCategory(mSelectedOutfitUUID);
- }
-}
-
-void LLOutfitListBase::setSelectedOutfitByUUID(const LLUUID& outfit_uuid)
-{
- onSetSelectedOutfitByUUID(outfit_uuid);
-}
-
-boost::signals2::connection LLOutfitListBase::setSelectionChangeCallback(selection_change_callback_t cb)
-{
- return mSelectionChangeSignal.connect(cb);
-}
-
-void LLOutfitListBase::signalSelectionOutfitUUID(const LLUUID& category_id)
-{
- mSelectionChangeSignal(category_id);
-}
-
-void LLOutfitListBase::outfitRightClickCallBack(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& cat_id)
-{
- onOutfitRightClick(ctrl, x, y, cat_id);
-}
-
-void LLOutfitListBase::ChangeOutfitSelection(LLWearableItemsList* list, const LLUUID& category_id)
-{
- onChangeOutfitSelection(list, category_id);
- mSelectedOutfitUUID = category_id;
- signalSelectionOutfitUUID(category_id);
-}
-
-bool LLOutfitListBase::postBuild()
-{
- mGearMenu = createGearMenu();
-
- LLMenuButton* menu_gear_btn = getChild<LLMenuButton>("options_gear_btn");
-
- menu_gear_btn->setMouseDownCallback(boost::bind(&LLOutfitListGearMenuBase::updateItemsVisibility, mGearMenu));
- menu_gear_btn->setMenu(mGearMenu->getMenu());
- return true;
-}
-
-void LLOutfitListBase::collapseAllFolders()
-{
- onCollapseAllFolders();
-}
-
-void LLOutfitListBase::expandAllFolders()
-{
- onExpandAllFolders();
-}
-
-void LLOutfitListBase::deselectOutfit(const LLUUID& category_id)
-{
- // Reset selection if the outfit is selected.
- if (category_id == mSelectedOutfitUUID)
- {
- mSelectedOutfitUUID = LLUUID::null;
- signalSelectionOutfitUUID(mSelectedOutfitUUID);
- }
-}
-
-LLContextMenu* LLOutfitContextMenu::createMenu()
-{
- LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
- LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar;
- LLUUID selected_id = mUUIDs.front();
-
- registrar.add("Outfit.WearReplace",
- boost::bind(&LLAppearanceMgr::replaceCurrentOutfit, &LLAppearanceMgr::instance(), selected_id));
- registrar.add("Outfit.WearAdd",
- boost::bind(&LLAppearanceMgr::addCategoryToCurrentOutfit, &LLAppearanceMgr::instance(), selected_id));
- registrar.add("Outfit.TakeOff",
- boost::bind(&LLAppearanceMgr::takeOffOutfit, &LLAppearanceMgr::instance(), selected_id));
- registrar.add("Outfit.Edit", boost::bind(editOutfit));
- registrar.add("Outfit.Rename", boost::bind(renameOutfit, selected_id));
- registrar.add("Outfit.Delete", boost::bind(&LLOutfitListBase::removeSelected, mOutfitList));
- registrar.add("Outfit.Thumbnail", boost::bind(&LLOutfitContextMenu::onThumbnail, this, selected_id));
- registrar.add("Outfit.Save", boost::bind(&LLOutfitContextMenu::onSave, this, selected_id));
-
- enable_registrar.add("Outfit.OnEnable", boost::bind(&LLOutfitContextMenu::onEnable, this, _2));
- enable_registrar.add("Outfit.OnVisible", boost::bind(&LLOutfitContextMenu::onVisible, this, _2));
-
- return createFromFile("menu_outfit_tab.xml");
-
-}
-
-bool LLOutfitContextMenu::onEnable(LLSD::String param)
-{
- LLUUID outfit_cat_id = mUUIDs.back();
-
- if ("rename" == param)
- {
- return get_is_category_renameable(&gInventory, outfit_cat_id);
- }
- else if ("wear_replace" == param)
- {
- return LLAppearanceMgr::instance().getCanReplaceCOF(outfit_cat_id);
- }
- else if ("wear_add" == param)
- {
- return LLAppearanceMgr::getCanAddToCOF(outfit_cat_id);
- }
- else if ("take_off" == param)
- {
- return LLAppearanceMgr::getCanRemoveFromCOF(outfit_cat_id);
- }
-
- return true;
-}
-
-bool LLOutfitContextMenu::onVisible(LLSD::String param)
-{
- LLUUID outfit_cat_id = mUUIDs.back();
-
- if ("edit" == param)
- {
- bool is_worn = LLAppearanceMgr::instance().getBaseOutfitUUID() == outfit_cat_id;
- return is_worn;
- }
- else if ("wear_replace" == param)
- {
- return true;
- }
- else if ("delete" == param)
- {
- return LLAppearanceMgr::instance().getCanRemoveOutfit(outfit_cat_id);
- }
-
- return true;
-}
-
-//static
-void LLOutfitContextMenu::editOutfit()
-{
- LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "edit_outfit"));
-}
-
-void LLOutfitContextMenu::renameOutfit(const LLUUID& outfit_cat_id)
-{
- LLAppearanceMgr::instance().renameOutfit(outfit_cat_id);
-}
-
-void LLOutfitContextMenu::onThumbnail(const LLUUID &outfit_cat_id)
-{
- if (outfit_cat_id.notNull())
- {
- LLSD data(outfit_cat_id);
- LLFloaterReg::showInstance("change_item_thumbnail", data);
- }
-}
-
-void LLOutfitContextMenu::onSave(const LLUUID &outfit_cat_id)
-{
- if (outfit_cat_id.notNull())
- {
- LLNotificationsUtil::add("ConfirmOverwriteOutfit", LLSD(), LLSD(),
- [outfit_cat_id](const LLSD &notif, const LLSD &resp)
- {
- S32 opt = LLNotificationsUtil::getSelectedOption(notif, resp);
- if (opt == 0)
- {
- LLAppearanceMgr::getInstance()->onOutfitFolderCreated(outfit_cat_id, true);
- }
- });
- }
-}
-
-LLOutfitListGearMenuBase::LLOutfitListGearMenuBase(LLOutfitListBase* olist)
- : mOutfitList(olist),
- mMenu(NULL)
-{
- llassert_always(mOutfitList);
-
- LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
- LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar;
-
- registrar.add("Gear.Wear", boost::bind(&LLOutfitListGearMenuBase::onWear, this));
- registrar.add("Gear.TakeOff", boost::bind(&LLOutfitListGearMenuBase::onTakeOff, this));
- registrar.add("Gear.Rename", boost::bind(&LLOutfitListGearMenuBase::onRename, this));
- registrar.add("Gear.Delete", boost::bind(&LLOutfitListBase::removeSelected, mOutfitList));
- registrar.add("Gear.Create", boost::bind(&LLOutfitListGearMenuBase::onCreate, this, _2));
- registrar.add("Gear.Collapse", boost::bind(&LLOutfitListBase::onCollapseAllFolders, mOutfitList));
- registrar.add("Gear.Expand", boost::bind(&LLOutfitListBase::onExpandAllFolders, mOutfitList));
-
- registrar.add("Gear.WearAdd", boost::bind(&LLOutfitListGearMenuBase::onAdd, this));
- registrar.add("Gear.Save", boost::bind(&LLOutfitListGearMenuBase::onSave, this));
-
- registrar.add("Gear.Thumbnail", boost::bind(&LLOutfitListGearMenuBase::onThumbnail, this));
- registrar.add("Gear.SortByName", boost::bind(&LLOutfitListGearMenuBase::onChangeSortOrder, this));
-
- enable_registrar.add("Gear.OnEnable", boost::bind(&LLOutfitListGearMenuBase::onEnable, this, _2));
- enable_registrar.add("Gear.OnVisible", boost::bind(&LLOutfitListGearMenuBase::onVisible, this, _2));
-
- mMenu = LLUICtrlFactory::getInstance()->createFromFile<LLToggleableMenu>(
- "menu_outfit_gear.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance());
- llassert(mMenu);
-}
-
-LLOutfitListGearMenuBase::~LLOutfitListGearMenuBase()
-{}
-
-void LLOutfitListGearMenuBase::updateItemsVisibility()
-{
- onUpdateItemsVisibility();
-}
-
-void LLOutfitListGearMenuBase::onUpdateItemsVisibility()
-{
- if (!mMenu) return;
-
- bool have_selection = getSelectedOutfitID().notNull();
- mMenu->setItemVisible("wear_separator", have_selection);
- mMenu->arrangeAndClear(); // update menu height
-}
-
-LLToggleableMenu* LLOutfitListGearMenuBase::getMenu()
-{
- return mMenu;
-}
-const LLUUID& LLOutfitListGearMenuBase::getSelectedOutfitID()
-{
- return mOutfitList->getSelectedOutfitUUID();
-}
-
-LLViewerInventoryCategory* LLOutfitListGearMenuBase::getSelectedOutfit()
-{
- const LLUUID& selected_outfit_id = getSelectedOutfitID();
- if (selected_outfit_id.isNull())
- {
- return NULL;
- }
-
- LLViewerInventoryCategory* cat = gInventory.getCategory(selected_outfit_id);
- return cat;
-}
-
-void LLOutfitListGearMenuBase::onWear()
-{
- LLViewerInventoryCategory* selected_outfit = getSelectedOutfit();
- if (selected_outfit)
- {
- LLAppearanceMgr::instance().wearInventoryCategory(
- selected_outfit, /*copy=*/ false, /*append=*/ false);
- }
-}
-
-void LLOutfitListGearMenuBase::onAdd()
-{
- const LLUUID& selected_id = getSelectedOutfitID();
-
- if (selected_id.notNull())
- {
- LLAppearanceMgr::getInstance()->addCategoryToCurrentOutfit(selected_id);
- }
-}
-
-void LLOutfitListGearMenuBase::onSave()
-{
- const LLUUID &selected_id = getSelectedOutfitID();
- LLNotificationsUtil::add("ConfirmOverwriteOutfit", LLSD(), LLSD(),
- [selected_id](const LLSD &notif, const LLSD &resp)
- {
- S32 opt = LLNotificationsUtil::getSelectedOption(notif, resp);
- if (opt == 0)
- {
- LLAppearanceMgr::getInstance()->onOutfitFolderCreated(selected_id, true);
- }
- });
-}
-
-void LLOutfitListGearMenuBase::onTakeOff()
-{
- // Take off selected outfit.
- const LLUUID& selected_outfit_id = getSelectedOutfitID();
- if (selected_outfit_id.notNull())
- {
- LLAppearanceMgr::instance().takeOffOutfit(selected_outfit_id);
- }
-}
-
-void LLOutfitListGearMenuBase::onRename()
-{
- const LLUUID& selected_outfit_id = getSelectedOutfitID();
- if (selected_outfit_id.notNull())
- {
- LLAppearanceMgr::instance().renameOutfit(selected_outfit_id);
- }
-}
-
-void LLOutfitListGearMenuBase::onCreate(const LLSD& data)
-{
- LLWearableType::EType type = LLWearableType::getInstance()->typeNameToType(data.asString());
- if (type == LLWearableType::WT_NONE)
- {
- LL_WARNS() << "Invalid wearable type" << LL_ENDL;
- return;
- }
-
- LLAgentWearables::createWearable(type, true);
-}
-
-bool LLOutfitListGearMenuBase::onEnable(LLSD::String param)
-{
- // Handle the "Wear - Replace Current Outfit" menu option specially
- // because LLOutfitList::isActionEnabled() checks whether it's allowed
- // to wear selected outfit OR selected items, while we're only
- // interested in the outfit (STORM-183).
- if ("wear" == param)
- {
- return LLAppearanceMgr::instance().getCanReplaceCOF(mOutfitList->getSelectedOutfitUUID());
- }
-
- return mOutfitList->isActionEnabled(param);
-}
-
-bool LLOutfitListGearMenuBase::onVisible(LLSD::String param)
-{
- const LLUUID& selected_outfit_id = getSelectedOutfitID();
- if (selected_outfit_id.isNull()) // no selection or invalid outfit selected
- {
- return false;
- }
-
- return true;
-}
-
-void LLOutfitListGearMenuBase::onThumbnail()
-{
- const LLUUID& selected_outfit_id = getSelectedOutfitID();
- LLSD data(selected_outfit_id);
- LLFloaterReg::showInstance("change_item_thumbnail", data);
-}
-
-void LLOutfitListGearMenuBase::onChangeSortOrder()
-{
-
-}
-
-LLOutfitListGearMenu::LLOutfitListGearMenu(LLOutfitListBase* olist)
- : LLOutfitListGearMenuBase(olist)
-{}
-
-LLOutfitListGearMenu::~LLOutfitListGearMenu()
-{}
-
-void LLOutfitListGearMenu::onUpdateItemsVisibility()
-{
- if (!mMenu) return;
- mMenu->setItemVisible("expand", true);
- mMenu->setItemVisible("collapse", true);
- mMenu->setItemVisible("thumbnail", getSelectedOutfitID().notNull());
- mMenu->setItemVisible("sepatator3", false);
- mMenu->setItemVisible("sort_folders_by_name", false);
- LLOutfitListGearMenuBase::onUpdateItemsVisibility();
-}
-
-bool LLOutfitAccordionCtrlTab::handleToolTip(S32 x, S32 y, MASK mask)
-{
- if (y >= getLocalRect().getHeight() - getHeaderHeight())
- {
- LLSD params;
- params["inv_type"] = LLInventoryType::IT_CATEGORY;
- params["thumbnail_id"] = gInventory.getCategory(mFolderID)->getThumbnailUUID();
- params["item_id"] = mFolderID;
-
- LLToolTipMgr::instance().show(LLToolTip::Params()
- .message(getToolTip())
- .sticky_rect(calcScreenRect())
- .delay_time(LLView::getTooltipTimeout())
- .create_callback(boost::bind(&LLInspectTextureUtil::createInventoryToolTip, _1))
- .create_params(params));
- return true;
- }
-
- return LLAccordionCtrlTab::handleToolTip(x, y, mask);
-}
-// EOF
+/**
+ * @file lloutfitslist.cpp
+ * @brief List of agent's outfits for My Appearance side panel.
+ *
+ * $LicenseInfo:firstyear=2010&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 "lloutfitslist.h"
+
+// llcommon
+#include "llcommonutils.h"
+
+#include "llaccordionctrl.h"
+#include "llaccordionctrltab.h"
+#include "llagentwearables.h"
+#include "llappearancemgr.h"
+#include "llfloaterreg.h"
+#include "llfloatersidepanelcontainer.h"
+#include "llinspecttexture.h"
+#include "llinventoryfunctions.h"
+#include "llinventorymodel.h"
+#include "llmenubutton.h"
+#include "llnotificationsutil.h"
+#include "lloutfitobserver.h"
+#include "lltoggleablemenu.h"
+#include "lltransutil.h"
+#include "llviewermenu.h"
+#include "llvoavatar.h"
+#include "llvoavatarself.h"
+#include "llwearableitemslist.h"
+
+static bool is_tab_header_clicked(LLAccordionCtrlTab* tab, S32 y);
+
+static const LLOutfitTabNameComparator OUTFIT_TAB_NAME_COMPARATOR;
+
+/*virtual*/
+bool LLOutfitTabNameComparator::compare(const LLAccordionCtrlTab* tab1, const LLAccordionCtrlTab* tab2) const
+{
+ std::string name1 = tab1->getTitle();
+ std::string name2 = tab2->getTitle();
+
+ return (LLStringUtil::compareDict(name1, name2) < 0);
+}
+
+struct outfit_accordion_tab_params : public LLInitParam::Block<outfit_accordion_tab_params, LLOutfitAccordionCtrlTab::Params>
+{
+ Mandatory<LLWearableItemsList::Params> wearable_list;
+
+ outfit_accordion_tab_params()
+ : wearable_list("wearable_items_list")
+ {}
+};
+
+const outfit_accordion_tab_params& get_accordion_tab_params()
+{
+ static outfit_accordion_tab_params tab_params;
+ static bool initialized = false;
+ if (!initialized)
+ {
+ initialized = true;
+
+ LLXMLNodePtr xmlNode;
+ if (LLUICtrlFactory::getLayeredXMLNode("outfit_accordion_tab.xml", xmlNode))
+ {
+ LLXUIParser parser;
+ parser.readXUI(xmlNode, tab_params, "outfit_accordion_tab.xml");
+ }
+ else
+ {
+ LL_WARNS() << "Failed to read xml of Outfit's Accordion Tab from outfit_accordion_tab.xml" << LL_ENDL;
+ }
+ }
+
+ return tab_params;
+}
+
+
+static LLPanelInjector<LLOutfitsList> t_outfits_list("outfits_list");
+
+LLOutfitsList::LLOutfitsList()
+ : LLOutfitListBase()
+ , mAccordion(NULL)
+ , mListCommands(NULL)
+ , mItemSelected(false)
+{
+}
+
+LLOutfitsList::~LLOutfitsList()
+{
+}
+
+bool LLOutfitsList::postBuild()
+{
+ mAccordion = getChild<LLAccordionCtrl>("outfits_accordion");
+ mAccordion->setComparator(&OUTFIT_TAB_NAME_COMPARATOR);
+
+ return LLOutfitListBase::postBuild();
+}
+
+//virtual
+void LLOutfitsList::onOpen(const LLSD& info)
+{
+ if (!mIsInitialized)
+ {
+ // Start observing changes in Current Outfit category.
+ LLOutfitObserver::instance().addCOFChangedCallback(boost::bind(&LLOutfitsList::onCOFChanged, this));
+ }
+
+ LLOutfitListBase::onOpen(info);
+
+ LLAccordionCtrlTab* selected_tab = mAccordion->getSelectedTab();
+ if (!selected_tab) return;
+
+ // Pass focus to the selected outfit tab.
+ selected_tab->showAndFocusHeader();
+}
+
+
+void LLOutfitsList::updateAddedCategory(LLUUID cat_id)
+{
+ LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id);
+ if (!cat) return;
+
+ std::string name = cat->getName();
+
+ outfit_accordion_tab_params tab_params(get_accordion_tab_params());
+ tab_params.cat_id = cat_id;
+ LLOutfitAccordionCtrlTab *tab = LLUICtrlFactory::create<LLOutfitAccordionCtrlTab>(tab_params);
+ if (!tab) return;
+ LLWearableItemsList* wearable_list = LLUICtrlFactory::create<LLWearableItemsList>(tab_params.wearable_list);
+ wearable_list->setShape(tab->getLocalRect());
+ tab->addChild(wearable_list);
+
+ tab->setName(name);
+ tab->setTitle(name);
+
+ // *TODO: LLUICtrlFactory::defaultBuilder does not use "display_children" from xml. Should be investigated.
+ tab->setDisplayChildren(false);
+ mAccordion->addCollapsibleCtrl(tab);
+
+ // Start observing the new outfit category.
+ LLWearableItemsList* list = tab->getChild<LLWearableItemsList>("wearable_items_list");
+ if (!mCategoriesObserver->addCategory(cat_id, boost::bind(&LLWearableItemsList::updateList, list, cat_id)))
+ {
+ // Remove accordion tab if category could not be added to observer.
+ mAccordion->removeCollapsibleCtrl(tab);
+
+ // kill removed tab
+ tab->die();
+ return;
+ }
+
+ // Map the new tab with outfit category UUID.
+ mOutfitsMap.insert(LLOutfitsList::outfits_map_value_t(cat_id, tab));
+
+ tab->setRightMouseDownCallback(boost::bind(&LLOutfitListBase::outfitRightClickCallBack, this,
+ _1, _2, _3, cat_id));
+
+ // Setting tab focus callback to monitor currently selected outfit.
+ tab->setFocusReceivedCallback(boost::bind(&LLOutfitListBase::ChangeOutfitSelection, this, list, cat_id));
+
+ // Setting callback to reset items selection inside outfit on accordion collapsing and expanding (EXT-7875)
+ tab->setDropDownStateChangedCallback(boost::bind(&LLOutfitsList::resetItemSelection, this, list, cat_id));
+
+ // force showing list items that don't match current filter(EXT-7158)
+ list->setForceShowingUnmatchedItems(true);
+
+ // Setting list commit callback to monitor currently selected wearable item.
+ list->setCommitCallback(boost::bind(&LLOutfitsList::onListSelectionChange, this, _1));
+
+ // Setting list refresh callback to apply filter on list change.
+ list->setRefreshCompleteCallback(boost::bind(&LLOutfitsList::onRefreshComplete, this, _1));
+
+ list->setRightMouseDownCallback(boost::bind(&LLOutfitsList::onWearableItemsListRightClick, this, _1, _2, _3));
+
+ // Fetch the new outfit contents.
+ cat->fetch();
+
+ // Refresh the list of outfit items after fetch().
+ // Further list updates will be triggered by the category observer.
+ list->updateList(cat_id);
+
+ // If filter is currently applied we store the initial tab state.
+ if (!getFilterSubString().empty())
+ {
+ tab->notifyChildren(LLSD().with("action", "store_state"));
+
+ // Setting mForceRefresh flag will make the list refresh its contents
+ // even if it is not currently visible. This is required to apply the
+ // filter to the newly added list.
+ list->setForceRefresh(true);
+
+ list->setFilterSubString(getFilterSubString(), false);
+ }
+}
+
+void LLOutfitsList::updateRemovedCategory(LLUUID cat_id)
+{
+ outfits_map_t::iterator outfits_iter = mOutfitsMap.find(cat_id);
+ if (outfits_iter != mOutfitsMap.end())
+ {
+ const LLUUID& outfit_id = outfits_iter->first;
+ LLAccordionCtrlTab* tab = outfits_iter->second;
+
+ // An outfit is removed from the list. Do the following:
+ // 1. Remove outfit category from observer to stop monitoring its changes.
+ mCategoriesObserver->removeCategory(outfit_id);
+
+ // 2. Remove the outfit from selection.
+ deselectOutfit(outfit_id);
+
+ // 3. Remove category UUID to accordion tab mapping.
+ mOutfitsMap.erase(outfits_iter);
+
+ // 4. Remove outfit tab from accordion.
+ mAccordion->removeCollapsibleCtrl(tab);
+
+ // kill removed tab
+ if (tab != NULL)
+ {
+ tab->die();
+ }
+ }
+}
+
+//virtual
+void LLOutfitsList::onHighlightBaseOutfit(LLUUID base_id, LLUUID prev_id)
+{
+ if (mOutfitsMap[prev_id])
+ {
+ mOutfitsMap[prev_id]->setTitleFontStyle("NORMAL");
+ mOutfitsMap[prev_id]->setTitleColor(LLUIColorTable::instance().getColor("AccordionHeaderTextColor"));
+ }
+ if (mOutfitsMap[base_id])
+ {
+ mOutfitsMap[base_id]->setTitleFontStyle("BOLD");
+ mOutfitsMap[base_id]->setTitleColor(LLUIColorTable::instance().getColor("SelectedOutfitTextColor"));
+ }
+}
+
+void LLOutfitsList::onListSelectionChange(LLUICtrl* ctrl)
+{
+ LLWearableItemsList* list = dynamic_cast<LLWearableItemsList*>(ctrl);
+ if (!list) return;
+
+ LLViewerInventoryItem *item = gInventory.getItem(list->getSelectedUUID());
+ if (!item) return;
+
+ ChangeOutfitSelection(list, item->getParentUUID());
+}
+
+void LLOutfitListBase::performAction(std::string action)
+{
+ if (mSelectedOutfitUUID.isNull()) return;
+
+ LLViewerInventoryCategory* cat = gInventory.getCategory(mSelectedOutfitUUID);
+ if (!cat) return;
+
+ if ("replaceoutfit" == action)
+ {
+ LLAppearanceMgr::instance().wearInventoryCategory( cat, false, false );
+ }
+ else if ("addtooutfit" == action)
+ {
+ LLAppearanceMgr::instance().wearInventoryCategory( cat, false, true );
+ }
+ else if ("rename_outfit" == action)
+ {
+ LLAppearanceMgr::instance().renameOutfit(mSelectedOutfitUUID);
+ }
+}
+
+void LLOutfitsList::onSetSelectedOutfitByUUID(const LLUUID& outfit_uuid)
+{
+ for (outfits_map_t::iterator iter = mOutfitsMap.begin();
+ iter != mOutfitsMap.end();
+ ++iter)
+ {
+ if (outfit_uuid == iter->first)
+ {
+ LLAccordionCtrlTab* tab = iter->second;
+ if (!tab) continue;
+
+ LLWearableItemsList* list = dynamic_cast<LLWearableItemsList*>(tab->getAccordionView());
+ if (!list) continue;
+
+ tab->setFocus(true);
+ ChangeOutfitSelection(list, outfit_uuid);
+
+ tab->changeOpenClose(false);
+ }
+ }
+}
+
+// virtual
+bool LLOutfitListBase::isActionEnabled(const LLSD& userdata)
+{
+ if (mSelectedOutfitUUID.isNull()) return false;
+
+ const std::string command_name = userdata.asString();
+ if (command_name == "delete")
+ {
+ return !hasItemSelected() && LLAppearanceMgr::instance().getCanRemoveOutfit(mSelectedOutfitUUID);
+ }
+ if (command_name == "rename")
+ {
+ return get_is_category_renameable(&gInventory, mSelectedOutfitUUID);
+ }
+ if (command_name == "save_outfit")
+ {
+ bool outfit_locked = LLAppearanceMgr::getInstance()->isOutfitLocked();
+ bool outfit_dirty = LLAppearanceMgr::getInstance()->isOutfitDirty();
+ // allow save only if outfit isn't locked and is dirty
+ return !outfit_locked && outfit_dirty;
+ }
+ if (command_name == "wear")
+ {
+ if (gAgentWearables.isCOFChangeInProgress())
+ {
+ return false;
+ }
+
+ if (hasItemSelected())
+ {
+ return canWearSelected();
+ }
+
+ // outfit selected
+ return LLAppearanceMgr::instance().getCanReplaceCOF(mSelectedOutfitUUID);
+ }
+ if (command_name == "take_off")
+ {
+ // Enable "Take Off" if any of selected items can be taken off
+ // or the selected outfit contains items that can be taken off.
+ return ( hasItemSelected() && canTakeOffSelected() )
+ || ( !hasItemSelected() && LLAppearanceMgr::getCanRemoveFromCOF(mSelectedOutfitUUID) );
+ }
+
+ if (command_name == "wear_add")
+ {
+ // *TODO: do we ever get here?
+ return LLAppearanceMgr::getCanAddToCOF(mSelectedOutfitUUID);
+ }
+
+ return false;
+}
+
+void LLOutfitsList::getSelectedItemsUUIDs(uuid_vec_t& selected_uuids) const
+{
+ // Collect selected items from all selected lists.
+ for (wearables_lists_map_t::const_iterator iter = mSelectedListsMap.begin();
+ iter != mSelectedListsMap.end();
+ ++iter)
+ {
+ uuid_vec_t uuids;
+ (*iter).second->getSelectedUUIDs(uuids);
+
+ S32 prev_size = selected_uuids.size();
+ selected_uuids.resize(prev_size + uuids.size());
+ std::copy(uuids.begin(), uuids.end(), selected_uuids.begin() + prev_size);
+ }
+}
+
+void LLOutfitsList::onCollapseAllFolders()
+{
+ for (outfits_map_t::iterator iter = mOutfitsMap.begin();
+ iter != mOutfitsMap.end();
+ ++iter)
+ {
+ LLAccordionCtrlTab* tab = iter->second;
+ if(tab && tab->isExpanded())
+ {
+ tab->changeOpenClose(true);
+ }
+ }
+}
+
+void LLOutfitsList::onExpandAllFolders()
+{
+ for (outfits_map_t::iterator iter = mOutfitsMap.begin();
+ iter != mOutfitsMap.end();
+ ++iter)
+ {
+ LLAccordionCtrlTab* tab = iter->second;
+ if(tab && !tab->isExpanded())
+ {
+ tab->changeOpenClose(false);
+ }
+ }
+}
+
+bool LLOutfitsList::hasItemSelected()
+{
+ return mItemSelected;
+}
+
+//////////////////////////////////////////////////////////////////////////
+// Private methods
+//////////////////////////////////////////////////////////////////////////
+
+void LLOutfitsList::updateChangedCategoryName(LLViewerInventoryCategory *cat, std::string name)
+{
+ outfits_map_t::iterator outfits_iter = mOutfitsMap.find(cat->getUUID());
+ if (outfits_iter != mOutfitsMap.end())
+ {
+ // Update tab name with the new category name.
+ LLAccordionCtrlTab* tab = outfits_iter->second;
+ if (tab)
+ {
+ tab->setName(name);
+ tab->setTitle(name);
+ }
+ }
+}
+
+void LLOutfitsList::resetItemSelection(LLWearableItemsList* list, const LLUUID& category_id)
+{
+ list->resetSelection();
+ mItemSelected = false;
+ signalSelectionOutfitUUID(category_id);
+}
+
+void LLOutfitsList::onChangeOutfitSelection(LLWearableItemsList* list, const LLUUID& category_id)
+{
+ MASK mask = gKeyboard->currentMask(true);
+
+ // Reset selection in all previously selected tabs except for the current
+ // if new selection is started.
+ if (list && !(mask & MASK_CONTROL))
+ {
+ for (wearables_lists_map_t::iterator iter = mSelectedListsMap.begin();
+ iter != mSelectedListsMap.end();
+ ++iter)
+ {
+ LLWearableItemsList* selected_list = (*iter).second;
+ if (selected_list != list)
+ {
+ selected_list->resetSelection();
+ }
+ }
+
+ // Clear current selection.
+ mSelectedListsMap.clear();
+ }
+
+ mItemSelected = list && (list->getSelectedItem() != NULL);
+
+ mSelectedListsMap.insert(wearables_lists_map_value_t(category_id, list));
+}
+
+void LLOutfitsList::deselectOutfit(const LLUUID& category_id)
+{
+ // Remove selected lists map entry.
+ mSelectedListsMap.erase(category_id);
+
+ LLOutfitListBase::deselectOutfit(category_id);
+}
+
+void LLOutfitsList::restoreOutfitSelection(LLAccordionCtrlTab* tab, const LLUUID& category_id)
+{
+ // Try restoring outfit selection after filtering.
+ if (mAccordion->getSelectedTab() == tab)
+ {
+ signalSelectionOutfitUUID(category_id);
+ }
+}
+
+void LLOutfitsList::onRefreshComplete(LLUICtrl* ctrl)
+{
+ if (!ctrl || getFilterSubString().empty())
+ return;
+
+ for (outfits_map_t::iterator
+ iter = mOutfitsMap.begin(),
+ iter_end = mOutfitsMap.end();
+ iter != iter_end; ++iter)
+ {
+ LLAccordionCtrlTab* tab = iter->second;
+ if (!tab) continue;
+
+ LLWearableItemsList* list = dynamic_cast<LLWearableItemsList*>(tab->getAccordionView());
+ if (list != ctrl) continue;
+
+ applyFilterToTab(iter->first, tab, getFilterSubString());
+ }
+}
+
+// virtual
+void LLOutfitsList::onFilterSubStringChanged(const std::string& new_string, const std::string& old_string)
+{
+ mAccordion->setFilterSubString(new_string);
+
+ outfits_map_t::iterator iter = mOutfitsMap.begin(), iter_end = mOutfitsMap.end();
+ while (iter != iter_end)
+ {
+ const LLUUID& category_id = iter->first;
+ LLAccordionCtrlTab* tab = iter++->second;
+ if (!tab) continue;
+
+ LLWearableItemsList* list = dynamic_cast<LLWearableItemsList*>(tab->getAccordionView());
+ if (list)
+ {
+ list->setFilterSubString(new_string, tab->getDisplayChildren());
+ }
+
+ if (old_string.empty())
+ {
+ // Store accordion tab state when filter is not empty
+ tab->notifyChildren(LLSD().with("action", "store_state"));
+ }
+
+ if (!new_string.empty())
+ {
+ applyFilterToTab(category_id, tab, new_string);
+ }
+ else
+ {
+ tab->setVisible(true);
+
+ // Restore tab title when filter is empty
+ tab->setTitle(tab->getTitle());
+
+ // Restore accordion state after all those accodrion tab manipulations
+ tab->notifyChildren(LLSD().with("action", "restore_state"));
+
+ // Try restoring the tab selection.
+ restoreOutfitSelection(tab, category_id);
+ }
+ }
+
+ mAccordion->arrange();
+}
+
+void LLOutfitsList::applyFilterToTab(
+ const LLUUID& category_id,
+ LLAccordionCtrlTab* tab,
+ const std::string& filter_substring)
+{
+ if (!tab) return;
+ LLWearableItemsList* list = dynamic_cast<LLWearableItemsList*>(tab->getAccordionView());
+ if (!list) return;
+
+ std::string title = tab->getTitle();
+ LLStringUtil::toUpper(title);
+
+ std::string cur_filter = filter_substring;
+ LLStringUtil::toUpper(cur_filter);
+
+ tab->setTitle(tab->getTitle(), cur_filter);
+
+ if (std::string::npos == title.find(cur_filter))
+ {
+ // Hide tab if its title doesn't pass filter
+ // and it has no matched items
+ tab->setVisible(list->hasMatchedItems());
+
+ // Remove title highlighting because it might
+ // have been previously highlighted by less restrictive filter
+ tab->setTitle(tab->getTitle());
+
+ // Remove the tab from selection.
+ deselectOutfit(category_id);
+ }
+ else
+ {
+ // Try restoring the tab selection.
+ restoreOutfitSelection(tab, category_id);
+ }
+}
+
+bool LLOutfitsList::canWearSelected()
+{
+ if (!isAgentAvatarValid())
+ {
+ return false;
+ }
+
+ uuid_vec_t selected_items;
+ getSelectedItemsUUIDs(selected_items);
+ S32 nonreplacable_objects = 0;
+
+ for (uuid_vec_t::const_iterator it = selected_items.begin(); it != selected_items.end(); ++it)
+ {
+ const LLUUID& id = *it;
+
+ // Check whether the item is worn.
+ if (!get_can_item_be_worn(id))
+ {
+ return false;
+ }
+
+ const LLViewerInventoryItem* item = gInventory.getItem(id);
+ if (!item)
+ {
+ return false;
+ }
+
+ if (item->getType() == LLAssetType::AT_OBJECT)
+ {
+ nonreplacable_objects++;
+ }
+ }
+
+ // All selected items can be worn. But do we have enough space for them?
+ return nonreplacable_objects == 0 || gAgentAvatarp->canAttachMoreObjects(nonreplacable_objects);
+}
+
+void LLOutfitsList::wearSelectedItems()
+{
+ uuid_vec_t selected_uuids;
+ getSelectedItemsUUIDs(selected_uuids);
+
+ if(selected_uuids.empty())
+ {
+ return;
+ }
+
+ wear_multiple(selected_uuids, false);
+}
+
+void LLOutfitsList::onWearableItemsListRightClick(LLUICtrl* ctrl, S32 x, S32 y)
+{
+ LLWearableItemsList* list = dynamic_cast<LLWearableItemsList*>(ctrl);
+ if (!list) return;
+
+ uuid_vec_t selected_uuids;
+
+ getSelectedItemsUUIDs(selected_uuids);
+
+ LLWearableItemsList::ContextMenu::instance().show(list, selected_uuids, x, y);
+}
+
+void LLOutfitsList::onCOFChanged()
+{
+ LLInventoryModel::cat_array_t cat_array;
+ LLInventoryModel::item_array_t item_array;
+
+ // Collect current COF items
+ gInventory.collectDescendents(
+ LLAppearanceMgr::instance().getCOF(),
+ cat_array,
+ item_array,
+ LLInventoryModel::EXCLUDE_TRASH);
+
+ uuid_vec_t vnew;
+ uuid_vec_t vadded;
+ uuid_vec_t vremoved;
+
+ // From gInventory we get the UUIDs of links that are currently in COF.
+ // These links UUIDs are not the same UUIDs that we have in each wearable items list.
+ // So we collect base items' UUIDs to find them or links that point to them in wearable
+ // items lists and update their worn state there.
+ LLInventoryModel::item_array_t::const_iterator array_iter = item_array.begin(), array_end = item_array.end();
+ while (array_iter < array_end)
+ {
+ vnew.push_back((*(array_iter++))->getLinkedUUID());
+ }
+
+ // We need to update only items that were added or removed from COF.
+ LLCommonUtils::computeDifference(vnew, mCOFLinkedItems, vadded, vremoved);
+
+ // Store the ids of items currently linked from COF.
+ mCOFLinkedItems = vnew;
+
+ // Append removed ids to added ids because we should update all of them.
+ vadded.reserve(vadded.size() + vremoved.size());
+ vadded.insert(vadded.end(), vremoved.begin(), vremoved.end());
+ vremoved.clear();
+
+ outfits_map_t::iterator map_iter = mOutfitsMap.begin(), map_end = mOutfitsMap.end();
+ while (map_iter != map_end)
+ {
+ LLAccordionCtrlTab* tab = (map_iter++)->second;
+ if (!tab) continue;
+
+ LLWearableItemsList* list = dynamic_cast<LLWearableItemsList*>(tab->getAccordionView());
+ if (!list) continue;
+
+ // Every list updates the labels of changed items or
+ // the links that point to these items.
+ list->updateChangedItems(vadded);
+ }
+}
+
+void LLOutfitsList::getCurrentCategories(uuid_vec_t& vcur)
+{
+ // Creating a vector of currently displayed sub-categories UUIDs.
+ for (outfits_map_t::const_iterator iter = mOutfitsMap.begin();
+ iter != mOutfitsMap.end();
+ iter++)
+ {
+ vcur.push_back((*iter).first);
+ }
+}
+
+
+void LLOutfitsList::sortOutfits()
+{
+ mAccordion->sort();
+}
+
+void LLOutfitsList::onOutfitRightClick(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& cat_id)
+{
+ LLAccordionCtrlTab* tab = dynamic_cast<LLAccordionCtrlTab*>(ctrl);
+ if (mOutfitMenu && is_tab_header_clicked(tab, y) && cat_id.notNull())
+ {
+ // Focus tab header to trigger tab selection change.
+ LLUICtrl* header = tab->findChild<LLUICtrl>("dd_header");
+ if (header)
+ {
+ header->setFocus(true);
+ }
+
+ uuid_vec_t selected_uuids;
+ selected_uuids.push_back(cat_id);
+ mOutfitMenu->show(ctrl, selected_uuids, x, y);
+ }
+}
+
+LLOutfitListGearMenuBase* LLOutfitsList::createGearMenu()
+{
+ return new LLOutfitListGearMenu(this);
+}
+
+
+bool is_tab_header_clicked(LLAccordionCtrlTab* tab, S32 y)
+{
+ if(!tab || !tab->getHeaderVisible()) return false;
+
+ S32 header_bottom = tab->getLocalRect().getHeight() - tab->getHeaderHeight();
+ return y >= header_bottom;
+}
+
+LLOutfitListBase::LLOutfitListBase()
+ : LLPanelAppearanceTab()
+ , mIsInitialized(false)
+{
+ mCategoriesObserver = new LLInventoryCategoriesObserver();
+ mOutfitMenu = new LLOutfitContextMenu(this);
+ //mGearMenu = createGearMenu();
+}
+
+LLOutfitListBase::~LLOutfitListBase()
+{
+ delete mOutfitMenu;
+ delete mGearMenu;
+
+ if (gInventory.containsObserver(mCategoriesObserver))
+ {
+ gInventory.removeObserver(mCategoriesObserver);
+ }
+ delete mCategoriesObserver;
+}
+
+void LLOutfitListBase::onOpen(const LLSD& info)
+{
+ if (!mIsInitialized)
+ {
+ // *TODO: I'm not sure is this check necessary but it never match while developing.
+ if (!gInventory.isInventoryUsable())
+ return;
+
+ const LLUUID outfits = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS);
+
+ // *TODO: I'm not sure is this check necessary but it never match while developing.
+ LLViewerInventoryCategory* category = gInventory.getCategory(outfits);
+ if (!category)
+ return;
+
+ gInventory.addObserver(mCategoriesObserver);
+
+ // Start observing changes in "My Outfits" category.
+ mCategoriesObserver->addCategory(outfits,
+ boost::bind(&LLOutfitListBase::observerCallback, this, outfits));
+
+ //const LLUUID cof = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT);
+ // Start observing changes in Current Outfit category.
+ //mCategoriesObserver->addCategory(cof, boost::bind(&LLOutfitsList::onCOFChanged, this));
+
+ LLOutfitObserver::instance().addBOFChangedCallback(boost::bind(&LLOutfitListBase::highlightBaseOutfit, this));
+ LLOutfitObserver::instance().addBOFReplacedCallback(boost::bind(&LLOutfitListBase::highlightBaseOutfit, this));
+
+ // Fetch "My Outfits" contents and refresh the list to display
+ // initially fetched items. If not all items are fetched now
+ // the observer will refresh the list as soon as the new items
+ // arrive.
+ category->fetch();
+ refreshList(outfits);
+
+ mIsInitialized = true;
+ }
+}
+
+void LLOutfitListBase::observerCallback(const LLUUID& category_id)
+{
+ const LLInventoryModel::changed_items_t& changed_items = gInventory.getChangedIDs();
+ mChangedItems.insert(changed_items.begin(), changed_items.end());
+ refreshList(category_id);
+}
+
+void LLOutfitListBase::refreshList(const LLUUID& category_id)
+{
+ bool wasNull = mRefreshListState.CategoryUUID.isNull();
+ mRefreshListState.CategoryUUID.setNull();
+
+ LLInventoryModel::cat_array_t cat_array;
+ LLInventoryModel::item_array_t item_array;
+
+ // Collect all sub-categories of a given category.
+ LLIsType is_category(LLAssetType::AT_CATEGORY);
+ gInventory.collectDescendentsIf(
+ category_id,
+ cat_array,
+ item_array,
+ LLInventoryModel::EXCLUDE_TRASH,
+ is_category);
+
+ // Memorize item names for each UUID
+ std::map<LLUUID, std::string> names;
+ for (const LLPointer<LLViewerInventoryCategory>& cat : cat_array)
+ {
+ names.emplace(std::make_pair(cat->getUUID(), cat->getName()));
+ }
+
+ // Fill added and removed items vectors.
+ mRefreshListState.Added.clear();
+ mRefreshListState.Removed.clear();
+ computeDifference(cat_array, mRefreshListState.Added, mRefreshListState.Removed);
+ // Sort added items vector by item name.
+ std::sort(mRefreshListState.Added.begin(), mRefreshListState.Added.end(),
+ [names](const LLUUID& a, const LLUUID& b)
+ {
+ return LLStringUtil::compareDict(names.at(a), names.at(b)) < 0;
+ });
+ // Initialize iterators for added and removed items vectors.
+ mRefreshListState.AddedIterator = mRefreshListState.Added.begin();
+ mRefreshListState.RemovedIterator = mRefreshListState.Removed.begin();
+
+ LL_INFOS() << "added: " << mRefreshListState.Added.size() <<
+ ", removed: " << mRefreshListState.Removed.size() <<
+ ", changed: " << gInventory.getChangedIDs().size() <<
+ LL_ENDL;
+
+ mRefreshListState.CategoryUUID = category_id;
+ if (wasNull)
+ {
+ gIdleCallbacks.addFunction(onIdle, this);
+ }
+}
+
+// static
+void LLOutfitListBase::onIdle(void* userdata)
+{
+ LLOutfitListBase* self = (LLOutfitListBase*)userdata;
+
+ self->onIdleRefreshList();
+}
+
+void LLOutfitListBase::onIdleRefreshList()
+{
+ if (mRefreshListState.CategoryUUID.isNull())
+ return;
+
+ const F64 MAX_TIME = 0.05f;
+ F64 curent_time = LLTimer::getTotalSeconds();
+ const F64 end_time = curent_time + MAX_TIME;
+
+ // Handle added tabs.
+ while (mRefreshListState.AddedIterator < mRefreshListState.Added.end())
+ {
+ const LLUUID cat_id = (*mRefreshListState.AddedIterator++);
+ updateAddedCategory(cat_id);
+
+ curent_time = LLTimer::getTotalSeconds();
+ if (curent_time >= end_time)
+ return;
+ }
+ mRefreshListState.Added.clear();
+ mRefreshListState.AddedIterator = mRefreshListState.Added.end();
+
+ // Handle removed tabs.
+ while (mRefreshListState.RemovedIterator < mRefreshListState.Removed.end())
+ {
+ const LLUUID cat_id = (*mRefreshListState.RemovedIterator++);
+ updateRemovedCategory(cat_id);
+
+ curent_time = LLTimer::getTotalSeconds();
+ if (curent_time >= end_time)
+ return;
+ }
+ mRefreshListState.Removed.clear();
+ mRefreshListState.RemovedIterator = mRefreshListState.Removed.end();
+
+ // Get changed items from inventory model and update outfit tabs
+ // which might have been renamed.
+ while (!mChangedItems.empty())
+ {
+ std::set<LLUUID>::const_iterator items_iter = mChangedItems.begin();
+ LLViewerInventoryCategory *cat = gInventory.getCategory(*items_iter);
+ mChangedItems.erase(items_iter);
+
+ // Links aren't supposed to be allowed here, check only cats
+ if (cat)
+ {
+ std::string name = cat->getName();
+ updateChangedCategoryName(cat, name);
+ }
+
+ curent_time = LLTimer::getTotalSeconds();
+ if (curent_time >= end_time)
+ return;
+ }
+
+ sortOutfits();
+ highlightBaseOutfit();
+
+ gIdleCallbacks.deleteFunction(onIdle, this);
+ mRefreshListState.CategoryUUID.setNull();
+
+ LL_INFOS() << "done" << LL_ENDL;
+}
+
+void LLOutfitListBase::computeDifference(
+ const LLInventoryModel::cat_array_t& vcats,
+ uuid_vec_t& vadded,
+ uuid_vec_t& vremoved)
+{
+ uuid_vec_t vnew;
+ // Creating a vector of newly collected sub-categories UUIDs.
+ for (LLInventoryModel::cat_array_t::const_iterator iter = vcats.begin();
+ iter != vcats.end();
+ iter++)
+ {
+ vnew.push_back((*iter)->getUUID());
+ }
+
+ uuid_vec_t vcur;
+ getCurrentCategories(vcur);
+
+ LLCommonUtils::computeDifference(vnew, vcur, vadded, vremoved);
+}
+
+void LLOutfitListBase::sortOutfits()
+{
+}
+
+void LLOutfitListBase::highlightBaseOutfit()
+{
+ // id of base outfit
+ LLUUID base_id = LLAppearanceMgr::getInstance()->getBaseOutfitUUID();
+ if (base_id != mHighlightedOutfitUUID)
+ {
+ LLUUID prev_id = mHighlightedOutfitUUID;
+ mHighlightedOutfitUUID = base_id;
+ onHighlightBaseOutfit(base_id, prev_id);
+ }
+}
+
+void LLOutfitListBase::removeSelected()
+{
+ LLNotificationsUtil::add("DeleteOutfits", LLSD(), LLSD(), boost::bind(&LLOutfitListBase::onOutfitsRemovalConfirmation, this, _1, _2));
+}
+
+void LLOutfitListBase::onOutfitsRemovalConfirmation(const LLSD& notification, const LLSD& response)
+{
+ S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
+ if (option != 0) return; // canceled
+
+ if (mSelectedOutfitUUID.notNull())
+ {
+ gInventory.removeCategory(mSelectedOutfitUUID);
+ }
+}
+
+void LLOutfitListBase::setSelectedOutfitByUUID(const LLUUID& outfit_uuid)
+{
+ onSetSelectedOutfitByUUID(outfit_uuid);
+}
+
+boost::signals2::connection LLOutfitListBase::setSelectionChangeCallback(selection_change_callback_t cb)
+{
+ return mSelectionChangeSignal.connect(cb);
+}
+
+void LLOutfitListBase::signalSelectionOutfitUUID(const LLUUID& category_id)
+{
+ mSelectionChangeSignal(category_id);
+}
+
+void LLOutfitListBase::outfitRightClickCallBack(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& cat_id)
+{
+ onOutfitRightClick(ctrl, x, y, cat_id);
+}
+
+void LLOutfitListBase::ChangeOutfitSelection(LLWearableItemsList* list, const LLUUID& category_id)
+{
+ onChangeOutfitSelection(list, category_id);
+ mSelectedOutfitUUID = category_id;
+ signalSelectionOutfitUUID(category_id);
+}
+
+bool LLOutfitListBase::postBuild()
+{
+ mGearMenu = createGearMenu();
+
+ LLMenuButton* menu_gear_btn = getChild<LLMenuButton>("options_gear_btn");
+
+ menu_gear_btn->setMouseDownCallback(boost::bind(&LLOutfitListGearMenuBase::updateItemsVisibility, mGearMenu));
+ menu_gear_btn->setMenu(mGearMenu->getMenu());
+ return true;
+}
+
+void LLOutfitListBase::collapseAllFolders()
+{
+ onCollapseAllFolders();
+}
+
+void LLOutfitListBase::expandAllFolders()
+{
+ onExpandAllFolders();
+}
+
+void LLOutfitListBase::deselectOutfit(const LLUUID& category_id)
+{
+ // Reset selection if the outfit is selected.
+ if (category_id == mSelectedOutfitUUID)
+ {
+ mSelectedOutfitUUID = LLUUID::null;
+ signalSelectionOutfitUUID(mSelectedOutfitUUID);
+ }
+}
+
+LLContextMenu* LLOutfitContextMenu::createMenu()
+{
+ LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
+ LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar;
+ LLUUID selected_id = mUUIDs.front();
+
+ registrar.add("Outfit.WearReplace",
+ boost::bind(&LLAppearanceMgr::replaceCurrentOutfit, &LLAppearanceMgr::instance(), selected_id));
+ registrar.add("Outfit.WearAdd",
+ boost::bind(&LLAppearanceMgr::addCategoryToCurrentOutfit, &LLAppearanceMgr::instance(), selected_id));
+ registrar.add("Outfit.TakeOff",
+ boost::bind(&LLAppearanceMgr::takeOffOutfit, &LLAppearanceMgr::instance(), selected_id));
+ registrar.add("Outfit.Edit", boost::bind(editOutfit));
+ registrar.add("Outfit.Rename", boost::bind(renameOutfit, selected_id));
+ registrar.add("Outfit.Delete", boost::bind(&LLOutfitListBase::removeSelected, mOutfitList));
+ registrar.add("Outfit.Thumbnail", boost::bind(&LLOutfitContextMenu::onThumbnail, this, selected_id));
+ registrar.add("Outfit.Save", boost::bind(&LLOutfitContextMenu::onSave, this, selected_id));
+
+ enable_registrar.add("Outfit.OnEnable", boost::bind(&LLOutfitContextMenu::onEnable, this, _2));
+ enable_registrar.add("Outfit.OnVisible", boost::bind(&LLOutfitContextMenu::onVisible, this, _2));
+
+ return createFromFile("menu_outfit_tab.xml");
+
+}
+
+bool LLOutfitContextMenu::onEnable(LLSD::String param)
+{
+ LLUUID outfit_cat_id = mUUIDs.back();
+
+ if ("rename" == param)
+ {
+ return get_is_category_renameable(&gInventory, outfit_cat_id);
+ }
+ else if ("wear_replace" == param)
+ {
+ return LLAppearanceMgr::instance().getCanReplaceCOF(outfit_cat_id);
+ }
+ else if ("wear_add" == param)
+ {
+ return LLAppearanceMgr::getCanAddToCOF(outfit_cat_id);
+ }
+ else if ("take_off" == param)
+ {
+ return LLAppearanceMgr::getCanRemoveFromCOF(outfit_cat_id);
+ }
+
+ return true;
+}
+
+bool LLOutfitContextMenu::onVisible(LLSD::String param)
+{
+ LLUUID outfit_cat_id = mUUIDs.back();
+
+ if ("edit" == param)
+ {
+ bool is_worn = LLAppearanceMgr::instance().getBaseOutfitUUID() == outfit_cat_id;
+ return is_worn;
+ }
+ else if ("wear_replace" == param)
+ {
+ return true;
+ }
+ else if ("delete" == param)
+ {
+ return LLAppearanceMgr::instance().getCanRemoveOutfit(outfit_cat_id);
+ }
+
+ return true;
+}
+
+//static
+void LLOutfitContextMenu::editOutfit()
+{
+ LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "edit_outfit"));
+}
+
+void LLOutfitContextMenu::renameOutfit(const LLUUID& outfit_cat_id)
+{
+ LLAppearanceMgr::instance().renameOutfit(outfit_cat_id);
+}
+
+void LLOutfitContextMenu::onThumbnail(const LLUUID &outfit_cat_id)
+{
+ if (outfit_cat_id.notNull())
+ {
+ LLSD data(outfit_cat_id);
+ LLFloaterReg::showInstance("change_item_thumbnail", data);
+ }
+}
+
+void LLOutfitContextMenu::onSave(const LLUUID &outfit_cat_id)
+{
+ if (outfit_cat_id.notNull())
+ {
+ LLNotificationsUtil::add("ConfirmOverwriteOutfit", LLSD(), LLSD(),
+ [outfit_cat_id](const LLSD &notif, const LLSD &resp)
+ {
+ S32 opt = LLNotificationsUtil::getSelectedOption(notif, resp);
+ if (opt == 0)
+ {
+ LLAppearanceMgr::getInstance()->onOutfitFolderCreated(outfit_cat_id, true);
+ }
+ });
+ }
+}
+
+LLOutfitListGearMenuBase::LLOutfitListGearMenuBase(LLOutfitListBase* olist)
+ : mOutfitList(olist),
+ mMenu(NULL)
+{
+ llassert_always(mOutfitList);
+
+ LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
+ LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar;
+
+ registrar.add("Gear.Wear", boost::bind(&LLOutfitListGearMenuBase::onWear, this));
+ registrar.add("Gear.TakeOff", boost::bind(&LLOutfitListGearMenuBase::onTakeOff, this));
+ registrar.add("Gear.Rename", boost::bind(&LLOutfitListGearMenuBase::onRename, this));
+ registrar.add("Gear.Delete", boost::bind(&LLOutfitListBase::removeSelected, mOutfitList));
+ registrar.add("Gear.Create", boost::bind(&LLOutfitListGearMenuBase::onCreate, this, _2));
+ registrar.add("Gear.Collapse", boost::bind(&LLOutfitListBase::onCollapseAllFolders, mOutfitList));
+ registrar.add("Gear.Expand", boost::bind(&LLOutfitListBase::onExpandAllFolders, mOutfitList));
+
+ registrar.add("Gear.WearAdd", boost::bind(&LLOutfitListGearMenuBase::onAdd, this));
+ registrar.add("Gear.Save", boost::bind(&LLOutfitListGearMenuBase::onSave, this));
+
+ registrar.add("Gear.Thumbnail", boost::bind(&LLOutfitListGearMenuBase::onThumbnail, this));
+ registrar.add("Gear.SortByName", boost::bind(&LLOutfitListGearMenuBase::onChangeSortOrder, this));
+
+ enable_registrar.add("Gear.OnEnable", boost::bind(&LLOutfitListGearMenuBase::onEnable, this, _2));
+ enable_registrar.add("Gear.OnVisible", boost::bind(&LLOutfitListGearMenuBase::onVisible, this, _2));
+
+ mMenu = LLUICtrlFactory::getInstance()->createFromFile<LLToggleableMenu>(
+ "menu_outfit_gear.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance());
+ llassert(mMenu);
+}
+
+LLOutfitListGearMenuBase::~LLOutfitListGearMenuBase()
+{}
+
+void LLOutfitListGearMenuBase::updateItemsVisibility()
+{
+ onUpdateItemsVisibility();
+}
+
+void LLOutfitListGearMenuBase::onUpdateItemsVisibility()
+{
+ if (!mMenu) return;
+
+ bool have_selection = getSelectedOutfitID().notNull();
+ mMenu->setItemVisible("wear_separator", have_selection);
+ mMenu->arrangeAndClear(); // update menu height
+}
+
+LLToggleableMenu* LLOutfitListGearMenuBase::getMenu()
+{
+ return mMenu;
+}
+const LLUUID& LLOutfitListGearMenuBase::getSelectedOutfitID()
+{
+ return mOutfitList->getSelectedOutfitUUID();
+}
+
+LLViewerInventoryCategory* LLOutfitListGearMenuBase::getSelectedOutfit()
+{
+ const LLUUID& selected_outfit_id = getSelectedOutfitID();
+ if (selected_outfit_id.isNull())
+ {
+ return NULL;
+ }
+
+ LLViewerInventoryCategory* cat = gInventory.getCategory(selected_outfit_id);
+ return cat;
+}
+
+void LLOutfitListGearMenuBase::onWear()
+{
+ LLViewerInventoryCategory* selected_outfit = getSelectedOutfit();
+ if (selected_outfit)
+ {
+ LLAppearanceMgr::instance().wearInventoryCategory(
+ selected_outfit, /*copy=*/ false, /*append=*/ false);
+ }
+}
+
+void LLOutfitListGearMenuBase::onAdd()
+{
+ const LLUUID& selected_id = getSelectedOutfitID();
+
+ if (selected_id.notNull())
+ {
+ LLAppearanceMgr::getInstance()->addCategoryToCurrentOutfit(selected_id);
+ }
+}
+
+void LLOutfitListGearMenuBase::onSave()
+{
+ const LLUUID &selected_id = getSelectedOutfitID();
+ LLNotificationsUtil::add("ConfirmOverwriteOutfit", LLSD(), LLSD(),
+ [selected_id](const LLSD &notif, const LLSD &resp)
+ {
+ S32 opt = LLNotificationsUtil::getSelectedOption(notif, resp);
+ if (opt == 0)
+ {
+ LLAppearanceMgr::getInstance()->onOutfitFolderCreated(selected_id, true);
+ }
+ });
+}
+
+void LLOutfitListGearMenuBase::onTakeOff()
+{
+ // Take off selected outfit.
+ const LLUUID& selected_outfit_id = getSelectedOutfitID();
+ if (selected_outfit_id.notNull())
+ {
+ LLAppearanceMgr::instance().takeOffOutfit(selected_outfit_id);
+ }
+}
+
+void LLOutfitListGearMenuBase::onRename()
+{
+ const LLUUID& selected_outfit_id = getSelectedOutfitID();
+ if (selected_outfit_id.notNull())
+ {
+ LLAppearanceMgr::instance().renameOutfit(selected_outfit_id);
+ }
+}
+
+void LLOutfitListGearMenuBase::onCreate(const LLSD& data)
+{
+ LLWearableType::EType type = LLWearableType::getInstance()->typeNameToType(data.asString());
+ if (type == LLWearableType::WT_NONE)
+ {
+ LL_WARNS() << "Invalid wearable type" << LL_ENDL;
+ return;
+ }
+
+ LLAgentWearables::createWearable(type, true);
+}
+
+bool LLOutfitListGearMenuBase::onEnable(LLSD::String param)
+{
+ // Handle the "Wear - Replace Current Outfit" menu option specially
+ // because LLOutfitList::isActionEnabled() checks whether it's allowed
+ // to wear selected outfit OR selected items, while we're only
+ // interested in the outfit (STORM-183).
+ if ("wear" == param)
+ {
+ return LLAppearanceMgr::instance().getCanReplaceCOF(mOutfitList->getSelectedOutfitUUID());
+ }
+
+ return mOutfitList->isActionEnabled(param);
+}
+
+bool LLOutfitListGearMenuBase::onVisible(LLSD::String param)
+{
+ const LLUUID& selected_outfit_id = getSelectedOutfitID();
+ if (selected_outfit_id.isNull()) // no selection or invalid outfit selected
+ {
+ return false;
+ }
+
+ return true;
+}
+
+void LLOutfitListGearMenuBase::onThumbnail()
+{
+ const LLUUID& selected_outfit_id = getSelectedOutfitID();
+ LLSD data(selected_outfit_id);
+ LLFloaterReg::showInstance("change_item_thumbnail", data);
+}
+
+void LLOutfitListGearMenuBase::onChangeSortOrder()
+{
+
+}
+
+LLOutfitListGearMenu::LLOutfitListGearMenu(LLOutfitListBase* olist)
+ : LLOutfitListGearMenuBase(olist)
+{}
+
+LLOutfitListGearMenu::~LLOutfitListGearMenu()
+{}
+
+void LLOutfitListGearMenu::onUpdateItemsVisibility()
+{
+ if (!mMenu) return;
+ mMenu->setItemVisible("expand", true);
+ mMenu->setItemVisible("collapse", true);
+ mMenu->setItemVisible("thumbnail", getSelectedOutfitID().notNull());
+ mMenu->setItemVisible("sepatator3", false);
+ mMenu->setItemVisible("sort_folders_by_name", false);
+ LLOutfitListGearMenuBase::onUpdateItemsVisibility();
+}
+
+bool LLOutfitAccordionCtrlTab::handleToolTip(S32 x, S32 y, MASK mask)
+{
+ if (y >= getLocalRect().getHeight() - getHeaderHeight())
+ {
+ LLSD params;
+ params["inv_type"] = LLInventoryType::IT_CATEGORY;
+ params["thumbnail_id"] = gInventory.getCategory(mFolderID)->getThumbnailUUID();
+ params["item_id"] = mFolderID;
+
+ LLToolTipMgr::instance().show(LLToolTip::Params()
+ .message(getToolTip())
+ .sticky_rect(calcScreenRect())
+ .delay_time(LLView::getTooltipTimeout())
+ .create_callback(boost::bind(&LLInspectTextureUtil::createInventoryToolTip, _1))
+ .create_params(params));
+ return true;
+ }
+
+ return LLAccordionCtrlTab::handleToolTip(x, y, mask);
+}
+// EOF