/** * @file llinventoryview.cpp * @brief Implementation of the inventory view and associated stuff. * * $LicenseInfo:firstyear=2001&license=viewergpl$ * * Copyright (c) 2001-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 "llinventorypanel.h" #include // for std::pair<> #include "llagent.h" #include "llagentwearables.h" #include "lluictrlfactory.h" #include "llfloatercustomize.h" #include "llfolderview.h" #include "llimview.h" #include "llinventorybridge.h" #include "llinventoryfunctions.h" #include "llinventorymodelbackgroundfetch.h" #include "llviewerfoldertype.h" #include "llvoavatarself.h" #include "llscrollcontainer.h" #include "llviewerassettype.h" #include "llsdserialize.h" // [RLVa:KB] #include "rlvhandler.h" // [/RLVa:KB] static LLRegisterWidget r("inventory_panel"); ///---------------------------------------------------------------------------- /// Local function declarations, constants, enums, and typedefs ///---------------------------------------------------------------------------- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLInventoryPanelObserver // // Bridge to support knowing when the inventory has changed. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ class LLInventoryPanelObserver : public LLInventoryObserver { public: LLInventoryPanelObserver(LLInventoryPanel* ip) : mIP(ip) {} virtual ~LLInventoryPanelObserver() {} virtual void changed(U32 mask) { mIP->modelChanged(mask); } protected: LLInventoryPanel* mIP; }; const std::string LLInventoryPanel::DEFAULT_SORT_ORDER = std::string("InventorySortOrder"); const std::string LLInventoryPanel::RECENTITEMS_SORT_ORDER = std::string("RecentItemsSortOrder"); const std::string LLInventoryPanel::WORNITEMS_SORT_ORDER = std::string("WornItemsSortOrder"); const std::string LLInventoryPanel::INHERIT_SORT_ORDER = std::string(""); LLInventoryPanel::LLInventoryPanel(const std::string& name, const std::string& sort_order_setting, const LLRect& rect, LLInventoryModel* inventory, BOOL allow_multi_select, LLView *parent_view) : LLPanel(name, rect, TRUE), mInventory(inventory), mInventoryObserver(NULL), mFolderRoot(NULL), mScroller(NULL), mAllowMultiSelect(allow_multi_select), mSortOrderSetting(sort_order_setting) { setBackgroundColor(gColors.getColor("InventoryBackgroundColor")); setBackgroundVisible(TRUE); setBackgroundOpaque(TRUE); } BOOL LLInventoryPanel::postBuild() { init_inventory_panel_actions(this); LLRect folder_rect(0, 0, getRect().getWidth(), 0); mFolderRoot = new LLFolderView(getName(), NULL, folder_rect, LLUUID::null, this); mFolderRoot->setAllowMultiSelect(mAllowMultiSelect); // scroller LLRect scroller_view_rect = getRect(); scroller_view_rect.translate(-scroller_view_rect.mLeft, -scroller_view_rect.mBottom); mScroller = new LLScrollableContainerView(std::string("Inventory Scroller"), scroller_view_rect, mFolderRoot); mScroller->setFollowsAll(); mScroller->setReserveScrollCorner(TRUE); addChild(mScroller); mFolderRoot->setScrollContainer(mScroller); // set up the callbacks from the inventory we're viewing, and then // build everything. mInventoryObserver = new LLInventoryPanelObserver(this); mInventory->addObserver(mInventoryObserver); rebuildViewsFor(LLUUID::null, LLInventoryObserver::ADD); // bit of a hack to make sure the inventory is open. mFolderRoot->openFolder(std::string("My Inventory")); if (mSortOrderSetting != INHERIT_SORT_ORDER) { setSortOrder(gSavedSettings.getU32(mSortOrderSetting)); } else { setSortOrder(gSavedSettings.getU32(DEFAULT_SORT_ORDER)); } mFolderRoot->setSortOrder(mFolderRoot->getFilter()->getSortOrder()); return TRUE; } LLInventoryPanel::~LLInventoryPanel() { if (mFolderRoot) { U32 sort_order = mFolderRoot->getSortOrder(); if (mSortOrderSetting != INHERIT_SORT_ORDER) { gSavedSettings.setU32(mSortOrderSetting, sort_order); } } // LLView destructor will take care of the sub-views. mInventory->removeObserver(mInventoryObserver); delete mInventoryObserver; mScroller = NULL; } // virtual LLXMLNodePtr LLInventoryPanel::getXML(bool save_children) const { LLXMLNodePtr node = LLPanel::getXML(false); // Do not print out children node->setName(LL_INVENTORY_PANEL_TAG); node->createChild("allow_multi_select", TRUE)->setBoolValue(mFolderRoot->getAllowMultiSelect()); return node; } LLView* LLInventoryPanel::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) { LLInventoryPanel* panel; std::string name("inventory_panel"); node->getAttributeString("name", name); BOOL allow_multi_select = TRUE; node->getAttributeBOOL("allow_multi_select", allow_multi_select); LLRect rect; createRect(node, rect, parent, LLRect()); std::string sort_order(INHERIT_SORT_ORDER); node->getAttributeString("sort_order", sort_order); panel = new LLInventoryPanel(name, sort_order, rect, &gInventory, allow_multi_select, parent); panel->initFromXML(node, parent); panel->postBuild(); return panel; } void LLInventoryPanel::draw() { // select the desired item (in case it wasn't loaded when the selection was requested) if (mSelectThisID.notNull()) { setSelection(mSelectThisID, false); } LLPanel::draw(); } LLInventoryFilter* LLInventoryPanel::getFilter() { if (mFolderRoot) { return mFolderRoot->getFilter(); } return NULL; } const LLInventoryFilter* LLInventoryPanel::getFilter() const { if (mFolderRoot) { return mFolderRoot->getFilter(); } return NULL; } void LLInventoryPanel::setFilterTypes(U32 filter_types) { getFilter()->setFilterTypes(filter_types); } U32 LLInventoryPanel::getFilterTypes() const { return mFolderRoot->getFilterTypes(); } U32 LLInventoryPanel::getFilterPermMask() const { return mFolderRoot->getFilterPermissions(); } void LLInventoryPanel::setFilterPermMask(PermissionMask filter_perm_mask) { getFilter()->setFilterPermissions(filter_perm_mask); } void LLInventoryPanel::setFilterWorn(bool worn) { getFilter()->setFilterWorn(worn); } void LLInventoryPanel::setFilterSubString(const std::string& string) { getFilter()->setFilterSubString(string); } const std::string LLInventoryPanel::getFilterSubString() { return mFolderRoot->getFilterSubString(); } void LLInventoryPanel::setSortOrder(U32 order) { getFilter()->setSortOrder(order); if (getFilter()->isModified()) { mFolderRoot->setSortOrder(order); // try to keep selection onscreen, even if it wasn't to start with mFolderRoot->scrollToShowSelection(); } } U32 LLInventoryPanel::getSortOrder() const { return mFolderRoot->getSortOrder(); } void LLInventoryPanel::setSinceLogoff(BOOL sl) { getFilter()->setDateRangeLastLogoff(sl); } void LLInventoryPanel::setHoursAgo(U32 hours) { getFilter()->setHoursAgo(hours); } void LLInventoryPanel::setShowFolderState(LLInventoryFilter::EFolderShow show) { getFilter()->setShowFolderState(show); } LLInventoryFilter::EFolderShow LLInventoryPanel::getShowFolderState() { return getFilter()->getShowFolderState(); } void LLInventoryPanel::modelChanged(U32 mask) { static LLFastTimer::DeclareTimer FTM_REFRESH("Inventory Refresh"); LLFastTimer t2(FTM_REFRESH); bool handled = false; //if (!mViewsInitialized) return; const LLInventoryModel* model = getModel(); if (!model) return; const LLInventoryModel::changed_items_t& changed_items = model->getChangedIDs(); if (changed_items.empty()) return; for (LLInventoryModel::changed_items_t::const_iterator items_iter = changed_items.begin(); items_iter != changed_items.end(); ++items_iter) { const LLUUID& item_id = (*items_iter); const LLInventoryObject* model_item = model->getObject(item_id); LLFolderViewItem* view_item = mFolderRoot->getItemByID(item_id); // LLFolderViewFolder is derived from LLFolderViewItem so dynamic_cast from item // to folder is the fast way to get a folder without searching through folders tree. //LLFolderViewFolder* view_folder = dynamic_cast(view_item); ////////////////////////////// // LABEL Operation // Empty out the display name for relabel. if (mask & LLInventoryObserver::LABEL) { handled = true; if (view_item) { // Request refresh on this item (also flags for filtering) LLInvFVBridge* bridge = (LLInvFVBridge*)view_item->getListener(); if(bridge) { // Clear the display name first, so it gets properly re-built during refresh() bridge->clearDisplayName(); view_item->refresh(); } } } ////////////////////////////// // REBUILD Operation // Destroy and regenerate the UI. /*if (mask & LLInventoryObserver::REBUILD) { handled = true; if (model_item && view_item) { view_item->destroyView(); } view_item = buildNewViews(item_id); view_folder = dynamic_cast(view_item); }*/ ////////////////////////////// // INTERNAL Operation // This could be anything. For now, just refresh the item. if (mask & LLInventoryObserver::INTERNAL) { if (view_item) { view_item->refresh(); } } ////////////////////////////// // SORT Operation // Sort the folder. /*if (mask & LLInventoryObserver::SORT) { if (view_folder) { view_folder->requestSort(); } }*/ // We don't typically care which of these masks the item is actually flagged with, since the masks // may not be accurate (e.g. in the main inventory panel, I move an item from My Inventory into // Landmarks; this is a STRUCTURE change for that panel but is an ADD change for the Landmarks // panel). What's relevant is that the item and UI are probably out of sync and thus need to be // resynchronized. if (mask & (LLInventoryObserver::STRUCTURE | LLInventoryObserver::ADD | LLInventoryObserver::REMOVE)) { handled = true; ////////////////////////////// // ADD Operation // Item exists in memory but a UI element hasn't been created for it. if (model_item && !view_item) { // Add the UI element for this item. buildNewViews(item_id); // Select any newly created object that has the auto rename at top of folder root set. if(mFolderRoot->getRoot()->needsAutoRename()) { setSelection(item_id, FALSE); } } ////////////////////////////// // STRUCTURE Operation // This item already exists in both memory and UI. It was probably reparented. else if (model_item && view_item) { // Don't process the item if it is the root if (view_item->getRoot() != view_item) { LLFolderViewFolder* new_parent = (LLFolderViewFolder*)mFolderRoot->getItemByID(model_item->getParentUUID()); // Item has been moved. if (view_item->getParentFolder() != new_parent) { if (new_parent != NULL) { // Item is to be moved and we found its new parent in the panel's directory, so move the item's UI. view_item->getParentFolder()->extractItem(view_item); view_item->addToFolder(new_parent, mFolderRoot); } else { // Item is to be moved outside the panel's directory (e.g. moved to trash for a panel that // doesn't include trash). Just remove the item's UI. view_item->destroyView(); } } } } ////////////////////////////// // REMOVE Operation // This item has been removed from memory, but its associated UI element still exists. else if (!model_item && view_item) { // Remove the item's UI. view_item->destroyView(); } } } } LLFolderView* LLInventoryPanel::getRootFolder() { return mFolderRoot; } const LLUUID& LLInventoryPanel::getRootFolderID() const { return mFolderRoot->getListener()->getUUID(); } void LLInventoryPanel::rebuildViewsFor(const LLUUID& id, U32 mask) { // Destroy the old view for this ID so we can rebuild it. LLFolderViewItem* old_view = mFolderRoot->getItemByID(id); if (old_view && id.notNull()) { old_view->destroyView(); } buildNewViews(id); } LLFolderViewFolder * LLInventoryPanel::createFolderViewFolder(LLInvFVBridge * bridge) { return new LLFolderViewFolder( bridge->getDisplayName(), bridge->getIcon(), mFolderRoot, bridge); } LLFolderViewItem * LLInventoryPanel::createFolderViewItem(LLInvFVBridge * bridge) { return new LLFolderViewItem( bridge->getDisplayName(), bridge->getIcon(), bridge->getCreationDate(), mFolderRoot, bridge); } void LLInventoryPanel::buildNewViews(const LLUUID& id) { LLInventoryObject* const objectp = gInventory.getObject(id); LLFolderViewItem* itemp = NULL; if (objectp) { const LLUUID &parent_id = objectp->getParentUUID(); LLFolderViewFolder* parent_folder = (LLFolderViewFolder*)mFolderRoot->getItemByID(parent_id); if (objectp->getType() <= LLAssetType::AT_NONE || objectp->getType() >= LLAssetType::AT_COUNT) { llwarns << "LLInventoryPanel::buildNewViews called with invalid objectp->mType : " << ((S32) objectp->getType()) << " name " << objectp->getName() << " UUID " << objectp->getUUID() << llendl; } else if ((objectp->getType() == LLAssetType::AT_CATEGORY) && (objectp->getActualType() != LLAssetType::AT_LINK_FOLDER)) // build new view for category { LLInvFVBridge* new_listener = LLInvFVBridge::createBridge(objectp->getType(), objectp->getType(), LLInventoryType::IT_CATEGORY, this, objectp->getUUID()); if (new_listener) { LLFolderViewFolder* folderp = createFolderViewFolder(new_listener); if (folderp) { folderp->setItemSortOrder(mFolderRoot->getSortOrder()); } itemp = folderp; } } else // build new view for item { LLInventoryItem* item = (LLInventoryItem*)objectp; LLInvFVBridge* new_listener = LLInvFVBridge::createBridge( item->getType(), item->getActualType(), item->getInventoryType(), this, item->getUUID(), item->getFlags()); if (new_listener) { itemp = createFolderViewItem(new_listener); } } if (itemp) { if (parent_folder) { itemp->addToFolder(parent_folder, mFolderRoot); } else { llwarns << "Couldn't find parent folder for child " << itemp->getLabel() << llendl; delete itemp; } } } // If this is a folder, add the children of the folder and recursively add any // child folders. if (id.isNull() || (objectp && objectp->getType() == LLAssetType::AT_CATEGORY)) { LLViewerInventoryCategory::cat_array_t* categories; LLViewerInventoryItem::item_array_t* items; mInventory->lockDirectDescendentArrays(id, categories, items); if(categories) { for (LLViewerInventoryCategory::cat_array_t::const_iterator cat_iter = categories->begin(); cat_iter != categories->end(); ++cat_iter) { const LLViewerInventoryCategory* cat = (*cat_iter); buildNewViews(cat->getUUID()); } } if(items) { for (LLViewerInventoryItem::item_array_t::const_iterator item_iter = items->begin(); item_iter != items->end(); ++item_iter) { const LLViewerInventoryItem* item = (*item_iter); buildNewViews(item->getUUID()); } } mInventory->unlockDirectDescendentArrays(id); } } struct LLConfirmPurgeData { LLUUID mID; LLInventoryModel* mModel; }; class LLIsNotWorn : public LLInventoryCollectFunctor { public: LLIsNotWorn() {} virtual ~LLIsNotWorn() {} virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) { return !gAgentWearables.isWearingItem(item->getUUID()); } }; class LLOpenFolderByID : public LLFolderViewFunctor { public: LLOpenFolderByID(const LLUUID& id) : mID(id) {} virtual ~LLOpenFolderByID() {} virtual void doFolder(LLFolderViewFolder* folder) { if (folder->getListener() && folder->getListener()->getUUID() == mID) folder->setOpenArrangeRecursively(TRUE, LLFolderViewFolder::RECURSE_UP); } virtual void doItem(LLFolderViewItem* item) {} protected: const LLUUID& mID; }; void LLInventoryPanel::openSelected() { LLFolderViewItem* folder_item = mFolderRoot->getCurSelectedItem(); if(!folder_item) return; LLInvFVBridge* bridge = (LLInvFVBridge*)folder_item->getListener(); if(!bridge) return; bridge->openItem(); } void LLInventoryPanel::unSelectAll() { mFolderRoot->setSelection(NULL, FALSE, FALSE); } BOOL LLInventoryPanel::handleHover(S32 x, S32 y, MASK mask) { BOOL handled = LLView::handleHover(x, y, mask); if(handled) { ECursorType cursor = getWindow()->getCursor(); if (LLInventoryModelBackgroundFetch::instance().backgroundFetchActive() && cursor == UI_CURSOR_ARROW) { // replace arrow cursor with arrow and hourglass cursor getWindow()->setCursor(UI_CURSOR_WORKING); } } else { getWindow()->setCursor(UI_CURSOR_ARROW); } return TRUE; } BOOL LLInventoryPanel::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg) { BOOL handled = LLPanel::handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); if (handled) { mFolderRoot->setDragAndDropThisFrame(); } return handled; } void LLInventoryPanel::openAllFolders() { mFolderRoot->setOpenArrangeRecursively(TRUE, LLFolderViewFolder::RECURSE_DOWN); mFolderRoot->arrangeAll(); } void LLInventoryPanel::closeAllFolders() { mFolderRoot->setOpenArrangeRecursively(FALSE, LLFolderViewFolder::RECURSE_DOWN); mFolderRoot->arrangeAll(); } void LLInventoryPanel::openDefaultFolderForType(LLAssetType::EType type) { LLUUID category_id = mInventory->findCategoryUUIDForType(LLFolderType::assetTypeToFolderType(type)); LLOpenFolderByID opener(category_id); mFolderRoot->applyFunctorRecursively(opener); } void LLInventoryPanel::setSelection(const LLUUID& obj_id, BOOL take_keyboard_focus) { LLFolderViewItem* itemp = mFolderRoot->getItemByID(obj_id); if(itemp && itemp->getListener()) { itemp->getListener()->arrangeAndSet(itemp, TRUE, take_keyboard_focus); mSelectThisID.setNull(); return; } else { // save the desired item to be selected later (if/when ready) mSelectThisID = obj_id; } } void LLInventoryPanel::setSelectCallback(LLFolderView::SelectCallback callback, void* user_data) { if (mFolderRoot) { mFolderRoot->setSelectCallback(callback, user_data); } } void LLInventoryPanel::clearSelection() { mFolderRoot->clearSelection(); mSelectThisID.setNull(); } void LLInventoryPanel::createNewItem(const std::string& name, const LLUUID& parent_id, LLAssetType::EType asset_type, LLInventoryType::EType inv_type, U32 next_owner_perm) { std::string desc; LLViewerAssetType::generateDescriptionFor(asset_type, desc); next_owner_perm = (next_owner_perm) ? next_owner_perm : PERM_MOVE | PERM_TRANSFER; if (inv_type == LLInventoryType::IT_GESTURE) { LLPointer cb = new CreateGestureCallback(); create_inventory_item(gAgent.getID(), gAgent.getSessionID(), parent_id, LLTransactionID::tnull, name, desc, asset_type, inv_type, NOT_WEARABLE, next_owner_perm, cb); } else { LLPointer cb = NULL; create_inventory_item(gAgent.getID(), gAgent.getSessionID(), parent_id, LLTransactionID::tnull, name, desc, asset_type, inv_type, NOT_WEARABLE, next_owner_perm, cb); } } BOOL LLInventoryPanel::getSinceLogoff() { return getFilter()->isSinceLogoff(); } // DEBUG ONLY // static void LLInventoryPanel::dumpSelectionInformation(void* user_data) { LLInventoryPanel* iv = (LLInventoryPanel*)user_data; iv->mFolderRoot->dumpSelectionInformation(); }