/** * @file llfavoritesbar.cpp * @brief LLFavoritesBarCtrl class implementation * * $LicenseInfo:firstyear=2009&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" #include "llfavoritesbar.h" #include "llfloaterreg.h" #include "llfocusmgr.h" #include "llinventory.h" #include "lllandmarkactions.h" #include "lltoolbarview.h" #include "lltrans.h" #include "lluictrlfactory.h" #include "llmenugl.h" #include "lltooltip.h" #include "llagent.h" #include "llagentpicksinfo.h" #include "llavatarnamecache.h" #include "llclipboard.h" #include "llinventorybridge.h" #include "llinventoryfunctions.h" #include "llfloatersidepanelcontainer.h" #include "llfloaterworldmap.h" #include "lllandmarkactions.h" #include "lllogininstance.h" #include "llnotificationsutil.h" #include "lltoggleablemenu.h" #include "llviewerinventory.h" #include "llviewermenu.h" #include "llviewernetwork.h" #include "lltooldraganddrop.h" #include "llsdserialize.h" static LLDefaultChildRegistry::Register<LLFavoritesBarCtrl> r("favorites_bar"); const S32 DROP_DOWN_MENU_WIDTH = 250; const S32 DROP_DOWN_MENU_TOP_PAD = 13; /** * Helper for LLFavoriteLandmarkButton and LLFavoriteLandmarkMenuItem. * Performing requests for SLURL for given Landmark ID */ class LLLandmarkInfoGetter { public: LLLandmarkInfoGetter() : mLandmarkID(LLUUID::null), mName("(Loading...)"), mPosX(0), mPosY(0), mPosZ(0), mLoaded(false) { mHandle.bind(this); } void setLandmarkID(const LLUUID& id) { mLandmarkID = id; } const LLUUID& getLandmarkID() const { return mLandmarkID; } const std::string& getName() { if(!mLoaded) requestNameAndPos(); return mName; } S32 getPosX() { if (!mLoaded) requestNameAndPos(); return mPosX; } S32 getPosY() { if (!mLoaded) requestNameAndPos(); return mPosY; } S32 getPosZ() { if (!mLoaded) requestNameAndPos(); return mPosZ; } private: /** * Requests landmark data from server. */ void requestNameAndPos() { if (mLandmarkID.isNull()) return; LLVector3d g_pos; if(LLLandmarkActions::getLandmarkGlobalPos(mLandmarkID, g_pos)) { LLLandmarkActions::getRegionNameAndCoordsFromPosGlobal(g_pos, boost::bind(&LLLandmarkInfoGetter::landmarkNameCallback, static_cast<LLHandle<LLLandmarkInfoGetter> >(mHandle), _1, _2, _3, _4)); } } static void landmarkNameCallback(LLHandle<LLLandmarkInfoGetter> handle, const std::string& name, S32 x, S32 y, S32 z) { LLLandmarkInfoGetter* getter = handle.get(); if (getter) { getter->mPosX = x; getter->mPosY = y; getter->mPosZ = z; getter->mName = name; getter->mLoaded = true; } } LLUUID mLandmarkID; std::string mName; S32 mPosX; S32 mPosY; S32 mPosZ; bool mLoaded; LLRootHandle<LLLandmarkInfoGetter> mHandle; }; /** * This class is needed to override LLButton default handleToolTip function and * show SLURL as button tooltip. * *NOTE: dzaporozhan: This is a workaround. We could set tooltips for buttons * in createButtons function but landmark data is not available when Favorites Bar is * created. Thats why we are requesting landmark data after */ class LLFavoriteLandmarkButton : public LLButton { public: bool handleToolTip(S32 x, S32 y, MASK mask) { std::string region_name = mLandmarkInfoGetter.getName(); if (!region_name.empty()) { std::string extra_message = llformat("%s (%d, %d, %d)", region_name.c_str(), mLandmarkInfoGetter.getPosX(), mLandmarkInfoGetter.getPosY(), mLandmarkInfoGetter.getPosZ()); LLToolTip::Params params; params.message = llformat("%s\n%s", getLabelSelected().c_str(), extra_message.c_str()); params.max_width = 1000; params.sticky_rect = calcScreenRect(); LLToolTipMgr::instance().show(params); } return true; } /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask) { LLFavoritesBarCtrl* fb = dynamic_cast<LLFavoritesBarCtrl*>(getParent()); if (fb) { fb->handleHover(x, y, mask); } return LLButton::handleHover(x, y, mask); } void setLandmarkID(const LLUUID& id){ mLandmarkInfoGetter.setLandmarkID(id); } const LLUUID& getLandmarkID() const { return mLandmarkInfoGetter.getLandmarkID(); } void onMouseEnter(S32 x, S32 y, MASK mask) { if (LLToolDragAndDrop::getInstance()->hasMouseCapture()) { LLUICtrl::onMouseEnter(x, y, mask); } else { LLButton::onMouseEnter(x, y, mask); } } protected: LLFavoriteLandmarkButton(const LLButton::Params& p) : LLButton(p) {} friend class LLUICtrlFactory; private: LLLandmarkInfoGetter mLandmarkInfoGetter; }; /** * This class is needed to override LLMenuItemCallGL default handleToolTip function and * show SLURL as button tooltip. * *NOTE: dzaporozhan: This is a workaround. We could set tooltips for buttons * in showDropDownMenu function but landmark data is not available when Favorites Bar is * created. Thats why we are requesting landmark data after */ class LLFavoriteLandmarkMenuItem : public LLMenuItemCallGL { public: bool handleToolTip(S32 x, S32 y, MASK mask) { std::string region_name = mLandmarkInfoGetter.getName(); if (!region_name.empty()) { LLToolTip::Params params; params.message = llformat("%s\n%s (%d, %d)", getLabel().c_str(), region_name.c_str(), mLandmarkInfoGetter.getPosX(), mLandmarkInfoGetter.getPosY()); params.sticky_rect = calcScreenRect(); LLToolTipMgr::instance().show(params); } return true; } const LLUUID& getLandmarkID() const { return mLandmarkInfoGetter.getLandmarkID(); } void setLandmarkID(const LLUUID& id) { mLandmarkInfoGetter.setLandmarkID(id); } virtual bool handleMouseDown(S32 x, S32 y, MASK mask) { if (mMouseDownSignal) (*mMouseDownSignal)(this, x, y, mask); return LLMenuItemCallGL::handleMouseDown(x, y, mask); } virtual bool handleMouseUp(S32 x, S32 y, MASK mask) { if (mMouseUpSignal) (*mMouseUpSignal)(this, x, y, mask); return LLMenuItemCallGL::handleMouseUp(x, y, mask); } virtual bool handleHover(S32 x, S32 y, MASK mask) { if (fb) { fb->handleHover(x, y, mask); } return true; } void initFavoritesBarPointer(LLFavoritesBarCtrl* fb) { this->fb = fb; } protected: LLFavoriteLandmarkMenuItem(const LLMenuItemCallGL::Params& p) : LLMenuItemCallGL(p) {} friend class LLUICtrlFactory; private: LLLandmarkInfoGetter mLandmarkInfoGetter; LLFavoritesBarCtrl* fb; }; /** * This class was introduced just for fixing the following issue: * EXT-836 Nav bar: Favorites overflow menu passes left-mouse click through. * We must explicitly handle drag and drop event by returning true * because otherwise LLToolDragAndDrop will initiate drag and drop operation * with the world. */ class LLFavoriteLandmarkToggleableMenu : public LLToggleableMenu { public: // virtual bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg) override { mToolbar->handleDragAndDropToMenu(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); return true; } // virtual bool handleHover(S32 x, S32 y, MASK mask) override { mIsHovering = true; LLToggleableMenu::handleHover(x, y, mask); mIsHovering = false; return true; } // virtual void setVisible(bool visible) override { // Avoid of hiding the menu during hovering if (visible || !mIsHovering) { LLToggleableMenu::setVisible(visible); } } void setToolbar(LLFavoritesBarCtrl* toolbar) { mToolbar = toolbar; } ~LLFavoriteLandmarkToggleableMenu() { // Enable subsequent setVisible(false) mIsHovering = false; setVisible(false); } protected: LLFavoriteLandmarkToggleableMenu(const LLToggleableMenu::Params& p): LLToggleableMenu(p) { } private: LLFavoritesBarCtrl* mToolbar { nullptr }; bool mIsHovering { false }; friend class LLUICtrlFactory; }; /** * This class is needed to update an item being copied to the favorites folder * with a sort field value (required to save favorites bar's tabs order). * See method handleNewFavoriteDragAndDrop for more details on how this class is used. */ class LLItemCopiedCallback : public LLInventoryCallback { public: LLItemCopiedCallback(S32 sortField): mSortField(sortField) {} virtual void fire(const LLUUID& inv_item) { LLViewerInventoryItem* item = gInventory.getItem(inv_item); if (item) { LLFavoritesBarCtrl::sWaitingForCallabck = 0.f; LLFavoritesOrderStorage::instance().setSortIndex(item, mSortField); item->setComplete(true); item->updateServer(false); gInventory.updateItem(item); gInventory.notifyObservers(); LLFavoritesOrderStorage::instance().saveOrder(); } LLView::getWindow()->setCursor(UI_CURSOR_ARROW); } private: S32 mSortField; }; // updateButtons's helper struct LLFavoritesSort { // Sorting by creation date and name // TODO - made it customizible using gSavedSettings bool operator()(const LLViewerInventoryItem* const& a, const LLViewerInventoryItem* const& b) { S32 sortField1 = LLFavoritesOrderStorage::instance().getSortIndex(a->getUUID()); S32 sortField2 = LLFavoritesOrderStorage::instance().getSortIndex(b->getUUID()); if (!(sortField1 < 0 && sortField2 < 0)) { return sortField2 > sortField1; } time_t first_create = a->getCreationDate(); time_t second_create = b->getCreationDate(); if (first_create == second_create) { return (LLStringUtil::compareDict(a->getName(), b->getName()) < 0); } else { return (first_create > second_create); } } }; F64 LLFavoritesBarCtrl::sWaitingForCallabck = 0.f; LLFavoritesBarCtrl::Params::Params() : image_drag_indication("image_drag_indication"), more_button("more_button"), label("label") { } LLFavoritesBarCtrl::LLFavoritesBarCtrl(const LLFavoritesBarCtrl::Params& p) : LLUICtrl(p), mFont(p.font.isProvided() ? p.font() : LLFontGL::getFontSansSerifSmall()), mOverflowMenuHandle(), mContextMenuHandle(), mImageDragIndication(p.image_drag_indication), mShowDragMarker(false), mLandingTab(NULL), mLastTab(NULL), mItemsListDirty(false), mUpdateDropDownItems(true), mRestoreOverflowMenu(false), mDragToOverflowMenu(false), mGetPrevItems(true), mMouseX(0), mMouseY(0), mItemsChangedTimer() { // Register callback for menus with current registrar (will be parent panel's registrar) LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Favorites.DoToSelected", { boost::bind(&LLFavoritesBarCtrl::doToSelected, this, _2), cb_info::UNTRUSTED_BLOCK }); // Add this if we need to selectively enable items LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Favorites.EnableSelected", boost::bind(&LLFavoritesBarCtrl::enableSelected, this, _2)); gInventory.addObserver(this); //make chevron button LLTextBox::Params more_button_params(p.more_button); mMoreTextBox = LLUICtrlFactory::create<LLTextBox> (more_button_params); mMoreTextBox->setClickedCallback(boost::bind(&LLFavoritesBarCtrl::onMoreTextBoxClicked, this)); addChild(mMoreTextBox); LLRect rect = mMoreTextBox->getRect(); mMoreTextBox->setRect(LLRect(rect.mLeft - rect.getWidth(), rect.mTop, rect.mRight, rect.mBottom)); mDropDownItemsCount = 0; LLTextBox::Params label_param(p.label); mBarLabel = LLUICtrlFactory::create<LLTextBox> (label_param); addChild(mBarLabel); } LLFavoritesBarCtrl::~LLFavoritesBarCtrl() { gInventory.removeObserver(this); if (mOverflowMenuHandle.get()) mOverflowMenuHandle.get()->die(); if (mContextMenuHandle.get()) mContextMenuHandle.get()->die(); } bool LLFavoritesBarCtrl::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg) { *accept = ACCEPT_NO; LLToolDragAndDrop::ESource source = LLToolDragAndDrop::getInstance()->getSource(); if (LLToolDragAndDrop::SOURCE_AGENT != source && LLToolDragAndDrop::SOURCE_LIBRARY != source) return false; switch (cargo_type) { case DAD_LANDMARK: { /* * add a callback to the end drag event. * the callback will disconnet itself immediately after execution * this is done because LLToolDragAndDrop is a common tool so it shouldn't * be overloaded with redundant callbacks. */ if (!mEndDragConnection.connected()) { mEndDragConnection = LLToolDragAndDrop::getInstance()->setEndDragCallback(boost::bind(&LLFavoritesBarCtrl::onEndDrag, this)); } // Copy the item into the favorites folder (if it's not already there). LLInventoryItem *item = (LLInventoryItem *)cargo_data; if (mDragToOverflowMenu) { LLView* overflow_menu = mOverflowMenuHandle.get(); if (overflow_menu && !overflow_menu->isDead() && overflow_menu->getVisible()) { overflow_menu->handleHover(x, y, mask); } } else // Drag to the toolbar itself { // Drag to a landmark button? if (LLFavoriteLandmarkButton* dest = dynamic_cast<LLFavoriteLandmarkButton*>(findChildByLocalCoords(x, y))) { setLandingTab(dest); } else { // Drag to the "More" button? if (mMoreTextBox && mMoreTextBox->getVisible() && mMoreTextBox->getRect().pointInRect(x, y)) { LLView* overflow_menu = mOverflowMenuHandle.get(); if (!overflow_menu || overflow_menu->isDead() || !overflow_menu->getVisible()) { showDropDownMenu(); } } // Drag to the right of the last landmark button? if (mLastTab && (x >= mLastTab->getRect().mRight)) { /* * the condition dest == NULL can be satisfied not only in the case * of dragging to the right from the last tab of the favbar. there is a * small gap between each tab. if the user drags something exactly there * then mLandingTab will be set to NULL and the dragged item will be pushed * to the end of the favorites bar. this is incorrect behavior. that's why * we need an additional check which excludes the case described previously * making sure that the mouse pointer is beyond the last tab. */ setLandingTab(NULL); } } } // Check whether we are dragging an existing item from the favorites bar bool existing_item = false; if (item && mDragItemId == item->getUUID()) { // There is a chance of mDragItemId being obsolete // ex: can happen if something interrupts viewer, which // results in viewer not geting a 'mouse up' signal for (LLInventoryModel::item_array_t::iterator i = mItems.begin(); i != mItems.end(); ++i) { if ((*i)->getUUID() == mDragItemId) { existing_item = true; break; } } } if (existing_item) { *accept = ACCEPT_YES_SINGLE; showDragMarker(true); if (drop) { handleExistingFavoriteDragAndDrop(x, y); } } else { const LLUUID favorites_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE); if (item->getParentUUID() == favorites_id) { LL_WARNS("FavoritesBar") << "Attemt to copy a favorite item into the same folder." << LL_ENDL; break; } *accept = ACCEPT_YES_COPY_MULTI; showDragMarker(true); if (drop) { if (mItems.empty()) { setLandingTab(NULL); mLastTab = NULL; } handleNewFavoriteDragAndDrop(item, favorites_id, x, y); } } } break; default: break; } return true; } bool LLFavoritesBarCtrl::handleDragAndDropToMenu(S32 x, S32 y, MASK mask, bool drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg) { mDragToOverflowMenu = true; bool handled = handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); mDragToOverflowMenu = false; return handled; } void LLFavoritesBarCtrl::handleExistingFavoriteDragAndDrop(S32 x, S32 y) { if (LL_UNLIKELY(mItems.empty())) return; LLUUID target_id; bool insert_before = false; if (!findDragAndDropTarget(target_id, insert_before, x, y)) return; // There is no need to handle if an item was dragged onto itself if (target_id == mDragItemId) return; // Move the dragged item to the right place in the array LLInventoryModel::updateItemsOrder(mItems, mDragItemId, target_id, insert_before); LLFavoritesOrderStorage::instance().saveItemsOrder(mItems); LLView* menu = mOverflowMenuHandle.get(); if (menu && !menu->isDead() && menu->getVisible()) { updateOverflowMenuItems(); positionAndShowOverflowMenu(); } } void LLFavoritesBarCtrl::handleNewFavoriteDragAndDrop(LLInventoryItem *item, const LLUUID& favorites_id, S32 x, S32 y) { // Identify the button hovered and the side to drop LLUUID target_id; bool insert_before = false; // There is no need to handle if an item was dragged onto itself if (findDragAndDropTarget(target_id, insert_before, x, y) && (target_id == mDragItemId)) return; LLPointer<LLViewerInventoryItem> viewer_item = new LLViewerInventoryItem(item); // Insert the dragged item to the right place if (target_id.notNull()) { insertItem(mItems, target_id, viewer_item, insert_before); } else { // This can happen when the item list is empty mItems.push_back(viewer_item); } int sortField = 0; LLPointer<LLItemCopiedCallback> cb; const F64 CALLBACK_WAIT_TIME = 30.f; sWaitingForCallabck = LLTimer::getTotalSeconds() + CALLBACK_WAIT_TIME; // current order is saved by setting incremental values (1, 2, 3, ...) for the sort field for (LLInventoryModel::item_array_t::iterator i = mItems.begin(); i != mItems.end(); ++i) { LLViewerInventoryItem* currItem = *i; if (currItem->getUUID() == item->getUUID()) { cb = new LLItemCopiedCallback(++sortField); } else { LLFavoritesOrderStorage::instance().setSortIndex(currItem, ++sortField); currItem->setComplete(true); currItem->updateServer(false); gInventory.updateItem(currItem); } } LLToolDragAndDrop* tool_dad = LLToolDragAndDrop::getInstance(); if (tool_dad->getSource() == LLToolDragAndDrop::SOURCE_NOTECARD) { viewer_item->setType(LLAssetType::AT_LANDMARK); copy_inventory_from_notecard(favorites_id, tool_dad->getObjectID(), tool_dad->getSourceID(), viewer_item.get(), gInventoryCallbacks.registerCB(cb)); } else { copy_inventory_item( gAgent.getID(), item->getPermissions().getOwner(), item->getUUID(), favorites_id, std::string(), cb); } // [MAINT-2386] Ensure the favorite button has been created and is valid. // This also ensures that mLastTab will be valid when dropping multiple // landmarks to an empty favorites bar. updateButtons(); LLView* overflow_menu = mOverflowMenuHandle.get(); if (overflow_menu && !overflow_menu->isDead() && overflow_menu->getVisible()) { updateOverflowMenuItems(); positionAndShowOverflowMenu(); } LL_INFOS("FavoritesBar") << "Copied inventory item #" << item->getUUID() << " to favorites." << LL_ENDL; } bool LLFavoritesBarCtrl::findDragAndDropTarget(LLUUID& target_id, bool& insert_before, S32 x, S32 y) { if (mItems.empty()) return false; if (mDragToOverflowMenu) { LLView* overflow_menu = mOverflowMenuHandle.get(); if (LL_UNLIKELY(!overflow_menu || overflow_menu->isDead() || !overflow_menu->getVisible())) return false; // Identify the menu item hovered and the side to drop LLFavoriteLandmarkMenuItem* target_item = dynamic_cast<LLFavoriteLandmarkMenuItem*>(overflow_menu->childFromPoint(x, y)); if (target_item) { insert_before = true; } else { // Choose the bottom landmark menu item auto begin = overflow_menu->getChildList()->begin(); auto end = overflow_menu->getChildList()->end(); auto check = [](const LLView* child) -> bool { return dynamic_cast<const LLFavoriteLandmarkMenuItem*>(child); }; // Menu items are placed in the backward order, so the bottom goes first auto it = std::find_if(begin, end, check); if (LL_UNLIKELY(it == end)) return false; target_item = (LLFavoriteLandmarkMenuItem*)*it; insert_before = false; } target_id = target_item->getLandmarkID(); } else { // Identify the button hovered and the side to drop LLFavoriteLandmarkButton* hovered_button = dynamic_cast<LLFavoriteLandmarkButton*>(mLandingTab); if (hovered_button) { insert_before = true; } else { // Choose the right landmark button hovered_button = dynamic_cast<LLFavoriteLandmarkButton*>(mLastTab); if (LL_UNLIKELY(!hovered_button)) return false; insert_before = false; } target_id = hovered_button->getLandmarkID(); } return true; } //virtual void LLFavoritesBarCtrl::changed(U32 mask) { if (mFavoriteFolderId.isNull()) { mFavoriteFolderId = gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE); if (mFavoriteFolderId.notNull()) { gInventory.fetchDescendentsOf(mFavoriteFolderId); } } else { LLInventoryModel::item_array_t items; LLInventoryModel::cat_array_t cats; LLIsType is_type(LLAssetType::AT_LANDMARK); gInventory.collectDescendentsIf(mFavoriteFolderId, cats, items, LLInventoryModel::EXCLUDE_TRASH, is_type); for (LLInventoryModel::item_array_t::iterator i = items.begin(); i != items.end(); ++i) { LLFavoritesOrderStorage::instance().getSLURL((*i)->getAssetUUID()); } if (sWaitingForCallabck < LLTimer::getTotalSeconds()) { updateButtons(); if (!mItemsChangedTimer.getStarted()) { mItemsChangedTimer.start(); } else { mItemsChangedTimer.reset(); } } else { mItemsListDirty = true; } } } //virtual void LLFavoritesBarCtrl::reshape(S32 width, S32 height, bool called_from_parent) { S32 delta_width = width - getRect().getWidth(); S32 delta_height = height - getRect().getHeight(); bool force_update = delta_width || delta_height || sForceReshape; LLUICtrl::reshape(width, height, called_from_parent); updateButtons(force_update); } void LLFavoritesBarCtrl::draw() { LLUICtrl::draw(); if (mShowDragMarker) { S32 w = mImageDragIndication->getWidth(); S32 h = mImageDragIndication->getHeight(); if (mLandingTab) { // mouse pointer hovers over an existing tab LLRect rect = mLandingTab->getRect(); mImageDragIndication->draw(rect.mLeft, rect.getHeight(), w, h); } else if (mLastTab) { // mouse pointer hovers over the favbar empty space (right to the last tab) LLRect rect = mLastTab->getRect(); mImageDragIndication->draw(rect.mRight, rect.getHeight(), w, h); } // Once drawn, mark this false so we won't draw it again (unless we hit the favorite bar again) mShowDragMarker = false; } if (mItemsChangedTimer.getStarted()) { if (mItemsChangedTimer.getElapsedTimeF32() > 1.f) { LLFavoritesOrderStorage::instance().saveFavoritesRecord(); mItemsChangedTimer.stop(); } } if(!mItemsChangedTimer.getStarted() && LLFavoritesOrderStorage::instance().mUpdateRequired) { LLFavoritesOrderStorage::instance().mUpdateRequired = false; mItemsChangedTimer.start(); } if (mItemsListDirty && sWaitingForCallabck < LLTimer::getTotalSeconds()) { updateButtons(); if (!mItemsChangedTimer.getStarted()) { mItemsChangedTimer.start(); } else { mItemsChangedTimer.reset(); } } } const LLButton::Params& LLFavoritesBarCtrl::getButtonParams() { static LLButton::Params button_params; static bool params_initialized = false; if (!params_initialized) { LLXMLNodePtr button_xml_node; if(LLUICtrlFactory::getLayeredXMLNode("favorites_bar_button.xml", button_xml_node)) { LLXUIParser parser; parser.readXUI(button_xml_node, button_params, "favorites_bar_button.xml"); } params_initialized = true; } return button_params; } void LLFavoritesBarCtrl::updateButtons(bool force_update) { if (LLApp::isExiting()) { return; } mItemsListDirty = false; mItems.clear(); if (!collectFavoriteItems(mItems)) { return; } if(mGetPrevItems && gInventory.isCategoryComplete(mFavoriteFolderId)) { for (LLInventoryModel::item_array_t::iterator it = mItems.begin(); it != mItems.end(); it++) { LLFavoritesOrderStorage::instance().mFavoriteNames[(*it)->getUUID()]= (*it)->getName(); } LLFavoritesOrderStorage::instance().mPrevFavorites = mItems; mGetPrevItems = false; if (LLFavoritesOrderStorage::instance().isStorageUpdateNeeded()) { if (!mItemsChangedTimer.getStarted()) { mItemsChangedTimer.start(); } } } const LLButton::Params& button_params = getButtonParams(); if(mItems.empty()) { mBarLabel->setVisible(true); mLastTab = NULL; } else { mBarLabel->setVisible(false); } const child_list_t* childs = getChildList(); child_list_const_iter_t child_it = childs->begin(); int first_changed_item_index = 0; if (!force_update) { //lets find first changed button while (child_it != childs->end() && first_changed_item_index < mItems.size()) { LLFavoriteLandmarkButton* button = dynamic_cast<LLFavoriteLandmarkButton*> (*child_it); if (button) { const LLViewerInventoryItem *item = mItems[first_changed_item_index].get(); if (item) { // an child's order and mItems should be same if (button->getLandmarkID() != item->getUUID() // sort order has been changed || button->getLabelSelected() != item->getName()) // favorite's name has been changed { break; } } first_changed_item_index++; } child_it++; } } // now first_changed_item_index should contains a number of button that need to change if (first_changed_item_index <= mItems.size()) { // Rebuild the buttons only // child_list_t is a linked list, so safe to erase from the middle if we pre-increment the iterator while (child_it != childs->end()) { //lets remove other landmarks button and rebuild it child_list_const_iter_t cur_it = child_it++; LLFavoriteLandmarkButton* button = dynamic_cast<LLFavoriteLandmarkButton*> (*cur_it); if (button) { if (mLastTab == button) { mLastTab = NULL; } removeChild(button); delete button; } } // we have to remove ChevronButton to make sure that the last item will be LandmarkButton to get the right aligning // keep in mind that we are cutting all buttons in space between the last visible child of favbar and ChevronButton if (mMoreTextBox->getParent() == this) { removeChild(mMoreTextBox); } int last_right_edge = 0; //calculate new buttons offset if (getChildList()->size() > 0) { //find last visible child to get the rightest button offset child_list_const_reverse_iter_t last_visible_it = std::find_if( childs->rbegin(), childs->rend(), [](const child_list_t::value_type& child) { return child->getVisible(); }); if(last_visible_it != childs->rend()) { last_right_edge = (*last_visible_it)->getRect().mRight; } } //last_right_edge is saving coordinates LLButton* last_new_button = NULL; int j = first_changed_item_index; for (; j < mItems.size(); j++) { last_new_button = createButton(mItems[j], button_params, last_right_edge); if (!last_new_button) { break; } sendChildToBack(last_new_button); last_right_edge = last_new_button->getRect().mRight; mLastTab = last_new_button; } if (!mLastTab && mItems.size() > 0) { // mMoreTextBox was removed, so LLFavoriteLandmarkButtons // should be the only ones in the list mLastTab = dynamic_cast<LLFavoriteLandmarkButton*>(childs->back()); } mFirstDropDownItem = j; // Chevron button if (mFirstDropDownItem < mItems.size()) { // if updateButton had been called it means: // or there are some new favorites, or width had been changed // so if we need to display chevron button, we must update dropdown items too. mUpdateDropDownItems = true; S32 buttonHGap = button_params.rect.left; // default value // Chevron button should stay right aligned LLRect rect(mMoreTextBox->getRect()); rect.translate(getRect().mRight - rect.mRight - buttonHGap, 0); addChild(mMoreTextBox); mMoreTextBox->setRect(rect); mMoreTextBox->setVisible(true); } // Update overflow menu LLToggleableMenu* overflow_menu = static_cast <LLToggleableMenu*> (mOverflowMenuHandle.get()); if (overflow_menu && overflow_menu->getVisible() && (overflow_menu->getItemCount() != mDropDownItemsCount)) { overflow_menu->setVisible(false); if (mUpdateDropDownItems) { showDropDownMenu(); } } } else { mUpdateDropDownItems = false; } } LLButton* LLFavoritesBarCtrl::createButton(const LLPointer<LLViewerInventoryItem> item, const LLButton::Params& button_params, S32 x_offset) { S32 def_button_width = button_params.rect.width; S32 button_x_delta = button_params.rect.left; // default value S32 curr_x = x_offset; /** * WORKAROUND: * There are some problem with displaying of fonts in buttons. * Empty space or ellipsis might be displayed instead of last symbols, even though the width of the button is enough. * The problem disappears if we pad the button with 20 pixels. */ int required_width = mFont->getWidth(item->getName()) + 20; int width = required_width > def_button_width? def_button_width : required_width; LLFavoriteLandmarkButton* fav_btn = NULL; // do we have a place for next button + double buttonHGap + mMoreTextBox ? if(curr_x + width + 2*button_x_delta + mMoreTextBox->getRect().getWidth() > getRect().mRight ) { return NULL; } LLButton::Params fav_btn_params(button_params); fav_btn = LLUICtrlFactory::create<LLFavoriteLandmarkButton>(fav_btn_params); if (NULL == fav_btn) { LL_WARNS("FavoritesBar") << "Unable to create LLFavoriteLandmarkButton widget: " << item->getName() << LL_ENDL; return NULL; } addChild(fav_btn); LLRect butt_rect (fav_btn->getRect()); fav_btn->setLandmarkID(item->getUUID()); butt_rect.setOriginAndSize(curr_x + button_x_delta, fav_btn->getRect().mBottom, width, fav_btn->getRect().getHeight()); fav_btn->setRect(butt_rect); // change only left and save bottom fav_btn->setFont(mFont); fav_btn->setLabel(item->getName()); fav_btn->setToolTip(item->getName()); fav_btn->setCommitCallback(boost::bind(&LLFavoritesBarCtrl::onButtonClick, this, item->getUUID())); fav_btn->setRightMouseDownCallback(boost::bind(&LLFavoritesBarCtrl::onButtonRightClick, this, item->getUUID(), _1, _2, _3,_4 )); fav_btn->LLUICtrl::setMouseDownCallback(boost::bind(&LLFavoritesBarCtrl::onButtonMouseDown, this, item->getUUID(), _1, _2, _3, _4)); fav_btn->LLUICtrl::setMouseUpCallback(boost::bind(&LLFavoritesBarCtrl::onButtonMouseUp, this, item->getUUID(), _1, _2, _3, _4)); return fav_btn; } bool LLFavoritesBarCtrl::postBuild() { // make the popup menu available LLMenuGL* menu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>("menu_favorites.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); if (!menu) { menu = LLUICtrlFactory::getDefaultWidget<LLMenuGL>("inventory_menu"); } menu->setBackgroundColor(LLUIColorTable::instance().getColor("MenuPopupBgColor")); mContextMenuHandle = menu->getHandle(); return true; } bool LLFavoritesBarCtrl::collectFavoriteItems(LLInventoryModel::item_array_t &items) { if (mFavoriteFolderId.isNull()) return false; LLInventoryModel::cat_array_t cats; LLIsType is_type(LLAssetType::AT_LANDMARK); gInventory.collectDescendentsIf(mFavoriteFolderId, cats, items, LLInventoryModel::EXCLUDE_TRASH, is_type); std::sort(items.begin(), items.end(), LLFavoritesSort()); if (needToSaveItemsOrder(items)) { S32 sortField = 0; for (LLInventoryModel::item_array_t::iterator i = items.begin(); i != items.end(); ++i) { LLFavoritesOrderStorage::instance().setSortIndex((*i), ++sortField); } LLFavoritesOrderStorage::instance().mSaveOnExit = true; } return true; } void LLFavoritesBarCtrl::onMoreTextBoxClicked() { LLUI::getInstance()->getMousePositionScreen(&mMouseX, &mMouseY); showDropDownMenu(); } void LLFavoritesBarCtrl::showDropDownMenu() { if (mOverflowMenuHandle.isDead()) { createOverflowMenu(); } LLToggleableMenu* menu = (LLToggleableMenu*)mOverflowMenuHandle.get(); if (menu && menu->toggleVisibility()) { if (mUpdateDropDownItems) { updateOverflowMenuItems(); } else { menu->buildDrawLabels(); } menu->updateParent(LLMenuGL::sMenuContainer); menu->setButtonRect(mMoreTextBox->getRect(), this); positionAndShowOverflowMenu(); } } void LLFavoritesBarCtrl::createOverflowMenu() { LLToggleableMenu::Params menu_p; menu_p.name("favorites menu"); menu_p.can_tear_off(false); menu_p.visible(false); menu_p.scrollable(true); menu_p.max_scrollable_items = 10; menu_p.preferred_width = DROP_DOWN_MENU_WIDTH; LLFavoriteLandmarkToggleableMenu* menu = LLUICtrlFactory::create<LLFavoriteLandmarkToggleableMenu>(menu_p); menu->setToolbar(this); mOverflowMenuHandle = menu->getHandle(); } void LLFavoritesBarCtrl::updateOverflowMenuItems() { LLToggleableMenu* menu = (LLToggleableMenu*)mOverflowMenuHandle.get(); menu->empty(); U32 widest_item = 0; for (S32 i = mFirstDropDownItem; i < mItems.size(); i++) { LLViewerInventoryItem* item = mItems.at(i); const std::string& item_name = item->getName(); LLFavoriteLandmarkMenuItem::Params item_params; item_params.name(item_name); item_params.label(item_name); item_params.on_click.function(boost::bind(&LLFavoritesBarCtrl::onButtonClick, this, item->getUUID())); LLFavoriteLandmarkMenuItem *menu_item = LLUICtrlFactory::create<LLFavoriteLandmarkMenuItem>(item_params); menu_item->initFavoritesBarPointer(this); menu_item->setRightMouseDownCallback(boost::bind(&LLFavoritesBarCtrl::onButtonRightClick, this, item->getUUID(), _1, _2, _3, _4)); menu_item->LLUICtrl::setMouseDownCallback(boost::bind(&LLFavoritesBarCtrl::onButtonMouseDown, this, item->getUUID(), _1, _2, _3, _4)); menu_item->LLUICtrl::setMouseUpCallback(boost::bind(&LLFavoritesBarCtrl::onButtonMouseUp, this, item->getUUID(), _1, _2, _3, _4)); menu_item->setLandmarkID(item->getUUID()); fitLabelWidth(menu_item); widest_item = llmax(widest_item, menu_item->getNominalWidth()); menu->addChild(menu_item); } menu->buildDrawLabels(); mDropDownItemsCount = menu->getItemCount(); addOpenLandmarksMenuItem(menu); mUpdateDropDownItems = false; } void LLFavoritesBarCtrl::fitLabelWidth(LLMenuItemCallGL* menu_item) { U32 max_width = llmin(DROP_DOWN_MENU_WIDTH, getRect().getWidth()); std::string item_name = menu_item->getName(); // Check whether item name wider than menu if (menu_item->getNominalWidth() > max_width) { S32 chars_total = static_cast<S32>(item_name.length()); S32 chars_fitted = 1; menu_item->setLabel(LLStringExplicit("")); S32 label_space = max_width - menu_item->getFont()->getWidth("...") - menu_item->getNominalWidth();// This returns width of menu item with empty label (pad pixels) while (chars_fitted < chars_total && menu_item->getFont()->getWidth(item_name, 0, chars_fitted) < label_space) { chars_fitted++; } chars_fitted--; // Rolling back one char, that doesn't fit menu_item->setLabel(item_name.substr(0, chars_fitted) + "..."); } } void LLFavoritesBarCtrl::addOpenLandmarksMenuItem(LLToggleableMenu* menu) { std::string label_untrans = "Open landmarks"; std::string label_transl; bool translated = LLTrans::findString(label_transl, label_untrans); LLMenuItemCallGL::Params item_params; item_params.name("open_my_landmarks"); item_params.label(translated ? label_transl: label_untrans); LLSD key; key["type"] = "open_landmark_tab"; item_params.on_click.function(boost::bind(&LLFloaterSidePanelContainer::showPanel, "places", key)); LLMenuItemCallGL* menu_item = LLUICtrlFactory::create<LLMenuItemCallGL>(item_params); fitLabelWidth(menu_item); LLMenuItemSeparatorGL::Params sep_params; sep_params.enabled_color=LLUIColorTable::instance().getColor("MenuItemEnabledColor"); sep_params.disabled_color=LLUIColorTable::instance().getColor("MenuItemDisabledColor"); sep_params.highlight_bg_color=LLUIColorTable::instance().getColor("MenuItemHighlightBgColor"); sep_params.highlight_fg_color=LLUIColorTable::instance().getColor("MenuItemHighlightFgColor"); LLMenuItemSeparatorGL* separator = LLUICtrlFactory::create<LLMenuItemSeparatorGL>(sep_params); menu->addChild(separator); menu->addChild(menu_item); } void LLFavoritesBarCtrl::positionAndShowOverflowMenu() { LLToggleableMenu* menu = (LLToggleableMenu*)mOverflowMenuHandle.get(); U32 max_width = llmin(DROP_DOWN_MENU_WIDTH, getRect().getWidth()); S32 menu_x = getRect().getWidth() - max_width; S32 menu_y = getParent()->getRect().mBottom - DROP_DOWN_MENU_TOP_PAD; // the menu should be offset of the right edge of the window // so it's no covered by buttons in the right-side toolbar. LLToolBar* right_toolbar = gToolBarView->getChild<LLToolBar>("toolbar_right"); if (right_toolbar && right_toolbar->hasButtons()) { S32 toolbar_top = 0; if (LLView* top_border_panel = right_toolbar->getChild<LLView>("button_panel")) { toolbar_top = top_border_panel->calcScreenRect().mTop; } // Calculating the bottom (in screen coord) of the drop down menu S32 menu_top = getParent()->getRect().mBottom - DROP_DOWN_MENU_TOP_PAD; S32 menu_bottom = menu_top - menu->getRect().getHeight(); S32 menu_bottom_screen = 0; localPointToScreen(0, menu_bottom, &menu_top, &menu_bottom_screen); if (menu_bottom_screen < toolbar_top) { menu_x -= right_toolbar->getRect().getWidth(); } } LLMenuGL::showPopup(this, menu, menu_x, menu_y, mMouseX, mMouseY); } void LLFavoritesBarCtrl::onButtonClick(LLUUID item_id) { // We only have one Inventory, gInventory. Some day this should be better abstracted. LLInvFVBridgeAction::doAction(item_id,&gInventory); } void LLFavoritesBarCtrl::onButtonRightClick( LLUUID item_id,LLView* fav_button,S32 x,S32 y,MASK mask) { mSelectedItemID = item_id; LLMenuGL* menu = (LLMenuGL*)mContextMenuHandle.get(); if (!menu) { return; } // Remember that the context menu was shown simultaneously with the overflow menu, // so that we can restore the overflow menu when user clicks a context menu item // (which hides the overflow menu). { LLView* overflow_menu = mOverflowMenuHandle.get(); mRestoreOverflowMenu = overflow_menu && overflow_menu->getVisible(); } // Release mouse capture so hover events go to the popup menu // because this is happening during a mouse down. gFocusMgr.setMouseCapture(NULL); menu->updateParent(LLMenuGL::sMenuContainer); LLMenuGL::showPopup(fav_button, menu, x, y); } bool LLFavoritesBarCtrl::handleRightMouseDown(S32 x, S32 y, MASK mask) { bool handled = childrenHandleRightMouseDown( x, y, mask) != NULL; if(!handled && !gMenuHolder->hasVisibleMenu()) { show_navbar_context_menu(this,x,y); handled = true; } return handled; } void copy_slurl_to_clipboard_cb(std::string& slurl) { LLClipboard::instance().copyToClipboard(utf8str_to_wstring(slurl), 0, static_cast<S32>(slurl.size())); LLSD args; args["SLURL"] = slurl; LLNotificationsUtil::add("CopySLURL", args); } bool LLFavoritesBarCtrl::enableSelected(const LLSD& userdata) { std::string param = userdata.asString(); if (param == std::string("can_paste")) { return isClipboardPasteable(); } else if (param == "create_pick") { return !LLAgentPicksInfo::getInstance()->isPickLimitReached(); } return false; } void LLFavoritesBarCtrl::doToSelected(const LLSD& userdata) { std::string action = userdata.asString(); LL_INFOS("FavoritesBar") << "Action = " << action << " Item = " << mSelectedItemID.asString() << LL_ENDL; LLViewerInventoryItem* item = gInventory.getItem(mSelectedItemID); if (!item) return; if (action == "open") { onButtonClick(item->getUUID()); } else if (action == "about") { LLSD key; key["type"] = "landmark"; key["id"] = mSelectedItemID; LLFloaterSidePanelContainer::showPanel("places", key); } else if (action == "copy_slurl") { LLVector3d posGlobal; LLLandmarkActions::getLandmarkGlobalPos(mSelectedItemID, posGlobal); if (!posGlobal.isExactlyZero()) { LLLandmarkActions::getSLURLfromPosGlobal(posGlobal, copy_slurl_to_clipboard_cb); } } else if (action == "show_on_map") { LLFloaterWorldMap* worldmap_instance = LLFloaterWorldMap::getInstance(); LLVector3d posGlobal; LLLandmarkActions::getLandmarkGlobalPos(mSelectedItemID, posGlobal); if (!posGlobal.isExactlyZero() && worldmap_instance) { worldmap_instance->trackLocation(posGlobal); LLFloaterReg::showInstance("world_map", "center"); } } else if (action == "create_pick") { LLSD args; args["type"] = "create_pick"; args["item_id"] = item->getUUID(); LLFloaterSidePanelContainer::showPanel("places", args); } else if (action == "cut") { } else if (action == "copy") { LLClipboard::instance().copyToClipboard(mSelectedItemID, LLAssetType::AT_LANDMARK); } else if (action == "paste") { pasteFromClipboard(); } else if (action == "delete") { gInventory.removeItem(mSelectedItemID); } else if (action == "rename") { LLSD args; args["NAME"] = item->getName(); LLSD payload; payload["id"] = mSelectedItemID; LLNotificationsUtil::add("RenameLandmark", args, payload, boost::bind(onRenameCommit, _1, _2)); } else if (action == "move_to_landmarks") { change_item_parent(mSelectedItemID, gInventory.findCategoryUUIDForType(LLFolderType::FT_LANDMARK)); } // Pop-up the overflow menu again (it gets hidden whenever the user clicks a context menu item). // See EXT-4217 and STORM-207. LLToggleableMenu* menu = (LLToggleableMenu*) mOverflowMenuHandle.get(); if (mRestoreOverflowMenu && menu && !menu->getVisible()) { menu->resetScrollPositionOnShow(false); showDropDownMenu(); menu->resetScrollPositionOnShow(true); } } bool LLFavoritesBarCtrl::onRenameCommit(const LLSD& notification, const LLSD& response) { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); if (0 == option) { LLUUID id = notification["payload"]["id"].asUUID(); LLInventoryItem *item = gInventory.getItem(id); std::string landmark_name = response["new_name"].asString(); LLStringUtil::trim(landmark_name); if (!landmark_name.empty() && item && item->getName() != landmark_name) { LLPointer<LLViewerInventoryItem> new_item = new LLViewerInventoryItem(item); new_item->rename(landmark_name); new_item->updateServer(false); gInventory.updateItem(new_item); } } return false; } bool LLFavoritesBarCtrl::isClipboardPasteable() const { if (!LLClipboard::instance().hasContents()) { return false; } std::vector<LLUUID> objects; LLClipboard::instance().pasteFromClipboard(objects); auto count = objects.size(); for(size_t i = 0; i < count; i++) { const LLUUID &item_id = objects.at(i); // Can't paste folders const LLInventoryCategory *cat = gInventory.getCategory(item_id); if (cat) { return false; } const LLInventoryItem *item = gInventory.getItem(item_id); if (item && LLAssetType::AT_LANDMARK != item->getType()) { return false; } } return true; } void LLFavoritesBarCtrl::pasteFromClipboard() const { LLInventoryModel* model = &gInventory; if(model && isClipboardPasteable()) { LLInventoryItem* item = NULL; std::vector<LLUUID> objects; LLClipboard::instance().pasteFromClipboard(objects); auto count = objects.size(); LLUUID parent_id(mFavoriteFolderId); for(size_t i = 0; i < count; i++) { item = model->getItem(objects.at(i)); if (item) { copy_inventory_item( gAgent.getID(), item->getPermissions().getOwner(), item->getUUID(), parent_id, std::string(), LLPointer<LLInventoryCallback>(NULL)); } } } } void LLFavoritesBarCtrl::onButtonMouseDown(LLUUID id, LLUICtrl* ctrl, S32 x, S32 y, MASK mask) { // EXT-6997 (Fav bar: Pop-up menu for LM in overflow dropdown is kept after LM was dragged away) // mContextMenuHandle.get() - is a pop-up menu (of items) in already opened dropdown menu. // We have to check and set visibility of pop-up menu in such a way instead of using // LLMenuHolderGL::hideMenus() because it will close both menus(dropdown and pop-up), but // we need to close only pop-up menu while dropdown one should be still opened. LLMenuGL* menu = (LLMenuGL*)mContextMenuHandle.get(); if(menu && menu->getVisible()) { menu->setVisible(false); } mDragItemId = id; mStartDrag = true; S32 screenX, screenY; localPointToScreen(x, y, &screenX, &screenY); LLToolDragAndDrop::getInstance()->setDragStart(screenX, screenY); } void LLFavoritesBarCtrl::onButtonMouseUp(LLUUID id, LLUICtrl* ctrl, S32 x, S32 y, MASK mask) { mStartDrag = false; mDragItemId = LLUUID::null; } void LLFavoritesBarCtrl::onEndDrag() { mEndDragConnection.disconnect(); showDragMarker(false); mDragItemId = LLUUID::null; LLView::getWindow()->setCursor(UI_CURSOR_ARROW); } bool LLFavoritesBarCtrl::handleHover(S32 x, S32 y, MASK mask) { if (mDragItemId != LLUUID::null && mStartDrag) { S32 screenX, screenY; localPointToScreen(x, y, &screenX, &screenY); if(LLToolDragAndDrop::getInstance()->isOverThreshold(screenX, screenY)) { LLToolDragAndDrop::getInstance()->beginDrag( DAD_LANDMARK, mDragItemId, LLToolDragAndDrop::SOURCE_LIBRARY); mStartDrag = false; return LLToolDragAndDrop::getInstance()->handleHover(x, y, mask); } } return true; } LLUICtrl* LLFavoritesBarCtrl::findChildByLocalCoords(S32 x, S32 y) { LLUICtrl* ctrl = NULL; const child_list_t* list = getChildList(); for (child_list_const_iter_t i = list->begin(); i != list->end(); ++i) { // Look only for children that are favorite buttons if ((*i)->getName() == "favorites_bar_btn") { LLRect rect = (*i)->getRect(); // We consider a button hit if the cursor is left of the right side // This makes the hit a bit less finicky than hitting directly on the button itself if (x <= rect.mRight) { ctrl = dynamic_cast<LLUICtrl*>(*i); break; } } } return ctrl; } bool LLFavoritesBarCtrl::needToSaveItemsOrder(const LLInventoryModel::item_array_t& items) { bool result = false; // if there is an item without sort order field set, we need to save items order for (LLInventoryModel::item_array_t::const_iterator i = items.begin(); i != items.end(); ++i) { if (LLFavoritesOrderStorage::instance().getSortIndex((*i)->getUUID()) < 0) { result = true; break; } } return result; } void LLFavoritesBarCtrl::insertItem(LLInventoryModel::item_array_t& items, const LLUUID& dest_item_id, LLViewerInventoryItem* insertedItem, bool insert_before) { // Get the iterator to the destination item LLInventoryModel::item_array_t::iterator it_dest = LLInventoryModel::findItemIterByUUID(items, dest_item_id); if (it_dest == items.end()) return; // Go to the next element if one wishes to insert after the dest element if (!insert_before) { ++it_dest; } // Insert the source item in the right place if (it_dest != items.end()) { items.insert(it_dest, insertedItem); } else { // Append to the list if it_dest reached the end items.push_back(insertedItem); } } const std::string LLFavoritesOrderStorage::SORTING_DATA_FILE_NAME = "landmarks_sorting.xml"; const S32 LLFavoritesOrderStorage::NO_INDEX = -1; bool LLFavoritesOrderStorage::mSaveOnExit = false; void LLFavoritesOrderStorage::setSortIndex(const LLViewerInventoryItem* inv_item, S32 sort_index) { mSortIndexes[inv_item->getUUID()] = sort_index; mIsDirty = true; getSLURL(inv_item->getAssetUUID()); } S32 LLFavoritesOrderStorage::getSortIndex(const LLUUID& inv_item_id) { sort_index_map_t::const_iterator it = mSortIndexes.find(inv_item_id); if (it != mSortIndexes.end()) { return it->second; } return NO_INDEX; } void LLFavoritesOrderStorage::removeSortIndex(const LLUUID& inv_item_id) { mSortIndexes.erase(inv_item_id); mIsDirty = true; } void LLFavoritesOrderStorage::getSLURL(const LLUUID& asset_id) { slurls_map_t::iterator slurl_iter = mSLURLs.find(asset_id); if (slurl_iter != mSLURLs.end()) return; // SLURL for current landmark is already cached LLLandmark* lm = gLandmarkList.getAsset(asset_id, boost::bind(&LLFavoritesOrderStorage::onLandmarkLoaded, this, asset_id, _1)); if (lm) { LL_DEBUGS("FavoritesBar") << "landmark for " << asset_id << " already loaded" << LL_ENDL; onLandmarkLoaded(asset_id, lm); } return; } // static std::string LLFavoritesOrderStorage::getStoredFavoritesFilename(const std::string &grid) { std::string user_dir = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, ""); return (user_dir.empty() ? "" : gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "stored_favorites_" + grid + ".xml") ); } // static std::string LLFavoritesOrderStorage::getStoredFavoritesFilename() { return getStoredFavoritesFilename(LLGridManager::getInstance()->getGrid()); } // static void LLFavoritesOrderStorage::destroyClass() { LLFavoritesOrderStorage::instance().cleanup(); std::string old_filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "stored_favorites.xml"); llifstream file; file.open(old_filename.c_str()); if (file.is_open()) { file.close(); std::string new_filename = getStoredFavoritesFilename(); LL_INFOS("FavoritesBar") << "moving favorites from old name '" << old_filename << "' to new name '" << new_filename << "'" << LL_ENDL; LLFile::copy(old_filename,new_filename); LLFile::remove(old_filename); } std::string filename = getSavedOrderFileName(); file.open(filename.c_str()); if (file.is_open()) { file.close(); LLFile::remove(filename); } if(mSaveOnExit || gSavedSettings.getBOOL("UpdateRememberPasswordSetting")) { LLFavoritesOrderStorage::instance().saveFavoritesRecord(true); } } std::string LLFavoritesOrderStorage::getSavedOrderFileName() { // If we quit from the login screen we will not have an SL account // name. Don't try to save, otherwise we'll dump a file in // C:\Program Files\SecondLife\ or similar. JC std::string user_dir = gDirUtilp->getLindenUserDir(); return (user_dir.empty() ? "" : gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, SORTING_DATA_FILE_NAME)); } void LLFavoritesOrderStorage::load() { std::string filename = getSavedOrderFileName(); LLSD settings_llsd; llifstream file; file.open(filename.c_str()); if (file.is_open()) { LLSDSerialize::fromXML(settings_llsd, file); LL_INFOS("FavoritesBar") << "loaded favorites order from '" << filename << "' " << (settings_llsd.isMap() ? "" : "un") << "successfully" << LL_ENDL; file.close(); mSaveOnExit = true; for (LLSD::map_const_iterator iter = settings_llsd.beginMap(); iter != settings_llsd.endMap(); ++iter) { mSortIndexes.insert(std::make_pair(LLUUID(iter->first), (S32)iter->second.asInteger())); } } else { filename = getStoredFavoritesFilename(); if (!filename.empty()) { llifstream in_file; in_file.open(filename.c_str()); LLSD fav_llsd; if (in_file.is_open()) { LLSDSerialize::fromXML(fav_llsd, in_file); LL_INFOS("FavoritesBar") << "loaded favorites from '" << filename << "' " << (fav_llsd.isMap() ? "" : "un") << "successfully" << LL_ENDL; in_file.close(); if (fav_llsd.isMap() && fav_llsd.has(gAgentUsername)) { mStorageFavorites = fav_llsd[gAgentUsername]; S32 index = 0; bool needs_validation = gSavedPerAccountSettings.getBOOL("ShowFavoritesOnLogin"); for (LLSD::array_iterator iter = mStorageFavorites.beginArray(); iter != mStorageFavorites.endArray(); ++iter) { // Validation LLUUID fv_id = iter->get("id").asUUID(); if (needs_validation && (fv_id.isNull() || iter->get("asset_id").asUUID().isNull() || iter->get("name").asString().empty() || iter->get("slurl").asString().empty())) { mRecreateFavoriteStorage = true; } mSortIndexes.insert(std::make_pair(fv_id, index)); index++; } } } else { LL_WARNS("FavoritesBar") << "unable to open favorites from '" << filename << "'" << LL_ENDL; } } } } // static void LLFavoritesOrderStorage::removeFavoritesRecordOfUser(const std::string &user, const std::string &grid) { std::string filename = getStoredFavoritesFilename(grid); if (!filename.empty()) { LLSD fav_llsd; llifstream file; file.open(filename.c_str()); if (file.is_open()) { LLSDSerialize::fromXML(fav_llsd, file); file.close(); // Note : use the "John Doe" and not the "john.doe" version of the name. // See saveFavoritesSLURLs() here above for the reason why. if (fav_llsd.has(user)) { LLSD user_llsd = fav_llsd[user]; if ((user_llsd.beginArray() != user_llsd.endArray()) && user_llsd.beginArray()->has("id")) { for (LLSD::array_iterator iter = user_llsd.beginArray(); iter != user_llsd.endArray(); ++iter) { LLSD value; value["id"] = iter->get("id").asUUID(); iter->assign(value); } fav_llsd[user] = user_llsd; llofstream file; file.open(filename.c_str()); if (file.is_open()) { LLSDSerialize::toPrettyXML(fav_llsd, file); file.close(); } } else { LL_INFOS("FavoritesBar") << "Removed favorites for " << user << LL_ENDL; fav_llsd.erase(user); } } llofstream out_file; out_file.open(filename.c_str()); if (out_file.is_open()) { LLSDSerialize::toPrettyXML(fav_llsd, out_file); LL_INFOS("FavoritesBar") << "saved favorites to '" << filename << "' " << LL_ENDL; out_file.close(); } } } } // static void LLFavoritesOrderStorage::removeFavoritesRecordOfUser() { std::string filename = getStoredFavoritesFilename(); if (!filename.empty()) { LLSD fav_llsd; llifstream file; file.open(filename.c_str()); if (file.is_open()) { LLSDSerialize::fromXML(fav_llsd, file); file.close(); LLAvatarName av_name; LLAvatarNameCache::get( gAgentID, &av_name ); // Note : use the "John Doe" and not the "john.doe" version of the name. // See saveFavoritesSLURLs() here above for the reason why. if (fav_llsd.has(av_name.getUserName())) { LLSD user_llsd = fav_llsd[av_name.getUserName()]; if ((user_llsd.beginArray()!= user_llsd.endArray()) && user_llsd.beginArray()->has("id")) { for (LLSD::array_iterator iter = user_llsd.beginArray();iter != user_llsd.endArray(); ++iter) { LLSD value; value["id"]= iter->get("id").asUUID(); iter->assign(value); } fav_llsd[av_name.getUserName()] = user_llsd; llofstream file; file.open(filename.c_str()); if ( file.is_open() ) { LLSDSerialize::toPrettyXML(fav_llsd, file); file.close(); } } else { LL_INFOS("FavoritesBar") << "Removed favorites for " << av_name.getUserName() << LL_ENDL; fav_llsd.erase(av_name.getUserName()); } } llofstream out_file; out_file.open(filename.c_str()); if ( out_file.is_open() ) { LLSDSerialize::toPrettyXML(fav_llsd, out_file); LL_INFOS("FavoritesBar") << "saved favorites to '" << filename << "' " << LL_ENDL; out_file.close(); } } } } void LLFavoritesOrderStorage::onLandmarkLoaded(const LLUUID& asset_id, LLLandmark* landmark) { if (landmark) { LL_DEBUGS("FavoritesBar") << "landmark for " << asset_id << " loaded" << LL_ENDL; LLVector3d pos_global; if (!landmark->getGlobalPos(pos_global)) { // If global position was unknown on first getGlobalPos() call // it should be set for the subsequent calls. landmark->getGlobalPos(pos_global); } if (!pos_global.isExactlyZero()) { LL_DEBUGS("FavoritesBar") << "requesting slurl for landmark " << asset_id << LL_ENDL; LLLandmarkActions::getSLURLfromPosGlobal(pos_global, boost::bind(&LLFavoritesOrderStorage::storeFavoriteSLURL, this, asset_id, _1)); } } } void LLFavoritesOrderStorage::storeFavoriteSLURL(const LLUUID& asset_id, std::string& slurl) { LL_DEBUGS("FavoritesBar") << "Saving landmark SLURL '" << slurl << "' for " << asset_id << LL_ENDL; mSLURLs[asset_id] = slurl; } void LLFavoritesOrderStorage::cleanup() { // nothing to clean if (!mIsDirty) return; const LLUUID fav_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE); LLInventoryModel::cat_array_t cats; LLInventoryModel::item_array_t items; gInventory.collectDescendents(fav_id, cats, items, LLInventoryModel::EXCLUDE_TRASH); IsNotInFavorites is_not_in_fav(items); sort_index_map_t aTempMap; //copy unremoved values from mSortIndexes to aTempMap std::remove_copy_if(mSortIndexes.begin(), mSortIndexes.end(), inserter(aTempMap, aTempMap.begin()), is_not_in_fav); //Swap the contents of mSortIndexes and aTempMap mSortIndexes.swap(aTempMap); } // See also LLInventorySort where landmarks in the Favorites folder are sorted. class LLViewerInventoryItemSort { public: bool operator()(const LLPointer<LLViewerInventoryItem>& a, const LLPointer<LLViewerInventoryItem>& b) { return LLFavoritesOrderStorage::instance().getSortIndex(a->getUUID()) < LLFavoritesOrderStorage::instance().getSortIndex(b->getUUID()); } }; void LLFavoritesOrderStorage::saveOrder() { LLInventoryModel::cat_array_t cats; LLInventoryModel::item_array_t items; LLIsType is_type(LLAssetType::AT_LANDMARK); LLUUID favorites_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE); gInventory.collectDescendentsIf(favorites_id, cats, items, LLInventoryModel::EXCLUDE_TRASH, is_type); std::sort(items.begin(), items.end(), LLViewerInventoryItemSort()); saveItemsOrder(items); } void LLFavoritesOrderStorage::saveItemsOrder( const LLInventoryModel::item_array_t& items ) { int sortField = 0; // current order is saved by setting incremental values (1, 2, 3, ...) for the sort field for (LLInventoryModel::item_array_t::const_iterator i = items.begin(); i != items.end(); ++i) { LLViewerInventoryItem* item = *i; setSortIndex(item, ++sortField); item->setComplete(true); item->updateServer(false); gInventory.updateItem(item); // Tell the parent folder to refresh its sort order. gInventory.addChangedMask(LLInventoryObserver::SORT, item->getParentUUID()); } gInventory.notifyObservers(); } // * @param source_item_id - LLUUID of the source item to be moved into new position // * @param target_item_id - LLUUID of the target item before which source item should be placed. void LLFavoritesOrderStorage::rearrangeFavoriteLandmarks(const LLUUID& source_item_id, const LLUUID& target_item_id) { LLInventoryModel::cat_array_t cats; LLInventoryModel::item_array_t items; LLIsType is_type(LLAssetType::AT_LANDMARK); LLUUID favorites_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE); gInventory.collectDescendentsIf(favorites_id, cats, items, LLInventoryModel::EXCLUDE_TRASH, is_type); // ensure items are sorted properly before changing order. EXT-3498 std::sort(items.begin(), items.end(), LLViewerInventoryItemSort()); // update order gInventory.updateItemsOrder(items, source_item_id, target_item_id); saveItemsOrder(items); } bool LLFavoritesOrderStorage::saveFavoritesRecord(bool pref_changed) { pref_changed |= mRecreateFavoriteStorage; mRecreateFavoriteStorage = false; // Can get called before inventory is done initializing. if (!gInventory.isInventoryUsable()) { return false; } LLUUID favorite_folder= gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE); if (favorite_folder.isNull()) { return false; } LLInventoryModel::item_array_t items; LLInventoryModel::cat_array_t cats; LLIsType is_type(LLAssetType::AT_LANDMARK); gInventory.collectDescendentsIf(favorite_folder, cats, items, LLInventoryModel::EXCLUDE_TRASH, is_type); std::sort(items.begin(), items.end(), LLFavoritesSort()); bool name_changed = false; for (LLInventoryModel::item_array_t::iterator it = items.begin(); it != items.end(); it++) { if(mFavoriteNames[(*it)->getUUID()] != ((*it)->getName())) { mFavoriteNames[(*it)->getUUID()] = (*it)->getName(); name_changed = true; } } for (std::set<LLUUID>::iterator it = mMissingSLURLs.begin(); it != mMissingSLURLs.end(); it++) { slurls_map_t::iterator slurl_iter = mSLURLs.find(*it); if (slurl_iter != mSLURLs.end()) { pref_changed = true; break; } } if((items != mPrevFavorites) || name_changed || pref_changed || gSavedSettings.getBOOL("UpdateRememberPasswordSetting")) { std::string filename = getStoredFavoritesFilename(); if (!filename.empty()) { llifstream in_file; in_file.open(filename.c_str()); LLSD fav_llsd; if (in_file.is_open()) { LLSDSerialize::fromXML(fav_llsd, in_file); in_file.close(); } else { LL_WARNS("FavoritesBar") << "unable to open favorites from '" << filename << "'" << LL_ENDL; } LLSD user_llsd; S32 fav_iter = 0; mMissingSLURLs.clear(); LLSD save_pass; save_pass["save_password"] = gSavedSettings.getBOOL("RememberPassword"); user_llsd[fav_iter] = save_pass; fav_iter++; for (LLInventoryModel::item_array_t::iterator it = items.begin(); it != items.end(); it++) { LLSD value; if (gSavedPerAccountSettings.getBOOL("ShowFavoritesOnLogin")) { value["name"] = (*it)->getName(); value["asset_id"] = (*it)->getAssetUUID(); value["id"] = (*it)->getUUID(); slurls_map_t::iterator slurl_iter = mSLURLs.find(value["asset_id"]); if (slurl_iter != mSLURLs.end()) { value["slurl"] = slurl_iter->second; user_llsd[fav_iter] = value; } else { getSLURL((*it)->getAssetUUID()); value["slurl"] = ""; user_llsd[fav_iter] = value; mUpdateRequired = true; mMissingSLURLs.insert((*it)->getAssetUUID()); } } else { value["id"] = (*it)->getUUID(); user_llsd[fav_iter] = value; } fav_iter ++; } LLAvatarName av_name; LLAvatarNameCache::get( gAgentID, &av_name ); // Note : use the "John Doe" and not the "john.doe" version of the name // as we'll compare it with the stored credentials in the login panel. fav_llsd[av_name.getUserName()] = user_llsd; llofstream file; file.open(filename.c_str()); if ( file.is_open() ) { LLSDSerialize::toPrettyXML(fav_llsd, file); file.close(); mSaveOnExit = false; } else { LL_WARNS("FavoritesBar") << "unable to open favorites storage for '" << av_name.getUserName() << "' at '" << filename << "' " << LL_ENDL; } } mPrevFavorites = items; } return true; } void LLFavoritesOrderStorage::showFavoritesOnLoginChanged(bool show) { if (show) { saveFavoritesRecord(true); } else { removeFavoritesRecordOfUser(); } } bool LLFavoritesOrderStorage::isStorageUpdateNeeded() { if (!mRecreateFavoriteStorage) { for (LLSD::array_iterator iter = mStorageFavorites.beginArray(); iter != mStorageFavorites.endArray(); ++iter) { if (mFavoriteNames[iter->get("id").asUUID()] != iter->get("name").asString()) { mRecreateFavoriteStorage = true; return true; } } } return false; } void AddFavoriteLandmarkCallback::fire(const LLUUID& inv_item_id) { if (!mTargetLandmarkId.isNull()) { LLFavoritesOrderStorage::instance().rearrangeFavoriteLandmarks(inv_item_id, mTargetLandmarkId); } } // EOF