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