summaryrefslogtreecommitdiff
path: root/indra/newview/llinventorygallery.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/newview/llinventorygallery.cpp')
-rw-r--r--indra/newview/llinventorygallery.cpp3824
1 files changed, 3824 insertions, 0 deletions
diff --git a/indra/newview/llinventorygallery.cpp b/indra/newview/llinventorygallery.cpp
new file mode 100644
index 0000000000..4838ba7a47
--- /dev/null
+++ b/indra/newview/llinventorygallery.cpp
@@ -0,0 +1,3824 @@
+/**
+ * @file llinventorygallery.cpp
+ * @brief LLInventoryGallery class implementation
+ *
+ * $LicenseInfo:firstyear=2023&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2023, 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 "llinventorygallery.h"
+#include "llinventorygallerymenu.h"
+
+#include "llclipboard.h"
+#include "llcommonutils.h"
+#include "lliconctrl.h"
+#include "llinventorybridge.h"
+#include "llinventoryfunctions.h"
+#include "llinventoryicon.h"
+#include "llinventorymodel.h"
+#include "llinventorymodelbackgroundfetch.h"
+#include "llthumbnailctrl.h"
+#include "lltextbox.h"
+#include "llviewerfoldertype.h"
+
+#include "llagent.h"
+#include "llappearancemgr.h"
+#include "llenvironment.h"
+#include "llfriendcard.h"
+#include "llgesturemgr.h"
+#include "llmarketplacefunctions.h"
+#include "llnotificationsutil.h"
+#include "lloutfitobserver.h"
+#include "lltrans.h"
+#include "llviewerassettype.h"
+#include "llviewermessage.h"
+#include "llviewerobjectlist.h"
+#include "llvoavatarself.h"
+
+static LLPanelInjector<LLInventoryGallery> t_inventory_gallery("inventory_gallery");
+
+const S32 GALLERY_ITEMS_PER_ROW_MIN = 2;
+
+// Helper dnd functions
+BOOL dragCategoryIntoFolder(LLUUID dest_id, LLInventoryCategory* inv_cat, BOOL drop, std::string& tooltip_msg, BOOL is_link);
+BOOL dragItemIntoFolder(LLUUID folder_id, LLInventoryItem* inv_item, BOOL drop, std::string& tooltip_msg, BOOL user_confirm);
+void dropToMyOutfits(LLInventoryCategory* inv_cat);
+
+class LLGalleryPanel: public LLPanel
+{
+public:
+
+ BOOL canFocusChildren() const override
+ {
+ // Tell Tab to not focus children
+ return FALSE;
+ }
+
+protected:
+
+ LLGalleryPanel(const LLPanel::Params& params): LLPanel(params)
+ {
+ };
+
+ friend class LLUICtrlFactory;
+};
+
+//-----------------------------
+// LLInventoryGallery
+//-----------------------------
+
+LLInventoryGallery::LLInventoryGallery(const LLInventoryGallery::Params& p)
+ : LLPanel(),
+ mScrollPanel(NULL),
+ mGalleryPanel(NULL),
+ mLastRowPanel(NULL),
+ mGalleryCreated(false),
+ mRowCount(0),
+ mItemsAddedCount(0),
+ mRowPanelHeight(p.row_panel_height),
+ mVerticalGap(p.vertical_gap),
+ mHorizontalGap(p.horizontal_gap),
+ mItemWidth(p.item_width),
+ mItemHeight(p.item_height),
+ mItemHorizontalGap(p.item_horizontal_gap),
+ mItemsInRow(p.items_in_row),
+ mRowPanWidthFactor(p.row_panel_width_factor),
+ mGalleryWidthFactor(p.gallery_width_factor),
+ mIsInitialized(false),
+ mRootDirty(false),
+ mNeedsArrange(false),
+ mSearchType(LLInventoryFilter::SEARCHTYPE_NAME),
+ mSortOrder(LLInventoryFilter::SO_DATE)
+{
+ updateGalleryWidth();
+ mFilter = new LLInventoryFilter();
+ mCategoriesObserver = new LLInventoryCategoriesObserver();
+ mThumbnailsObserver = new LLThumbnailsObserver();
+ gInventory.addObserver(mThumbnailsObserver);
+
+ mGestureObserver = new LLGalleryGestureObserver(this);
+ LLGestureMgr::instance().addObserver(mGestureObserver);
+
+ mUsername = gAgentUsername;
+ LLStringUtil::toUpper(mUsername);
+}
+
+LLInventoryGallery::Params::Params()
+ : row_panel_height("row_panel_height", 180),
+ vertical_gap("vertical_gap", 10),
+ horizontal_gap("horizontal_gap", 10),
+ item_width("item_width", 150),
+ item_height("item_height", 175),
+ item_horizontal_gap("item_horizontal_gap", 16),
+ items_in_row("items_in_row", GALLERY_ITEMS_PER_ROW_MIN),
+ row_panel_width_factor("row_panel_width_factor", 166),
+ gallery_width_factor("gallery_width_factor", 163)
+{
+ addSynonym(row_panel_height, "row_height");
+}
+
+const LLInventoryGallery::Params& LLInventoryGallery::getDefaultParams()
+{
+ return LLUICtrlFactory::getDefaultParams<LLInventoryGallery>();
+}
+
+BOOL LLInventoryGallery::postBuild()
+{
+ mScrollPanel = getChild<LLScrollContainer>("gallery_scroll_panel");
+ LLPanel::Params params = LLPanel::getDefaultParams();
+ mGalleryPanel = LLUICtrlFactory::create<LLPanel>(params);
+ mMessageTextBox = getChild<LLTextBox>("empty_txt");
+ mInventoryGalleryMenu = new LLInventoryGalleryContextMenu(this);
+ mRootGalleryMenu = new LLInventoryGalleryContextMenu(this);
+ mRootGalleryMenu->setRootFolder(true);
+ return TRUE;
+}
+
+LLInventoryGallery::~LLInventoryGallery()
+{
+ if (gEditMenuHandler == this)
+ {
+ gEditMenuHandler = NULL;
+ }
+
+ delete mInventoryGalleryMenu;
+ delete mRootGalleryMenu;
+ delete mFilter;
+
+ gIdleCallbacks.deleteFunction(onIdle, (void*)this);
+
+ while (!mUnusedRowPanels.empty())
+ {
+ LLPanel* panelp = mUnusedRowPanels.back();
+ mUnusedRowPanels.pop_back();
+ panelp->die();
+ }
+ while (!mUnusedItemPanels.empty())
+ {
+ LLPanel* panelp = mUnusedItemPanels.back();
+ mUnusedItemPanels.pop_back();
+ panelp->die();
+ }
+ while (!mHiddenItems.empty())
+ {
+ LLPanel* panelp = mHiddenItems.back();
+ mHiddenItems.pop_back();
+ panelp->die();
+ }
+
+
+ if (gInventory.containsObserver(mCategoriesObserver))
+ {
+ gInventory.removeObserver(mCategoriesObserver);
+ }
+ delete mCategoriesObserver;
+
+ if (gInventory.containsObserver(mThumbnailsObserver))
+ {
+ gInventory.removeObserver(mThumbnailsObserver);
+ }
+ delete mThumbnailsObserver;
+
+ LLGestureMgr::instance().removeObserver(mGestureObserver);
+ delete mGestureObserver;
+}
+
+void LLInventoryGallery::setRootFolder(const LLUUID cat_id)
+{
+ LLViewerInventoryCategory* category = gInventory.getCategory(cat_id);
+ if(!category || (mFolderID == cat_id))
+ {
+ return;
+ }
+ if(mFolderID.notNull())
+ {
+ mBackwardFolders.push_back(mFolderID);
+ }
+
+ gIdleCallbacks.deleteFunction(onIdle, (void*)this);
+
+ for (const LLUUID& id : mSelectedItemIDs)
+ {
+ if (mItemMap[id])
+ {
+ mItemMap[id]->setSelected(FALSE);
+ }
+ }
+
+ mFolderID = cat_id;
+ mItemsToSelect.clear();
+ mSelectedItemIDs.clear();
+ mItemBuildQuery.clear();
+ mNeedsArrange = false;
+ dirtyRootFolder();
+}
+
+void LLInventoryGallery::dirtyRootFolder()
+{
+ if (getVisible())
+ {
+ updateRootFolder();
+ }
+ else
+ {
+ mRootDirty = true;
+ }
+}
+
+void LLInventoryGallery::updateRootFolder()
+{
+ llassert(mFolderID.notNull());
+ if (mIsInitialized && mFolderID.notNull())
+ {
+ S32 count = mItemsAddedCount;
+ for (S32 i = count - 1; i >= 0; i--)
+ {
+ updateRemovedItem(mItems[i]->getUUID());
+ }
+ S32 hidden_count = mHiddenItems.size();
+ for (S32 i = hidden_count - 1; i >= 0; i--)
+ {
+ updateRemovedItem(mHiddenItems[i]->getUUID());
+ }
+ mItemBuildQuery.clear();
+
+ if (gInventory.containsObserver(mCategoriesObserver))
+ {
+ gInventory.removeObserver(mCategoriesObserver);
+ }
+ delete mCategoriesObserver;
+
+ mCategoriesObserver = new LLInventoryCategoriesObserver();
+
+ if (gInventory.containsObserver(mThumbnailsObserver))
+ {
+ gInventory.removeObserver(mThumbnailsObserver);
+ }
+ delete mThumbnailsObserver;
+ mThumbnailsObserver = new LLThumbnailsObserver();
+ gInventory.addObserver(mThumbnailsObserver);
+ }
+ {
+ mRootChangedSignal();
+
+ gInventory.addObserver(mCategoriesObserver);
+
+ // Start observing changes in selected category.
+ mCategoriesObserver->addCategory(mFolderID,
+ boost::bind(&LLInventoryGallery::refreshList, this, mFolderID));
+
+ LLViewerInventoryCategory* category = gInventory.getCategory(mFolderID);
+ //If not all items are fetched now
+ // the observer will refresh the list as soon as the new items
+ // arrive.
+ category->fetch();
+
+ //refreshList(cat_id);
+ LLInventoryModel::cat_array_t* cat_array;
+ LLInventoryModel::item_array_t* item_array;
+
+ gInventory.getDirectDescendentsOf(mFolderID, cat_array, item_array);
+
+ // Creating a vector of newly collected sub-categories UUIDs.
+ for (LLInventoryModel::cat_array_t::const_iterator iter = cat_array->begin();
+ iter != cat_array->end();
+ iter++)
+ {
+ mItemBuildQuery.insert((*iter)->getUUID());
+ }
+
+ for (LLInventoryModel::item_array_t::const_iterator iter = item_array->begin();
+ iter != item_array->end();
+ iter++)
+ {
+ mItemBuildQuery.insert((*iter)->getUUID());
+ }
+ mIsInitialized = true;
+ mRootDirty = false;
+
+ if (mScrollPanel)
+ {
+ mScrollPanel->goToTop();
+ }
+ }
+
+ LLOutfitObserver::instance().addCOFChangedCallback(boost::bind(&LLInventoryGallery::onCOFChanged, this));
+
+ if (!mGalleryCreated)
+ {
+ initGallery();
+ }
+
+ if (!mItemBuildQuery.empty())
+ {
+ gIdleCallbacks.addFunction(onIdle, (void*)this);
+ }
+}
+
+void LLInventoryGallery::initGallery()
+{
+ if (!mGalleryCreated)
+ {
+ uuid_vec_t cats;
+ getCurrentCategories(cats);
+ int n = cats.size();
+ buildGalleryPanel(n);
+ mScrollPanel->addChild(mGalleryPanel);
+ for (int i = 0; i < n; i++)
+ {
+ addToGallery(mItemMap[cats[i]]);
+ }
+ reArrangeRows();
+ mGalleryCreated = true;
+ }
+}
+
+void LLInventoryGallery::draw()
+{
+ LLPanel::draw();
+ if (mGalleryCreated)
+ {
+ if(!updateRowsIfNeeded())
+ {
+ handleModifiedFilter();
+ }
+ }
+}
+
+void LLInventoryGallery::onVisibilityChange(BOOL new_visibility)
+{
+ if (new_visibility)
+ {
+ if (mRootDirty)
+ {
+ updateRootFolder();
+ }
+ else if (mNeedsArrange)
+ {
+ gIdleCallbacks.addFunction(onIdle, (void*)this);
+ }
+ }
+ LLPanel::onVisibilityChange(new_visibility);
+}
+
+bool LLInventoryGallery::updateRowsIfNeeded()
+{
+ S32 scroll_content_width = mScrollPanel ? mScrollPanel->getVisibleContentRect().getWidth() : getRect().getWidth();
+ if(((scroll_content_width - mRowPanelWidth) > mItemWidth)
+ && mRowCount > 1)
+ {
+ reArrangeRows(1);
+ return true;
+ }
+ else if((mRowPanelWidth > (scroll_content_width + mItemHorizontalGap))
+ && mItemsInRow > GALLERY_ITEMS_PER_ROW_MIN)
+ {
+ reArrangeRows(-1);
+ return true;
+ }
+ return false;
+}
+
+bool compareGalleryItem(LLInventoryGalleryItem* item1, LLInventoryGalleryItem* item2, bool sort_by_date, bool sort_folders_by_name)
+{
+ if (item1->getSortGroup() != item2->getSortGroup())
+ {
+ return (item1->getSortGroup() < item2->getSortGroup());
+ }
+
+ if(sort_folders_by_name && (item1->getSortGroup() != LLInventoryGalleryItem::SG_ITEM))
+ {
+ std::string name1 = item1->getItemName();
+ std::string name2 = item2->getItemName();
+
+ return (LLStringUtil::compareDict(name1, name2) < 0);
+ }
+
+ if(((item1->isDefaultImage() && item2->isDefaultImage()) || (!item1->isDefaultImage() && !item2->isDefaultImage())))
+ {
+ if(sort_by_date)
+ {
+ return item1->getCreationDate() > item2->getCreationDate();
+ }
+ else
+ {
+ std::string name1 = item1->getItemName();
+ std::string name2 = item2->getItemName();
+
+ return (LLStringUtil::compareDict(name1, name2) < 0);
+ }
+ }
+ else
+ {
+ return item2->isDefaultImage();
+ }
+}
+
+void LLInventoryGallery::reArrangeRows(S32 row_diff)
+{
+ std::vector<LLInventoryGalleryItem*> buf_items = mItems;
+ for (std::vector<LLInventoryGalleryItem*>::const_reverse_iterator it = buf_items.rbegin(); it != buf_items.rend(); ++it)
+ {
+ removeFromGalleryLast(*it, false);
+ }
+ for (std::vector<LLInventoryGalleryItem*>::const_reverse_iterator it = mHiddenItems.rbegin(); it != mHiddenItems.rend(); ++it)
+ {
+ buf_items.push_back(*it);
+ }
+ mHiddenItems.clear();
+
+ mItemsInRow+= row_diff;
+ updateGalleryWidth();
+
+ bool sort_by_date = (mSortOrder & LLInventoryFilter::SO_DATE);
+ bool sort_folders_by_name = (mSortOrder & LLInventoryFilter::SO_FOLDERS_BY_NAME);
+ std::sort(buf_items.begin(), buf_items.end(), [sort_by_date, sort_folders_by_name](LLInventoryGalleryItem* item1, LLInventoryGalleryItem* item2)
+ {
+ return compareGalleryItem(item1, item2, sort_by_date, sort_folders_by_name);
+ });
+
+ for (std::vector<LLInventoryGalleryItem*>::const_iterator it = buf_items.begin(); it != buf_items.end(); ++it)
+ {
+ (*it)->setHidden(false);
+ applyFilter(*it, mFilterSubString);
+ addToGallery(*it);
+ }
+ mFilter->clearModified();
+ updateMessageVisibility();
+}
+
+void LLInventoryGallery::updateGalleryWidth()
+{
+ mRowPanelWidth = mRowPanWidthFactor * mItemsInRow - mItemHorizontalGap;
+ mGalleryWidth = mGalleryWidthFactor * mItemsInRow - mItemHorizontalGap;
+}
+
+LLPanel* LLInventoryGallery::addLastRow()
+{
+ mRowCount++;
+ int row = 0;
+ int vgap = mVerticalGap * row;
+ LLPanel* result = buildRowPanel(0, row * mRowPanelHeight + vgap);
+ mGalleryPanel->addChild(result);
+ return result;
+}
+
+void LLInventoryGallery::moveRowUp(int row)
+{
+ moveRow(row, mRowCount - 1 - row + 1);
+}
+
+void LLInventoryGallery::moveRowDown(int row)
+{
+ moveRow(row, mRowCount - 1 - row - 1);
+}
+
+void LLInventoryGallery::moveRow(int row, int pos)
+{
+ int vgap = mVerticalGap * pos;
+ moveRowPanel(mRowPanels[row], 0, pos * mRowPanelHeight + vgap);
+}
+
+void LLInventoryGallery::removeLastRow()
+{
+ mRowCount--;
+ mGalleryPanel->removeChild(mLastRowPanel);
+ mUnusedRowPanels.push_back(mLastRowPanel);
+ mRowPanels.pop_back();
+ if (mRowPanels.size() > 0)
+ {
+ // Just removed last row
+ mLastRowPanel = mRowPanels.back();
+ }
+ else
+ {
+ mLastRowPanel = NULL;
+ }
+}
+
+LLPanel* LLInventoryGallery::addToRow(LLPanel* row_stack, LLInventoryGalleryItem* item, int pos, int hgap)
+{
+ LLPanel* lpanel = buildItemPanel(pos * mItemWidth + hgap);
+ lpanel->addChild(item);
+ row_stack->addChild(lpanel);
+ mItemPanels.push_back(lpanel);
+ return lpanel;
+}
+
+void LLInventoryGallery::addToGallery(LLInventoryGalleryItem* item)
+{
+ if(item->isHidden())
+ {
+ mHiddenItems.push_back(item);
+ return;
+ }
+ mItemIndexMap[item] = mItemsAddedCount;
+ mIndexToItemMap[mItemsAddedCount] = item;
+ mItemsAddedCount++;
+ int n = mItemsAddedCount;
+ int row_count = (n % mItemsInRow) == 0 ? n / mItemsInRow : n / mItemsInRow + 1;
+ int n_prev = n - 1;
+ int row_count_prev = (n_prev % mItemsInRow) == 0 ? n_prev / mItemsInRow : n_prev / mItemsInRow + 1;
+
+ bool add_row = row_count != row_count_prev;
+ int pos = 0;
+ if (add_row)
+ {
+ for (int i = 0; i < row_count_prev; i++)
+ {
+ moveRowUp(i);
+ }
+ mLastRowPanel = addLastRow();
+ mRowPanels.push_back(mLastRowPanel);
+ }
+ pos = (n - 1) % mItemsInRow;
+ mItems.push_back(item);
+ addToRow(mLastRowPanel, item, pos, mHorizontalGap * pos);
+ reshapeGalleryPanel(row_count);
+}
+
+
+void LLInventoryGallery::removeFromGalleryLast(LLInventoryGalleryItem* item, bool needs_reshape)
+{
+ if(item->isHidden())
+ {
+ mHiddenItems.pop_back();
+ // Note: item still exists!!!
+ return;
+ }
+ int n_prev = mItemsAddedCount;
+ int n = mItemsAddedCount - 1;
+ int row_count = (n % mItemsInRow) == 0 ? n / mItemsInRow : n / mItemsInRow + 1;
+ int row_count_prev = (n_prev % mItemsInRow) == 0 ? n_prev / mItemsInRow : n_prev / mItemsInRow + 1;
+ mItemsAddedCount--;
+ mIndexToItemMap.erase(mItemsAddedCount);
+
+ bool remove_row = row_count != row_count_prev;
+ removeFromLastRow(mItems[mItemsAddedCount]);
+ mItems.pop_back();
+ if (remove_row)
+ {
+ for (int i = 0; i < row_count_prev - 1; i++)
+ {
+ moveRowDown(i);
+ }
+ removeLastRow();
+ }
+ if (needs_reshape)
+ {
+ reshapeGalleryPanel(row_count);
+ }
+}
+
+
+void LLInventoryGallery::removeFromGalleryMiddle(LLInventoryGalleryItem* item)
+{
+ if(item->isHidden())
+ {
+ mHiddenItems.erase(std::remove(mHiddenItems.begin(), mHiddenItems.end(), item), mHiddenItems.end());
+ // item still exists and needs to be deleted or used!!!
+ return;
+ }
+ int n = mItemIndexMap[item];
+ mItemIndexMap.erase(item);
+ mIndexToItemMap.erase(n);
+ std::vector<LLInventoryGalleryItem*> saved;
+ for (int i = mItemsAddedCount - 1; i > n; i--)
+ {
+ saved.push_back(mItems[i]);
+ removeFromGalleryLast(mItems[i]);
+ }
+ removeFromGalleryLast(mItems[n]);
+ int saved_count = saved.size();
+ for (int i = 0; i < saved_count; i++)
+ {
+ addToGallery(saved.back());
+ saved.pop_back();
+ }
+}
+
+void LLInventoryGallery::removeFromLastRow(LLInventoryGalleryItem* item)
+{
+ mItemPanels.back()->removeChild(item);
+ mLastRowPanel->removeChild(mItemPanels.back());
+ mUnusedItemPanels.push_back(mItemPanels.back());
+ mItemPanels.pop_back();
+}
+
+LLInventoryGalleryItem* LLInventoryGallery::buildGalleryItem(std::string name, LLUUID item_id, LLAssetType::EType type, LLUUID thumbnail_id, LLInventoryType::EType inventory_type, U32 flags, time_t creation_date, bool is_link, bool is_worn)
+{
+ LLInventoryGalleryItem::Params giparams;
+ giparams.visible = true;
+ giparams.follows.flags(FOLLOWS_LEFT | FOLLOWS_TOP);
+ giparams.rect(LLRect(0,mItemHeight, mItemWidth, 0));
+ LLInventoryGalleryItem* gitem = LLUICtrlFactory::create<LLInventoryGalleryItem>(giparams);
+ gitem->setItemName(name);
+ gitem->setUUID(item_id);
+ gitem->setGallery(this);
+ gitem->setType(type, inventory_type, flags, is_link);
+ gitem->setThumbnail(thumbnail_id);
+ gitem->setWorn(is_worn);
+ gitem->setCreatorName(get_searchable_creator_name(&gInventory, item_id));
+ gitem->setDescription(get_searchable_description(&gInventory, item_id));
+ gitem->setAssetIDStr(get_searchable_UUID(&gInventory, item_id));
+ gitem->setCreationDate(creation_date);
+ return gitem;
+}
+
+void LLInventoryGallery::buildGalleryPanel(int row_count)
+{
+ LLPanel::Params params;
+ params.follows.flags(FOLLOWS_LEFT | FOLLOWS_TOP);
+ params.visible = true;
+ params.use_bounding_rect = false;
+ mGalleryPanel = LLUICtrlFactory::create<LLGalleryPanel>(params);
+ reshapeGalleryPanel(row_count);
+}
+
+void LLInventoryGallery::reshapeGalleryPanel(int row_count)
+{
+ int bottom = 0;
+ int left = 0;
+ int height = row_count * (mRowPanelHeight + mVerticalGap);
+ LLRect rect = LLRect(left, bottom + height, left + mGalleryWidth, bottom);
+ mGalleryPanel->setRect(rect);
+ mGalleryPanel->reshape(mGalleryWidth, height);
+}
+
+LLPanel* LLInventoryGallery::buildItemPanel(int left)
+{
+ int top = 0;
+ LLPanel* lpanel = NULL;
+ if(mUnusedItemPanels.empty())
+ {
+ LLPanel::Params lpparams;
+ lpparams.follows.flags(FOLLOWS_LEFT | FOLLOWS_TOP);
+ lpparams.visible = true;
+ lpparams.rect(LLRect(left, top + mItemHeight, left + mItemWidth + mItemHorizontalGap, top));
+ lpparams.use_bounding_rect = false;
+ lpparams.focus_root = false;
+ //lpparams.tab_stop = false;
+ lpanel = LLUICtrlFactory::create<LLPanel>(lpparams);
+ }
+ else
+ {
+ lpanel = mUnusedItemPanels.back();
+ mUnusedItemPanels.pop_back();
+
+ LLRect rect = LLRect(left, top + mItemHeight, left + mItemWidth + mItemHorizontalGap, top);
+ lpanel->setShape(rect, false);
+ }
+ return lpanel;
+}
+
+LLPanel* LLInventoryGallery::buildRowPanel(int left, int bottom)
+{
+ LLPanel* stack = NULL;
+ if(mUnusedRowPanels.empty())
+ {
+ LLPanel::Params sparams;
+ sparams.follows.flags(FOLLOWS_LEFT | FOLLOWS_TOP);
+ sparams.use_bounding_rect = false;
+ sparams.visible = true;
+ sparams.focus_root = false;
+ //sparams.tab_stop = false;
+ stack = LLUICtrlFactory::create<LLPanel>(sparams);
+ }
+ else
+ {
+ stack = mUnusedRowPanels.back();
+ mUnusedRowPanels.pop_back();
+ }
+ moveRowPanel(stack, left, bottom);
+ return stack;
+}
+
+void LLInventoryGallery::moveRowPanel(LLPanel* stack, int left, int bottom)
+{
+ LLRect rect = LLRect(left, bottom + mRowPanelHeight, left + mRowPanelWidth, bottom);
+ stack->setRect(rect);
+ stack->reshape(mRowPanelWidth, mRowPanelHeight);
+}
+
+void LLInventoryGallery::setFilterSubString(const std::string& string)
+{
+ mFilterSubString = string;
+ mFilter->setFilterSubString(string);
+
+ //reArrangeRows();
+}
+
+bool LLInventoryGallery::applyFilter(LLInventoryGalleryItem* item, const std::string& filter_substring)
+{
+ if(item)
+ {
+ bool visible = checkAgainstFilters(item, filter_substring);
+ item->setHidden(!visible);
+ return visible;
+ }
+ return false;
+}
+
+bool LLInventoryGallery::checkAgainstFilters(LLInventoryGalleryItem* item, const std::string& filter_substring)
+{
+ if (!item) return false;
+
+ if (item->isFolder() && (mFilter->getShowFolderState() == LLInventoryFilter::SHOW_ALL_FOLDERS))
+ {
+ return true;
+ }
+
+ if(item->isLink() && ((mFilter->getSearchVisibilityTypes() & LLInventoryFilter::VISIBILITY_LINKS) == 0) && !filter_substring.empty())
+ {
+ return false;
+ }
+
+ bool hidden = false;
+
+ if(mFilter->getFilterCreatorType() == LLInventoryFilter::FILTERCREATOR_SELF)
+ {
+ hidden = (item->getCreatorName() == mUsername) || item->isFolder();
+ }
+ else if(mFilter->getFilterCreatorType() == LLInventoryFilter::FILTERCREATOR_OTHERS)
+ {
+ hidden = (item->getCreatorName() != mUsername) || item->isFolder();
+ }
+ if(hidden)
+ {
+ return false;
+ }
+
+ if(!mFilter->checkAgainstFilterThumbnails(item->getUUID()))
+ {
+ return false;
+ }
+
+ if(!checkAgainstFilterType(item->getUUID()))
+ {
+ return false;
+ }
+
+ std::string desc;
+ switch(mSearchType)
+ {
+ case LLInventoryFilter::SEARCHTYPE_CREATOR:
+ desc = item->getCreatorName();
+ break;
+ case LLInventoryFilter::SEARCHTYPE_DESCRIPTION:
+ desc = item->getDescription();
+ break;
+ case LLInventoryFilter::SEARCHTYPE_UUID:
+ desc = item->getAssetIDStr();
+ break;
+ case LLInventoryFilter::SEARCHTYPE_NAME:
+ default:
+ desc = item->getItemName() + item->getItemNameSuffix();
+ break;
+ }
+
+ LLStringUtil::toUpper(desc);
+
+ std::string cur_filter = filter_substring;
+ LLStringUtil::toUpper(cur_filter);
+
+ hidden = (std::string::npos == desc.find(cur_filter));
+ return !hidden;
+}
+
+void LLInventoryGallery::onIdle(void* userdata)
+{
+ LLInventoryGallery* self = (LLInventoryGallery*)userdata;
+
+ if (!self->mIsInitialized || !self->mGalleryCreated)
+ {
+ self->mNeedsArrange = false;
+ return;
+ }
+
+ bool visible = self->getVisible(); // In visible chain?
+ const F64 MAX_TIME_VISIBLE = 0.020f;
+ const F64 MAX_TIME_HIDDEN = 0.001f; // take it slow
+ const F64 max_time = visible ? MAX_TIME_VISIBLE : MAX_TIME_HIDDEN;
+ F64 curent_time = LLTimer::getTotalSeconds();
+ const F64 end_time = curent_time + max_time;
+
+ while (!self->mItemBuildQuery.empty() && end_time > curent_time)
+ {
+ uuid_set_t::iterator iter = self->mItemBuildQuery.begin();
+ LLUUID item_id = *iter;
+ self->mNeedsArrange |= self->updateAddedItem(item_id);
+ self->mItemBuildQuery.erase(iter);
+ curent_time = LLTimer::getTotalSeconds();
+ }
+
+ if (self->mNeedsArrange && visible)
+ {
+ self->mNeedsArrange = false;
+ self->reArrangeRows();
+ self->updateMessageVisibility();
+ }
+
+ if (!self->mItemsToSelect.empty() && !self->mNeedsArrange)
+ {
+ selection_deque selection_list(self->mItemsToSelect);
+ self->mItemsToSelect.clear();
+ for (LLUUID & item_to_select : selection_list)
+ {
+ self->addItemSelection(item_to_select, true);
+ }
+ }
+
+ if (self->mItemsToSelect.empty() && self->mItemBuildQuery.empty())
+ {
+ gIdleCallbacks.deleteFunction(onIdle, (void*)self);
+ }
+}
+
+void LLInventoryGallery::setSearchType(LLInventoryFilter::ESearchType type)
+{
+ if(mSearchType != type)
+ {
+ mSearchType = type;
+ if(!mFilterSubString.empty())
+ {
+ reArrangeRows();
+ }
+ }
+}
+
+void LLInventoryGallery::getCurrentCategories(uuid_vec_t& vcur)
+{
+ for (gallery_item_map_t::const_iterator iter = mItemMap.begin();
+ iter != mItemMap.end();
+ iter++)
+ {
+ if ((*iter).second != NULL)
+ {
+ vcur.push_back((*iter).first);
+ }
+ }
+}
+
+bool LLInventoryGallery::updateAddedItem(LLUUID item_id)
+{
+ LLInventoryObject* obj = gInventory.getObject(item_id);
+ if (!obj)
+ {
+ LL_WARNS("InventoryGallery") << "Failed to find item: " << item_id << LL_ENDL;
+ return false;
+ }
+
+ std::string name = obj->getName();
+ LLUUID thumbnail_id = obj->getThumbnailUUID();;
+ LLInventoryType::EType inventory_type(LLInventoryType::IT_CATEGORY);
+ U32 misc_flags = 0;
+ bool is_worn = false;
+ LLInventoryItem* inv_item = gInventory.getItem(item_id);
+ if (inv_item)
+ {
+ inventory_type = inv_item->getInventoryType();
+ misc_flags = inv_item->getFlags();
+ if (LLAssetType::AT_GESTURE == obj->getType())
+ {
+ is_worn = LLGestureMgr::instance().isGestureActive(item_id);
+ }
+ else
+ {
+ is_worn = LLAppearanceMgr::instance().isLinkedInCOF(item_id);
+ }
+ }
+ else if (LLAssetType::AT_CATEGORY == obj->getType())
+ {
+ name = get_localized_folder_name(item_id);
+ if(thumbnail_id.isNull())
+ {
+ thumbnail_id = getOutfitImageID(item_id);
+ }
+ }
+
+ bool res = false;
+
+ LLInventoryGalleryItem* item = buildGalleryItem(name, item_id, obj->getType(), thumbnail_id, inventory_type, misc_flags, obj->getCreationDate(), obj->getIsLinkType(), is_worn);
+ mItemMap.insert(LLInventoryGallery::gallery_item_map_t::value_type(item_id, item));
+ if (mGalleryCreated)
+ {
+ res = applyFilter(item, mFilterSubString);
+ addToGallery(item);
+ }
+
+ mThumbnailsObserver->addItem(item_id,
+ boost::bind(&LLInventoryGallery::updateItemThumbnail, this, item_id));
+ return res;
+}
+
+void LLInventoryGallery::updateRemovedItem(LLUUID item_id)
+{
+ gallery_item_map_t::iterator item_iter = mItemMap.find(item_id);
+ if (item_iter != mItemMap.end())
+ {
+ mThumbnailsObserver->removeItem(item_id);
+
+ LLInventoryGalleryItem* item = item_iter->second;
+
+ deselectItem(item_id);
+ mItemMap.erase(item_iter);
+ removeFromGalleryMiddle(item);
+
+ // kill removed item
+ if (item != NULL)
+ {
+ // Todo: instead of deleting, store somewhere to reuse later
+ item->die();
+ }
+ }
+
+ mItemBuildQuery.erase(item_id);
+}
+
+void LLInventoryGallery::updateChangedItemName(LLUUID item_id, std::string name)
+{
+ gallery_item_map_t::iterator iter = mItemMap.find(item_id);
+ if (iter != mItemMap.end())
+ {
+ LLInventoryGalleryItem* item = iter->second;
+ if (item)
+ {
+ item->setItemName(name);
+ }
+ }
+}
+
+void LLInventoryGallery::updateWornItem(LLUUID item_id, bool is_worn)
+{
+ gallery_item_map_t::iterator iter = mItemMap.find(item_id);
+ if (iter != mItemMap.end())
+ {
+ LLInventoryGalleryItem* item = iter->second;
+ if (item)
+ {
+ item->setWorn(is_worn);
+ }
+ }
+}
+
+void LLInventoryGallery::updateItemThumbnail(LLUUID item_id)
+{
+ LLInventoryObject* obj = gInventory.getObject(item_id);
+ if (!obj)
+ {
+ return;
+ }
+ LLUUID thumbnail_id = obj->getThumbnailUUID();
+
+ if ((LLAssetType::AT_CATEGORY == obj->getType()) && thumbnail_id.isNull())
+ {
+ thumbnail_id = getOutfitImageID(item_id);
+ }
+
+ if (mItemMap[item_id])
+ {
+ mItemMap[item_id]->setThumbnail(thumbnail_id);
+
+ bool passes_filter = checkAgainstFilters(mItemMap[item_id], mFilterSubString);
+ if((mItemMap[item_id]->isHidden() && passes_filter)
+ || (!mItemMap[item_id]->isHidden() && !passes_filter))
+ {
+ reArrangeRows();
+ }
+ }
+}
+
+BOOL LLInventoryGallery::handleRightMouseDown(S32 x, S32 y, MASK mask)
+{
+ if (mSelectedItemIDs.size() > 0)
+ {
+ setFocus(true);
+ }
+ mLastInteractedUUID = LLUUID::null;
+
+ // Scroll is going to always return true
+ BOOL res = LLPanel::handleRightMouseDown(x, y, mask);
+
+ if (mLastInteractedUUID.isNull()) // no child were hit
+ {
+ clearSelection();
+ if (mInventoryGalleryMenu && mFolderID.notNull())
+ {
+ uuid_vec_t selected_uuids;
+ selected_uuids.push_back(mFolderID);
+ mRootGalleryMenu->show(this, selected_uuids, x, y);
+ return TRUE;
+ }
+ }
+ return res;
+}
+
+
+BOOL LLInventoryGallery::handleKeyHere(KEY key, MASK mask)
+{
+ BOOL handled = FALSE;
+ switch (key)
+ {
+ case KEY_RETURN:
+ // Open selected items if enter key hit on the inventory panel
+ if (mask == MASK_NONE && mInventoryGalleryMenu && mSelectedItemIDs.size() == 1)
+ {
+ selection_deque::iterator iter = mSelectedItemIDs.begin();
+ LLViewerInventoryCategory* category = gInventory.getCategory(*iter);
+ if (category)
+ {
+ setRootFolder(*iter);
+ handled = TRUE;
+ }
+ else
+ {
+ LLViewerInventoryItem* item = gInventory.getItem(*iter);
+ if (item)
+ {
+ LLInvFVBridgeAction::doAction(item->getType(), *iter, &gInventory);
+ }
+ }
+ }
+ handled = TRUE;
+ break;
+ case KEY_DELETE:
+#if LL_DARWIN
+ case KEY_BACKSPACE:
+#endif
+ // Delete selected items if delete or backspace key hit on the inventory panel
+ // Note: on Mac laptop keyboards, backspace and delete are one and the same
+ if (canDeleteSelection())
+ {
+ deleteSelection();
+ }
+ handled = TRUE;
+ break;
+
+ case KEY_F2:
+ mFilterSubString.clear();
+ if (mInventoryGalleryMenu && mSelectedItemIDs.size() == 1)
+ {
+ mInventoryGalleryMenu->rename(mSelectedItemIDs.front());
+ }
+ handled = TRUE;
+ break;
+
+ case KEY_PAGE_UP:
+ mFilterSubString.clear();
+ if (mScrollPanel)
+ {
+ mScrollPanel->pageUp(30);
+ }
+ handled = TRUE;
+ break;
+
+ case KEY_PAGE_DOWN:
+ mFilterSubString.clear();
+ if (mScrollPanel)
+ {
+ mScrollPanel->pageDown(30);
+ }
+ handled = TRUE;
+ break;
+
+ case KEY_HOME:
+ mFilterSubString.clear();
+ if (mScrollPanel)
+ {
+ mScrollPanel->goToTop();
+ }
+ handled = TRUE;
+ break;
+
+ case KEY_END:
+ mFilterSubString.clear();
+ if (mScrollPanel)
+ {
+ mScrollPanel->goToBottom();
+ }
+ handled = TRUE;
+ break;
+
+ case KEY_LEFT:
+ moveLeft(mask);
+ handled = TRUE;
+ break;
+
+ case KEY_RIGHT:
+ moveRight(mask);
+ handled = TRUE;
+ break;
+
+ case KEY_UP:
+ moveUp(mask);
+ handled = TRUE;
+ break;
+
+ case KEY_DOWN:
+ moveDown(mask);
+ handled = TRUE;
+ break;
+
+ default:
+ break;
+ }
+
+ if (handled)
+ {
+ mInventoryGalleryMenu->hide();
+ }
+
+ return handled;
+}
+
+void LLInventoryGallery::moveUp(MASK mask)
+{
+ mFilterSubString.clear();
+
+ if (mInventoryGalleryMenu && mSelectedItemIDs.size() > 0 && mItemsAddedCount > 1)
+ {
+ LLInventoryGalleryItem* item = mItemMap[mLastInteractedUUID];
+ if (item)
+ {
+ if (mask == MASK_NONE || mask == MASK_CONTROL)
+ {
+ S32 n = mItemIndexMap[item];
+ n -= mItemsInRow;
+ if (n >= 0)
+ {
+ item = mIndexToItemMap[n];
+ LLUUID item_id = item->getUUID();
+ if (mask == MASK_CONTROL)
+ {
+ addItemSelection(item_id, true);
+ }
+ else
+ {
+ changeItemSelection(item_id, true);
+ }
+ item->setFocus(TRUE);
+ claimEditHandler();
+ }
+ }
+ else if (mask == MASK_SHIFT)
+ {
+ S32 n = mItemIndexMap[item];
+ S32 target = llmax(0, n - mItemsInRow);
+ if (target != n)
+ {
+ item = mIndexToItemMap[target];
+ toggleSelectionRangeFromLast(item->getUUID());
+ item->setFocus(TRUE);
+ claimEditHandler();
+ }
+ }
+ }
+ }
+}
+
+void LLInventoryGallery::moveDown(MASK mask)
+{
+ mFilterSubString.clear();
+
+ if (mInventoryGalleryMenu && mSelectedItemIDs.size() > 0 && mItemsAddedCount > 1)
+ {
+ LLInventoryGalleryItem* item = mItemMap[mLastInteractedUUID];
+ if (item)
+ {
+ if (mask == MASK_NONE || mask == MASK_CONTROL)
+ {
+ S32 n = mItemIndexMap[item];
+ n += mItemsInRow;
+ if (n < mItemsAddedCount)
+ {
+ item = mIndexToItemMap[n];
+ LLUUID item_id = item->getUUID();
+ if (mask == MASK_CONTROL)
+ {
+ addItemSelection(item_id, true);
+ }
+ else
+ {
+ changeItemSelection(item_id, true);
+ }
+ item->setFocus(TRUE);
+ claimEditHandler();
+ }
+ }
+ else if (mask == MASK_SHIFT)
+ {
+ S32 n = mItemIndexMap[item];
+ S32 target = llmin(mItemsAddedCount - 1, n + mItemsInRow);
+ if (target != n)
+ {
+ item = mIndexToItemMap[target];
+ toggleSelectionRangeFromLast(item->getUUID());
+ item->setFocus(TRUE);
+ claimEditHandler();
+ }
+ }
+ }
+ }
+}
+
+void LLInventoryGallery::moveLeft(MASK mask)
+{
+ mFilterSubString.clear();
+
+ if (mInventoryGalleryMenu && mSelectedItemIDs.size() > 0 && mItemsAddedCount > 1)
+ {
+ LLInventoryGalleryItem* item = mItemMap[mLastInteractedUUID];
+ if (mask == MASK_SHIFT)
+ {
+ item = mItemMap[mLastInteractedUUID];
+ }
+ if (item)
+ {
+ // Might be better to get item from panel
+ S32 n = mItemIndexMap[item];
+ n--;
+ if (n < 0)
+ {
+ n = mItemsAddedCount - 1;
+ }
+ item = mIndexToItemMap[n];
+ LLUUID item_id = item->getUUID();
+ if (mask == MASK_CONTROL)
+ {
+ addItemSelection(item_id, true);
+ }
+ else if (mask == MASK_SHIFT)
+ {
+ if (item->isSelected())
+ {
+ toggleItemSelection(mLastInteractedUUID, true);
+ }
+ else
+ {
+ toggleItemSelection(item_id, true);
+ }
+ mLastInteractedUUID = item_id;
+ }
+ else
+ {
+ changeItemSelection(item_id, true);
+ }
+ item->setFocus(TRUE);
+ claimEditHandler();
+ }
+ }
+}
+
+void LLInventoryGallery::moveRight(MASK mask)
+{
+ mFilterSubString.clear();
+
+ if (mInventoryGalleryMenu && mSelectedItemIDs.size() > 0 && mItemsAddedCount > 1)
+ {
+ LLInventoryGalleryItem* item = mItemMap[mLastInteractedUUID];
+ if (item)
+ {
+ S32 n = mItemIndexMap[item];
+ n++;
+ if (n == mItemsAddedCount)
+ {
+ n = 0;
+ }
+ item = mIndexToItemMap[n];
+ LLUUID item_id = item->getUUID();
+ if (mask == MASK_CONTROL)
+ {
+ addItemSelection(item_id, true);
+ }
+ else if (mask == MASK_SHIFT)
+ {
+ if (item->isSelected())
+ {
+ toggleItemSelection(mLastInteractedUUID, true);
+ }
+ else
+ {
+ toggleItemSelection(item_id, true);
+ }
+ mLastInteractedUUID = item_id;
+ }
+ else
+ {
+ changeItemSelection(item_id, true);
+ }
+ item->setFocus(TRUE);
+ claimEditHandler();
+ }
+ }
+}
+
+void LLInventoryGallery::toggleSelectionRange(S32 start_idx, S32 end_idx)
+{
+ LLInventoryGalleryItem* item = NULL;
+ if (end_idx > start_idx)
+ {
+ for (S32 i = start_idx; i <= end_idx; i++)
+ {
+ item = mIndexToItemMap[i];
+ LLUUID item_id = item->getUUID();
+ toggleItemSelection(item_id, true);
+ }
+ }
+ else
+ {
+ for (S32 i = start_idx; i >= end_idx; i--)
+ {
+ item = mIndexToItemMap[i];
+ LLUUID item_id = item->getUUID();
+ toggleItemSelection(item_id, true);
+ }
+ }
+}
+
+void LLInventoryGallery::toggleSelectionRangeFromLast(const LLUUID target)
+{
+ if (mLastInteractedUUID == target)
+ {
+ return;
+ }
+ LLInventoryGalleryItem* last_item = mItemMap[mLastInteractedUUID];
+ LLInventoryGalleryItem* next_item = mItemMap[target];
+ if (last_item && next_item)
+ {
+ S32 last_idx = mItemIndexMap[last_item];
+ S32 next_idx = mItemIndexMap[next_item];
+ if (next_item->isSelected())
+ {
+ if (last_idx < next_idx)
+ {
+ toggleSelectionRange(last_idx, next_idx - 1);
+ }
+ else
+ {
+ toggleSelectionRange(last_idx, next_idx + 1);
+ }
+ }
+ else
+ {
+ if (last_idx < next_idx)
+ {
+ toggleSelectionRange(last_idx + 1, next_idx);
+ }
+ else
+ {
+ toggleSelectionRange(last_idx - 1, next_idx);
+ }
+ }
+ }
+ mLastInteractedUUID = next_item->getUUID();
+}
+
+void LLInventoryGallery::onFocusLost()
+{
+ // inventory no longer handles cut/copy/paste/delete
+ if (gEditMenuHandler == this)
+ {
+ gEditMenuHandler = NULL;
+ }
+
+ LLPanel::onFocusLost();
+
+ for (const LLUUID& id : mSelectedItemIDs)
+ {
+ if (mItemMap[id])
+ {
+ mItemMap[id]->setSelected(false);
+ }
+ }
+}
+
+void LLInventoryGallery::onFocusReceived()
+{
+ // inventory now handles cut/copy/paste/delete
+ gEditMenuHandler = this;
+
+ // Tab support, when tabbing into this view, select first item
+ if (mSelectedItemIDs.size() > 0)
+ {
+ LLInventoryGalleryItem* focus_item = NULL;
+ for (const LLUUID& id : mSelectedItemIDs)
+ {
+ if (mItemMap[id])
+ {
+ focus_item = mItemMap[id];
+ focus_item->setSelected(true);
+ }
+ }
+ if (focus_item)
+ {
+ focus_item->setFocus(TRUE);
+ }
+ }
+ else if (mIndexToItemMap.size() > 0 && mItemsToSelect.empty())
+ {
+ // choose any items from visible rect
+ S32 vert_offset = mScrollPanel->getDocPosVertical();
+ S32 panel_size = mVerticalGap + mRowPanelHeight;
+ S32 n = llclamp((S32)(vert_offset / panel_size) * mItemsInRow, 0, (S32)(mIndexToItemMap.size() - 1) );
+
+ LLInventoryGalleryItem* focus_item = mIndexToItemMap[n];
+ changeItemSelection(focus_item->getUUID(), true);
+ focus_item->setFocus(TRUE);
+ }
+
+ LLPanel::onFocusReceived();
+}
+
+void LLInventoryGallery::showContextMenu(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& item_id)
+{
+ if (mInventoryGalleryMenu && item_id.notNull())
+ {
+ if (std::find(mSelectedItemIDs.begin(), mSelectedItemIDs.end(), item_id) == mSelectedItemIDs.end())
+ {
+ changeItemSelection(item_id, false);
+ }
+ uuid_vec_t selected_uuids(mSelectedItemIDs.begin(), mSelectedItemIDs.end());
+ mInventoryGalleryMenu->show(ctrl, selected_uuids, x, y);
+ }
+}
+
+void LLInventoryGallery::changeItemSelection(const LLUUID& item_id, bool scroll_to_selection)
+{
+ for (const LLUUID& id : mSelectedItemIDs)
+ {
+ if (mItemMap[id])
+ {
+ mItemMap[id]->setSelected(FALSE);
+ }
+ }
+ mSelectedItemIDs.clear();
+ mItemsToSelect.clear();
+
+ if ((mItemMap.count(item_id) == 0) || mNeedsArrange)
+ {
+ mItemsToSelect.push_back(item_id);
+ return;
+ }
+ if (mSelectedItemIDs.size() == 1
+ && std::find(mSelectedItemIDs.begin(), mSelectedItemIDs.end(), item_id) != mSelectedItemIDs.end())
+ {
+ // Already selected
+ mLastInteractedUUID = item_id;
+ return;
+ }
+
+ if (mItemMap[item_id])
+ {
+ mItemMap[item_id]->setSelected(TRUE);
+ }
+ mSelectedItemIDs.push_back(item_id);
+ signalSelectionItemID(item_id);
+ mLastInteractedUUID = item_id;
+
+ if (scroll_to_selection)
+ {
+ scrollToShowItem(item_id);
+ }
+}
+
+void LLInventoryGallery::addItemSelection(const LLUUID& item_id, bool scroll_to_selection)
+{
+ if ((mItemMap.count(item_id) == 0) || mNeedsArrange)
+ {
+ mItemsToSelect.push_back(item_id);
+ return;
+ }
+ if (std::find(mSelectedItemIDs.begin(), mSelectedItemIDs.end(), item_id) != mSelectedItemIDs.end())
+ {
+ // Already selected
+ mLastInteractedUUID = item_id;
+ return;
+ }
+
+ if (mItemMap[item_id])
+ {
+ mItemMap[item_id]->setSelected(TRUE);
+ }
+ mSelectedItemIDs.push_back(item_id);
+ signalSelectionItemID(item_id);
+ mLastInteractedUUID = item_id;
+
+ if (scroll_to_selection)
+ {
+ scrollToShowItem(item_id);
+ }
+}
+
+bool LLInventoryGallery::toggleItemSelection(const LLUUID& item_id, bool scroll_to_selection)
+{
+ bool result = false;
+ if ((mItemMap.count(item_id) == 0) || mNeedsArrange)
+ {
+ mItemsToSelect.push_back(item_id);
+ return result;
+ }
+ selection_deque::iterator found = std::find(mSelectedItemIDs.begin(), mSelectedItemIDs.end(), item_id);
+ if (found != mSelectedItemIDs.end())
+ {
+ if (mItemMap[item_id])
+ {
+ mItemMap[item_id]->setSelected(FALSE);
+ }
+ mSelectedItemIDs.erase(found);
+ result = false;
+ }
+ else
+ {
+ if (mItemMap[item_id])
+ {
+ mItemMap[item_id]->setSelected(TRUE);
+ }
+ mSelectedItemIDs.push_back(item_id);
+ signalSelectionItemID(item_id);
+ result = true;
+ }
+ mLastInteractedUUID = item_id;
+
+ if (scroll_to_selection)
+ {
+ scrollToShowItem(item_id);
+ }
+ return result;
+}
+
+void LLInventoryGallery::scrollToShowItem(const LLUUID& item_id)
+{
+ LLInventoryGalleryItem* item = mItemMap[item_id];
+ if(item)
+ {
+ const LLRect visible_content_rect = mScrollPanel->getVisibleContentRect();
+
+ LLRect item_rect;
+ item->localRectToOtherView(item->getLocalRect(), &item_rect, mScrollPanel);
+ LLRect overlap_rect(item_rect);
+ overlap_rect.intersectWith(visible_content_rect);
+
+ //Scroll when the selected item is outside the visible area
+ if (overlap_rect.getHeight() + 5 < item->getRect().getHeight())
+ {
+ LLRect content_rect = mScrollPanel->getContentWindowRect();
+ LLRect constraint_rect;
+ constraint_rect.setOriginAndSize(0, 0, content_rect.getWidth(), content_rect.getHeight());
+
+ LLRect item_doc_rect;
+ item->localRectToOtherView(item->getLocalRect(), &item_doc_rect, mGalleryPanel);
+
+ mScrollPanel->scrollToShowRect( item_doc_rect, constraint_rect );
+ }
+ }
+}
+
+LLInventoryGalleryItem* LLInventoryGallery::getFirstSelectedItem()
+{
+ if (mSelectedItemIDs.size() > 0)
+ {
+ selection_deque::iterator iter = mSelectedItemIDs.begin();
+ return mItemMap[*iter];
+ }
+ return NULL;
+}
+
+void LLInventoryGallery::copy()
+{
+ if (!getVisible() || !getEnabled())
+ {
+ return;
+ }
+
+ LLClipboard::instance().reset();
+
+ for (const LLUUID& id : mSelectedItemIDs)
+ {
+ LLClipboard::instance().addToClipboard(id);
+ }
+ mFilterSubString.clear();
+}
+
+BOOL LLInventoryGallery::canCopy() const
+{
+ if (!getVisible() || !getEnabled() || mSelectedItemIDs.empty())
+ {
+ return FALSE;
+ }
+
+ for (const LLUUID& id : mSelectedItemIDs)
+ {
+ if (!isItemCopyable(id))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+void LLInventoryGallery::cut()
+{
+ if (!getVisible() || !getEnabled())
+ {
+ return;
+ }
+
+ // clear the inventory clipboard
+ LLClipboard::instance().reset();
+ LLClipboard::instance().setCutMode(true);
+ for (const LLUUID& id : mSelectedItemIDs)
+ {
+ // todo: fade out selected item
+ LLClipboard::instance().addToClipboard(id);
+ }
+
+ mFilterSubString.clear();
+}
+
+BOOL LLInventoryGallery::canCut() const
+{
+ if (!getVisible() || !getEnabled() || mSelectedItemIDs.empty())
+ {
+ return FALSE;
+ }
+
+ for (const LLUUID& id : mSelectedItemIDs)
+ {
+ LLViewerInventoryCategory* cat = gInventory.getCategory(id);
+ if (cat)
+ {
+ if (!get_is_category_removable(&gInventory, id))
+ {
+ return FALSE;
+ }
+ }
+ else if (!get_is_item_removable(&gInventory, id))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+void LLInventoryGallery::paste()
+{
+ if (!LLClipboard::instance().hasContents())
+ {
+ return;
+ }
+
+ const LLUUID& marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS);
+ if (mSelectedItemIDs.size() == 1 && gInventory.isObjectDescendentOf(*mSelectedItemIDs.begin(), marketplacelistings_id))
+ {
+ return;
+ }
+
+ bool is_cut_mode = LLClipboard::instance().isCutMode();
+ std::vector<LLUUID> objects;
+ LLClipboard::instance().pasteFromClipboard(objects);
+
+ bool paste_into_root = mSelectedItemIDs.empty();
+ for (LLUUID& dest : mSelectedItemIDs)
+ {
+ LLInventoryObject* obj = gInventory.getObject(dest);
+ if (!obj || (obj->getType() != LLAssetType::AT_CATEGORY))
+ {
+ paste_into_root = true;
+ continue;
+ }
+
+ paste(dest, objects, is_cut_mode, marketplacelistings_id);
+ is_cut_mode = false;
+ }
+
+ if (paste_into_root)
+ {
+ for (const LLUUID& id : mSelectedItemIDs)
+ {
+ if (mItemMap[id])
+ {
+ mItemMap[id]->setSelected(FALSE);
+ }
+ }
+ mSelectedItemIDs.clear();
+
+ paste(mFolderID, objects, is_cut_mode, marketplacelistings_id);
+ }
+
+ LLClipboard::instance().setCutMode(false);
+}
+
+void LLInventoryGallery::paste(const LLUUID& dest,
+ std::vector<LLUUID>& objects,
+ bool is_cut_mode,
+ const LLUUID& marketplacelistings_id)
+{
+ LLHandle<LLPanel> handle = getHandle();
+ std::function <void(const LLUUID)> on_copy_callback = NULL;
+ LLPointer<LLInventoryCallback> cb = NULL;
+ if (dest == mFolderID)
+ {
+ on_copy_callback = [handle](const LLUUID& inv_item)
+ {
+ LLInventoryGallery* panel = (LLInventoryGallery*)handle.get();
+ if (panel)
+ {
+ // Scroll to pasted item and highlight it
+ // Should it only highlight the last one?
+ panel->addItemSelection(inv_item, true);
+ }
+ };
+ cb = new LLBoostFuncInventoryCallback(on_copy_callback);
+ }
+
+ for (std::vector<LLUUID>::const_iterator iter = objects.begin(); iter != objects.end(); ++iter)
+ {
+ const LLUUID& item_id = (*iter);
+ if (gInventory.isObjectDescendentOf(item_id, marketplacelistings_id) && (LLMarketplaceData::instance().isInActiveFolder(item_id) ||
+ LLMarketplaceData::instance().isListedAndActive(item_id)))
+ {
+ return;
+ }
+ LLViewerInventoryCategory* cat = gInventory.getCategory(item_id);
+ if (cat)
+ {
+ if (is_cut_mode)
+ {
+ gInventory.changeCategoryParent(cat, dest, false);
+ if (dest == mFolderID)
+ {
+ // Don't select immediately, wait for item to arrive
+ mItemsToSelect.push_back(item_id);
+ }
+ }
+ else
+ {
+ copy_inventory_category(&gInventory, cat, dest, LLUUID::null, false, on_copy_callback);
+ }
+ }
+ else
+ {
+ LLViewerInventoryItem* item = gInventory.getItem(item_id);
+ if (item)
+ {
+ if (is_cut_mode)
+ {
+ gInventory.changeItemParent(item, dest, false);
+ if (dest == mFolderID)
+ {
+ // Don't select immediately, wait for item to arrive
+ mItemsToSelect.push_back(item_id);
+ }
+ }
+ else
+ {
+ if (item->getIsLinkType())
+ {
+ link_inventory_object(dest, item_id, cb);
+ }
+ else
+ {
+ copy_inventory_item(
+ gAgent.getID(),
+ item->getPermissions().getOwner(),
+ item->getUUID(),
+ dest,
+ std::string(),
+ cb);
+ }
+ }
+ }
+ }
+ }
+
+ LLClipboard::instance().setCutMode(false);
+}
+
+BOOL LLInventoryGallery::canPaste() const
+{
+ // Return FALSE on degenerated cases: empty clipboard, no inventory, no agent
+ if (!LLClipboard::instance().hasContents())
+ {
+ return FALSE;
+ }
+
+ // In cut mode, whatever is on the clipboard is always pastable
+ if (LLClipboard::instance().isCutMode())
+ {
+ return TRUE;
+ }
+
+ // In normal mode, we need to check each element of the clipboard to know if we can paste or not
+ std::vector<LLUUID> objects;
+ LLClipboard::instance().pasteFromClipboard(objects);
+ S32 count = objects.size();
+ for (S32 i = 0; i < count; i++)
+ {
+ const LLUUID& item_id = objects.at(i);
+
+ // Each item must be copyable to be pastable
+ if (!isItemCopyable(item_id))
+ {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+void LLInventoryGallery::onDelete(const LLSD& notification, const LLSD& response, const selection_deque selected_ids)
+{
+ S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
+ if (option == 0)
+ {
+ for (const LLUUID& id : selected_ids)
+ {
+ LLInventoryObject* obj = gInventory.getObject(id);
+ if (!obj)
+ {
+ return;
+ }
+ if (obj->getType() == LLAssetType::AT_CATEGORY)
+ {
+ if (get_is_category_removable(&gInventory, id))
+ {
+ gInventory.removeCategory(id);
+ }
+ }
+ else
+ {
+ if (get_is_item_removable(&gInventory, id))
+ {
+ gInventory.removeItem(id);
+ }
+ }
+ }
+ }
+}
+
+void LLInventoryGallery::deleteSelection()
+{
+ if (!LLInventoryAction::sDeleteConfirmationDisplayed) // ask for the confirmation at least once per session
+ {
+ LLNotifications::instance().setIgnored("DeleteItems", false);
+ LLInventoryAction::sDeleteConfirmationDisplayed = true;
+ }
+
+ LLSD args;
+ args["QUESTION"] = LLTrans::getString("DeleteItem");
+ LLNotificationsUtil::add("DeleteItems", args, LLSD(), boost::bind(&LLInventoryGallery::onDelete, _1, _2, mSelectedItemIDs));
+}
+
+bool LLInventoryGallery::canDeleteSelection()
+{
+ if (mSelectedItemIDs.empty())
+ {
+ return false;
+ }
+
+ const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH);
+ if (mFolderID == trash_id || gInventory.isObjectDescendentOf(mFolderID, trash_id))
+ {
+ return false;
+ }
+
+ for (const LLUUID& id : mSelectedItemIDs)
+ {
+ LLViewerInventoryCategory* cat = gInventory.getCategory(id);
+ if (cat)
+ {
+ if (!get_is_category_removable(&gInventory, id))
+ {
+ return false;
+ }
+ }
+ else if (!get_is_item_removable(&gInventory, id))
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void LLInventoryGallery::pasteAsLink()
+{
+ if (!LLClipboard::instance().hasContents())
+ {
+ return;
+ }
+
+ const LLUUID& current_outfit_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT);
+ const LLUUID& marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS);
+ const LLUUID& my_outifts_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS);
+
+ std::vector<LLUUID> objects;
+ LLClipboard::instance().pasteFromClipboard(objects);
+
+ bool paste_into_root = mSelectedItemIDs.empty();
+ for (LLUUID& dest : mSelectedItemIDs)
+ {
+ LLInventoryObject* obj = gInventory.getObject(dest);
+ if (!obj || obj->getType() != LLAssetType::AT_CATEGORY)
+ {
+ paste_into_root = true;
+ continue;
+ }
+
+ pasteAsLink(dest, objects, current_outfit_id, marketplacelistings_id, my_outifts_id);
+ }
+
+ if (paste_into_root)
+ {
+ for (const LLUUID& id : mSelectedItemIDs)
+ {
+ if (mItemMap[id])
+ {
+ mItemMap[id]->setSelected(FALSE);
+ }
+ }
+ mSelectedItemIDs.clear();
+
+ pasteAsLink(mFolderID, objects, current_outfit_id, marketplacelistings_id, my_outifts_id);
+ }
+
+ LLClipboard::instance().setCutMode(false);
+}
+
+void LLInventoryGallery::pasteAsLink(const LLUUID& dest,
+ std::vector<LLUUID>& objects,
+ const LLUUID& current_outfit_id,
+ const LLUUID& marketplacelistings_id,
+ const LLUUID& my_outifts_id)
+{
+ const BOOL move_is_into_current_outfit = (dest == current_outfit_id);
+ const BOOL move_is_into_my_outfits = (dest == my_outifts_id) || gInventory.isObjectDescendentOf(dest, my_outifts_id);
+ const BOOL move_is_into_marketplacelistings = gInventory.isObjectDescendentOf(dest, marketplacelistings_id);
+
+ if (move_is_into_marketplacelistings || move_is_into_current_outfit || move_is_into_my_outfits)
+ {
+ return;
+ }
+
+ LLPointer<LLInventoryCallback> cb = NULL;
+ if (dest == mFolderID)
+ {
+ LLHandle<LLPanel> handle = getHandle();
+ std::function <void(const LLUUID)> on_link_callback = [handle](const LLUUID& inv_item)
+ {
+ LLInventoryGallery* panel = (LLInventoryGallery*)handle.get();
+ if (panel)
+ {
+ // Scroll to pasted item and highlight it
+ // Should it only highlight the last one?
+ panel->addItemSelection(inv_item, true);
+ }
+ };
+ cb = new LLBoostFuncInventoryCallback(on_link_callback);
+ }
+
+ for (std::vector<LLUUID>::const_iterator iter = objects.begin();
+ iter != objects.end();
+ ++iter)
+ {
+ const LLUUID& object_id = (*iter);
+ if (LLConstPointer<LLInventoryObject> link_obj = gInventory.getObject(object_id))
+ {
+ link_inventory_object(dest, link_obj, cb);
+ }
+ }
+}
+
+void LLInventoryGallery::claimEditHandler()
+{
+ gEditMenuHandler = this;
+}
+
+void LLInventoryGallery::resetEditHandler()
+{
+ if (gEditMenuHandler == this)
+ {
+ gEditMenuHandler = NULL;
+ }
+}
+
+bool LLInventoryGallery::isItemCopyable(const LLUUID & item_id)
+{
+ const LLInventoryCategory* cat = gInventory.getCategory(item_id);
+ if (cat)
+ {
+ // Folders are copyable if items in them are, recursively, copyable.
+ // Get the content of the folder
+ LLInventoryModel::cat_array_t* cat_array;
+ LLInventoryModel::item_array_t* item_array;
+ gInventory.getDirectDescendentsOf(item_id, cat_array, item_array);
+
+ // Check the items
+ LLInventoryModel::item_array_t item_array_copy = *item_array;
+ for (LLInventoryModel::item_array_t::iterator iter = item_array_copy.begin(); iter != item_array_copy.end(); iter++)
+ {
+ LLInventoryItem* item = *iter;
+ if (!isItemCopyable(item->getUUID()))
+ {
+ return false;
+ }
+ }
+
+ // Check the folders
+ LLInventoryModel::cat_array_t cat_array_copy = *cat_array;
+ for (LLInventoryModel::cat_array_t::iterator iter = cat_array_copy.begin(); iter != cat_array_copy.end(); iter++)
+ {
+ LLViewerInventoryCategory* category = *iter;
+ if (!isItemCopyable(category->getUUID()))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ LLViewerInventoryItem* item = gInventory.getItem(item_id);
+ if (item)
+ {
+ // Can't copy worn objects.
+ // Worn objects are tied to their inworld conterparts
+ // Copy of modified worn object will return object with obsolete asset and inventory
+ if (get_is_item_worn(item_id))
+ {
+ return false;
+ }
+
+ static LLCachedControl<bool> inventory_linking(gSavedSettings, "InventoryLinking", true);
+ return (item->getIsLinkType() && inventory_linking)
+ || item->getPermissions().allowCopyBy(gAgent.getID());
+ }
+
+ return false;
+}
+
+void LLInventoryGallery::updateMessageVisibility()
+{
+
+ mMessageTextBox->setVisible(mItems.empty());
+ if(mItems.empty())
+ {
+ mMessageTextBox->setText(hasDescendents(mFolderID) ? LLTrans::getString("InventorySingleFolderEmpty") : LLTrans::getString("InventorySingleFolderNoMatches"));
+ }
+
+ mScrollPanel->setVisible(!mItems.empty());
+}
+
+void LLInventoryGallery::refreshList(const LLUUID& category_id)
+{
+ LLInventoryModel::cat_array_t* cat_array;
+ LLInventoryModel::item_array_t* item_array;
+
+ gInventory.getDirectDescendentsOf(category_id, cat_array, item_array);
+ uuid_vec_t vadded;
+ uuid_vec_t vremoved;
+
+ // Create added and removed items vectors.
+ computeDifference(*cat_array, *item_array, vadded, vremoved);
+
+ // Handle added tabs.
+ for (uuid_vec_t::const_iterator iter = vadded.begin();
+ iter != vadded.end();
+ ++iter)
+ {
+ const LLUUID cat_id = (*iter);
+ updateAddedItem(cat_id);
+ mNeedsArrange = true;
+ }
+
+ // Handle removed tabs.
+ for (uuid_vec_t::const_iterator iter = vremoved.begin(); iter != vremoved.end(); ++iter)
+ {
+ const LLUUID cat_id = (*iter);
+ updateRemovedItem(cat_id);
+ }
+
+ const LLInventoryModel::changed_items_t& changed_items = gInventory.getChangedIDs();
+ for (LLInventoryModel::changed_items_t::const_iterator items_iter = changed_items.begin();
+ items_iter != changed_items.end();
+ ++items_iter)
+ {
+ LLInventoryObject* obj = gInventory.getObject(*items_iter);
+ if(!obj)
+ {
+ return;
+ }
+
+ updateChangedItemName(*items_iter, obj->getName());
+ mNeedsArrange = true;
+ }
+
+ if(mNeedsArrange || !mItemsToSelect.empty())
+ {
+ // Don't scroll to target/arrange immediately
+ // since more updates might be pending
+ gIdleCallbacks.addFunction(onIdle, (void*)this);
+ }
+ updateMessageVisibility();
+}
+
+void LLInventoryGallery::computeDifference(
+ const LLInventoryModel::cat_array_t vcats,
+ const LLInventoryModel::item_array_t vitems,
+ uuid_vec_t& vadded,
+ uuid_vec_t& vremoved)
+{
+ uuid_vec_t vnew;
+ // Creating a vector of newly collected UUIDs.
+ for (LLInventoryModel::cat_array_t::const_iterator iter = vcats.begin();
+ iter != vcats.end();
+ iter++)
+ {
+ vnew.push_back((*iter)->getUUID());
+ }
+ for (LLInventoryModel::item_array_t::const_iterator iter = vitems.begin();
+ iter != vitems.end();
+ iter++)
+ {
+ vnew.push_back((*iter)->getUUID());
+ }
+
+ uuid_vec_t vcur;
+ getCurrentCategories(vcur);
+ std::copy(mItemBuildQuery.begin(), mItemBuildQuery.end(), std::back_inserter(vcur));
+
+ LLCommonUtils::computeDifference(vnew, vcur, vadded, vremoved);
+}
+
+void LLInventoryGallery::onCOFChanged()
+{
+ LLInventoryModel::cat_array_t cat_array;
+ LLInventoryModel::item_array_t item_array;
+
+ gInventory.collectDescendents(
+ LLAppearanceMgr::instance().getCOF(),
+ cat_array,
+ item_array,
+ LLInventoryModel::EXCLUDE_TRASH);
+
+ uuid_vec_t vnew;
+ uuid_vec_t vadded;
+ uuid_vec_t vremoved;
+
+ for (LLInventoryModel::item_array_t::const_iterator iter = item_array.begin();
+ iter != item_array.end();
+ ++iter)
+ {
+ vnew.push_back((*iter)->getLinkedUUID());
+ }
+
+ // We need to update only items that were added or removed from COF.
+ LLCommonUtils::computeDifference(vnew, mCOFLinkedItems, vadded, vremoved);
+
+ mCOFLinkedItems = vnew;
+
+ for (uuid_vec_t::const_iterator iter = vadded.begin();
+ iter != vadded.end();
+ ++iter)
+ {
+ updateWornItem(*iter, true);
+ }
+
+ for (uuid_vec_t::const_iterator iter = vremoved.begin(); iter != vremoved.end(); ++iter)
+ {
+ updateWornItem(*iter, false);
+ }
+}
+
+void LLInventoryGallery::onGesturesChanged()
+{
+ uuid_vec_t vnew;
+ uuid_vec_t vadded;
+ uuid_vec_t vremoved;
+
+ const LLGestureMgr::item_map_t& active_gestures = LLGestureMgr::instance().getActiveGestures();
+ for (LLGestureMgr::item_map_t::const_iterator iter = active_gestures.begin();
+ iter != active_gestures.end();
+ ++iter)
+ {
+ vnew.push_back(iter->first);
+ }
+
+ LLCommonUtils::computeDifference(vnew, mActiveGestures, vadded, vremoved);
+
+ mActiveGestures = vnew;
+
+ for (uuid_vec_t::const_iterator iter = vadded.begin();
+ iter != vadded.end();
+ ++iter)
+ {
+ updateWornItem(*iter, true);
+ }
+
+ for (uuid_vec_t::const_iterator iter = vremoved.begin(); iter != vremoved.end(); ++iter)
+ {
+ updateWornItem(*iter, false);
+ }
+}
+
+void LLInventoryGallery::deselectItem(const LLUUID& category_id)
+{
+ // Reset selection if the item is selected.
+ LLInventoryGalleryItem* item = mItemMap[category_id];
+ if (item && item->isSelected())
+ {
+ mItemMap[category_id]->setSelected(FALSE);
+ setFocus(true);
+ // Todo: support multiselect
+ // signalSelectionItemID(LLUUID::null);
+ }
+
+ selection_deque::iterator found = std::find(mSelectedItemIDs.begin(), mSelectedItemIDs.end(), category_id);
+ if (found != mSelectedItemIDs.end())
+ {
+ mSelectedItemIDs.erase(found);
+ }
+}
+
+void LLInventoryGallery::clearSelection()
+{
+ for (const LLUUID& id: mSelectedItemIDs)
+ {
+ if (mItemMap[id])
+ {
+ mItemMap[id]->setSelected(FALSE);
+ }
+ }
+ if (!mSelectedItemIDs.empty())
+ {
+ mSelectedItemIDs.clear();
+ // BUG: wrong, item can be null
+ signalSelectionItemID(LLUUID::null);
+ }
+}
+
+void LLInventoryGallery::signalSelectionItemID(const LLUUID& category_id)
+{
+ mSelectionChangeSignal(category_id);
+}
+
+boost::signals2::connection LLInventoryGallery::setSelectionChangeCallback(selection_change_callback_t cb)
+{
+ return mSelectionChangeSignal.connect(cb);
+}
+
+LLUUID LLInventoryGallery::getFirstSelectedItemID()
+{
+ if (mSelectedItemIDs.size() > 0)
+ {
+ return *mSelectedItemIDs.begin();
+ }
+ return LLUUID::null;
+}
+
+LLUUID LLInventoryGallery::getOutfitImageID(LLUUID outfit_id)
+{
+ LLUUID thumbnail_id;
+ LLViewerInventoryCategory* cat = gInventory.getCategory(outfit_id);
+ if (cat && cat->getPreferredType() == LLFolderType::FT_OUTFIT)
+ {
+ LLInventoryModel::cat_array_t cats;
+ LLInventoryModel::item_array_t items;
+ // Not LLIsOfAssetType, because we allow links
+ LLIsTextureType f;
+ gInventory.getDirectDescendentsOf(outfit_id, cats, items, f);
+
+ // Exactly one texture found => show the texture as thumbnail
+ if (1 == items.size())
+ {
+ LLViewerInventoryItem* item = items.front();
+ if (item && item->getIsLinkType())
+ {
+ item = item->getLinkedItem();
+ }
+ if (item)
+ {
+ thumbnail_id = item->getAssetUUID();
+ }
+ }
+ }
+ return thumbnail_id;
+}
+
+boost::signals2::connection LLInventoryGallery::setRootChangedCallback(callback_t cb)
+{
+ return mRootChangedSignal.connect(cb);
+}
+
+void LLInventoryGallery::onForwardFolder()
+{
+ if(isForwardAvailable())
+ {
+ mBackwardFolders.push_back(mFolderID);
+ mFolderID = mForwardFolders.back();
+ mForwardFolders.pop_back();
+ dirtyRootFolder();
+ }
+}
+
+void LLInventoryGallery::onBackwardFolder()
+{
+ if(isBackwardAvailable())
+ {
+ mForwardFolders.push_back(mFolderID);
+ mFolderID = mBackwardFolders.back();
+ mBackwardFolders.pop_back();
+ dirtyRootFolder();
+ }
+}
+
+void LLInventoryGallery::clearNavigationHistory()
+{
+ mForwardFolders.clear();
+ mBackwardFolders.clear();
+}
+
+bool LLInventoryGallery::isBackwardAvailable()
+{
+ return (!mBackwardFolders.empty() && (mFolderID != mBackwardFolders.back()));
+}
+
+bool LLInventoryGallery::isForwardAvailable()
+{
+ return (!mForwardFolders.empty() && (mFolderID != mForwardFolders.back()));
+}
+
+BOOL LLInventoryGallery::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop,
+ EDragAndDropType cargo_type, void* cargo_data,
+ EAcceptance* accept, std::string& tooltip_msg)
+{
+ // have children handle it first
+ BOOL handled = LLView::handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data,
+ accept, tooltip_msg);
+
+ // when drop is not handled by child, it should be handled by the root folder .
+ if (!handled || (*accept == ACCEPT_NO))
+ {
+ handled = baseHandleDragAndDrop(mFolderID, drop, cargo_type, cargo_data, accept, tooltip_msg);
+ }
+
+ return handled;
+}
+
+void LLInventoryGallery::startDrag()
+{
+ std::vector<EDragAndDropType> types;
+ uuid_vec_t ids;
+ LLToolDragAndDrop::ESource src = LLToolDragAndDrop::SOURCE_AGENT;
+ for (LLUUID& selected_id : mSelectedItemIDs)
+ {
+ const LLInventoryItem* item = gInventory.getItem(selected_id);
+ if (item)
+ {
+ if (item->getPermissions().getOwner() == ALEXANDRIA_LINDEN_ID)
+ {
+ src = LLToolDragAndDrop::SOURCE_LIBRARY;
+ }
+
+ EDragAndDropType type = LLViewerAssetType::lookupDragAndDropType(item->getType());
+ types.push_back(type);
+ ids.push_back(selected_id);
+ }
+
+ const LLViewerInventoryCategory* cat = gInventory.getCategory(selected_id);
+ if (cat && gInventory.isObjectDescendentOf(selected_id, gInventory.getRootFolderID())
+ && !LLFolderType::lookupIsProtectedType((cat)->getPreferredType()))
+ {
+ if (cat->getOwnerID() == ALEXANDRIA_LINDEN_ID)
+ {
+ src = LLToolDragAndDrop::SOURCE_LIBRARY;
+ }
+
+ EDragAndDropType type = LLViewerAssetType::lookupDragAndDropType(cat->getType());
+ types.push_back(type);
+ ids.push_back(selected_id);
+ }
+ }
+ LLToolDragAndDrop::getInstance()->beginMultiDrag(types, ids, LLToolDragAndDrop::SOURCE_AGENT);
+}
+
+bool LLInventoryGallery::areViewsInitialized()
+{
+ return mGalleryCreated && mItemBuildQuery.empty();
+}
+
+bool LLInventoryGallery::hasDescendents(const LLUUID& cat_id)
+{
+ LLInventoryModel::cat_array_t* cats;
+ LLInventoryModel::item_array_t* items;
+ gInventory.getDirectDescendentsOf(cat_id, cats, items);
+
+ return (cats->empty() && items->empty());
+}
+
+bool LLInventoryGallery::checkAgainstFilterType(const LLUUID& object_id)
+{
+ const LLInventoryObject *object = gInventory.getObject(object_id);
+ if(!object) return false;
+
+ LLInventoryType::EType object_type = LLInventoryType::IT_CATEGORY;
+ LLInventoryItem* inv_item = gInventory.getItem(object_id);
+ if (inv_item)
+ {
+ object_type = inv_item->getInventoryType();
+ }
+ const U32 filterTypes = mFilter->getFilterTypes();
+
+ if ((filterTypes & LLInventoryFilter::FILTERTYPE_OBJECT) && inv_item)
+ {
+ switch (object_type)
+ {
+ case LLInventoryType::IT_NONE:
+ // If it has no type, pass it, unless it's a link.
+ if (object && object->getIsLinkType())
+ {
+ return false;
+ }
+ break;
+ case LLInventoryType::IT_UNKNOWN:
+ {
+ // Unknows are only shown when we show every type.
+ // Unknows are 255 and won't fit in 64 bits.
+ if (mFilter->getFilterObjectTypes() != 0xffffffffffffffffULL)
+ {
+ return false;
+ }
+ break;
+ }
+ default:
+ if ((1LL << object_type & mFilter->getFilterObjectTypes()) == U64(0))
+ {
+ return false;
+ }
+ break;
+ }
+ }
+
+ if (filterTypes & LLInventoryFilter::FILTERTYPE_DATE)
+ {
+ const U16 HOURS_TO_SECONDS = 3600;
+ time_t earliest = time_corrected() - mFilter->getHoursAgo() * HOURS_TO_SECONDS;
+
+ if (mFilter->getMinDate() > time_min() && mFilter->getMinDate() < earliest)
+ {
+ earliest = mFilter->getMinDate();
+ }
+ else if (!mFilter->getHoursAgo())
+ {
+ earliest = 0;
+ }
+
+ if (LLInventoryFilter::FILTERDATEDIRECTION_NEWER == mFilter->getDateSearchDirection() || mFilter->isSinceLogoff())
+ {
+ if (object->getCreationDate() < earliest ||
+ object->getCreationDate() > mFilter->getMaxDate())
+ return false;
+ }
+ else
+ {
+ if (object->getCreationDate() > earliest ||
+ object->getCreationDate() > mFilter->getMaxDate())
+ return false;
+ }
+ }
+ return true;
+}
+
+bool LLInventoryGallery::hasVisibleItems()
+{
+ return mItemsAddedCount > 0;
+}
+
+void LLInventoryGallery::handleModifiedFilter()
+{
+ if(mFilter->isModified())
+ {
+ reArrangeRows();
+ }
+}
+
+void LLInventoryGallery::setSortOrder(U32 order, bool update)
+{
+ bool dirty = (mSortOrder != order);
+
+ mSortOrder = order;
+ if(update && dirty)
+ {
+ mNeedsArrange = true;
+ gIdleCallbacks.addFunction(onIdle, (void*)this);
+ }
+}
+//-----------------------------
+// LLInventoryGalleryItem
+//-----------------------------
+
+static LLDefaultChildRegistry::Register<LLInventoryGalleryItem> r("inventory_gallery_item");
+
+LLInventoryGalleryItem::LLInventoryGalleryItem(const Params& p)
+ : LLPanel(p),
+ mSelected(false),
+ mDefaultImage(true),
+ mItemName(""),
+ mWornSuffix(""),
+ mPermSuffix(""),
+ mUUID(LLUUID()),
+ mIsFolder(true),
+ mIsLink(false),
+ mHidden(false),
+ mGallery(NULL),
+ mType(LLAssetType::AT_NONE),
+ mSortGroup(SG_ITEM),
+ mCutGeneration(0),
+ mSelectedForCut(false)
+{
+ buildFromFile("panel_inventory_gallery_item.xml");
+}
+
+LLInventoryGalleryItem::~LLInventoryGalleryItem()
+{
+}
+
+BOOL LLInventoryGalleryItem::postBuild()
+{
+ mNameText = getChild<LLTextBox>("item_name");
+ mTextBgPanel = getChild<LLPanel>("text_bg_panel");
+
+ return TRUE;
+}
+
+void LLInventoryGalleryItem::setType(LLAssetType::EType type, LLInventoryType::EType inventory_type, U32 flags, bool is_link)
+{
+ mType = type;
+ mIsFolder = (mType == LLAssetType::AT_CATEGORY);
+ mIsLink = is_link;
+
+ std::string icon_name = LLInventoryIcon::getIconName(mType, inventory_type, flags);
+ if(mIsFolder)
+ {
+ mSortGroup = SG_NORMAL_FOLDER;
+ LLUUID folder_id = mUUID;
+ if(mIsLink)
+ {
+ LLInventoryObject* obj = gInventory.getObject(mUUID);
+ if (obj)
+ {
+ folder_id = obj->getLinkedUUID();
+ }
+ }
+ LLViewerInventoryCategory* cat = gInventory.getCategory(folder_id);
+ if (cat)
+ {
+ LLFolderType::EType preferred_type = cat->getPreferredType();
+ icon_name = LLViewerFolderType::lookupIconName(preferred_type);
+
+ if (preferred_type == LLFolderType::FT_TRASH)
+ {
+ mSortGroup = SG_TRASH_FOLDER;
+ }
+ else if(LLFolderType::lookupIsProtectedType(cat->getPreferredType()))
+ {
+ mSortGroup = SG_SYSTEM_FOLDER;
+ }
+ }
+ }
+ else
+ {
+ const LLInventoryItem *item = gInventory.getItem(mUUID);
+ if(item && (LLAssetType::AT_CALLINGCARD != item->getType()) && !mIsLink)
+ {
+ std::string delim(" --");
+ bool copy = item->getPermissions().allowCopyBy(gAgent.getID());
+ if (!copy)
+ {
+ mPermSuffix += delim;
+ mPermSuffix += LLTrans::getString("no_copy_lbl");
+ }
+ bool mod = item->getPermissions().allowModifyBy(gAgent.getID());
+ if (!mod)
+ {
+ mPermSuffix += mPermSuffix.empty() ? delim : ",";
+ mPermSuffix += LLTrans::getString("no_modify_lbl");
+ }
+ bool xfer = item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID());
+ if (!xfer)
+ {
+ mPermSuffix += mPermSuffix.empty() ? delim : ",";
+ mPermSuffix += LLTrans::getString("no_transfer_lbl");
+ }
+ }
+ }
+
+ getChild<LLIconCtrl>("item_type")->setValue(icon_name);
+ getChild<LLIconCtrl>("link_overlay")->setVisible(is_link);
+}
+
+void LLInventoryGalleryItem::setThumbnail(LLUUID id)
+{
+ mDefaultImage = id.isNull();
+ if(mDefaultImage)
+ {
+ getChild<LLThumbnailCtrl>("preview_thumbnail")->clearTexture();
+ }
+ else
+ {
+ getChild<LLThumbnailCtrl>("preview_thumbnail")->setValue(id);
+ }
+}
+
+void LLInventoryGalleryItem::draw()
+{
+ if (isFadeItem())
+ {
+ // Fade out to indicate it's being cut
+ LLViewDrawContext context(0.5f);
+ LLPanel::draw();
+ }
+ else
+ {
+ LLPanel::draw();
+
+ // Draw border
+ LLUIColor border_color = LLUIColorTable::instance().getColor(mSelected ? "MenuItemHighlightBgColor" : "TextFgTentativeColor", LLColor4::white);
+ LLRect border = getChildView("preview_thumbnail")->getRect();
+ border.mRight = border.mRight + 1;
+ border.mTop = border.mTop + 1;
+ gl_rect_2d(border, border_color.get(), FALSE);
+ }
+}
+
+void LLInventoryGalleryItem::setItemName(std::string name)
+{
+ mItemName = name;
+ updateNameText();
+}
+
+void LLInventoryGalleryItem::setSelected(bool value)
+{
+ mSelected = value;
+ mTextBgPanel->setBackgroundVisible(value);
+
+ if(mSelected)
+ {
+ LLViewerInventoryItem* item = gInventory.getItem(mUUID);
+ if(item && !item->isFinished())
+ {
+ LLInventoryModelBackgroundFetch::instance().start(mUUID, false);
+ }
+ }
+}
+
+BOOL LLInventoryGalleryItem::handleMouseDown(S32 x, S32 y, MASK mask)
+{
+ // call changeItemSelection directly, before setFocus
+ // to avoid autoscroll from LLInventoryGallery::onFocusReceived()
+ if (mask == MASK_CONTROL)
+ {
+ mGallery->addItemSelection(mUUID, false);
+ }
+ else if (mask == MASK_SHIFT)
+ {
+ mGallery->toggleSelectionRangeFromLast(mUUID);
+ }
+ else
+ {
+ mGallery->changeItemSelection(mUUID, false);
+ }
+ setFocus(TRUE);
+ mGallery->claimEditHandler();
+
+ gFocusMgr.setMouseCapture(this);
+ S32 screen_x;
+ S32 screen_y;
+ localPointToScreen(x, y, &screen_x, &screen_y );
+ LLToolDragAndDrop::getInstance()->setDragStart(screen_x, screen_y);
+ return TRUE;
+}
+
+BOOL LLInventoryGalleryItem::handleRightMouseDown(S32 x, S32 y, MASK mask)
+{
+ if (!isSelected())
+ {
+ mGallery->changeItemSelection(mUUID, false);
+ }
+ else
+ {
+ // refresh last interacted
+ mGallery->addItemSelection(mUUID, false);
+ }
+ setFocus(TRUE);
+ mGallery->claimEditHandler();
+ mGallery->showContextMenu(this, x, y, mUUID);
+
+ LLUICtrl::handleRightMouseDown(x, y, mask);
+ return TRUE;
+}
+
+BOOL LLInventoryGalleryItem::handleMouseUp(S32 x, S32 y, MASK mask)
+{
+ if(hasMouseCapture())
+ {
+ gFocusMgr.setMouseCapture(NULL);
+ return TRUE;
+ }
+ return LLPanel::handleMouseUp(x, y, mask);
+}
+
+BOOL LLInventoryGalleryItem::handleHover(S32 x, S32 y, MASK mask)
+{
+ if(hasMouseCapture())
+ {
+ S32 screen_x;
+ S32 screen_y;
+ localPointToScreen(x, y, &screen_x, &screen_y );
+
+ if(LLToolDragAndDrop::getInstance()->isOverThreshold(screen_x, screen_y) && mGallery)
+ {
+ mGallery->startDrag();
+ return LLToolDragAndDrop::getInstance()->handleHover(x, y, mask);
+ }
+ }
+ return LLUICtrl::handleHover(x,y,mask);
+}
+
+BOOL LLInventoryGalleryItem::handleDoubleClick(S32 x, S32 y, MASK mask)
+{
+ if (mIsFolder && mGallery)
+ {
+ // setRootFolder can destroy this item.
+ // Delay it until handleDoubleClick processing is complete
+ // or make gallery handle doubleclicks.
+ LLHandle<LLPanel> handle = mGallery->getHandle();
+ LLUUID navigate_to = mUUID;
+ doOnIdleOneTime([handle, navigate_to]()
+ {
+ LLInventoryGallery* gallery = (LLInventoryGallery*)handle.get();
+ if (gallery)
+ {
+ gallery->setRootFolder(navigate_to);
+ }
+ });
+ }
+ else
+ {
+ LLInvFVBridgeAction::doAction(mUUID, &gInventory);
+ }
+
+ return TRUE;
+}
+
+BOOL LLInventoryGalleryItem::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop,
+ EDragAndDropType cargo_type,
+ void* cargo_data,
+ EAcceptance* accept,
+ std::string& tooltip_msg)
+{
+ if (!mIsFolder)
+ {
+ return FALSE;
+ }
+ return mGallery->baseHandleDragAndDrop(mUUID, drop, cargo_type, cargo_data, accept, tooltip_msg);
+}
+
+BOOL LLInventoryGalleryItem::handleKeyHere(KEY key, MASK mask)
+{
+ if (!mGallery)
+ {
+ return FALSE;
+ }
+
+ BOOL handled = FALSE;
+ switch (key)
+ {
+ case KEY_LEFT:
+ mGallery->moveLeft(mask);
+ handled = true;
+ break;
+
+ case KEY_RIGHT:
+ mGallery->moveRight(mask);
+ handled = true;
+ break;
+
+ case KEY_UP:
+ mGallery->moveUp(mask);
+ handled = true;
+ break;
+
+ case KEY_DOWN:
+ mGallery->moveDown(mask);
+ handled = true;
+ break;
+
+ default:
+ break;
+ }
+ return handled;
+}
+
+void LLInventoryGalleryItem::onFocusLost()
+{
+ // inventory no longer handles cut/copy/paste/delete
+ mGallery->resetEditHandler();
+
+ LLPanel::onFocusLost();
+}
+
+void LLInventoryGalleryItem::onFocusReceived()
+{
+ // inventory now handles cut/copy/paste/delete
+ mGallery->claimEditHandler();
+
+ LLPanel::onFocusReceived();
+}
+
+void LLInventoryGalleryItem::setWorn(bool value)
+{
+ mWorn = value;
+
+ if(mWorn)
+ {
+ mWornSuffix = (mType == LLAssetType::AT_GESTURE) ? LLTrans::getString("active") : LLTrans::getString("worn");
+ }
+ else
+ {
+ mWornSuffix = "";
+ }
+
+ updateNameText();
+}
+
+LLFontGL* LLInventoryGalleryItem::getTextFont()
+{
+ if(mWorn)
+ {
+ return LLFontGL::getFontSansSerifSmallBold();
+ }
+ return mIsLink ? LLFontGL::getFontSansSerifSmallItalic() : LLFontGL::getFontSansSerifSmall();
+}
+
+void LLInventoryGalleryItem::updateNameText()
+{
+ mNameText->setFont(getTextFont());
+ mNameText->setText(mItemName + mPermSuffix + mWornSuffix);
+ mNameText->setToolTip(mItemName + mPermSuffix + mWornSuffix);
+ getChild<LLThumbnailCtrl>("preview_thumbnail")->setToolTip(mItemName + mPermSuffix + mWornSuffix);
+}
+
+bool LLInventoryGalleryItem::isFadeItem()
+{
+ LLClipboard& clipboard = LLClipboard::instance();
+ if (mCutGeneration == clipboard.getGeneration())
+ {
+ return mSelectedForCut;
+ }
+
+ mCutGeneration = clipboard.getGeneration();
+ mSelectedForCut = clipboard.isCutMode() && clipboard.isOnClipboard(mUUID);
+ return mSelectedForCut;
+}
+
+//-----------------------------
+// LLThumbnailsObserver
+//-----------------------------
+
+void LLThumbnailsObserver::changed(U32 mask)
+{
+ std::vector<LLUUID> deleted_ids;
+ for (item_map_t::iterator iter = mItemMap.begin();
+ iter != mItemMap.end();
+ ++iter)
+ {
+ const LLUUID& obj_id = (*iter).first;
+ LLItemData& data = (*iter).second;
+
+ LLInventoryObject* obj = gInventory.getObject(obj_id);
+ if (!obj)
+ {
+ deleted_ids.push_back(obj_id);
+ continue;
+ }
+
+ const LLUUID thumbnail_id = obj->getThumbnailUUID();
+ if (data.mThumbnailID != thumbnail_id)
+ {
+ data.mThumbnailID = thumbnail_id;
+ data.mCallback();
+ }
+ }
+
+ // Remove deleted items from the list
+ for (std::vector<LLUUID>::iterator deleted_id = deleted_ids.begin(); deleted_id != deleted_ids.end(); ++deleted_id)
+ {
+ removeItem(*deleted_id);
+ }
+}
+
+bool LLThumbnailsObserver::addItem(const LLUUID& obj_id, callback_t cb)
+{
+ LLInventoryObject* obj = gInventory.getObject(obj_id);
+ if (obj)
+ {
+ mItemMap.insert(item_map_value_t(obj_id, LLItemData(obj_id, obj->getThumbnailUUID(), cb)));
+ return true;
+ }
+ return false;
+}
+
+void LLThumbnailsObserver::removeItem(const LLUUID& obj_id)
+{
+ mItemMap.erase(obj_id);
+}
+
+//-----------------------------
+// Helper drag&drop functions
+//-----------------------------
+
+BOOL LLInventoryGallery::baseHandleDragAndDrop(LLUUID dest_id, BOOL drop,
+ EDragAndDropType cargo_type,
+ void* cargo_data,
+ EAcceptance* accept,
+ std::string& tooltip_msg)
+{
+ LLInventoryItem* inv_item = (LLInventoryItem*)cargo_data;
+
+ if (drop && LLToolDragAndDrop::getInstance()->getCargoIndex() == 0)
+ {
+ clearSelection();
+ }
+
+ BOOL accepted = FALSE;
+ switch(cargo_type)
+ {
+ case DAD_TEXTURE:
+ case DAD_SOUND:
+ case DAD_CALLINGCARD:
+ case DAD_LANDMARK:
+ case DAD_SCRIPT:
+ case DAD_CLOTHING:
+ case DAD_OBJECT:
+ case DAD_NOTECARD:
+ case DAD_BODYPART:
+ case DAD_ANIMATION:
+ case DAD_GESTURE:
+ case DAD_MESH:
+ case DAD_SETTINGS:
+ accepted = dragItemIntoFolder(dest_id, inv_item, drop, tooltip_msg, true);
+ if (accepted && drop)
+ {
+ // Don't select immediately, wait for item to arrive
+ mItemsToSelect.push_back(inv_item->getUUID());
+ }
+ break;
+ case DAD_LINK:
+ // DAD_LINK type might mean one of two asset types: AT_LINK or AT_LINK_FOLDER.
+ // If we have an item of AT_LINK_FOLDER type we should process the linked
+ // category being dragged or dropped into folder.
+ if (inv_item && LLAssetType::AT_LINK_FOLDER == inv_item->getActualType())
+ {
+ LLInventoryCategory* linked_category = gInventory.getCategory(inv_item->getLinkedUUID());
+ if (linked_category)
+ {
+ accepted = dragCategoryIntoFolder(dest_id, (LLInventoryCategory*)linked_category, drop, tooltip_msg, TRUE);
+ }
+ }
+ else
+ {
+ accepted = dragItemIntoFolder(dest_id, inv_item, drop, tooltip_msg, TRUE);
+ }
+ if (accepted && drop && inv_item)
+ {
+ mItemsToSelect.push_back(inv_item->getUUID());
+ }
+ break;
+ case DAD_CATEGORY:
+ if (LLFriendCardsManager::instance().isAnyFriendCategory(dest_id))
+ {
+ accepted = FALSE;
+ }
+ else
+ {
+ LLInventoryCategory* cat_ptr = (LLInventoryCategory*)cargo_data;
+ accepted = dragCategoryIntoFolder(dest_id, cat_ptr, drop, tooltip_msg, FALSE);
+ if (accepted && drop)
+ {
+ mItemsToSelect.push_back(cat_ptr->getUUID());
+ }
+ }
+ break;
+ case DAD_ROOT_CATEGORY:
+ case DAD_NONE:
+ break;
+ default:
+ LL_WARNS() << "Unhandled cargo type for drag&drop " << cargo_type << LL_ENDL;
+ break;
+ }
+ if (accepted)
+ {
+ *accept = ACCEPT_YES_MULTI;
+ }
+ else
+ {
+ *accept = ACCEPT_NO;
+ }
+ return accepted;
+}
+
+// copy of LLFolderBridge::dragItemIntoFolder
+BOOL dragItemIntoFolder(LLUUID folder_id, LLInventoryItem* inv_item, BOOL drop, std::string& tooltip_msg, BOOL user_confirm)
+{
+ LLViewerInventoryCategory * cat = gInventory.getCategory(folder_id);
+ if (!cat)
+ {
+ return FALSE;
+ }
+ LLInventoryModel* model = &gInventory;
+
+ if (!model || !inv_item) return FALSE;
+
+ // cannot drag into library
+ if((gInventory.getRootFolderID() != folder_id) && !model->isObjectDescendentOf(folder_id, gInventory.getRootFolderID()))
+ {
+ return FALSE;
+ }
+ if (!isAgentAvatarValid()) return FALSE;
+
+ const LLUUID &current_outfit_id = model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT);
+ const LLUUID &favorites_id = model->findCategoryUUIDForType(LLFolderType::FT_FAVORITE);
+ const LLUUID &landmarks_id = model->findCategoryUUIDForType(LLFolderType::FT_LANDMARK);
+ const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS);
+ const LLUUID &my_outifts_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS);
+
+ const BOOL move_is_into_current_outfit = (folder_id == current_outfit_id);
+ const BOOL move_is_into_favorites = (folder_id == favorites_id);
+ const BOOL move_is_into_my_outfits = (folder_id == my_outifts_id) || model->isObjectDescendentOf(folder_id, my_outifts_id);
+ const BOOL move_is_into_outfit = move_is_into_my_outfits || (cat && cat->getPreferredType()==LLFolderType::FT_OUTFIT);
+ const BOOL move_is_into_landmarks = (folder_id == landmarks_id) || model->isObjectDescendentOf(folder_id, landmarks_id);
+ const BOOL move_is_into_marketplacelistings = model->isObjectDescendentOf(folder_id, marketplacelistings_id);
+ const BOOL move_is_from_marketplacelistings = model->isObjectDescendentOf(inv_item->getUUID(), marketplacelistings_id);
+
+ LLToolDragAndDrop::ESource source = LLToolDragAndDrop::getInstance()->getSource();
+ BOOL accept = FALSE;
+ LLViewerObject* object = NULL;
+ if(LLToolDragAndDrop::SOURCE_AGENT == source)
+ {
+ const LLUUID &trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH);
+
+ const BOOL move_is_into_trash = (folder_id == trash_id) || model->isObjectDescendentOf(folder_id, trash_id);
+ const BOOL move_is_outof_current_outfit = LLAppearanceMgr::instance().getIsInCOF(inv_item->getUUID());
+
+ //--------------------------------------------------------------------------------
+ // Determine if item can be moved.
+ //
+
+ BOOL is_movable = TRUE;
+
+ switch (inv_item->getActualType())
+ {
+ case LLAssetType::AT_CATEGORY:
+ is_movable = !LLFolderType::lookupIsProtectedType(((LLInventoryCategory*)inv_item)->getPreferredType());
+ break;
+ default:
+ break;
+ }
+ // Can't explicitly drag things out of the COF.
+ if (move_is_outof_current_outfit)
+ {
+ is_movable = FALSE;
+ }
+ if (move_is_into_trash)
+ {
+ is_movable &= inv_item->getIsLinkType() || !get_is_item_worn(inv_item->getUUID());
+ }
+ if (is_movable)
+ {
+ // Don't allow creating duplicates in the Calling Card/Friends
+ // subfolders, see bug EXT-1599. Check is item direct descendent
+ // of target folder and forbid item's movement if it so.
+ // Note: isItemDirectDescendentOfCategory checks if
+ // passed category is in the Calling Card/Friends folder
+ is_movable &= !LLFriendCardsManager::instance().isObjDirectDescendentOfCategory(inv_item, cat);
+ }
+
+ //
+ //--------------------------------------------------------------------------------
+
+ //--------------------------------------------------------------------------------
+ // Determine if item can be moved & dropped
+ // Note: if user_confirm is false, we already went through those accept logic test and can skip them
+
+ accept = TRUE;
+
+ if (user_confirm && !is_movable)
+ {
+ accept = FALSE;
+ }
+ else if (user_confirm && (folder_id == inv_item->getParentUUID()) && !move_is_into_favorites)
+ {
+ accept = FALSE;
+ }
+ else if (user_confirm && (move_is_into_current_outfit || move_is_into_outfit))
+ {
+ accept = can_move_to_outfit(inv_item, move_is_into_current_outfit);
+ }
+ else if (user_confirm && (move_is_into_favorites || move_is_into_landmarks))
+ {
+ accept = can_move_to_landmarks(inv_item);
+ }
+ else if (user_confirm && move_is_into_marketplacelistings)
+ {
+ //disable dropping in or out of marketplace for now
+ return FALSE;
+
+ /*const LLViewerInventoryCategory * master_folder = model->getFirstDescendantOf(marketplacelistings_id, folder_id);
+ LLViewerInventoryCategory * dest_folder = cat;
+ accept = can_move_item_to_marketplace(master_folder, dest_folder, inv_item, tooltip_msg, LLToolDragAndDrop::instance().getCargoCount() - LLToolDragAndDrop::instance().getCargoIndex());*/
+ }
+
+ // Check that the folder can accept this item based on folder/item type compatibility (e.g. stock folder compatibility)
+ if (user_confirm && accept)
+ {
+ LLViewerInventoryCategory * dest_folder = cat;
+ accept = dest_folder->acceptItem(inv_item);
+ }
+
+ LLInventoryPanel* active_panel = LLInventoryPanel::getActiveInventoryPanel(FALSE);
+
+ if (accept && drop)
+ {
+ if (inv_item->getType() == LLAssetType::AT_GESTURE
+ && LLGestureMgr::instance().isGestureActive(inv_item->getUUID()) && move_is_into_trash)
+ {
+ LLGestureMgr::instance().deactivateGesture(inv_item->getUUID());
+ }
+ // If an item is being dragged between windows, unselect everything in the active window
+ // so that we don't follow the selection to its new location (which is very annoying).
+ // RN: a better solution would be to deselect automatically when an item is moved
+ // and then select any item that is dropped only in the panel that it is dropped in
+ if (active_panel)
+ {
+ active_panel->unSelectAll();
+ }
+ // Dropping in or out of marketplace needs (sometimes) confirmation
+ if (user_confirm && (move_is_from_marketplacelistings || move_is_into_marketplacelistings))
+ {
+ //disable dropping in or out of marketplace for now
+ return FALSE;
+ }
+
+ //--------------------------------------------------------------------------------
+ // Destination folder logic
+ //
+
+ // FAVORITES folder
+ // (copy the item)
+ else if (move_is_into_favorites)
+ {
+ copy_inventory_item(
+ gAgent.getID(),
+ inv_item->getPermissions().getOwner(),
+ inv_item->getUUID(),
+ folder_id,
+ std::string(),
+ LLPointer<LLInventoryCallback>(NULL));
+ }
+ // CURRENT OUTFIT or OUTFIT folder
+ // (link the item)
+ else if (move_is_into_current_outfit || move_is_into_outfit)
+ {
+ if (move_is_into_current_outfit)
+ {
+ LLAppearanceMgr::instance().wearItemOnAvatar(inv_item->getUUID(), true, true);
+ }
+ else
+ {
+ LLPointer<LLInventoryCallback> cb = NULL;
+ link_inventory_object(folder_id, LLConstPointer<LLInventoryObject>(inv_item), cb);
+ }
+ }
+ // MARKETPLACE LISTINGS folder
+ // Move the item
+ else if (move_is_into_marketplacelistings)
+ {
+ //move_item_to_marketplacelistings(inv_item, mUUID);
+ return FALSE;
+ }
+ // NORMAL or TRASH folder
+ // (move the item, restamp if into trash)
+ else
+ {
+ // set up observer to select item once drag and drop from inbox is complete
+ if (gInventory.isObjectDescendentOf(inv_item->getUUID(), gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX)))
+ {
+ set_dad_inbox_object(inv_item->getUUID());
+ }
+
+ gInventory.changeItemParent((LLViewerInventoryItem*)inv_item, folder_id, move_is_into_trash);
+ }
+
+ if (move_is_from_marketplacelistings)
+ {
+ // If we move from an active (listed) listing, checks that it's still valid, if not, unlist
+ /*LLUUID version_folder_id = LLMarketplaceData::instance().getActiveFolder(from_folder_uuid);
+ if (version_folder_id.notNull())
+ {
+ LLMarketplaceValidator::getInstance()->validateMarketplaceListings(
+ version_folder_id,
+ [version_folder_id](bool result)
+ {
+ if (!result)
+ {
+ LLMarketplaceData::instance().activateListing(version_folder_id, false);
+ }
+ });
+ }*/
+ return FALSE;
+ }
+
+ //
+ //--------------------------------------------------------------------------------
+ }
+ }
+ else if (LLToolDragAndDrop::SOURCE_WORLD == source)
+ {
+ // Make sure the object exists. If we allowed dragging from
+ // anonymous objects, it would be possible to bypass
+ // permissions.
+ object = gObjectList.findObject(inv_item->getParentUUID());
+ if (!object)
+ {
+ LL_INFOS() << "Object not found for drop." << LL_ENDL;
+ return FALSE;
+ }
+
+ // coming from a task. Need to figure out if the person can
+ // move/copy this item.
+ LLPermissions perm(inv_item->getPermissions());
+ bool is_move = false;
+ if ((perm.allowCopyBy(gAgent.getID(), gAgent.getGroupID())
+ && perm.allowTransferTo(gAgent.getID())))
+ // || gAgent.isGodlike())
+ {
+ accept = TRUE;
+ }
+ else if(object->permYouOwner())
+ {
+ // If the object cannot be copied, but the object the
+ // inventory is owned by the agent, then the item can be
+ // moved from the task to agent inventory.
+ is_move = true;
+ accept = TRUE;
+ }
+
+ // Don't allow placing an original item into Current Outfit or an outfit folder
+ // because they must contain only links to wearable items.
+ if (move_is_into_current_outfit || move_is_into_outfit)
+ {
+ accept = FALSE;
+ }
+ // Don't allow to move a single item to Favorites or Landmarks
+ // if it is not a landmark or a link to a landmark.
+ else if ((move_is_into_favorites || move_is_into_landmarks)
+ && !can_move_to_landmarks(inv_item))
+ {
+ accept = FALSE;
+ }
+ else if (move_is_into_marketplacelistings)
+ {
+ tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory");
+ accept = FALSE;
+ }
+
+ if (accept && drop)
+ {
+ boost::shared_ptr<LLMoveInv> move_inv (new LLMoveInv());
+ move_inv->mObjectID = inv_item->getParentUUID();
+ std::pair<LLUUID, LLUUID> item_pair(folder_id, inv_item->getUUID());
+ move_inv->mMoveList.push_back(item_pair);
+ move_inv->mCallback = NULL;
+ move_inv->mUserData = NULL;
+ if(is_move)
+ {
+ warn_move_inventory(object, move_inv);
+ }
+ else
+ {
+ // store dad inventory item to select added one later. See EXT-4347
+ set_dad_inventory_item(inv_item, folder_id);
+
+ LLNotification::Params params("MoveInventoryFromObject");
+ params.functor.function(boost::bind(move_task_inventory_callback, _1, _2, move_inv));
+ LLNotifications::instance().forceResponse(params, 0);
+ }
+ }
+ }
+ else if(LLToolDragAndDrop::SOURCE_NOTECARD == source)
+ {
+ if (move_is_into_marketplacelistings)
+ {
+ tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory");
+ accept = FALSE;
+ }
+ else if ((inv_item->getActualType() == LLAssetType::AT_SETTINGS) && !LLEnvironment::instance().isInventoryEnabled())
+ {
+ tooltip_msg = LLTrans::getString("NoEnvironmentSettings");
+ accept = FALSE;
+ }
+ else
+ {
+ // Don't allow placing an original item from a notecard to Current Outfit or an outfit folder
+ // because they must contain only links to wearable items.
+ accept = !(move_is_into_current_outfit || move_is_into_outfit);
+ }
+
+ if (accept && drop)
+ {
+ copy_inventory_from_notecard(folder_id, // Drop to the chosen destination folder
+ LLToolDragAndDrop::getInstance()->getObjectID(),
+ LLToolDragAndDrop::getInstance()->getSourceID(),
+ inv_item);
+ }
+ }
+ else if(LLToolDragAndDrop::SOURCE_LIBRARY == source)
+ {
+ LLViewerInventoryItem* item = (LLViewerInventoryItem*)inv_item;
+ if(item && item->isFinished())
+ {
+ accept = TRUE;
+
+ if (move_is_into_marketplacelistings)
+ {
+ tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory");
+ accept = FALSE;
+ }
+ else if (move_is_into_current_outfit || move_is_into_outfit)
+ {
+ accept = can_move_to_outfit(inv_item, move_is_into_current_outfit);
+ }
+ // Don't allow to move a single item to Favorites or Landmarks
+ // if it is not a landmark or a link to a landmark.
+ else if (move_is_into_favorites || move_is_into_landmarks)
+ {
+ accept = can_move_to_landmarks(inv_item);
+ }
+
+ if (accept && drop)
+ {
+ // FAVORITES folder
+ // (copy the item)
+ if (move_is_into_favorites)
+ {
+ copy_inventory_item(
+ gAgent.getID(),
+ inv_item->getPermissions().getOwner(),
+ inv_item->getUUID(),
+ folder_id,
+ std::string(),
+ LLPointer<LLInventoryCallback>(NULL));
+ }
+ // CURRENT OUTFIT or OUTFIT folder
+ // (link the item)
+ else if (move_is_into_current_outfit || move_is_into_outfit)
+ {
+ if (move_is_into_current_outfit)
+ {
+ LLAppearanceMgr::instance().wearItemOnAvatar(inv_item->getUUID(), true, true);
+ }
+ else
+ {
+ LLPointer<LLInventoryCallback> cb = NULL;
+ link_inventory_object(folder_id, LLConstPointer<LLInventoryObject>(inv_item), cb);
+ }
+ }
+ else
+ {
+ copy_inventory_item(
+ gAgent.getID(),
+ inv_item->getPermissions().getOwner(),
+ inv_item->getUUID(),
+ folder_id,
+ std::string(),
+ LLPointer<LLInventoryCallback>(NULL));
+ }
+ }
+ }
+ }
+ else
+ {
+ LL_WARNS() << "unhandled drag source" << LL_ENDL;
+ }
+ return accept;
+}
+
+// copy of LLFolderBridge::dragCategoryIntoFolder
+BOOL dragCategoryIntoFolder(LLUUID dest_id, LLInventoryCategory* inv_cat,
+ BOOL drop, std::string& tooltip_msg, BOOL is_link)
+{
+ BOOL user_confirm = TRUE;
+ LLInventoryModel* model = &gInventory;
+ LLViewerInventoryCategory * dest_cat = gInventory.getCategory(dest_id);
+ if (!dest_cat)
+ {
+ return FALSE;
+ }
+
+ if (!inv_cat) return FALSE; // shouldn't happen, but in case item is incorrectly parented in which case inv_cat will be NULL
+
+ if (!isAgentAvatarValid()) return FALSE;
+ // cannot drag into library
+ if((gInventory.getRootFolderID() != dest_id) && !model->isObjectDescendentOf(dest_id, gInventory.getRootFolderID()))
+ {
+ return FALSE;
+ }
+
+ const LLUUID &cat_id = inv_cat->getUUID();
+ const LLUUID &current_outfit_id = model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT);
+ const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS);
+ //const LLUUID from_folder_uuid = inv_cat->getParentUUID();
+
+ const BOOL move_is_into_current_outfit = (dest_id == current_outfit_id);
+ const BOOL move_is_into_marketplacelistings = model->isObjectDescendentOf(dest_id, marketplacelistings_id);
+ const BOOL move_is_from_marketplacelistings = model->isObjectDescendentOf(cat_id, marketplacelistings_id);
+
+ // check to make sure source is agent inventory, and is represented there.
+ LLToolDragAndDrop::ESource source = LLToolDragAndDrop::getInstance()->getSource();
+ const BOOL is_agent_inventory = (model->getCategory(cat_id) != NULL)
+ && (LLToolDragAndDrop::SOURCE_AGENT == source);
+
+ BOOL accept = FALSE;
+
+ if (is_agent_inventory)
+ {
+ const LLUUID &trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH);
+ const LLUUID &landmarks_id = model->findCategoryUUIDForType(LLFolderType::FT_LANDMARK);
+ const LLUUID &my_outifts_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS);
+ const LLUUID &lost_and_found_id = model->findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND);
+
+ const BOOL move_is_into_trash = (dest_id == trash_id) || model->isObjectDescendentOf(dest_id, trash_id);
+ const BOOL move_is_into_my_outfits = (dest_id == my_outifts_id) || model->isObjectDescendentOf(dest_id, my_outifts_id);
+ const BOOL move_is_into_outfit = move_is_into_my_outfits || (dest_cat && dest_cat->getPreferredType()==LLFolderType::FT_OUTFIT);
+ const BOOL move_is_into_current_outfit = (dest_cat && dest_cat->getPreferredType()==LLFolderType::FT_CURRENT_OUTFIT);
+ const BOOL move_is_into_landmarks = (dest_id == landmarks_id) || model->isObjectDescendentOf(dest_id, landmarks_id);
+ const BOOL move_is_into_lost_and_found = model->isObjectDescendentOf(dest_id, lost_and_found_id);
+
+ //--------------------------------------------------------------------------------
+ // Determine if folder can be moved.
+ //
+
+ BOOL is_movable = TRUE;
+
+ if (is_movable && (marketplacelistings_id == cat_id))
+ {
+ is_movable = FALSE;
+ tooltip_msg = LLTrans::getString("TooltipOutboxCannotMoveRoot");
+ }
+ if (is_movable && move_is_from_marketplacelistings)
+ //&& LLMarketplaceData::instance().getActivationState(cat_id))
+ {
+ // If the incoming folder is listed and active (and is therefore either the listing or the version folder),
+ // then moving is *not* allowed
+ is_movable = FALSE;
+ tooltip_msg = LLTrans::getString("TooltipOutboxDragActive");
+ }
+ if (is_movable && (dest_id == cat_id))
+ {
+ is_movable = FALSE;
+ tooltip_msg = LLTrans::getString("TooltipDragOntoSelf");
+ }
+ if (is_movable && (model->isObjectDescendentOf(dest_id, cat_id)))
+ {
+ is_movable = FALSE;
+ tooltip_msg = LLTrans::getString("TooltipDragOntoOwnChild");
+ }
+ if (is_movable && LLFolderType::lookupIsProtectedType(inv_cat->getPreferredType()))
+ {
+ is_movable = FALSE;
+ // tooltip?
+ }
+
+ U32 max_items_to_wear = gSavedSettings.getU32("WearFolderLimit");
+ if (is_movable && move_is_into_outfit)
+ {
+ if (dest_id == my_outifts_id)
+ {
+ if (source != LLToolDragAndDrop::SOURCE_AGENT || move_is_from_marketplacelistings)
+ {
+ tooltip_msg = LLTrans::getString("TooltipOutfitNotInInventory");
+ is_movable = false;
+ }
+ else if (can_move_to_my_outfits(model, inv_cat, max_items_to_wear))
+ {
+ is_movable = true;
+ }
+ else
+ {
+ tooltip_msg = LLTrans::getString("TooltipCantCreateOutfit");
+ is_movable = false;
+ }
+ }
+ else if(dest_cat && dest_cat->getPreferredType() == LLFolderType::FT_NONE)
+ {
+ is_movable = ((inv_cat->getPreferredType() == LLFolderType::FT_NONE) || (inv_cat->getPreferredType() == LLFolderType::FT_OUTFIT));
+ }
+ else
+ {
+ is_movable = false;
+ }
+ }
+ if(is_movable && move_is_into_current_outfit && is_link)
+ {
+ is_movable = FALSE;
+ }
+ if (is_movable && move_is_into_lost_and_found)
+ {
+ is_movable = FALSE;
+ }
+ if (is_movable && (dest_id == model->findCategoryUUIDForType(LLFolderType::FT_FAVORITE)))
+ {
+ is_movable = FALSE;
+ // tooltip?
+ }
+ if (is_movable && (dest_cat->getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK))
+ {
+ // One cannot move a folder into a stock folder
+ is_movable = FALSE;
+ // tooltip?
+ }
+
+ LLInventoryModel::cat_array_t descendent_categories;
+ LLInventoryModel::item_array_t descendent_items;
+ if (is_movable)
+ {
+ model->collectDescendents(cat_id, descendent_categories, descendent_items, FALSE);
+ for (S32 i=0; i < descendent_categories.size(); ++i)
+ {
+ LLInventoryCategory* category = descendent_categories[i];
+ if(LLFolderType::lookupIsProtectedType(category->getPreferredType()))
+ {
+ // Can't move "special folders" (e.g. Textures Folder).
+ is_movable = FALSE;
+ break;
+ }
+ }
+ }
+ if (is_movable
+ && move_is_into_current_outfit
+ && descendent_items.size() > max_items_to_wear)
+ {
+ LLInventoryModel::cat_array_t cats;
+ LLInventoryModel::item_array_t items;
+ LLFindWearablesEx not_worn(/*is_worn=*/ false, /*include_body_parts=*/ false);
+ gInventory.collectDescendentsIf(cat_id,
+ cats,
+ items,
+ LLInventoryModel::EXCLUDE_TRASH,
+ not_worn);
+
+ if (items.size() > max_items_to_wear)
+ {
+ // Can't move 'large' folders into current outfit: MAINT-4086
+ is_movable = FALSE;
+ LLStringUtil::format_map_t args;
+ args["AMOUNT"] = llformat("%d", max_items_to_wear);
+ tooltip_msg = LLTrans::getString("TooltipTooManyWearables",args);
+ }
+ }
+ if (is_movable && move_is_into_trash)
+ {
+ for (S32 i=0; i < descendent_items.size(); ++i)
+ {
+ LLInventoryItem* item = descendent_items[i];
+ if (get_is_item_worn(item->getUUID()))
+ {
+ is_movable = FALSE;
+ break; // It's generally movable, but not into the trash.
+ }
+ }
+ }
+ if (is_movable && move_is_into_landmarks)
+ {
+ for (S32 i=0; i < descendent_items.size(); ++i)
+ {
+ LLViewerInventoryItem* item = descendent_items[i];
+
+ // Don't move anything except landmarks and categories into Landmarks folder.
+ // We use getType() instead of getActua;Type() to allow links to landmarks and folders.
+ if (LLAssetType::AT_LANDMARK != item->getType() && LLAssetType::AT_CATEGORY != item->getType())
+ {
+ is_movable = FALSE;
+ break; // It's generally movable, but not into Landmarks.
+ }
+ }
+ }
+
+ if (is_movable && move_is_into_marketplacelistings)
+ {
+ const LLViewerInventoryCategory * master_folder = model->getFirstDescendantOf(marketplacelistings_id, dest_id);
+ LLViewerInventoryCategory * dest_folder = dest_cat;
+ S32 bundle_size = (drop ? 1 : LLToolDragAndDrop::instance().getCargoCount());
+ is_movable = can_move_folder_to_marketplace(master_folder, dest_folder, inv_cat, tooltip_msg, bundle_size);
+ }
+
+ //
+ //--------------------------------------------------------------------------------
+
+ accept = is_movable;
+
+ if (accept && drop)
+ {
+ // Dropping in or out of marketplace needs (sometimes) confirmation
+ if (user_confirm && (move_is_from_marketplacelistings || move_is_into_marketplacelistings))
+ {
+ //disable dropping in or out of marketplace for now
+ return FALSE;
+ }
+ // Look for any gestures and deactivate them
+ if (move_is_into_trash)
+ {
+ for (S32 i=0; i < descendent_items.size(); i++)
+ {
+ LLInventoryItem* item = descendent_items[i];
+ if (item->getType() == LLAssetType::AT_GESTURE
+ && LLGestureMgr::instance().isGestureActive(item->getUUID()))
+ {
+ LLGestureMgr::instance().deactivateGesture(item->getUUID());
+ }
+ }
+ }
+
+ if (dest_id == my_outifts_id)
+ {
+ // Category can contains objects,
+ // create a new folder and populate it with links to original objects
+ dropToMyOutfits(inv_cat);
+ }
+ // if target is current outfit folder we use link
+ else if (move_is_into_current_outfit &&
+ (inv_cat->getPreferredType() == LLFolderType::FT_NONE ||
+ inv_cat->getPreferredType() == LLFolderType::FT_OUTFIT))
+ {
+ // traverse category and add all contents to currently worn.
+ BOOL append = true;
+ LLAppearanceMgr::instance().wearInventoryCategory(inv_cat, false, append);
+ }
+ else if (move_is_into_marketplacelistings)
+ {
+ //move_folder_to_marketplacelistings(inv_cat, dest_id);
+ }
+ else
+ {
+ if (model->isObjectDescendentOf(cat_id, model->findCategoryUUIDForType(LLFolderType::FT_INBOX)))
+ {
+ set_dad_inbox_object(cat_id);
+ }
+
+ // Reparent the folder and restamp children if it's moving
+ // into trash.
+ gInventory.changeCategoryParent(
+ (LLViewerInventoryCategory*)inv_cat,
+ dest_id,
+ move_is_into_trash);
+ }
+ if (move_is_from_marketplacelistings)
+ {
+ //disable dropping in or out of marketplace for now
+ return FALSE;
+
+ // If we are moving a folder at the listing folder level (i.e. its parent is the marketplace listings folder)
+ /*if (from_folder_uuid == marketplacelistings_id)
+ {
+ // Clear the folder from the marketplace in case it is a listing folder
+ if (LLMarketplaceData::instance().isListed(cat_id))
+ {
+ LLMarketplaceData::instance().clearListing(cat_id);
+ }
+ }
+ else
+ {
+ // If we move from within an active (listed) listing, checks that it's still valid, if not, unlist
+ LLUUID version_folder_id = LLMarketplaceData::instance().getActiveFolder(from_folder_uuid);
+ if (version_folder_id.notNull())
+ {
+ LLMarketplaceValidator::getInstance()->validateMarketplaceListings(
+ version_folder_id,
+ [version_folder_id](bool result)
+ {
+ if (!result)
+ {
+ LLMarketplaceData::instance().activateListing(version_folder_id, false);
+ }
+ }
+ );
+ }
+ // In all cases, update the listing we moved from so suffix are updated
+ update_marketplace_category(from_folder_uuid);
+ }*/
+ }
+ }
+ }
+ else if (LLToolDragAndDrop::SOURCE_WORLD == source)
+ {
+ if (move_is_into_marketplacelistings)
+ {
+ tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory");
+ accept = FALSE;
+ }
+ else
+ {
+ accept = move_inv_category_world_to_agent(cat_id, dest_id, drop);
+ }
+ }
+ else if (LLToolDragAndDrop::SOURCE_LIBRARY == source)
+ {
+ if (move_is_into_marketplacelistings)
+ {
+ tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory");
+ accept = FALSE;
+ }
+ else
+ {
+ // Accept folders that contain complete outfits.
+ accept = move_is_into_current_outfit && LLAppearanceMgr::instance().getCanMakeFolderIntoOutfit(cat_id);
+ }
+
+ if (accept && drop)
+ {
+ LLAppearanceMgr::instance().wearInventoryCategory(inv_cat, true, false);
+ }
+ }
+
+ return accept;
+}
+
+void outfitFolderCreatedCallback(LLUUID cat_source_id, LLUUID cat_dest_id)
+{
+ LLInventoryModel::cat_array_t* categories;
+ LLInventoryModel::item_array_t* items;
+ gInventory.getDirectDescendentsOf(cat_source_id, categories, items);
+
+ LLInventoryObject::const_object_list_t link_array;
+
+
+ LLInventoryModel::item_array_t::iterator iter = items->begin();
+ LLInventoryModel::item_array_t::iterator end = items->end();
+ while (iter!=end)
+ {
+ const LLViewerInventoryItem* item = (*iter);
+ // By this point everything is supposed to be filtered,
+ // but there was a delay to create folder so something could have changed
+ LLInventoryType::EType inv_type = item->getInventoryType();
+ if ((inv_type == LLInventoryType::IT_WEARABLE) ||
+ (inv_type == LLInventoryType::IT_GESTURE) ||
+ (inv_type == LLInventoryType::IT_ATTACHMENT) ||
+ (inv_type == LLInventoryType::IT_OBJECT) ||
+ (inv_type == LLInventoryType::IT_SNAPSHOT) ||
+ (inv_type == LLInventoryType::IT_TEXTURE))
+ {
+ link_array.push_back(LLConstPointer<LLInventoryObject>(item));
+ }
+ iter++;
+ }
+
+ if (!link_array.empty())
+ {
+ LLPointer<LLInventoryCallback> cb = NULL;
+ link_inventory_array(cat_dest_id, link_array, cb);
+ }
+}
+
+void dropToMyOutfits(LLInventoryCategory* inv_cat)
+{
+ // make a folder in the My Outfits directory.
+ const LLUUID dest_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS);
+
+ // Note: creation will take time, so passing folder id to callback is slightly unreliable,
+ // but so is collecting and passing descendants' ids
+ inventory_func_type func = boost::bind(&outfitFolderCreatedCallback, inv_cat->getUUID(), _1);
+ gInventory.createNewCategory(dest_id, LLFolderType::FT_OUTFIT, inv_cat->getName(), func, inv_cat->getThumbnailUUID());
+}
+