From b15e2beabfc2987ba8d17b74014074ba313b5db7 Mon Sep 17 00:00:00 2001 From: Dmitry Zaporozhan Date: Thu, 22 Apr 2010 15:48:48 +0300 Subject: Implemented EXT-6724(normal task) - Implement flat list view of filtered inventory for "add wearables" panel of outfit editor 1. Modified LLInventoryItemsList - implemented delayed item construction. Items are added during draw, not more than 50 items will be added in single draw. This lets us reduce system overload during multiple panel construction. 2. Implemented LLFilteredWearableListManager to populate inventory flat list with data. This class filters entire inventory according to specified filter and populates specified inventory list with data. 3. Created LLCommonUtil::computeDifference() to avoid code copy-pasting(will use it in all appropriate places in next review request) Reviewed by Mike Antipov - https://codereview.productengine.com/secondlife/r/301/ --HG-- branch : product-engine --- indra/newview/llpaneloutfitedit.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'indra/newview/llpaneloutfitedit.cpp') diff --git a/indra/newview/llpaneloutfitedit.cpp b/indra/newview/llpaneloutfitedit.cpp index ce17e1d624..e139cb31d6 100644 --- a/indra/newview/llpaneloutfitedit.cpp +++ b/indra/newview/llpaneloutfitedit.cpp @@ -38,7 +38,9 @@ #include "llagent.h" #include "llagentwearables.h" #include "llappearancemgr.h" +#include "llfilteredwearablelist.h" #include "llinventory.h" +#include "llinventoryitemslist.h" #include "llviewercontrol.h" #include "llui.h" #include "llfloater.h" @@ -164,6 +166,7 @@ BOOL LLPanelOutfitEdit::postBuild() childSetCommitCallback("add_btn", boost::bind(&LLPanelOutfitEdit::showAddWearablesPanel, this), NULL); childSetCommitCallback("filter_button", boost::bind(&LLPanelOutfitEdit::showWearablesFilter, this), NULL); + childSetCommitCallback("list_view_btn", boost::bind(&LLPanelOutfitEdit::showFilteredWearablesPanel, this), NULL); mLookContents = getChild("look_items_list"); mLookContents->sortByColumn("look_item_sort", TRUE); @@ -229,6 +232,9 @@ BOOL LLPanelOutfitEdit::postBuild() save_registar.add("Outfit.SaveAsNew.Action", boost::bind(&LLPanelOutfitEdit::saveOutfit, this, true)); mSaveMenu = LLUICtrlFactory::getInstance()->createFromFile("menu_save_outfit.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + mWearableListManager = new LLFilteredWearableListManager( + getChild("filtered_wearables_list"), ALL_ITEMS_MASK); + return TRUE; } @@ -242,6 +248,11 @@ void LLPanelOutfitEdit::showWearablesFilter() childSetVisible("filter_combobox_panel", childGetValue("filter_button")); } +void LLPanelOutfitEdit::showFilteredWearablesPanel() +{ + childSetVisible("filtered_wearables_panel", !childIsVisible("filtered_wearables_panel")); +} + void LLPanelOutfitEdit::saveOutfit(bool as_new) { if (!as_new && LLAppearanceMgr::getInstance()->updateBaseOutfit()) @@ -275,6 +286,7 @@ void LLPanelOutfitEdit::onTypeFilterChanged(LLUICtrl* ctrl) { U32 curr_filter_type = type_filter->getCurrentIndex(); mInventoryItemsPanel->setFilterTypes(mLookItemTypes[curr_filter_type].inventoryMask); + mWearableListManager->setFilterMask(mLookItemTypes[curr_filter_type].inventoryMask); } mSavedFolderState->setApply(TRUE); @@ -577,4 +589,4 @@ void LLPanelOutfitEdit::displayCurrentOutfit() updateLookInfo(); } - +// EOF -- cgit v1.3 From 181315db32165d71f2e1baec66caca24cd74ced7 Mon Sep 17 00:00:00 2001 From: Igor Borovkov Date: Fri, 23 Apr 2010 14:35:24 +0300 Subject: completed EXT-6721 (Enable UI for user modification of wearable order) - added functionality to change order of wearables - added ad-hoc up and down buttons on a button bar ("up" means closer to the body) - https://jira.secondlife.com/secure/attachment/38464/screenshot-1.jpg - added displaying wearables as sorted by order on the Edit Outfit panel (top list) Reviewed by Neal Orman at https://codereview.productengine.com/secondlife/r/280/ --HG-- branch : product-engine --- indra/newview/llagentwearables.cpp | 33 ++++++++ indra/newview/llagentwearables.h | 2 + indra/newview/llappearancemgr.cpp | 93 +++++++++++++++++++++- indra/newview/llappearancemgr.h | 2 + indra/newview/llpaneloutfitedit.cpp | 36 ++++++++- indra/newview/llpaneloutfitedit.h | 4 + .../skins/default/xui/en/panel_outfit_edit.xml | 24 ++++++ 7 files changed, 189 insertions(+), 5 deletions(-) (limited to 'indra/newview/llpaneloutfitedit.cpp') diff --git a/indra/newview/llagentwearables.cpp b/indra/newview/llagentwearables.cpp index 466f2d499d..8a880e5ace 100644 --- a/indra/newview/llagentwearables.cpp +++ b/indra/newview/llagentwearables.cpp @@ -2031,6 +2031,39 @@ void LLAgentWearables::animateAllWearableParams(F32 delta, BOOL upload_bake) } } +bool LLAgentWearables::moveWearable(const LLViewerInventoryItem* item, bool closer_to_body) +{ + if (!item) return false; + if (!item->isWearableType()) return false; + + wearableentry_map_t::iterator wearable_iter = mWearableDatas.find(item->getWearableType()); + if (wearable_iter == mWearableDatas.end()) return false; + + wearableentry_vec_t& wearable_vec = wearable_iter->second; + if (wearable_vec.empty()) return false; + + const LLUUID& asset_id = item->getAssetUUID(); + + //nowhere to move if the wearable is already on any boundary (closest to the body/furthest from the body) + if (closer_to_body && asset_id == wearable_vec.front()->getAssetID()) return false; + if (!closer_to_body && asset_id == wearable_vec.back()->getAssetID()) return false; + + for (U32 i = 0; i < wearable_vec.size(); ++i) + { + LLWearable* wearable = wearable_vec[i]; + if (!wearable) continue; + if (wearable->getAssetID() != asset_id) continue; + + //swapping wearables + U32 swap_i = closer_to_body ? i-1 : i+1; + wearable_vec[i] = wearable_vec[swap_i]; + wearable_vec[swap_i] = wearable; + return true; + } + + return false; +} + void LLAgentWearables::updateServer() { sendAgentWearablesUpdate(); diff --git a/indra/newview/llagentwearables.h b/indra/newview/llagentwearables.h index 585fd3f8b3..d3b18f68f1 100644 --- a/indra/newview/llagentwearables.h +++ b/indra/newview/llagentwearables.h @@ -82,6 +82,8 @@ public: void animateAllWearableParams(F32 delta, BOOL upload_bake); + bool moveWearable(const LLViewerInventoryItem* item, bool closer_to_body); + //-------------------------------------------------------------------- // Accessors //-------------------------------------------------------------------- diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index 9e02886e4f..a6a3aa28d6 100644 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -122,6 +122,38 @@ private: bool mAppend; }; + +//Inventory callback updating "dirty" state when destroyed +class LLUpdateDirtyState: public LLInventoryCallback +{ +public: + LLUpdateDirtyState() {} + virtual ~LLUpdateDirtyState(){ LLAppearanceMgr::getInstance()->updateIsDirty(); } + virtual void fire(const LLUUID&) {} +}; + + +//Inventory collect functor collecting wearables of a specific wearable type +class LLFindClothesOfType : public LLInventoryCollectFunctor +{ +public: + LLFindClothesOfType(EWearableType type) : mWearableType(type) {} + virtual ~LLFindClothesOfType() {} + virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) + { + if (!item) return false; + if (item->getType() != LLAssetType::AT_CLOTHING) return false; + + LLViewerInventoryItem *vitem = dynamic_cast(item); + if (!vitem || vitem->getWearableType() != mWearableType) return false; + + return true; + } + + const EWearableType mWearableType; +}; + + LLUpdateAppearanceOnDestroy::LLUpdateAppearanceOnDestroy(): mFireCount(0) { @@ -1593,8 +1625,11 @@ bool LLAppearanceMgr::updateBaseOutfit() // in a Base Outfit we do not remove items, only links purgeCategory(base_outfit_id, false); + + LLPointer dirty_state_updater = new LLUpdateDirtyState(); + //COF contains only links so we copy to the Base Outfit only links - shallowCopyCategoryContents(getCOF(), base_outfit_id, NULL); + shallowCopyCategoryContents(getCOF(), base_outfit_id, dirty_state_updater); return true; } @@ -1802,6 +1837,62 @@ void LLAppearanceMgr::removeItemFromAvatar(const LLUUID& id_to_remove) } } + +bool LLAppearanceMgr::moveWearable(LLViewerInventoryItem* item, bool closer_to_body) +{ + if (!item || !item->isWearableType()) return false; + if (item->getType() != LLAssetType::AT_CLOTHING) return false; + if (!gInventory.isObjectDescendentOf(item->getUUID(), getCOF())) return false; + + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + gInventory.collectDescendentsIf(getCOF(), cats, items, true, LLFindClothesOfType(item->getWearableType())); + if (items.empty()) return false; + + //*TODO all items are not guarantied to have valid descriptions (check?) + std::sort(items.begin(), items.end(), WearablesOrderComparator(item->getWearableType())); + + if (closer_to_body && items.front() == item) return false; + if (!closer_to_body && items.back() == item) return false; + + LLInventoryModel::item_array_t::iterator it = std::find(items.begin(), items.end(), item); + if (items.end() == it) return false; + + + //swapping descriptions + closer_to_body ? --it : ++it; + LLViewerInventoryItem* swap_item = *it; + if (!swap_item) return false; + std::string tmp = swap_item->LLInventoryItem::getDescription(); + swap_item->setDescription(item->LLInventoryItem::getDescription()); + item->setDescription(tmp); + + + //items need to be updated on a dataserver + item->setComplete(TRUE); + item->updateServer(FALSE); + gInventory.updateItem(item); + + swap_item->setComplete(TRUE); + swap_item->updateServer(FALSE); + gInventory.updateItem(swap_item); + + //to cause appearance of the agent to be updated + bool result = false; + if (result = gAgentWearables.moveWearable(item, closer_to_body)) + { + gAgentAvatarp->wearableUpdated(item->getWearableType(), TRUE); + } + + setOutfitDirty(true); + + //*TODO do we need to notify observers here in such a way? + gInventory.notifyObservers(); + + return result; +} + + //#define DUMP_CAT_VERBOSE void LLAppearanceMgr::dumpCat(const LLUUID& cat_id, const std::string& msg) diff --git a/indra/newview/llappearancemgr.h b/indra/newview/llappearancemgr.h index efb5274c5b..a308a3efa9 100644 --- a/indra/newview/llappearancemgr.h +++ b/indra/newview/llappearancemgr.h @@ -141,6 +141,8 @@ public: LLUUID makeNewOutfitLinks(const std::string& new_folder_name); + bool moveWearable(LLViewerInventoryItem* item, bool closer_to_body); + protected: LLAppearanceMgr(); ~LLAppearanceMgr(); diff --git a/indra/newview/llpaneloutfitedit.cpp b/indra/newview/llpaneloutfitedit.cpp index ce17e1d624..ef6161de85 100644 --- a/indra/newview/llpaneloutfitedit.cpp +++ b/indra/newview/llpaneloutfitedit.cpp @@ -71,6 +71,9 @@ const U64 WEARABLE_MASK = (1LL << LLInventoryType::IT_WEARABLE); const U64 ATTACHMENT_MASK = (1LL << LLInventoryType::IT_ATTACHMENT) | (1LL << LLInventoryType::IT_OBJECT); const U64 ALL_ITEMS_MASK = WEARABLE_MASK | ATTACHMENT_MASK; +static const std::string SAVE_BTN("save_btn"); +static const std::string REVERT_BTN("revert_btn"); + class LLInventoryLookObserver : public LLInventoryObserver { public: @@ -218,10 +221,9 @@ BOOL LLPanelOutfitEdit::postBuild() mEditWearableBtn->setVisible(FALSE); mEditWearableBtn->setCommitCallback(boost::bind(&LLPanelOutfitEdit::onEditWearableClicked, this)); - childSetAction("revert_btn", boost::bind(&LLAppearanceMgr::wearBaseOutfit, LLAppearanceMgr::getInstance())); + childSetAction(REVERT_BTN, boost::bind(&LLAppearanceMgr::wearBaseOutfit, LLAppearanceMgr::getInstance())); - childSetAction("save_btn", boost::bind(&LLPanelOutfitEdit::saveOutfit, this, false)); - childSetAction("save_as_btn", boost::bind(&LLPanelOutfitEdit::saveOutfit, this, true)); + childSetAction(SAVE_BTN, boost::bind(&LLPanelOutfitEdit::saveOutfit, this, false)); childSetAction("save_flyout_btn", boost::bind(&LLPanelOutfitEdit::showSaveMenu, this)); LLUICtrl::CommitCallbackRegistry::ScopedRegistrar save_registar; @@ -229,9 +231,21 @@ BOOL LLPanelOutfitEdit::postBuild() save_registar.add("Outfit.SaveAsNew.Action", boost::bind(&LLPanelOutfitEdit::saveOutfit, this, true)); mSaveMenu = LLUICtrlFactory::getInstance()->createFromFile("menu_save_outfit.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + childSetAction("move_closer_btn", boost::bind(&LLPanelOutfitEdit::moveWearable, this, true)); + childSetAction("move_further_btn", boost::bind(&LLPanelOutfitEdit::moveWearable, this, false)); + return TRUE; } +void LLPanelOutfitEdit::moveWearable(bool closer_to_body) +{ + LLViewerInventoryItem* wearable_to_move = gInventory.getItem(mLookContents->getSelectionInterface()->getCurrentID()); + LLAppearanceMgr::getInstance()->moveWearable(wearable_to_move, closer_to_body); + + //*TODO why not to listen to inventory? + updateLookInfo(); +} + void LLPanelOutfitEdit::showAddWearablesPanel() { childSetVisible("add_wearables_panel", childGetValue("add_btn")); @@ -256,6 +270,8 @@ void LLPanelOutfitEdit::saveOutfit(bool as_new) { panel_outfits_inventory->onSave(); } + + //*TODO how to get to know when base outfit is updated or new outfit is created? } void LLPanelOutfitEdit::showSaveMenu() @@ -530,10 +546,12 @@ void LLPanelOutfitEdit::lookFetched(void) columns[0]["value"] = item->getName(); columns[1]["column"] = "look_item_sort"; columns[1]["type"] = "text"; // TODO: multi-wearable sort "type" should go here. - columns[1]["value"] = "BAR"; // TODO: Multi-wearable sort index should go here + columns[1]["value"] = item->LLInventoryItem::getDescription(); mLookContents->addElement(row); } + + updateVerbs(); } void LLPanelOutfitEdit::updateLookInfo() @@ -577,4 +595,14 @@ void LLPanelOutfitEdit::displayCurrentOutfit() updateLookInfo(); } +//private +void LLPanelOutfitEdit::updateVerbs() +{ + bool outfit_is_dirty = LLAppearanceMgr::getInstance()->isOutfitDirty(); + + childSetEnabled(SAVE_BTN, outfit_is_dirty); + childSetEnabled(REVERT_BTN, outfit_is_dirty); + + mSaveMenu->setItemEnabled("save_outfit", outfit_is_dirty); +} diff --git a/indra/newview/llpaneloutfitedit.h b/indra/newview/llpaneloutfitedit.h index 69e8016534..36f107b453 100644 --- a/indra/newview/llpaneloutfitedit.h +++ b/indra/newview/llpaneloutfitedit.h @@ -86,6 +86,8 @@ public: // Sends a request for data about the given parcel, which will // only update the location if there is none already available. + void moveWearable(bool closer_to_body); + void showAddWearablesPanel(); void showWearablesFilter(); void saveOutfit(bool as_new = false); @@ -108,6 +110,8 @@ public: private: + void updateVerbs(); + //*TODO got rid of mCurrentOutfitID LLUUID mCurrentOutfitID; diff --git a/indra/newview/skins/default/xui/en/panel_outfit_edit.xml b/indra/newview/skins/default/xui/en/panel_outfit_edit.xml index c77e4e8d5e..79b952237e 100644 --- a/indra/newview/skins/default/xui/en/panel_outfit_edit.xml +++ b/indra/newview/skins/default/xui/en/panel_outfit_edit.xml @@ -221,6 +221,30 @@ name="new_btn" top="1" width="31" /> +