PageRenderTime 162ms CodeModel.GetById 38ms app.highlight 111ms RepoModel.GetById 1ms app.codeStats 0ms

/indra/newview/lloutfitslist.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 1113 lines | 784 code | 202 blank | 127 comment | 160 complexity | eb0c174e6a326b5f1183900deec50afb MD5 | raw file
   1/**
   2 * @file lloutfitslist.cpp
   3 * @brief List of agent's outfits for My Appearance side panel.
   4 *
   5 * $LicenseInfo:firstyear=2010&license=viewerlgpl$
   6 * Second Life Viewer Source Code
   7 * Copyright (C) 2010, Linden Research, Inc.
   8 * 
   9 * This library is free software; you can redistribute it and/or
  10 * modify it under the terms of the GNU Lesser General Public
  11 * License as published by the Free Software Foundation;
  12 * version 2.1 of the License only.
  13 * 
  14 * This library is distributed in the hope that it will be useful,
  15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  17 * Lesser General Public License for more details.
  18 * 
  19 * You should have received a copy of the GNU Lesser General Public
  20 * License along with this library; if not, write to the Free Software
  21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  22 * 
  23 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
  24 * $/LicenseInfo$
  25 */
  26
  27#include "llviewerprecompiledheaders.h"
  28
  29#include "lloutfitslist.h"
  30
  31// llcommon
  32#include "llcommonutils.h"
  33
  34#include "llaccordionctrl.h"
  35#include "llaccordionctrltab.h"
  36#include "llagentwearables.h"
  37#include "llappearancemgr.h"
  38#include "llfloatersidepanelcontainer.h"
  39#include "llinventoryfunctions.h"
  40#include "llinventorymodel.h"
  41#include "lllistcontextmenu.h"
  42#include "llmenubutton.h"
  43#include "llnotificationsutil.h"
  44#include "lloutfitobserver.h"
  45#include "lltoggleablemenu.h"
  46#include "lltransutil.h"
  47#include "llviewermenu.h"
  48#include "llvoavatar.h"
  49#include "llvoavatarself.h"
  50#include "llwearableitemslist.h"
  51
  52static bool is_tab_header_clicked(LLAccordionCtrlTab* tab, S32 y);
  53
  54static const LLOutfitTabNameComparator OUTFIT_TAB_NAME_COMPARATOR;
  55
  56/*virtual*/
  57bool LLOutfitTabNameComparator::compare(const LLAccordionCtrlTab* tab1, const LLAccordionCtrlTab* tab2) const
  58{
  59	std::string name1 = tab1->getTitle();
  60	std::string name2 = tab2->getTitle();
  61
  62	LLStringUtil::toUpper(name1);
  63	LLStringUtil::toUpper(name2);
  64
  65	return name1 < name2;
  66}
  67
  68struct outfit_accordion_tab_params : public LLInitParam::Block<outfit_accordion_tab_params, LLAccordionCtrlTab::Params>
  69{
  70	Mandatory<LLWearableItemsList::Params> wearable_list;
  71
  72	outfit_accordion_tab_params()
  73	:	wearable_list("wearable_items_list")
  74	{}
  75};
  76
  77const outfit_accordion_tab_params& get_accordion_tab_params()
  78{
  79	static outfit_accordion_tab_params tab_params;
  80	static bool initialized = false;
  81	if (!initialized)
  82	{
  83		initialized = true;
  84
  85		LLXMLNodePtr xmlNode;
  86		if (LLUICtrlFactory::getLayeredXMLNode("outfit_accordion_tab.xml", xmlNode))
  87		{
  88			LLXUIParser parser;
  89			parser.readXUI(xmlNode, tab_params, "outfit_accordion_tab.xml");
  90		}
  91		else
  92		{
  93			llwarns << "Failed to read xml of Outfit's Accordion Tab from outfit_accordion_tab.xml" << llendl;
  94		}
  95	}
  96
  97	return tab_params;
  98}
  99
 100
 101//////////////////////////////////////////////////////////////////////////
 102
 103class LLOutfitListGearMenu
 104{
 105public:
 106	LLOutfitListGearMenu(LLOutfitsList* olist)
 107	:	mOutfitList(olist),
 108		mMenu(NULL)
 109	{
 110		llassert_always(mOutfitList);
 111
 112		LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
 113		LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar;
 114
 115		registrar.add("Gear.Wear", boost::bind(&LLOutfitListGearMenu::onWear, this));
 116		registrar.add("Gear.TakeOff", boost::bind(&LLOutfitListGearMenu::onTakeOff, this));
 117		registrar.add("Gear.Rename", boost::bind(&LLOutfitListGearMenu::onRename, this));
 118		registrar.add("Gear.Delete", boost::bind(&LLOutfitsList::removeSelected, mOutfitList));
 119		registrar.add("Gear.Create", boost::bind(&LLOutfitListGearMenu::onCreate, this, _2));
 120
 121		registrar.add("Gear.WearAdd", boost::bind(&LLOutfitListGearMenu::onAdd, this));
 122
 123		enable_registrar.add("Gear.OnEnable", boost::bind(&LLOutfitListGearMenu::onEnable, this, _2));
 124		enable_registrar.add("Gear.OnVisible", boost::bind(&LLOutfitListGearMenu::onVisible, this, _2));
 125
 126		mMenu = LLUICtrlFactory::getInstance()->createFromFile<LLToggleableMenu>(
 127			"menu_outfit_gear.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance());
 128		llassert(mMenu);
 129	}
 130
 131	void updateItemsVisibility()
 132	{
 133		if (!mMenu) return;
 134
 135		bool have_selection = getSelectedOutfitID().notNull();
 136		mMenu->setItemVisible("sepatator1", have_selection);
 137		mMenu->setItemVisible("sepatator2", have_selection);
 138		mMenu->arrangeAndClear(); // update menu height
 139	}
 140
 141	LLToggleableMenu* getMenu() { return mMenu; }
 142
 143private:
 144	const LLUUID& getSelectedOutfitID()
 145	{
 146		return mOutfitList->getSelectedOutfitUUID();
 147	}
 148
 149	LLViewerInventoryCategory* getSelectedOutfit()
 150	{
 151		const LLUUID& selected_outfit_id = getSelectedOutfitID();
 152		if (selected_outfit_id.isNull())
 153		{
 154			return NULL;
 155		}
 156
 157		LLViewerInventoryCategory* cat = gInventory.getCategory(selected_outfit_id);
 158		return cat;
 159	}
 160
 161	void onWear()
 162	{
 163		LLViewerInventoryCategory* selected_outfit = getSelectedOutfit();
 164		if (selected_outfit)
 165		{
 166			LLAppearanceMgr::instance().wearInventoryCategory(
 167				selected_outfit, /*copy=*/ FALSE, /*append=*/ FALSE);
 168		}
 169	}
 170
 171	void onAdd()
 172	{
 173		const LLUUID& selected_id = getSelectedOutfitID();
 174
 175		if (selected_id.notNull())
 176		{
 177			LLAppearanceMgr::getInstance()->addCategoryToCurrentOutfit(selected_id);
 178		}
 179	}
 180
 181	void onTakeOff()
 182	{
 183		// Take off selected outfit.
 184			const LLUUID& selected_outfit_id = getSelectedOutfitID();
 185			if (selected_outfit_id.notNull())
 186			{
 187				LLAppearanceMgr::instance().takeOffOutfit(selected_outfit_id);
 188			}
 189		}
 190
 191	void onRename()
 192	{
 193		const LLUUID& selected_outfit_id = getSelectedOutfitID();
 194		if (selected_outfit_id.notNull())
 195		{
 196			LLAppearanceMgr::instance().renameOutfit(selected_outfit_id);
 197		}
 198	}
 199
 200	void onCreate(const LLSD& data)
 201	{
 202		LLWearableType::EType type = LLWearableType::typeNameToType(data.asString());
 203		if (type == LLWearableType::WT_NONE)
 204		{
 205			llwarns << "Invalid wearable type" << llendl;
 206			return;
 207		}
 208
 209		LLAgentWearables::createWearable(type, true);
 210	}
 211
 212	bool onEnable(LLSD::String param)
 213	{
 214		// Handle the "Wear - Replace Current Outfit" menu option specially
 215		// because LLOutfitList::isActionEnabled() checks whether it's allowed
 216		// to wear selected outfit OR selected items, while we're only
 217		// interested in the outfit (STORM-183).
 218		if ("wear" == param)
 219		{
 220			return LLAppearanceMgr::instance().getCanReplaceCOF(mOutfitList->getSelectedOutfitUUID());
 221		}
 222
 223		return mOutfitList->isActionEnabled(param);
 224	}
 225
 226	bool onVisible(LLSD::String param)
 227	{
 228		const LLUUID& selected_outfit_id = getSelectedOutfitID();
 229		if (selected_outfit_id.isNull()) // no selection or invalid outfit selected
 230		{
 231			return false;
 232		}
 233
 234		// *TODO This condition leads to menu item behavior inconsistent with
 235		// "Wear" button behavior and should be modified or removed.
 236		bool is_worn = LLAppearanceMgr::instance().getBaseOutfitUUID() == selected_outfit_id;
 237
 238		if ("wear" == param)
 239		{
 240			return !is_worn;
 241		}
 242
 243		return true;
 244	}
 245
 246	LLOutfitsList*			mOutfitList;
 247	LLToggleableMenu*		mMenu;
 248};
 249
 250//////////////////////////////////////////////////////////////////////////
 251
 252class LLOutfitContextMenu : public LLListContextMenu
 253{
 254public:
 255
 256	LLOutfitContextMenu(LLOutfitsList* outfit_list)
 257	:		LLListContextMenu(),
 258	 		mOutfitList(outfit_list)
 259	{}
 260protected:
 261	/* virtual */ LLContextMenu* createMenu()
 262	{
 263		LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
 264		LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar;
 265		LLUUID selected_id = mUUIDs.front();
 266
 267		registrar.add("Outfit.WearReplace",
 268			boost::bind(&LLAppearanceMgr::replaceCurrentOutfit, &LLAppearanceMgr::instance(), selected_id));
 269		registrar.add("Outfit.WearAdd",
 270			boost::bind(&LLAppearanceMgr::addCategoryToCurrentOutfit, &LLAppearanceMgr::instance(), selected_id));
 271		registrar.add("Outfit.TakeOff",
 272				boost::bind(&LLAppearanceMgr::takeOffOutfit, &LLAppearanceMgr::instance(), selected_id));
 273		registrar.add("Outfit.Edit", boost::bind(editOutfit));
 274		registrar.add("Outfit.Rename", boost::bind(renameOutfit, selected_id));
 275		registrar.add("Outfit.Delete", boost::bind(&LLOutfitsList::removeSelected, mOutfitList));
 276
 277		enable_registrar.add("Outfit.OnEnable", boost::bind(&LLOutfitContextMenu::onEnable, this, _2));
 278		enable_registrar.add("Outfit.OnVisible", boost::bind(&LLOutfitContextMenu::onVisible, this, _2));
 279
 280		return createFromFile("menu_outfit_tab.xml");
 281	}
 282
 283	bool onEnable(LLSD::String param)
 284	{
 285		LLUUID outfit_cat_id = mUUIDs.back();
 286
 287		if ("rename" == param)
 288		{
 289			return get_is_category_renameable(&gInventory, outfit_cat_id);
 290		}
 291		else if ("wear_replace" == param)
 292		{
 293			return LLAppearanceMgr::instance().getCanReplaceCOF(outfit_cat_id);
 294		}
 295		else if ("wear_add" == param)
 296		{
 297			return LLAppearanceMgr::getCanAddToCOF(outfit_cat_id);
 298		}
 299		else if ("take_off" == param)
 300		{
 301			return LLAppearanceMgr::getCanRemoveFromCOF(outfit_cat_id);
 302		}
 303
 304		return true;
 305	}
 306
 307	bool onVisible(LLSD::String param)
 308	{
 309		LLUUID outfit_cat_id = mUUIDs.back();
 310		bool is_worn = LLAppearanceMgr::instance().getBaseOutfitUUID() == outfit_cat_id;
 311
 312		if ("edit" == param)
 313		{
 314			return is_worn;
 315		}
 316		else if ("wear_replace" == param)
 317		{
 318			return !is_worn;
 319		}
 320		else if ("delete" == param)
 321		{
 322			return LLAppearanceMgr::instance().getCanRemoveOutfit(outfit_cat_id);
 323		}
 324
 325		return true;
 326	}
 327
 328	static void editOutfit()
 329	{
 330		LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "edit_outfit"));
 331	}
 332
 333	static void renameOutfit(const LLUUID& outfit_cat_id)
 334	{
 335		LLAppearanceMgr::instance().renameOutfit(outfit_cat_id);
 336	}
 337
 338private:
 339	LLOutfitsList*	mOutfitList;
 340};
 341
 342//////////////////////////////////////////////////////////////////////////
 343
 344static LLRegisterPanelClassWrapper<LLOutfitsList> t_outfits_list("outfits_list");
 345
 346LLOutfitsList::LLOutfitsList()
 347	:	LLPanelAppearanceTab()
 348	,	mAccordion(NULL)
 349	,	mListCommands(NULL)
 350	,	mIsInitialized(false)
 351	,	mItemSelected(false)
 352{
 353	mCategoriesObserver = new LLInventoryCategoriesObserver();
 354
 355	mGearMenu = new LLOutfitListGearMenu(this);
 356	mOutfitMenu = new LLOutfitContextMenu(this);
 357}
 358
 359LLOutfitsList::~LLOutfitsList()
 360{
 361	delete mGearMenu;
 362	delete mOutfitMenu;
 363
 364	if (gInventory.containsObserver(mCategoriesObserver))
 365	{
 366		gInventory.removeObserver(mCategoriesObserver);
 367	}
 368	delete mCategoriesObserver;
 369}
 370
 371BOOL LLOutfitsList::postBuild()
 372{
 373	mAccordion = getChild<LLAccordionCtrl>("outfits_accordion");
 374	mAccordion->setComparator(&OUTFIT_TAB_NAME_COMPARATOR);
 375
 376	LLMenuButton* menu_gear_btn = getChild<LLMenuButton>("options_gear_btn");
 377
 378	menu_gear_btn->setMouseDownCallback(boost::bind(&LLOutfitListGearMenu::updateItemsVisibility, mGearMenu));
 379	menu_gear_btn->setMenu(mGearMenu->getMenu());
 380
 381	return TRUE;
 382}
 383
 384//virtual
 385void LLOutfitsList::onOpen(const LLSD& /*info*/)
 386{
 387	if (!mIsInitialized)
 388	{
 389		// *TODO: I'm not sure is this check necessary but it never match while developing.
 390		if (!gInventory.isInventoryUsable())
 391			return;
 392
 393		const LLUUID outfits = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS);
 394
 395		// *TODO: I'm not sure is this check necessary but it never match while developing.
 396		LLViewerInventoryCategory* category = gInventory.getCategory(outfits);
 397		if (!category)
 398			return;
 399
 400		gInventory.addObserver(mCategoriesObserver);
 401
 402		// Start observing changes in "My Outfits" category.
 403		mCategoriesObserver->addCategory(outfits,
 404			boost::bind(&LLOutfitsList::refreshList, this, outfits));
 405
 406		const LLUUID cof = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT);
 407
 408		// Start observing changes in Current Outfit category.
 409		mCategoriesObserver->addCategory(cof, boost::bind(&LLOutfitsList::onCOFChanged, this));
 410
 411		LLOutfitObserver::instance().addBOFChangedCallback(boost::bind(&LLOutfitsList::highlightBaseOutfit, this));
 412		LLOutfitObserver::instance().addBOFReplacedCallback(boost::bind(&LLOutfitsList::highlightBaseOutfit, this));
 413
 414		// Fetch "My Outfits" contents and refresh the list to display
 415		// initially fetched items. If not all items are fetched now
 416		// the observer will refresh the list as soon as the new items
 417		// arrive.
 418		category->fetch();
 419		refreshList(outfits);
 420		highlightBaseOutfit();
 421
 422		mIsInitialized = true;
 423	}
 424
 425	LLAccordionCtrlTab* selected_tab = mAccordion->getSelectedTab();
 426	if (!selected_tab) return;
 427
 428	// Pass focus to the selected outfit tab.
 429	selected_tab->showAndFocusHeader();
 430}
 431
 432void LLOutfitsList::refreshList(const LLUUID& category_id)
 433{
 434	LLInventoryModel::cat_array_t cat_array;
 435	LLInventoryModel::item_array_t item_array;
 436
 437	// Collect all sub-categories of a given category.
 438	LLIsType is_category(LLAssetType::AT_CATEGORY);
 439	gInventory.collectDescendentsIf(
 440		category_id,
 441		cat_array,
 442		item_array,
 443		LLInventoryModel::EXCLUDE_TRASH,
 444		is_category);
 445
 446	uuid_vec_t vadded;
 447	uuid_vec_t vremoved;
 448
 449	// Create added and removed items vectors.
 450	computeDifference(cat_array, vadded, vremoved);
 451
 452	// Handle added tabs.
 453	for (uuid_vec_t::const_iterator iter = vadded.begin();
 454		 iter != vadded.end();
 455		 ++iter)
 456	{
 457		const LLUUID cat_id = (*iter);
 458		LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id);
 459		if (!cat) continue;
 460
 461		std::string name = cat->getName();
 462
 463		outfit_accordion_tab_params tab_params(get_accordion_tab_params());
 464		LLAccordionCtrlTab* tab = LLUICtrlFactory::create<LLAccordionCtrlTab>(tab_params);
 465		if (!tab) continue;
 466		LLWearableItemsList* wearable_list = LLUICtrlFactory::create<LLWearableItemsList>(tab_params.wearable_list);
 467		wearable_list->setShape(tab->getLocalRect());
 468		tab->addChild(wearable_list);
 469
 470		tab->setName(name);
 471		tab->setTitle(name);
 472
 473		// *TODO: LLUICtrlFactory::defaultBuilder does not use "display_children" from xml. Should be investigated.
 474		tab->setDisplayChildren(false);
 475		mAccordion->addCollapsibleCtrl(tab);
 476
 477		// Start observing the new outfit category.
 478		LLWearableItemsList* list  = tab->getChild<LLWearableItemsList>("wearable_items_list");
 479		if (!mCategoriesObserver->addCategory(cat_id, boost::bind(&LLWearableItemsList::updateList, list, cat_id)))
 480		{
 481			// Remove accordion tab if category could not be added to observer.
 482			mAccordion->removeCollapsibleCtrl(tab);
 483
 484			// kill removed tab
 485				tab->die();
 486			continue;
 487		}
 488
 489		// Map the new tab with outfit category UUID.
 490		mOutfitsMap.insert(LLOutfitsList::outfits_map_value_t(cat_id, tab));
 491
 492		tab->setRightMouseDownCallback(boost::bind(&LLOutfitsList::onAccordionTabRightClick, this,
 493			_1, _2, _3, cat_id));
 494
 495		// Setting tab focus callback to monitor currently selected outfit.
 496		tab->setFocusReceivedCallback(boost::bind(&LLOutfitsList::changeOutfitSelection, this, list, cat_id));
 497
 498		// Setting callback to reset items selection inside outfit on accordion collapsing and expanding (EXT-7875)
 499		tab->setDropDownStateChangedCallback(boost::bind(&LLOutfitsList::resetItemSelection, this, list, cat_id));
 500
 501		// force showing list items that don't match current filter(EXT-7158)
 502		list->setForceShowingUnmatchedItems(true);
 503
 504		// Setting list commit callback to monitor currently selected wearable item.
 505		list->setCommitCallback(boost::bind(&LLOutfitsList::onSelectionChange, this, _1));
 506
 507		// Setting list refresh callback to apply filter on list change.
 508		list->setRefreshCompleteCallback(boost::bind(&LLOutfitsList::onFilteredWearableItemsListRefresh, this, _1));
 509
 510		list->setRightMouseDownCallback(boost::bind(&LLOutfitsList::onWearableItemsListRightClick, this, _1, _2, _3));
 511
 512		// Fetch the new outfit contents.
 513		cat->fetch();
 514
 515		// Refresh the list of outfit items after fetch().
 516		// Further list updates will be triggered by the category observer.
 517		list->updateList(cat_id);
 518
 519		// If filter is currently applied we store the initial tab state and
 520		// open it to show matched items if any.
 521		if (!sFilterSubString.empty())
 522		{
 523			tab->notifyChildren(LLSD().with("action","store_state"));
 524			tab->setDisplayChildren(true);
 525
 526			// Setting mForceRefresh flag will make the list refresh its contents
 527			// even if it is not currently visible. This is required to apply the
 528			// filter to the newly added list.
 529			list->setForceRefresh(true);
 530
 531			list->setFilterSubString(sFilterSubString);
 532		}
 533	}
 534
 535	// Handle removed tabs.
 536	for (uuid_vec_t::const_iterator iter=vremoved.begin(); iter != vremoved.end(); ++iter)
 537	{
 538		outfits_map_t::iterator outfits_iter = mOutfitsMap.find((*iter));
 539		if (outfits_iter != mOutfitsMap.end())
 540		{
 541			const LLUUID& outfit_id = outfits_iter->first;
 542			LLAccordionCtrlTab* tab = outfits_iter->second;
 543
 544			// An outfit is removed from the list. Do the following:
 545			// 1. Remove outfit category from observer to stop monitoring its changes.
 546			mCategoriesObserver->removeCategory(outfit_id);
 547
 548			// 2. Remove the outfit from selection.
 549			deselectOutfit(outfit_id);
 550
 551			// 3. Remove category UUID to accordion tab mapping.
 552			mOutfitsMap.erase(outfits_iter);
 553
 554			// 4. Remove outfit tab from accordion.
 555			mAccordion->removeCollapsibleCtrl(tab);
 556
 557			// kill removed tab
 558			if (tab != NULL)
 559			{
 560				tab->die();
 561			}
 562		}
 563	}
 564
 565	// Get changed items from inventory model and update outfit tabs
 566	// which might have been renamed.
 567	const LLInventoryModel::changed_items_t& changed_items = gInventory.getChangedIDs();
 568	for (LLInventoryModel::changed_items_t::const_iterator items_iter = changed_items.begin();
 569		 items_iter != changed_items.end();
 570		 ++items_iter)
 571	{
 572		updateOutfitTab(*items_iter);
 573	}
 574
 575	mAccordion->sort();
 576}
 577
 578void LLOutfitsList::highlightBaseOutfit()
 579{
 580	// id of base outfit
 581	LLUUID base_id = LLAppearanceMgr::getInstance()->getBaseOutfitUUID();
 582	if (base_id != mHighlightedOutfitUUID)
 583	{
 584		if (mOutfitsMap[mHighlightedOutfitUUID])
 585		{
 586			mOutfitsMap[mHighlightedOutfitUUID]->setTitleFontStyle("NORMAL");
 587			mOutfitsMap[mHighlightedOutfitUUID]->setTitleColor(LLUIColorTable::instance().getColor("AccordionHeaderTextColor"));
 588		}
 589
 590		mHighlightedOutfitUUID = base_id;
 591	}
 592	if (mOutfitsMap[base_id])
 593	{
 594		mOutfitsMap[base_id]->setTitleFontStyle("BOLD");
 595		mOutfitsMap[base_id]->setTitleColor(LLUIColorTable::instance().getColor("SelectedOutfitTextColor"));
 596	}
 597}
 598
 599void LLOutfitsList::onSelectionChange(LLUICtrl* ctrl)
 600{
 601	LLWearableItemsList* list = dynamic_cast<LLWearableItemsList*>(ctrl);
 602	if (!list) return;
 603
 604	LLViewerInventoryItem *item = gInventory.getItem(list->getSelectedUUID());
 605	if (!item) return;
 606
 607	changeOutfitSelection(list, item->getParentUUID());
 608}
 609
 610void LLOutfitsList::performAction(std::string action)
 611{
 612	if (mSelectedOutfitUUID.isNull()) return;
 613
 614	LLViewerInventoryCategory* cat = gInventory.getCategory(mSelectedOutfitUUID);
 615	if (!cat) return;
 616
 617	if ("replaceoutfit" == action)
 618	{
 619		LLAppearanceMgr::instance().wearInventoryCategory( cat, FALSE, FALSE );
 620	}
 621	else if ("addtooutfit" == action)
 622	{
 623		LLAppearanceMgr::instance().wearInventoryCategory( cat, FALSE, TRUE );
 624	}
 625	else if ("rename_outfit" == action)
 626	{
 627		LLAppearanceMgr::instance().renameOutfit(mSelectedOutfitUUID);
 628	}
 629}
 630
 631void LLOutfitsList::removeSelected()
 632{
 633	LLNotificationsUtil::add("DeleteOutfits", LLSD(), LLSD(), boost::bind(&LLOutfitsList::onOutfitsRemovalConfirmation, this, _1, _2));
 634}
 635
 636void LLOutfitsList::onOutfitsRemovalConfirmation(const LLSD& notification, const LLSD& response)
 637{
 638	S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
 639	if (option != 0) return; // canceled
 640
 641	if (mSelectedOutfitUUID.notNull())
 642	{
 643		remove_category(&gInventory, mSelectedOutfitUUID);
 644	}
 645}
 646
 647void LLOutfitsList::setSelectedOutfitByUUID(const LLUUID& outfit_uuid)
 648{
 649	for (outfits_map_t::iterator iter = mOutfitsMap.begin();
 650			iter != mOutfitsMap.end();
 651			++iter)
 652	{
 653		if (outfit_uuid == iter->first)
 654		{
 655			LLAccordionCtrlTab* tab = iter->second;
 656			if (!tab) continue;
 657
 658			LLWearableItemsList* list = dynamic_cast<LLWearableItemsList*>(tab->getAccordionView());
 659			if (!list) continue;
 660
 661			tab->setFocus(TRUE);
 662			changeOutfitSelection(list, outfit_uuid);
 663
 664			tab->setDisplayChildren(true);
 665		}
 666	}
 667}
 668
 669// virtual
 670void LLOutfitsList::setFilterSubString(const std::string& string)
 671{
 672	applyFilter(string);
 673
 674	sFilterSubString = string;
 675}
 676
 677// virtual
 678bool LLOutfitsList::isActionEnabled(const LLSD& userdata)
 679{
 680	if (mSelectedOutfitUUID.isNull()) return false;
 681
 682	const std::string command_name = userdata.asString();
 683	if (command_name == "delete")
 684	{
 685		return !mItemSelected && LLAppearanceMgr::instance().getCanRemoveOutfit(mSelectedOutfitUUID);
 686	}
 687	if (command_name == "rename")
 688	{
 689		return get_is_category_renameable(&gInventory, mSelectedOutfitUUID);
 690	}
 691	if (command_name == "save_outfit")
 692	{
 693		bool outfit_locked = LLAppearanceMgr::getInstance()->isOutfitLocked();
 694		bool outfit_dirty = LLAppearanceMgr::getInstance()->isOutfitDirty();
 695		// allow save only if outfit isn't locked and is dirty
 696		return !outfit_locked && outfit_dirty;
 697	}
 698	if (command_name == "wear")
 699	{
 700		if (gAgentWearables.isCOFChangeInProgress())
 701		{
 702			return false;
 703		}
 704
 705		if (hasItemSelected())
 706		{
 707			return canWearSelected();
 708		}
 709
 710		// outfit selected
 711		return LLAppearanceMgr::instance().getCanReplaceCOF(mSelectedOutfitUUID);
 712	}
 713	if (command_name == "take_off")
 714	{
 715		// Enable "Take Off" if any of selected items can be taken off
 716		// or the selected outfit contains items that can be taken off.
 717		return ( hasItemSelected() && canTakeOffSelected() )
 718				|| ( !hasItemSelected() && LLAppearanceMgr::getCanRemoveFromCOF(mSelectedOutfitUUID) );
 719	}
 720
 721	if (command_name == "wear_add")
 722	{
 723		// *TODO: do we ever get here?
 724		return LLAppearanceMgr::getCanAddToCOF(mSelectedOutfitUUID);
 725	}
 726
 727	return false;
 728}
 729
 730void LLOutfitsList::getSelectedItemsUUIDs(uuid_vec_t& selected_uuids) const
 731{
 732	// Collect selected items from all selected lists.
 733	for (wearables_lists_map_t::const_iterator iter = mSelectedListsMap.begin();
 734			iter != mSelectedListsMap.end();
 735			++iter)
 736	{
 737		uuid_vec_t uuids;
 738		(*iter).second->getSelectedUUIDs(uuids);
 739
 740		S32 prev_size = selected_uuids.size();
 741		selected_uuids.resize(prev_size + uuids.size());
 742		std::copy(uuids.begin(), uuids.end(), selected_uuids.begin() + prev_size);
 743	}
 744}
 745
 746boost::signals2::connection LLOutfitsList::setSelectionChangeCallback(selection_change_callback_t cb)
 747{
 748	return mSelectionChangeSignal.connect(cb);
 749}
 750
 751bool LLOutfitsList::hasItemSelected()
 752{
 753	return mItemSelected;
 754}
 755
 756//////////////////////////////////////////////////////////////////////////
 757// Private methods
 758//////////////////////////////////////////////////////////////////////////
 759void LLOutfitsList::computeDifference(
 760	const LLInventoryModel::cat_array_t& vcats, 
 761	uuid_vec_t& vadded, 
 762	uuid_vec_t& vremoved)
 763{
 764	uuid_vec_t vnew;
 765	// Creating a vector of newly collected sub-categories UUIDs.
 766	for (LLInventoryModel::cat_array_t::const_iterator iter = vcats.begin();
 767		iter != vcats.end();
 768		iter++)
 769	{
 770		vnew.push_back((*iter)->getUUID());
 771	}
 772
 773	uuid_vec_t vcur;
 774	// Creating a vector of currently displayed sub-categories UUIDs.
 775	for (outfits_map_t::const_iterator iter = mOutfitsMap.begin();
 776		iter != mOutfitsMap.end();
 777		iter++)
 778	{
 779		vcur.push_back((*iter).first);
 780	}
 781
 782	LLCommonUtils::computeDifference(vnew, vcur, vadded, vremoved);
 783}
 784
 785void LLOutfitsList::updateOutfitTab(const LLUUID& category_id)
 786{
 787	outfits_map_t::iterator outfits_iter = mOutfitsMap.find(category_id);
 788	if (outfits_iter != mOutfitsMap.end())
 789	{
 790		LLViewerInventoryCategory *cat = gInventory.getCategory(category_id);
 791		if (!cat) return;
 792
 793		std::string name = cat->getName();
 794
 795		// Update tab name with the new category name.
 796		LLAccordionCtrlTab* tab = outfits_iter->second;
 797		if (tab)
 798		{
 799			tab->setName(name);
 800			tab->setTitle(name);
 801		}
 802	}
 803}
 804
 805void LLOutfitsList::resetItemSelection(LLWearableItemsList* list, const LLUUID& category_id)
 806{
 807	list->resetSelection();
 808	mItemSelected = false;
 809	setSelectedOutfitUUID(category_id);
 810}
 811
 812void LLOutfitsList::changeOutfitSelection(LLWearableItemsList* list, const LLUUID& category_id)
 813{
 814	MASK mask = gKeyboard->currentMask(TRUE);
 815
 816	// Reset selection in all previously selected tabs except for the current
 817	// if new selection is started.
 818	if (list && !(mask & MASK_CONTROL))
 819	{
 820		for (wearables_lists_map_t::iterator iter = mSelectedListsMap.begin();
 821				iter != mSelectedListsMap.end();
 822				++iter)
 823		{
 824			LLWearableItemsList* selected_list = (*iter).second;
 825			if (selected_list != list)
 826			{
 827				selected_list->resetSelection();
 828			}
 829		}
 830
 831		// Clear current selection.
 832		mSelectedListsMap.clear();
 833	}
 834
 835	mItemSelected = list && (list->getSelectedItem() != NULL);
 836
 837	mSelectedListsMap.insert(wearables_lists_map_value_t(category_id, list));
 838	setSelectedOutfitUUID(category_id);
 839}
 840
 841void LLOutfitsList::setSelectedOutfitUUID(const LLUUID& category_id)
 842{
 843	mSelectionChangeSignal(mSelectedOutfitUUID = category_id);
 844}
 845
 846void LLOutfitsList::deselectOutfit(const LLUUID& category_id)
 847{
 848	// Remove selected lists map entry.
 849	mSelectedListsMap.erase(category_id);
 850
 851	// Reset selection if the outfit is selected.
 852	if (category_id == mSelectedOutfitUUID)
 853	{
 854		setSelectedOutfitUUID(LLUUID::null);
 855	}
 856}
 857
 858void LLOutfitsList::restoreOutfitSelection(LLAccordionCtrlTab* tab, const LLUUID& category_id)
 859{
 860	// Try restoring outfit selection after filtering.
 861	if (mAccordion->getSelectedTab() == tab)
 862	{
 863		setSelectedOutfitUUID(category_id);
 864	}
 865}
 866
 867void LLOutfitsList::onFilteredWearableItemsListRefresh(LLUICtrl* ctrl)
 868{
 869	if (!ctrl || sFilterSubString.empty())
 870		return;
 871
 872	for (outfits_map_t::iterator
 873			 iter = mOutfitsMap.begin(),
 874			 iter_end = mOutfitsMap.end();
 875		 iter != iter_end; ++iter)
 876	{
 877		LLAccordionCtrlTab* tab = iter->second;
 878		if (!tab) continue;
 879
 880		LLWearableItemsList* list = dynamic_cast<LLWearableItemsList*>(tab->getAccordionView());
 881		if (list != ctrl) continue;
 882
 883		applyFilterToTab(iter->first, tab, sFilterSubString);
 884	}
 885}
 886
 887void LLOutfitsList::applyFilter(const std::string& new_filter_substring)
 888{
 889	mAccordion->setFilterSubString(new_filter_substring);
 890
 891	for (outfits_map_t::iterator
 892			 iter = mOutfitsMap.begin(),
 893			 iter_end = mOutfitsMap.end();
 894		 iter != iter_end; ++iter)
 895	{
 896		LLAccordionCtrlTab* tab = iter->second;
 897		if (!tab) continue;
 898
 899		bool more_restrictive = sFilterSubString.size() < new_filter_substring.size() && !new_filter_substring.substr(0, sFilterSubString.size()).compare(sFilterSubString);
 900
 901		// Restore tab visibility in case of less restrictive filter
 902		// to compare it with updated string if it was previously hidden.
 903		if (!more_restrictive)
 904		{
 905			tab->setVisible(TRUE);
 906		}
 907
 908		LLWearableItemsList* list = dynamic_cast<LLWearableItemsList*>(tab->getAccordionView());
 909		if (list)
 910		{
 911			list->setFilterSubString(new_filter_substring);
 912		}
 913
 914		if(sFilterSubString.empty() && !new_filter_substring.empty())
 915		{
 916			//store accordion tab state when filter is not empty
 917			tab->notifyChildren(LLSD().with("action","store_state"));
 918		}
 919
 920		if (!new_filter_substring.empty())
 921		{
 922			applyFilterToTab(iter->first, tab, new_filter_substring);
 923		}
 924		else
 925		{
 926			// restore tab title when filter is empty
 927			tab->setTitle(tab->getTitle());
 928
 929			//restore accordion state after all those accodrion tab manipulations
 930			tab->notifyChildren(LLSD().with("action","restore_state"));
 931
 932			// Try restoring the tab selection.
 933			restoreOutfitSelection(tab, iter->first);
 934		}
 935	}
 936
 937	mAccordion->arrange();
 938}
 939
 940void LLOutfitsList::applyFilterToTab(
 941	const LLUUID&		category_id,
 942	LLAccordionCtrlTab*	tab,
 943	const std::string&	filter_substring)
 944{
 945	if (!tab) return;
 946	LLWearableItemsList* list = dynamic_cast<LLWearableItemsList*>(tab->getAccordionView());
 947	if (!list) return;
 948
 949	std::string title = tab->getTitle();
 950	LLStringUtil::toUpper(title);
 951
 952	std::string cur_filter = filter_substring;
 953	LLStringUtil::toUpper(cur_filter);
 954
 955	tab->setTitle(tab->getTitle(), cur_filter);
 956
 957	if (std::string::npos == title.find(cur_filter))
 958	{
 959		// hide tab if its title doesn't pass filter
 960		// and it has no visible items
 961		tab->setVisible(list->hasMatchedItems());
 962
 963		// remove title highlighting because it might
 964		// have been previously highlighted by less restrictive filter
 965		tab->setTitle(tab->getTitle());
 966
 967		// Remove the tab from selection.
 968		deselectOutfit(category_id);
 969	}
 970	else
 971	{
 972		// Try restoring the tab selection.
 973		restoreOutfitSelection(tab, category_id);
 974	}
 975
 976	if (tab->getVisible())
 977	{
 978		// Open tab if it has passed the filter.
 979		tab->setDisplayChildren(true);
 980	}
 981	else
 982	{
 983		// Set force refresh flag to refresh not visible list
 984		// when some changes occur in it.
 985		list->setForceRefresh(true);
 986	}
 987}
 988
 989bool LLOutfitsList::canWearSelected()
 990{
 991	uuid_vec_t selected_items;
 992	getSelectedItemsUUIDs(selected_items);
 993
 994	for (uuid_vec_t::const_iterator it = selected_items.begin(); it != selected_items.end(); ++it)
 995	{
 996		const LLUUID& id = *it;
 997
 998		// Check whether the item is worn.
 999		if (!get_can_item_be_worn(id))
1000		{
1001			return false;
1002		}
1003	}
1004
1005	// All selected items can be worn.
1006	return true;
1007}
1008
1009void LLOutfitsList::onAccordionTabRightClick(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& cat_id)
1010{
1011	LLAccordionCtrlTab* tab = dynamic_cast<LLAccordionCtrlTab*>(ctrl);
1012	if(mOutfitMenu && is_tab_header_clicked(tab, y) && cat_id.notNull())
1013	{
1014		// Focus tab header to trigger tab selection change.
1015		LLUICtrl* header = tab->findChild<LLUICtrl>("dd_header");
1016		if (header)
1017		{
1018			header->setFocus(TRUE);
1019		}
1020
1021		uuid_vec_t selected_uuids;
1022		selected_uuids.push_back(cat_id);
1023		mOutfitMenu->show(ctrl, selected_uuids, x, y);
1024	}
1025}
1026
1027void LLOutfitsList::wearSelectedItems()
1028{
1029	uuid_vec_t selected_uuids;
1030	getSelectedItemsUUIDs(selected_uuids);
1031
1032	if(selected_uuids.empty())
1033	{
1034		return;
1035	}
1036
1037	wear_multiple(selected_uuids, false);
1038}
1039
1040void LLOutfitsList::onWearableItemsListRightClick(LLUICtrl* ctrl, S32 x, S32 y)
1041{
1042	LLWearableItemsList* list = dynamic_cast<LLWearableItemsList*>(ctrl);
1043	if (!list) return;
1044
1045	uuid_vec_t selected_uuids;
1046
1047	getSelectedItemsUUIDs(selected_uuids);
1048
1049	LLWearableItemsList::ContextMenu::instance().show(list, selected_uuids, x, y);
1050}
1051
1052void LLOutfitsList::onCOFChanged()
1053{
1054	LLInventoryModel::cat_array_t cat_array;
1055	LLInventoryModel::item_array_t item_array;
1056
1057	// Collect current COF items
1058	gInventory.collectDescendents(
1059		LLAppearanceMgr::instance().getCOF(),
1060		cat_array,
1061		item_array,
1062		LLInventoryModel::EXCLUDE_TRASH);
1063
1064	uuid_vec_t vnew;
1065	uuid_vec_t vadded;
1066	uuid_vec_t vremoved;
1067
1068	// From gInventory we get the UUIDs of links that are currently in COF.
1069	// These links UUIDs are not the same UUIDs that we have in each wearable items list.
1070	// So we collect base items' UUIDs to find them or links that point to them in wearable
1071	// items lists and update their worn state there.
1072	for (LLInventoryModel::item_array_t::const_iterator iter = item_array.begin();
1073		iter != item_array.end();
1074		++iter)
1075	{
1076		vnew.push_back((*iter)->getLinkedUUID());
1077	}
1078
1079	// We need to update only items that were added or removed from COF.
1080	LLCommonUtils::computeDifference(vnew, mCOFLinkedItems, vadded, vremoved);
1081
1082	// Store the ids of items currently linked from COF.
1083	mCOFLinkedItems = vnew;
1084
1085	for (outfits_map_t::iterator iter = mOutfitsMap.begin();
1086			iter != mOutfitsMap.end();
1087			++iter)
1088	{
1089		LLAccordionCtrlTab* tab = iter->second;
1090		if (!tab) continue;
1091
1092		LLWearableItemsList* list = dynamic_cast<LLWearableItemsList*>(tab->getAccordionView());
1093		if (!list) continue;
1094
1095		// Append removed ids to added ids because we should update all of them.
1096		vadded.reserve(vadded.size() + vremoved.size());
1097		vadded.insert(vadded.end(), vremoved.begin(), vremoved.end());
1098
1099		// Every list updates the labels of changed items  or
1100		// the links that point to these items.
1101		list->updateChangedItems(vadded);
1102	}
1103}
1104
1105bool is_tab_header_clicked(LLAccordionCtrlTab* tab, S32 y)
1106{
1107	if(!tab || !tab->getHeaderVisible()) return false;
1108
1109	S32 header_bottom = tab->getLocalRect().getHeight() - tab->getHeaderHeight();
1110	return y >= header_bottom;
1111}
1112
1113// EOF