/** * @file llpaneloutfitedit.cpp * @brief Displays outfit edit information in Side Tray. * * $LicenseInfo:firstyear=2009&license=viewergpl$ * * Copyright (c) 2004-2009, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at * http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" #include "llpaneloutfitedit.h" // *TODO: reorder includes to match the coding standard #include "llagent.h" #include "llagentwearables.h" #include "llappearancemgr.h" #include "llinventory.h" #include "llviewercontrol.h" #include "llui.h" #include "llfloater.h" #include "llfloaterreg.h" #include "llinventoryfunctions.h" #include "llinventorypanel.h" #include "llviewermenu.h" #include "llviewerwindow.h" #include "llviewerinventory.h" #include "llbutton.h" #include "llcombobox.h" #include "llfiltereditor.h" #include "llfloaterinventory.h" #include "llinventorybridge.h" #include "llinventorymodel.h" #include "llinventorymodelbackgroundfetch.h" #include "llpaneloutfitsinventory.h" #include "lluiconstants.h" #include "llscrolllistctrl.h" #include "lltextbox.h" #include "lluictrlfactory.h" #include "llsdutil.h" #include "llsidepanelappearance.h" #include "lltoggleablemenu.h" #include "llwearablelist.h" static LLRegisterPanelClassWrapper t_outfit_edit("panel_outfit_edit"); 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; class LLInventoryLookObserver : public LLInventoryObserver { public: LLInventoryLookObserver(LLPanelOutfitEdit *panel) : mPanel(panel) {} virtual ~LLInventoryLookObserver() { if (gInventory.containsObserver(this)) { gInventory.removeObserver(this); } } virtual void changed(U32 mask) { if (mask & (LLInventoryObserver::ADD | LLInventoryObserver::REMOVE)) { mPanel->updateLookInfo(); } } protected: LLPanelOutfitEdit *mPanel; }; class LLLookFetchObserver : public LLInventoryFetchDescendentsObserver { public: LLLookFetchObserver(LLPanelOutfitEdit *panel) : mPanel(panel) {} LLLookFetchObserver() {} virtual void done() { mPanel->lookFetched(); if(gInventory.containsObserver(this)) { gInventory.removeObserver(this); } } private: LLPanelOutfitEdit *mPanel; }; LLPanelOutfitEdit::LLPanelOutfitEdit() : LLPanel(), mCurrentOutfitID(), mFetchLook(NULL), mSearchFilter(NULL), mLookContents(NULL), mInventoryItemsPanel(NULL), mAddToOutfitBtn(NULL), mRemoveFromOutfitBtn(NULL), mLookObserver(NULL) { mSavedFolderState = new LLSaveFolderState(); mSavedFolderState->setApply(FALSE); mFetchLook = new LLLookFetchObserver(this); mLookObserver = new LLInventoryLookObserver(this); gInventory.addObserver(mLookObserver); mLookItemTypes.reserve(NUM_LOOK_ITEM_TYPES); for (U32 i = 0; i < NUM_LOOK_ITEM_TYPES; i++) { mLookItemTypes.push_back(LLLookItemType()); } // TODO: make these strings translatable mLookItemTypes[LIT_ALL] = LLLookItemType("All Items", ALL_ITEMS_MASK); mLookItemTypes[LIT_WEARABLE] = LLLookItemType("Shape & Clothing", WEARABLE_MASK); mLookItemTypes[LIT_ATTACHMENT] = LLLookItemType("Attachments", ATTACHMENT_MASK); } LLPanelOutfitEdit::~LLPanelOutfitEdit() { delete mSavedFolderState; if (gInventory.containsObserver(mFetchLook)) { gInventory.removeObserver(mFetchLook); } delete mFetchLook; if (gInventory.containsObserver(mLookObserver)) { gInventory.removeObserver(mLookObserver); } delete mLookObserver; } BOOL LLPanelOutfitEdit::postBuild() { // gInventory.isInventoryUsable() no longer needs to be tested per Richard's fix for race conditions between inventory and panels mCurrentOutfitName = getChild("curr_outfit_name"); childSetCommitCallback("add_btn", boost::bind(&LLPanelOutfitEdit::showAddWearablesPanel, this), NULL); mLookContents = getChild("look_items_list"); mLookContents->sortByColumn("look_item_sort", TRUE); mLookContents->setCommitCallback(boost::bind(&LLPanelOutfitEdit::onOutfitItemSelectionChange, this)); mInventoryItemsPanel = getChild("inventory_items"); mInventoryItemsPanel->setFilterTypes(ALL_ITEMS_MASK); mInventoryItemsPanel->setShowFolderState(LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); mInventoryItemsPanel->setSelectCallback(boost::bind(&LLPanelOutfitEdit::onInventorySelectionChange, this, _1, _2)); mInventoryItemsPanel->getRootFolder()->setReshapeCallback(boost::bind(&LLPanelOutfitEdit::onInventorySelectionChange, this, _1, _2)); LLComboBox* type_filter = getChild("inventory_filter"); type_filter->setCommitCallback(boost::bind(&LLPanelOutfitEdit::onTypeFilterChanged, this, _1)); type_filter->removeall(); for (U32 i = 0; i < mLookItemTypes.size(); ++i) { type_filter->add(mLookItemTypes[i].displayName); } type_filter->setCurrentByIndex(LIT_ALL); mSearchFilter = getChild("look_item_filter"); mSearchFilter->setCommitCallback(boost::bind(&LLPanelOutfitEdit::onSearchEdit, this, _2)); /* Removing add to look inline button (not part of mvp for viewer 2) LLButton::Params add_params; add_params.name("add_to_look"); add_params.click_callback.function(boost::bind(&LLPanelOutfitEdit::onAddToLookClicked, this)); add_params.label("+"); mAddToLookBtn = LLUICtrlFactory::create(add_params); mAddToLookBtn->setEnabled(FALSE); mAddToLookBtn->setVisible(FALSE); */ childSetAction("add_to_outfit_btn", boost::bind(&LLPanelOutfitEdit::onAddToOutfitClicked, this)); childSetEnabled("add_to_outfit_btn", false); mUpBtn = getChild("up_btn"); mUpBtn->setEnabled(TRUE); mUpBtn->setClickedCallback(boost::bind(&LLPanelOutfitEdit::onUpClicked, this)); //*TODO rename mLookContents to mOutfitContents mLookContents = getChild("look_items_list"); mLookContents->sortByColumn("look_item_sort", TRUE); mLookContents->setCommitCallback(boost::bind(&LLPanelOutfitEdit::onOutfitItemSelectionChange, this)); mRemoveFromOutfitBtn = getChild("remove_from_outfit_btn"); mRemoveFromOutfitBtn->setEnabled(FALSE); mRemoveFromOutfitBtn->setCommitCallback(boost::bind(&LLPanelOutfitEdit::onRemoveFromOutfitClicked, this)); mEditWearableBtn = getChild("edit_wearable_btn"); mEditWearableBtn->setEnabled(FALSE); mEditWearableBtn->setVisible(FALSE); mEditWearableBtn->setCommitCallback(boost::bind(&LLPanelOutfitEdit::onEditWearableClicked, this)); 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_flyout_btn", boost::bind(&LLPanelOutfitEdit::showSaveMenu, this)); LLUICtrl::CommitCallbackRegistry::ScopedRegistrar save_registar; save_registar.add("Outfit.Save.Action", boost::bind(&LLPanelOutfitEdit::saveOutfit, this, false)); 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()); return TRUE; } void LLPanelOutfitEdit::showAddWearablesPanel() { childSetVisible("add_wearables_panel", childGetValue("add_btn")); } void LLPanelOutfitEdit::saveOutfit(bool as_new) { if (!as_new && LLAppearanceMgr::getInstance()->updateBaseOutfit()) { // we don't need to ask for an outfit name, and updateBaseOutfit() successfully saved. // If updateBaseOutfit fails, ask for an outfit name anyways return; } LLPanelOutfitsInventory* panel_outfits_inventory = LLPanelOutfitsInventory::findInstance(); if (panel_outfits_inventory) { panel_outfits_inventory->onSave(); } } void LLPanelOutfitEdit::showSaveMenu() { S32 x, y; LLUI::getMousePositionLocal(this, &x, &y); mSaveMenu->updateParent(LLMenuGL::sMenuContainer); LLMenuGL::showPopup(this, mSaveMenu, x, y); } void LLPanelOutfitEdit::onTypeFilterChanged(LLUICtrl* ctrl) { LLComboBox* type_filter = dynamic_cast(ctrl); llassert(type_filter); if (type_filter) { U32 curr_filter_type = type_filter->getCurrentIndex(); mInventoryItemsPanel->setFilterTypes(mLookItemTypes[curr_filter_type].inventoryMask); } mSavedFolderState->setApply(TRUE); mInventoryItemsPanel->getRootFolder()->applyFunctorRecursively(*mSavedFolderState); LLOpenFoldersWithSelection opener; mInventoryItemsPanel->getRootFolder()->applyFunctorRecursively(opener); mInventoryItemsPanel->getRootFolder()->scrollToShowSelection(); LLInventoryModelBackgroundFetch::instance().start(); } void LLPanelOutfitEdit::onSearchEdit(const std::string& string) { if (mSearchString != string) { mSearchString = string; // Searches are case-insensitive LLStringUtil::toUpper(mSearchString); LLStringUtil::trimHead(mSearchString); } if (mSearchString == "") { mInventoryItemsPanel->setFilterSubString(LLStringUtil::null); // re-open folders that were initially open mSavedFolderState->setApply(TRUE); mInventoryItemsPanel->getRootFolder()->applyFunctorRecursively(*mSavedFolderState); LLOpenFoldersWithSelection opener; mInventoryItemsPanel->getRootFolder()->applyFunctorRecursively(opener); mInventoryItemsPanel->getRootFolder()->scrollToShowSelection(); } LLInventoryModelBackgroundFetch::instance().start(); if (mInventoryItemsPanel->getFilterSubString().empty() && mSearchString.empty()) { // current filter and new filter empty, do nothing return; } // save current folder open state if no filter currently applied if (mInventoryItemsPanel->getRootFolder()->getFilterSubString().empty()) { mSavedFolderState->setApply(FALSE); mInventoryItemsPanel->getRootFolder()->applyFunctorRecursively(*mSavedFolderState); } // set new filter string mInventoryItemsPanel->setFilterSubString(mSearchString); } void LLPanelOutfitEdit::onAddToOutfitClicked(void) { LLFolderViewItem* curr_item = mInventoryItemsPanel->getRootFolder()->getCurSelectedItem(); if (!curr_item) return; LLFolderViewEventListener* listenerp = curr_item->getListener(); if (!listenerp) return; if (LLAppearanceMgr::getInstance()->wearItemOnAvatar(listenerp->getUUID())) { updateLookInfo(); } } void LLPanelOutfitEdit::onRemoveFromOutfitClicked(void) { LLUUID id_to_remove = mLookContents->getSelectionInterface()->getCurrentID(); LLAppearanceMgr::getInstance()->removeItemFromAvatar(id_to_remove); updateLookInfo(); mRemoveFromOutfitBtn->setEnabled(FALSE); } void LLPanelOutfitEdit::onUpClicked(void) { LLUUID inv_id = mLookContents->getSelectionInterface()->getCurrentID(); if (inv_id.isNull()) { //nothing selected, do nothing return; } LLViewerInventoryItem *link_item = gInventory.getItem(inv_id); if (!link_item) { llwarns << "could not find inventory item based on currently worn link." << llendl; return; } LLUUID asset_id = link_item->getAssetUUID(); if (asset_id.isNull()) { llwarns << "inventory link has null Asset ID. could not get object reference" << llendl; } static const std::string empty = ""; LLWearableList::instance().getAsset(asset_id, empty, // don't care about wearable name link_item->getActualType(), LLSidepanelAppearance::editWearable, (void*)getParentUICtrl()); } void LLPanelOutfitEdit::onEditWearableClicked(void) { LLUUID id_to_edit = mLookContents->getSelectionInterface()->getCurrentID(); LLViewerInventoryItem * item_to_edit = gInventory.getItem(id_to_edit); if (item_to_edit) { // returns null if not a wearable (attachment, etc). LLWearable* wearable_to_edit = gAgentWearables.getWearableFromAssetID(item_to_edit->getAssetUUID()); if(wearable_to_edit) { bool can_modify = false; bool is_complete = item_to_edit->isComplete(); // if item_to_edit is a link, its properties are not appropriate, // lets get original item with actual properties LLViewerInventoryItem* original_item = gInventory.getItem(wearable_to_edit->getItemID()); if(original_item) { can_modify = original_item->getPermissions().allowModifyBy(gAgentID); is_complete = original_item->isComplete(); } if (can_modify && is_complete) { LLSidepanelAppearance::editWearable(wearable_to_edit, getParent()); if (mEditWearableBtn->getVisible()) { mEditWearableBtn->setVisible(FALSE); } } } } } void LLPanelOutfitEdit::onInventorySelectionChange(const std::deque &items, BOOL user_action) { LLFolderViewItem* current_item = mInventoryItemsPanel->getRootFolder()->getCurSelectedItem(); if (!current_item) { return; } LLViewerInventoryItem* item = current_item->getInventoryItem(); if (!item) return; switch (item->getType()) { case LLAssetType::AT_CLOTHING: case LLAssetType::AT_BODYPART: case LLAssetType::AT_OBJECT: childSetEnabled("add_to_outfit_btn", true); break; default: childSetEnabled("add_to_outfit_btn", false); break; } /* Removing add to look inline button (not part of mvp for viewer 2) LLRect btn_rect(current_item->getLocalRect().mRight - 50, current_item->getLocalRect().mTop, current_item->getLocalRect().mRight - 30, current_item->getLocalRect().mBottom); mAddToLookBtn->setRect(btn_rect); mAddToLookBtn->setEnabled(TRUE); if (!mAddToLookBtn->getVisible()) { mAddToLookBtn->setVisible(TRUE); } current_item->addChild(mAddToLookBtn); */ } void LLPanelOutfitEdit::onOutfitItemSelectionChange(void) { S32 left_offset = -4; S32 top_offset = -10; LLScrollListItem* item = mLookContents->getLastSelectedItem(); if (!item) return; LLRect rect = item->getRect(); LLRect btn_rect( left_offset + rect.mRight - 50, top_offset + rect.mTop, left_offset + rect.mRight - 30, top_offset + rect.mBottom); mEditWearableBtn->setRect(btn_rect); mEditWearableBtn->setEnabled(TRUE); if (!mEditWearableBtn->getVisible()) { mEditWearableBtn->setVisible(TRUE); } const LLUUID& id_item_to_remove = item->getUUID(); LLViewerInventoryItem* item_to_remove = gInventory.getItem(id_item_to_remove); if (!item_to_remove) return; switch (item_to_remove->getType()) { case LLAssetType::AT_CLOTHING: case LLAssetType::AT_OBJECT: mRemoveFromOutfitBtn->setEnabled(TRUE); break; default: mRemoveFromOutfitBtn->setEnabled(FALSE); break; } } void LLPanelOutfitEdit::changed(U32 mask) { } void LLPanelOutfitEdit::lookFetched(void) { LLInventoryModel::cat_array_t cat_array; LLInventoryModel::item_array_t item_array; // collectDescendentsIf takes non-const reference: LLFindCOFValidItems is_cof_valid; gInventory.collectDescendentsIf(mCurrentOutfitID, cat_array, item_array, LLInventoryModel::EXCLUDE_TRASH, is_cof_valid); for (LLInventoryModel::item_array_t::const_iterator iter = item_array.begin(); iter != item_array.end(); iter++) { const LLViewerInventoryItem *item = (*iter); LLSD row; row["id"] = item->getUUID(); LLSD& columns = row["columns"]; columns[0]["column"] = "look_item"; columns[0]["type"] = "text"; 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 mLookContents->addElement(row); } } void LLPanelOutfitEdit::updateLookInfo() { if (getVisible()) { mLookContents->clearRows(); uuid_vec_t folders; folders.push_back(mCurrentOutfitID); mFetchLook->fetch(folders); if (mFetchLook->isEverythingComplete()) { mFetchLook->done(); } else { gInventory.addObserver(mFetchLook); } } } void LLPanelOutfitEdit::displayCurrentOutfit() { if (!getVisible()) { setVisible(TRUE); } mCurrentOutfitID = LLAppearanceMgr::getInstance()->getCOF(); std::string current_outfit_name; if (LLAppearanceMgr::getInstance()->getBaseOutfitName(current_outfit_name)) { mCurrentOutfitName->setText(current_outfit_name); } else { mCurrentOutfitName->setText(getString("No Outfit")); } updateLookInfo(); }