/** * @file llsidepanelappearance.cpp * @brief Side Bar "Appearance" panel * * $LicenseInfo:firstyear=2009&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" #include "llsidepanelappearance.h" #include "llaccordionctrltab.h" #include "llagent.h" #include "llagentcamera.h" #include "llagentwearables.h" #include "llappearancemgr.h" #include "llfloatersidepanelcontainer.h" #include "llfolderview.h" #include "llinventorypanel.h" #include "llfiltereditor.h" #include "llfloaterreg.h" #include "llfloaterworldmap.h" #include "llfolderviewmodel.h" #include "lloutfitobserver.h" #include "llpaneleditwearable.h" #include "llpaneloutfitsinventory.h" #include "lltextbox.h" #include "lluictrlfactory.h" #include "llviewercontrol.h" #include "llviewerregion.h" #include "llvoavatarself.h" #include "llviewerwearable.h" #include "llnotificationsutil.h" #include "llfloatersidepanelcontainer.h" #include "llviewerfoldertype.h" static LLRegisterPanelClassWrapper t_appearance("sidepanel_appearance"); class LLCurrentlyWornFetchObserver : public LLInventoryFetchItemsObserver { public: LLCurrentlyWornFetchObserver(const uuid_vec_t &ids, LLSidepanelAppearance *panel) : LLInventoryFetchItemsObserver(ids), mPanel(panel) {} ~LLCurrentlyWornFetchObserver() {} virtual void done() { mPanel->inventoryFetched(); gInventory.removeObserver(this); delete this; } private: LLSidepanelAppearance *mPanel; }; bool LLSidepanelAppearance::callBackExitWithoutSaveViaBack(const LLSD& notification, const LLSD& response) { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); if ( option == 0 ) { LLAppearanceMgr::instance().setOutfitDirty( true ); showOutfitsInventoryPanel(); LLAppearanceMgr::getInstance()->wearBaseOutfit(); return true; } return false; } void LLSidepanelAppearance::onCloseFromAppearance(LLFloaterSidePanelContainer* obj) { mLLFloaterSidePanelContainer = obj; if ( mEditWearable->isAvailable() && mEditWearable->isDirty() ) { LLSidepanelAppearance* pSelf = (LLSidepanelAppearance *)this; LLNotificationsUtil::add("ConfirmExitWithoutSave", LLSD(), LLSD(), boost::bind(&LLSidepanelAppearance::callBackExitWithoutSaveViaClose,pSelf,_1,_2) ); } else { LLVOAvatarSelf::onCustomizeEnd(FALSE); toggleWearableEditPanel(FALSE); mLLFloaterSidePanelContainer->mForceCloseAfterVerify=false; } } bool LLSidepanelAppearance::onSaveCommit(const LLSD& notification, const LLSD& response) { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); if (0 == option) { std::string outfit_name = response["message"].asString(); LLStringUtil::trim(outfit_name); std::string current_outfit_name; LLAppearanceMgr::getInstance()->getBaseOutfitName(current_outfit_name); if ( current_outfit_name == outfit_name ) { LLAppearanceMgr::getInstance()->updateBaseOutfit(); } else { LLUUID outfit_folder = LLAppearanceMgr::getInstance()->makeNewOutfitLinks( outfit_name,FALSE ); } LLVOAvatarSelf::onCustomizeEnd( FALSE ); mLLFloaterSidePanelContainer->close(); } return false; } bool LLSidepanelAppearance::callBackExitWithoutSaveViaClose(const LLSD& notification, const LLSD& response) { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); if ( option == 0 ) { std::string outfit_name; if (!LLAppearanceMgr::getInstance()->getBaseOutfitName(outfit_name)) { outfit_name = LLViewerFolderType::lookupNewCategoryName(LLFolderType::FT_OUTFIT); } LLSD args; args["DESC"] = outfit_name; LLSD payload; LLNotificationsUtil::add("SaveOutfitEither", args, payload, boost::bind(&LLSidepanelAppearance::onSaveCommit, this, _1, _2)); showOutfitEditPanel(); return false; } else if ( option == 1 ) { mEditWearable->revertChanges(); toggleWearableEditPanel(FALSE); showOutfitEditPanel(); LLVOAvatarSelf::onCustomizeEnd( FALSE ); if ( !mLLFloaterSidePanelContainer->mAppQuiting ) { mRevertSet = true; } else { mLLFloaterSidePanelContainer->closeFloater( true ); } return false; } mLLFloaterSidePanelContainer->mForceCloseAfterVerify = false; return false; } void LLSidepanelAppearance::onClickConfirmExitWithoutSaveIntoAppearance( LLFloaterSidePanelContainer* obj ) { mLLFloaterSidePanelContainer = obj; if ( LLAppearanceMgr::getInstance()->isOutfitDirty() || mEditWearable->isDirty() ) { LLSidepanelAppearance* pSelf = (LLSidepanelAppearance *)this; LLNotificationsUtil::add("ConfirmExitWithoutSave", LLSD(), LLSD(), boost::bind(&LLSidepanelAppearance::callBackExitWithoutSaveViaClose,pSelf,_1,_2) ); } else { showOutfitsInventoryPanel(); } } void LLSidepanelAppearance::onClickConfirmExitWithoutSaveViaBack() { showOutfitsInventoryPanel(); } void LLSidepanelAppearance::onClose(LLFloaterSidePanelContainer* obj) { mLLFloaterSidePanelContainer = obj; if ( mEditWearable->isAvailable() && mEditWearable->isDirty() ) { LLSidepanelAppearance* pSelf = (LLSidepanelAppearance *)this; LLNotificationsUtil::add("ConfirmExitWithoutSave", LLSD(), LLSD(), boost::bind(&LLSidepanelAppearance::callBackExitWithoutSaveViaClose,pSelf,_1,_2) ); } else { LLVOAvatarSelf::onCustomizeEnd(FALSE); mLLFloaterSidePanelContainer->close(); } } LLSidepanelAppearance::LLSidepanelAppearance() : LLPanel(), mFilterSubString(LLStringUtil::null), mFilterEditor(NULL), mOutfitEdit(NULL), mCurrOutfitPanel(NULL), mOpened(false), mSidePanelJustOpened(true), mRevertSet(false) { LLOutfitObserver& outfit_observer = LLOutfitObserver::instance(); outfit_observer.addBOFReplacedCallback(boost::bind(&LLSidepanelAppearance::refreshCurrentOutfitName, this, "")); outfit_observer.addBOFChangedCallback(boost::bind(&LLSidepanelAppearance::refreshCurrentOutfitName, this, "")); outfit_observer.addCOFChangedCallback(boost::bind(&LLSidepanelAppearance::refreshCurrentOutfitName, this, "")); gAgentWearables.addLoadingStartedCallback(boost::bind(&LLSidepanelAppearance::setWearablesLoading, this, true)); gAgentWearables.addLoadedCallback(boost::bind(&LLSidepanelAppearance::setWearablesLoading, this, false)); } LLSidepanelAppearance::~LLSidepanelAppearance() { } // virtual BOOL LLSidepanelAppearance::postBuild() { mOpenOutfitBtn = getChild("openoutfit_btn"); mOpenOutfitBtn->setClickedCallback(boost::bind(&LLSidepanelAppearance::onOpenOutfitButtonClicked, this)); mEditAppearanceBtn = getChild("editappearance_btn"); mEditAppearanceBtn->setClickedCallback(boost::bind(&LLSidepanelAppearance::onEditAppearanceButtonClicked, this)); childSetAction("edit_outfit_btn", boost::bind(&LLSidepanelAppearance::showOutfitEditPanel, this)); mNewOutfitBtn = getChild("newlook_btn"); mNewOutfitBtn->setClickedCallback(boost::bind(&LLSidepanelAppearance::onNewOutfitButtonClicked, this)); mNewOutfitBtn->setEnabled(false); mFilterEditor = getChild("Filter"); if (mFilterEditor) { mFilterEditor->setCommitCallback(boost::bind(&LLSidepanelAppearance::onFilterEdit, this, _2)); } mPanelOutfitsInventory = dynamic_cast(getChild("panel_outfits_inventory")); mOutfitEdit = dynamic_cast(getChild("panel_outfit_edit")); if (mOutfitEdit) { LLButton* back_btn = mOutfitEdit->getChild("back_btn"); if (back_btn) { back_btn->setClickedCallback(boost::bind(&LLSidepanelAppearance::onClickConfirmExitWithoutSaveViaBack, this)); } } mEditWearable = dynamic_cast(getChild("panel_edit_wearable")); if (mEditWearable) { LLButton* edit_wearable_back_btn = mEditWearable->getChild("back_btn"); if (edit_wearable_back_btn) { edit_wearable_back_btn->setClickedCallback(boost::bind(&LLSidepanelAppearance::showOutfitEditPanel, this)); } } mCurrentLookName = getChild("currentlook_name"); mOutfitStatus = getChild("currentlook_status"); mCurrOutfitPanel = getChild("panel_currentlook"); setVisibleCallback(boost::bind(&LLSidepanelAppearance::onVisibilityChange,this,_2)); return TRUE; } // virtual void LLSidepanelAppearance::onOpen(const LLSD& key) { if (!key.has("type")) { // No specific panel requested. // If we're opened for the first time then show My Outfits. // Else show outfit edit panel if (!mOpened) { showOutfitsInventoryPanel(); } else { showOutfitEditPanel(); } } else { // Switch to the requested panel. std::string type = key["type"].asString(); if (type == "my_outfits") { showOutfitsInventoryPanel(); } else if (type == "edit_outfit") { showOutfitEditPanel(); } else if (type == "edit_shape") { showWearableEditPanel(); } } mOpened = true; } void LLSidepanelAppearance::onVisibilityChange(const LLSD &new_visibility) { //handle leaving and subsequent user verification of discarding any unsaved data if ( mSidePanelJustOpened ) { mSidePanelJustOpened = false; } LLSD visibility; visibility["visible"] = new_visibility.asBoolean(); visibility["reset_accordion"] = false; updateToVisibility(visibility); } void LLSidepanelAppearance::updateToVisibility(const LLSD &new_visibility) { if (new_visibility["visible"].asBoolean() ) { const BOOL is_outfit_edit_visible = mOutfitEdit && mOutfitEdit->getVisible(); const BOOL is_wearable_edit_visible = mEditWearable && mEditWearable->getVisible(); if (is_outfit_edit_visible || is_wearable_edit_visible) { const LLViewerWearable *wearable_ptr = mEditWearable->getWearable(); if (!wearable_ptr) { llwarns << "Visibility change to invalid wearable" << llendl; return; } // Disable camera switch is currently just for WT_PHYSICS type since we don't want to freeze the avatar // when editing its physics. if (!gAgentCamera.cameraCustomizeAvatar()) { LLVOAvatarSelf::onCustomizeStart(LLWearableType::getDisableCameraSwitch(wearable_ptr->getType())); } if (is_wearable_edit_visible) { if (gAgentWearables.getWearableIndex(wearable_ptr) == LLAgentWearables::MAX_CLOTHING_PER_TYPE) { // we're no longer wearing the wearable we were last editing, switch back to outfit editor showOutfitEditPanel(); } } if (is_outfit_edit_visible && new_visibility["reset_accordion"].asBoolean()) { mOutfitEdit->resetAccordionState(); } } } else { if (gAgentCamera.cameraCustomizeAvatar() && gSavedSettings.getBOOL("AppearanceCameraMovement")) { gAgentCamera.changeCameraToDefault(); gAgentCamera.resetView(); } } } void LLSidepanelAppearance::onFilterEdit(const std::string& search_string) { if (mFilterSubString != search_string) { mFilterSubString = search_string; // Searches are case-insensitive // but we don't convert the typed string to upper-case so that it can be fed to the web search as-is. mPanelOutfitsInventory->onSearchEdit(mFilterSubString); } } void LLSidepanelAppearance::onOpenOutfitButtonClicked() { const LLViewerInventoryItem *outfit_link = LLAppearanceMgr::getInstance()->getBaseOutfitLink(); if (!outfit_link) return; if (!outfit_link->getIsLinkType()) return; LLAccordionCtrlTab* tab_outfits = mPanelOutfitsInventory->findChild("tab_outfits"); if (tab_outfits) { tab_outfits->changeOpenClose(FALSE); LLInventoryPanel *inventory_panel = tab_outfits->findChild("outfitslist_tab"); if (inventory_panel) { LLFolderView* root = inventory_panel->getRootFolder(); LLFolderViewItem *outfit_folder = inventory_panel->getItemByID(outfit_link->getLinkedUUID()); if (outfit_folder) { outfit_folder->setOpen(!outfit_folder->isOpen()); root->setSelection(outfit_folder,TRUE); root->scrollToShowSelection(); } } } } // *TODO: obsolete? void LLSidepanelAppearance::onEditAppearanceButtonClicked() { if (gAgentWearables.areWearablesLoaded()) { LLVOAvatarSelf::onCustomizeStart(); } } void LLSidepanelAppearance::onNewOutfitButtonClicked() { if (!mOutfitEdit->getVisible()) { mPanelOutfitsInventory->onSave(); } } void LLSidepanelAppearance::showOutfitsInventoryPanel() { toggleWearableEditPanel(FALSE); toggleOutfitEditPanel(FALSE); toggleMyOutfitsPanel(TRUE); } void LLSidepanelAppearance::showOutfitEditPanel() { if (mOutfitEdit && mOutfitEdit->getVisible()) return; // Accordion's state must be reset in all cases except the one when user // is returning back to the mOutfitEdit panel from the mEditWearable panel. // The simplest way to control this is to check the visibility state of the mEditWearable // BEFORE it is changed by the call to the toggleWearableEditPanel(FALSE, NULL, TRUE). if (mEditWearable != NULL && !mEditWearable->getVisible() && mOutfitEdit != NULL) { mOutfitEdit->resetAccordionState(); } // If we're exiting the edit wearable view, and the camera was not focused on the avatar // (e.g. such as if we were editing a physics param), then skip the outfits edit mode since // otherwise this would trigger the camera focus mode. if (mEditWearable != NULL && mEditWearable->getVisible() && !gAgentCamera.cameraCustomizeAvatar()) { showOutfitsInventoryPanel(); return; } toggleMyOutfitsPanel(FALSE); toggleWearableEditPanel(FALSE, NULL, TRUE); // don't switch out of edit appearance mode toggleOutfitEditPanel(TRUE); } void LLSidepanelAppearance::showWearableEditPanel(LLViewerWearable *wearable /* = NULL*/, BOOL disable_camera_switch) { toggleMyOutfitsPanel(FALSE); toggleOutfitEditPanel(FALSE, TRUE); // don't switch out of edit appearance mode toggleWearableEditPanel(TRUE, wearable, disable_camera_switch); } void LLSidepanelAppearance::toggleMyOutfitsPanel(BOOL visible) { if (!mPanelOutfitsInventory || mPanelOutfitsInventory->getVisible() == visible) { // visibility isn't changing, hence nothing to do return; } mPanelOutfitsInventory->setVisible(visible); // *TODO: Move these controls to panel_outfits_inventory.xml // so that we don't need to toggle them explicitly. mFilterEditor->setVisible(visible); mNewOutfitBtn->setVisible(visible); mCurrOutfitPanel->setVisible(visible); if (visible) { mPanelOutfitsInventory->onOpen(LLSD()); } } void LLSidepanelAppearance::toggleOutfitEditPanel(BOOL visible, BOOL disable_camera_switch) { if (!mOutfitEdit || mOutfitEdit->getVisible() == visible) { // visibility isn't changing, hence nothing to do return; } mOutfitEdit->setVisible(visible); if (visible) { mOutfitEdit->onOpen(LLSD()); LLVOAvatarSelf::onCustomizeStart(disable_camera_switch); } else { if (!disable_camera_switch) // if we're just switching between outfit and wearable editing, don't end customization. { LLVOAvatarSelf::onCustomizeEnd(disable_camera_switch); LLAppearanceMgr::getInstance()->updateIsDirty(); } } } void LLSidepanelAppearance::toggleWearableEditPanel(BOOL visible, LLViewerWearable *wearable, BOOL disable_camera_switch) { if (!mEditWearable || mEditWearable->getVisible() == visible) { // visibility isn't changing, hence nothing to do return; } if (!wearable) { wearable = gAgentWearables.getViewerWearable(LLWearableType::WT_SHAPE, 0); } if (!wearable) { return; } // Toggle panel visibility. mEditWearable->setVisible(visible); if (visible) { LLVOAvatarSelf::onCustomizeStart(disable_camera_switch); mEditWearable->setWearable(wearable, disable_camera_switch); mEditWearable->onOpen(LLSD()); // currently no-op, just for consistency } else { // Save changes if closing. mEditWearable->saveChanges(); LLAppearanceMgr::getInstance()->updateIsDirty(); if (!disable_camera_switch) // if we're just switching between outfit and wearable editing, don't end customization. { LLVOAvatarSelf::onCustomizeEnd(disable_camera_switch); } } } void LLSidepanelAppearance::refreshCurrentOutfitName(const std::string& name) { // Set current outfit status (wearing/unsaved). bool dirty = LLAppearanceMgr::getInstance()->isOutfitDirty(); std::string cof_status_str = getString(dirty ? "Unsaved Changes" : "Now Wearing"); mOutfitStatus->setText(cof_status_str); if (name == "") { std::string outfit_name; if (LLAppearanceMgr::getInstance()->getBaseOutfitName(outfit_name)) { mCurrentLookName->setText(outfit_name); return; } std::string string_name = gAgentWearables.isCOFChangeInProgress() ? "Changing outfits" : "No Outfit"; mCurrentLookName->setText(getString(string_name)); mOpenOutfitBtn->setEnabled(FALSE); } else { mCurrentLookName->setText(name); // Can't just call update verbs since the folder link may not have been created yet. mOpenOutfitBtn->setEnabled(TRUE); } } //static void LLSidepanelAppearance::editWearable(LLViewerWearable *wearable, LLView *data, BOOL disable_camera_switch) { LLFloaterSidePanelContainer::showPanel("appearance", LLSD()); LLSidepanelAppearance *panel = dynamic_cast(data); if (panel) { panel->showWearableEditPanel(wearable, disable_camera_switch); } } // Fetch currently worn items and only enable the New Look button after everything's been // fetched. Alternatively, we could stuff this logic into llagentwearables::makeNewOutfitLinks. void LLSidepanelAppearance::fetchInventory() { mNewOutfitBtn->setEnabled(false); uuid_vec_t ids; LLUUID item_id; for(S32 type = (S32)LLWearableType::WT_SHAPE; type < (S32)LLWearableType::WT_COUNT; ++type) { for (U32 index = 0; index < gAgentWearables.getWearableCount((LLWearableType::EType)type); ++index) { item_id = gAgentWearables.getWearableItemID((LLWearableType::EType)type, index); if(item_id.notNull()) { ids.push_back(item_id); } } } if (isAgentAvatarValid()) { for (LLVOAvatar::attachment_map_t::const_iterator iter = gAgentAvatarp->mAttachmentPoints.begin(); iter != gAgentAvatarp->mAttachmentPoints.end(); ++iter) { LLViewerJointAttachment* attachment = iter->second; if (!attachment) continue; for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); attachment_iter != attachment->mAttachedObjects.end(); ++attachment_iter) { LLViewerObject* attached_object = (*attachment_iter); if (!attached_object) continue; const LLUUID& item_id = attached_object->getAttachmentItemID(); if (item_id.isNull()) continue; ids.push_back(item_id); } } } LLCurrentlyWornFetchObserver *fetch_worn = new LLCurrentlyWornFetchObserver(ids, this); fetch_worn->startFetch(); // If no items to be fetched, done will never be triggered. // TODO: Change LLInventoryFetchItemsObserver::fetchItems to trigger done() on this condition. if (fetch_worn->isFinished()) { fetch_worn->done(); } else { gInventory.addObserver(fetch_worn); } } void LLSidepanelAppearance::inventoryFetched() { mNewOutfitBtn->setEnabled(true); } void LLSidepanelAppearance::setWearablesLoading(bool val) { getChildView("wearables_loading_indicator")->setVisible( val); getChildView("edit_outfit_btn")->setVisible( !val); if (!val) { // refresh outfit name when COF is already changed. refreshCurrentOutfitName(); } } void LLSidepanelAppearance::showDefaultSubpart() { if (mEditWearable->getVisible()) { mEditWearable->showDefaultSubpart(); } } void LLSidepanelAppearance::updateScrollingPanelList() { if (mEditWearable->getVisible()) { mEditWearable->updateScrollingPanelList(); } } bool LLSidepanelAppearance::checkForDirtyEdits() { return ( mEditWearable->isDirty() ) ? true : false; }