/** * @file llfolderview.cpp * @brief Implementation of the folder view collection of classes. * * $LicenseInfo:firstyear=2001&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 "linden_common.h" #include "llfolderview.h" #include "llfolderviewmodel.h" #include "llclipboard.h" // *TODO: remove this once hack below gone. #include "llkeyboard.h" #include "lllineeditor.h" #include "llmenugl.h" #include "llpanel.h" #include "llscrollcontainer.h" // hack to allow scrolling #include "lltextbox.h" #include "lltrans.h" #include "llui.h" #include "lluictrlfactory.h" // Linden library includes #include "lldbstrings.h" #include "llfocusmgr.h" #include "llfontgl.h" #include "llgl.h" #include "llrender.h" // Third-party library includes #include ///---------------------------------------------------------------------------- /// Local function declarations, constants, enums, and typedefs ///---------------------------------------------------------------------------- const S32 RENAME_HEIGHT_PAD = 1; const S32 AUTO_OPEN_STACK_DEPTH = 16; const S32 MINIMUM_RENAMER_WIDTH = 80; // *TODO: move in params in xml if necessary. Requires modification of LLFolderView & LLInventoryPanel Params. const S32 STATUS_TEXT_HPAD = 6; const S32 STATUS_TEXT_VPAD = 8; enum { SIGNAL_NO_KEYBOARD_FOCUS = 1, SIGNAL_KEYBOARD_FOCUS = 2 }; F32 LLFolderView::sAutoOpenTime = 1.f; //--------------------------------------------------------------------------- // Tells all folders in a folderview to close themselves // For efficiency, calls setOpenArrangeRecursively(). // The calling function must then call: // LLFolderView* root = getRoot(); // if( root ) // { // root->arrange( NULL, NULL ); // root->scrollToShowSelection(); // } // to patch things up. class LLCloseAllFoldersFunctor : public LLFolderViewFunctor { public: LLCloseAllFoldersFunctor(bool close) { mOpen = !close; } virtual ~LLCloseAllFoldersFunctor() {} virtual void doFolder(LLFolderViewFolder* folder); virtual void doItem(LLFolderViewItem* item); bool mOpen; }; void LLCloseAllFoldersFunctor::doFolder(LLFolderViewFolder* folder) { folder->setOpenArrangeRecursively(mOpen); } // Do nothing. void LLCloseAllFoldersFunctor::doItem(LLFolderViewItem* item) { } //--------------------------------------------------------------------------- void LLAllDescendentsPassedFilter::doFolder(LLFolderViewFolder* folder) { mAllDescendentsPassedFilter &= (folder) && (folder->passedFilter()) && (folder->descendantsPassedFilter()); } void LLAllDescendentsPassedFilter::doItem(LLFolderViewItem* item) { mAllDescendentsPassedFilter &= (item) && (item->passedFilter()); } ///---------------------------------------------------------------------------- /// Class LLFolderViewScrollContainer ///---------------------------------------------------------------------------- // virtual const LLRect LLFolderViewScrollContainer::getScrolledViewRect() const { LLRect rect = LLRect::null; if (mScrolledView) { LLFolderView* folder_view = dynamic_cast(mScrolledView); if (folder_view) { S32 height = folder_view->getRect().getHeight(); rect = mScrolledView->getRect(); rect.setLeftTopAndSize(rect.mLeft, rect.mTop, rect.getWidth(), height); } } return rect; } LLFolderViewScrollContainer::LLFolderViewScrollContainer(const LLScrollContainer::Params& p) : LLScrollContainer(p) {} ///---------------------------------------------------------------------------- /// Class LLFolderView ///---------------------------------------------------------------------------- LLFolderView::Params::Params() : title("title"), use_label_suffix("use_label_suffix"), allow_multiselect("allow_multiselect", true), allow_drag("allow_drag", true), show_empty_message("show_empty_message", true), suppress_folder_menu("suppress_folder_menu", false), use_ellipses("use_ellipses", false), options_menu("options_menu", "") { folder_indentation = -4; } // Default constructor LLFolderView::LLFolderView(const Params& p) : LLFolderViewFolder(p), mScrollContainer( NULL ), mPopupMenuHandle(), mMenuFileName(p.options_menu), mAllowMultiSelect(p.allow_multiselect), mAllowDrag(p.allow_drag), mShowEmptyMessage(p.show_empty_message), mShowFolderHierarchy(false), mRenameItem( NULL ), mNeedsScroll( false ), mUseLabelSuffix(p.use_label_suffix), mSuppressFolderMenu(p.suppress_folder_menu), mPinningSelectedItem(false), mNeedsAutoSelect( false ), mAutoSelectOverride(false), mNeedsAutoRename(false), mShowSelectionContext(false), mShowSingleSelection(false), mArrangeGeneration(0), mSignalSelectCallback(0), mMinWidth(0), mDragAndDropThisFrame(false), mCallbackRegistrar(NULL), mEnableRegistrar(NULL), mUseEllipses(p.use_ellipses), mDraggingOverItem(NULL), mStatusTextBox(NULL), mShowItemLinkOverlays(p.show_item_link_overlays), mViewModel(p.view_model), mGroupedItemModel(p.grouped_item_model), mForceArrange(false), mSingleFolderMode(false) { LLPanel* panel = p.parent_panel; mParentPanel = panel->getHandle(); mViewModel->setFolderView(this); mRoot = this; LLRect rect = p.rect; LLRect new_rect(rect.mLeft, rect.mBottom + getRect().getHeight(), rect.mLeft + getRect().getWidth(), rect.mBottom); setRect( rect ); reshape(rect.getWidth(), rect.getHeight()); mAutoOpenItems.setDepth(AUTO_OPEN_STACK_DEPTH); mAutoOpenCandidate = NULL; mAutoOpenTimer.stop(); mKeyboardSelection = false; mIndentation = getParentFolder() ? getParentFolder()->getIndentation() + mLocalIndentation : 0; //clear label // go ahead and render root folder as usual // just make sure the label ("Inventory Folder") never shows up mLabel.clear(); // Escape is handled by reverting the rename, not commiting it (default behavior) LLLineEditor::Params params; params.name("ren"); params.rect(rect); params.font(getLabelFontForStyle(LLFontGL::NORMAL)); params.max_length.bytes(DB_INV_ITEM_NAME_STR_LEN); params.commit_callback.function(boost::bind(&LLFolderView::commitRename, this, _2)); params.prevalidator(&LLTextValidate::validateASCIIPrintableNoPipe); params.commit_on_focus_lost(true); params.visible(false); mRenamer = LLUICtrlFactory::create (params); addChild(mRenamer); // Textbox LLTextBox::Params text_p; LLFontGL* font = getLabelFontForStyle(mLabelStyle); //mIconPad, mTextPad are set in folder_view_item.xml LLRect new_r = LLRect(rect.mLeft + mIconPad, rect.mTop - mTextPad, rect.mRight, rect.mTop - mTextPad - font->getLineHeight()); text_p.rect(new_r); text_p.name(std::string(p.name)); text_p.font(font); text_p.visible(false); text_p.parse_urls(true); text_p.wrap(true); // allow multiline text. See EXT-7564, EXT-7047 // set text padding the same as in People panel. EXT-7047, EXT-4837 text_p.h_pad(STATUS_TEXT_HPAD); text_p.v_pad(STATUS_TEXT_VPAD); mStatusTextBox = LLUICtrlFactory::create (text_p); mStatusTextBox->setFollowsLeft(); mStatusTextBox->setFollowsTop(); addChild(mStatusTextBox); mViewModelItem->openItem(); mAreChildrenInited = true; // root folder is a special case due to not being loaded normally, assume that it's inited. } // Destroys the object LLFolderView::~LLFolderView( void ) { mRenamerTopLostSignalConnection.disconnect(); if (mRenamer) { // instead of using closeRenamer remove it directly, // since it might already be hidden LLUI::getInstance()->removePopup(mRenamer); } // The release focus call can potentially call the // scrollcontainer, which can potentially be called with a partly // destroyed scollcontainer. Just null it out here, and no worries // about calling into the invalid scroll container. // Same with the renamer. mScrollContainer = NULL; mRenameItem = NULL; mRenamer = NULL; mStatusTextBox = NULL; if (mPopupMenuHandle.get()) mPopupMenuHandle.get()->die(); mPopupMenuHandle.markDead(); mAutoOpenItems.removeAllNodes(); clearSelection(); mItems.clear(); mFolders.clear(); //mViewModel->setFolderView(NULL); mViewModel = NULL; } bool LLFolderView::canFocusChildren() const { return false; } void LLFolderView::addFolder( LLFolderViewFolder* folder) { LLFolderViewFolder::addFolder(folder); } void LLFolderView::closeAllFolders() { // Close all the folders setOpenArrangeRecursively(false, LLFolderViewFolder::RECURSE_DOWN); arrangeAll(); } void LLFolderView::openTopLevelFolders() { for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; (*fit)->setOpen(true); } } // This view grows and shrinks to enclose all of its children items and folders. // *width should be 0 // conform show folder state works S32 LLFolderView::arrange( S32* unused_width, S32* unused_height ) { mMinWidth = 0; S32 target_height; LLFolderViewFolder::arrange(&mMinWidth, &target_height); LLRect scroll_rect = (mScrollContainer ? mScrollContainer->getContentWindowRect() : LLRect()); reshape( llmax(scroll_rect.getWidth(), mMinWidth), ll_round(mCurHeight) ); LLRect new_scroll_rect = (mScrollContainer ? mScrollContainer->getContentWindowRect() : LLRect()); if (new_scroll_rect.getWidth() != scroll_rect.getWidth()) { reshape( llmax(scroll_rect.getWidth(), mMinWidth), ll_round(mCurHeight) ); } // move item renamer text field to item's new position updateRenamerPosition(); return ll_round(mTargetHeight); } void LLFolderView::filter( LLFolderViewFilter& filter ) { LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; const S32 TIME_VISIBLE = 10; // in milliseconds const S32 TIME_INVISIBLE = 1; filter.resetTime(llclamp((mParentPanel.get()->getVisible() ? TIME_VISIBLE : TIME_INVISIBLE), 1, 100)); // Note: we filter the model, not the view getViewModelItem()->filter(filter); } void LLFolderView::reshape(S32 width, S32 height, bool called_from_parent) { LLRect scroll_rect; if (mScrollContainer) { LLView::reshape(width, height, called_from_parent); scroll_rect = mScrollContainer->getContentWindowRect(); } width = llmax(mMinWidth, scroll_rect.getWidth()); height = llmax(ll_round(mCurHeight), scroll_rect.getHeight()); // Restrict width within scroll container's width if (mUseEllipses && mScrollContainer) { width = scroll_rect.getWidth(); } LLView::reshape(width, height, called_from_parent); mReshapeSignal(mSelectedItems, false); } void LLFolderView::addToSelectionList(LLFolderViewItem* item) { if (item->isSelected()) { removeFromSelectionList(item); } if (mSelectedItems.size()) { mSelectedItems.back()->setIsCurSelection(false); } item->setIsCurSelection(true); mSelectedItems.push_back(item); } void LLFolderView::removeFromSelectionList(LLFolderViewItem* item) { if (mSelectedItems.size()) { mSelectedItems.back()->setIsCurSelection(false); } selected_items_t::iterator item_iter; for (item_iter = mSelectedItems.begin(); item_iter != mSelectedItems.end();) { if (*item_iter == item) { item_iter = mSelectedItems.erase(item_iter); } else { ++item_iter; } } if (mSelectedItems.size()) { mSelectedItems.back()->setIsCurSelection(true); } } LLFolderViewItem* LLFolderView::getCurSelectedItem( void ) { if(mSelectedItems.size()) { LLFolderViewItem* itemp = mSelectedItems.back(); llassert(itemp->getIsCurSelection()); return itemp; } return NULL; } LLFolderView::selected_items_t& LLFolderView::getSelectedItems( void ) { return mSelectedItems; } // Record the selected item and pass it down the hierachy. bool LLFolderView::setSelection(LLFolderViewItem* selection, bool openitem, bool take_keyboard_focus) { mSignalSelectCallback = take_keyboard_focus ? SIGNAL_KEYBOARD_FOCUS : SIGNAL_NO_KEYBOARD_FOCUS; if( selection == this ) { return false; } if( selection && take_keyboard_focus) { mParentPanel.get()->setFocus(true); } // clear selection down here because change of keyboard focus can potentially // affect selection clearSelection(); if(selection) { addToSelectionList(selection); } bool rv = LLFolderViewFolder::setSelection(selection, openitem, take_keyboard_focus); if(openitem && selection) { selection->getParentFolder()->requestArrange(); } llassert(mSelectedItems.size() <= 1); return rv; } bool LLFolderView::changeSelection(LLFolderViewItem* selection, bool selected) { bool rv = false; // can't select root folder if(!selection || selection == this) { return false; } if (!mAllowMultiSelect) { clearSelection(); } selected_items_t::iterator item_iter; for (item_iter = mSelectedItems.begin(); item_iter != mSelectedItems.end(); ++item_iter) { if (*item_iter == selection) { break; } } bool on_list = (item_iter != mSelectedItems.end()); if(selected && !on_list) { addToSelectionList(selection); } if(!selected && on_list) { removeFromSelectionList(selection); } rv = LLFolderViewFolder::changeSelection(selection, selected); mSignalSelectCallback = SIGNAL_KEYBOARD_FOCUS; return rv; } void LLFolderView::sanitizeSelection() { LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; // store off current item in case it is automatically deselected // and we want to preserve context LLFolderViewItem* original_selected_item = getCurSelectedItem(); std::vector items_to_remove; selected_items_t::iterator item_iter; for (item_iter = mSelectedItems.begin(); item_iter != mSelectedItems.end(); ++item_iter) { LLFolderViewItem* item = *item_iter; // ensure that each ancestor is open and potentially passes filtering bool visible = false; if(item->getViewModelItem() != NULL) { visible = item->getViewModelItem()->potentiallyVisible(); // initialize from filter state for this item } // modify with parent open and filters states LLFolderViewFolder* parent_folder = item->getParentFolder(); // Move up through parent folders and see what's visible while(parent_folder) { visible = visible && parent_folder->isOpen() && parent_folder->getViewModelItem()->potentiallyVisible(); parent_folder = parent_folder->getParentFolder(); } // deselect item if any ancestor is closed or didn't pass filter requirements. if (!visible) { items_to_remove.push_back(item); } // disallow nested selections (i.e. folder items plus one or more ancestors) // could check cached mum selections count and only iterate if there are any // but that may be a premature optimization. selected_items_t::iterator other_item_iter; for (other_item_iter = mSelectedItems.begin(); other_item_iter != mSelectedItems.end(); ++other_item_iter) { LLFolderViewItem* other_item = *other_item_iter; for( parent_folder = other_item->getParentFolder(); parent_folder; parent_folder = parent_folder->getParentFolder()) { if (parent_folder == item) { // this is a descendent of the current folder, remove from list items_to_remove.push_back(other_item); break; } } } // Don't allow invisible items (such as root folders) to be selected. if (item == getRoot()) { items_to_remove.push_back(item); } } std::vector::iterator item_it; for (item_it = items_to_remove.begin(); item_it != items_to_remove.end(); ++item_it ) { changeSelection(*item_it, false); // toggle selection (also removes from list) } // if nothing selected after prior constraints... if (mSelectedItems.empty()) { // ...select first available parent of original selection LLFolderViewItem* new_selection = NULL; if (original_selected_item) { for(LLFolderViewFolder* parent_folder = original_selected_item->getParentFolder(); parent_folder; parent_folder = parent_folder->getParentFolder()) { if (parent_folder->getViewModelItem() && parent_folder->getViewModelItem()->potentiallyVisible()) { // give initial selection to first ancestor folder that potentially passes the filter if (!new_selection) { new_selection = parent_folder; } // if any ancestor folder of original item is closed, move the selection up // to the highest closed if (!parent_folder->isOpen()) { new_selection = parent_folder; } } } } else { new_selection = NULL; } if (new_selection) { setSelection(new_selection, false, false); } } } void LLFolderView::clearSelection() { for (selected_items_t::const_iterator item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it) { (*item_it)->setUnselected(); } mSelectedItems.clear(); mNeedsScroll = false; } std::set LLFolderView::getSelectionList() const { std::set selection; std::copy(mSelectedItems.begin(), mSelectedItems.end(), std::inserter(selection, selection.begin())); return selection; } bool LLFolderView::startDrag() { std::vector selected_items; selected_items_t::iterator item_it; if (!mSelectedItems.empty()) { for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it) { selected_items.push_back((*item_it)->getViewModelItem()); } return getFolderViewModel()->startDrag(selected_items); } return false; } void LLFolderView::commitRename( const LLSD& data ) { finishRenamingItem(); arrange( NULL, NULL ); } void LLFolderView::draw() { //LLFontGL* font = getLabelFontForStyle(mLabelStyle); // if cursor has moved off of me during drag and drop // close all auto opened folders if (!mDragAndDropThisFrame) { closeAutoOpenedFolders(); } static LLCachedControl type_ahead_timeout(*LLUI::getInstance()->mSettingGroups["config"], "TypeAheadTimeout", 1.5f); if (mSearchTimer.getElapsedTimeF32() > type_ahead_timeout || !mSearchString.size()) { mSearchString.clear(); } if (hasVisibleChildren()) { mStatusTextBox->setVisible( false ); } else if (mShowEmptyMessage) { mStatusTextBox->setValue(getFolderViewModel()->getStatusText(mItems.empty() && mFolders.empty())); mStatusTextBox->setVisible( true ); // firstly reshape message textbox with current size. This is necessary to // LLTextBox::getTextPixelHeight works properly const LLRect local_rect = getLocalRect(); mStatusTextBox->setShape(local_rect); // get preferable text height... S32 pixel_height = mStatusTextBox->getTextPixelHeight(); bool height_changed = (local_rect.getHeight() < pixel_height); if (height_changed) { // ... if it does not match current height, lets rearrange current view. // This will indirectly call ::arrange and reshape of the status textbox. // We should call this method to also notify parent about required rect. // See EXT-7564, EXT-7047. S32 height = 0; S32 width = 0; S32 total_height = arrange( &width, &height ); notifyParent(LLSD().with("action", "size_changes").with("height", total_height)); LLUI::popMatrix(); LLUI::pushMatrix(); LLUI::translate((F32)getRect().mLeft, (F32)getRect().mBottom); } } if (mRenameItem && mRenamer && mRenamer->getVisible() && !getVisibleRect().overlaps(mRenamer->getRect())) { // renamer is not connected to the item we are renaming in any form so manage it manually // TODO: consider stopping on any scroll action instead of when out of visible area LL_DEBUGS("Inventory") << "Renamer out of bounds, hiding" << LL_ENDL; finishRenamingItem(); } // skip over LLFolderViewFolder::draw since we don't want the folder icon, label, // and arrow for the root folder LLView::draw(); mDragAndDropThisFrame = false; } void LLFolderView::finishRenamingItem( void ) { if(!mRenamer) { return; } if( mRenameItem ) { mRenameItem->rename( mRenamer->getText() ); } closeRenamer(); // This is moved to an inventory observer in llinventorybridge.cpp, to handle updating after operation completed in AISv3 (SH-4611). // List is re-sorted alphabetically, so scroll to make sure the selected item is visible. //scrollToShowSelection(); } void LLFolderView::closeRenamer( void ) { if (mRenamer && mRenamer->getVisible()) { // Triggers onRenamerLost() that actually closes the renamer. LLUI::getInstance()->removePopup(mRenamer); } } void LLFolderView::removeSelectedItems() { if(getVisible() && getEnabled()) { // just in case we're removing the renaming item. mRenameItem = NULL; // create a temporary structure which we will use to remove // items, since the removal will futz with internal data // structures. std::vector items; auto count = mSelectedItems.size(); if(count <= 0) return; LLFolderViewItem* item = NULL; selected_items_t::iterator item_it; for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it) { item = *item_it; if (item && item->isRemovable()) { items.push_back(item); } else { LL_DEBUGS() << "Cannot delete " << item->getName() << LL_ENDL; return; } } // iterate through the new container. count = items.size(); LLUUID new_selection_id; LLFolderViewItem* item_to_select = getNextUnselectedItem(); if(count == 1) { LLFolderViewItem* item_to_delete = items[0]; LLFolderViewFolder* parent = item_to_delete->getParentFolder(); if(parent) { if (item_to_delete->remove()) { // change selection on successful delete setSelection(item_to_select, item_to_select ? item_to_select->isOpen() : false, mParentPanel.get()->hasFocus()); } } arrangeAll(); } else if (count > 1) { std::vector listeners; LLFolderViewModelItem* listener; setSelection(item_to_select, item_to_select ? item_to_select->isOpen() : false, mParentPanel.get()->hasFocus()); listeners.reserve(count); for(size_t i = 0; i < count; ++i) { listener = items[i]->getViewModelItem(); if(listener && (std::find(listeners.begin(), listeners.end(), listener) == listeners.end())) { listeners.push_back(listener); } } listener = static_cast(listeners.at(0)); if(listener) { listener->removeBatch(listeners); } } arrangeAll(); scrollToShowSelection(); } } void LLFolderView::autoOpenItem( LLFolderViewFolder* item ) { if ((mAutoOpenItems.check() == item) || (mAutoOpenItems.getDepth() >= (U32)AUTO_OPEN_STACK_DEPTH) || item->isOpen()) { return; } // close auto-opened folders LLFolderViewFolder* close_item = mAutoOpenItems.check(); while (close_item && close_item != item->getParentFolder()) { mAutoOpenItems.pop(); close_item->setOpenArrangeRecursively(false); close_item = mAutoOpenItems.check(); } item->requestArrange(); mAutoOpenItems.push(item); item->setOpen(true); if(!item->isSingleFolderMode()) { LLRect content_rect = (mScrollContainer ? mScrollContainer->getContentWindowRect() : LLRect()); LLRect constraint_rect(0,content_rect.getHeight(), content_rect.getWidth(), 0); scrollToShowItem(item, constraint_rect); } } void LLFolderView::closeAutoOpenedFolders() { while (mAutoOpenItems.check()) { LLFolderViewFolder* close_item = mAutoOpenItems.pop(); close_item->setOpen(false); } if (mAutoOpenCandidate) { mAutoOpenCandidate->setAutoOpenCountdown(0.f); } mAutoOpenCandidate = NULL; mAutoOpenTimer.stop(); } bool LLFolderView::autoOpenTest(LLFolderViewFolder* folder) { if (folder && mAutoOpenCandidate == folder) { if (mAutoOpenTimer.getStarted()) { if (!mAutoOpenCandidate->isOpen()) { mAutoOpenCandidate->setAutoOpenCountdown(clamp_rescale(mAutoOpenTimer.getElapsedTimeF32(), 0.f, sAutoOpenTime, 0.f, 1.f)); } if (mAutoOpenTimer.getElapsedTimeF32() > sAutoOpenTime) { autoOpenItem(folder); mAutoOpenTimer.stop(); return true; } } return false; } // otherwise new candidate, restart timer if (mAutoOpenCandidate) { mAutoOpenCandidate->setAutoOpenCountdown(0.f); } mAutoOpenCandidate = folder; mAutoOpenTimer.start(); return false; } bool LLFolderView::canCopy() const { if (!(getVisible() && getEnabled() && (mSelectedItems.size() > 0))) { return false; } for (selected_items_t::const_iterator selected_it = mSelectedItems.begin(); selected_it != mSelectedItems.end(); ++selected_it) { const LLFolderViewItem* item = *selected_it; if (!item->getViewModelItem()->isItemCopyable()) { return false; } } return true; } // copy selected item void LLFolderView::copy() { // *NOTE: total hack to clear the inventory clipboard LLClipboard::instance().reset(); auto count = mSelectedItems.size(); if(getVisible() && getEnabled() && (count > 0)) { LLFolderViewModelItem* listener = NULL; selected_items_t::iterator item_it; for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it) { listener = (*item_it)->getViewModelItem(); if(listener) { listener->copyToClipboard(); } } } mSearchString.clear(); } bool LLFolderView::canCut() const { if (!(getVisible() && getEnabled() && (mSelectedItems.size() > 0))) { return false; } for (selected_items_t::const_iterator selected_it = mSelectedItems.begin(); selected_it != mSelectedItems.end(); ++selected_it) { const LLFolderViewItem* item = *selected_it; const LLFolderViewModelItem* listener = item->getViewModelItem(); if (!listener || !listener->isItemRemovable()) { return false; } } return true; } void LLFolderView::cut() { // clear the inventory clipboard LLClipboard::instance().reset(); if(getVisible() && getEnabled() && (mSelectedItems.size() > 0)) { // Find out which item will be selected once the selection will be cut LLFolderViewItem* item_to_select = getNextUnselectedItem(); // Get the selection: removeItem() modified mSelectedItems and makes iterating on it unwise std::set inventory_selected = getSelectionList(); // Move each item to the clipboard and out of their folder for (std::set::iterator item_it = inventory_selected.begin(); item_it != inventory_selected.end(); ++item_it) { LLFolderViewItem* item_to_cut = *item_it; LLFolderViewModelItem* listener = item_to_cut->getViewModelItem(); if (listener) { listener->cutToClipboard(); } } // Update the selection setSelection(item_to_select, item_to_select ? item_to_select->isOpen() : false, mParentPanel.get()->hasFocus()); } mSearchString.clear(); } bool LLFolderView::canPaste() const { if (mSelectedItems.empty()) { return false; } if(getVisible() && getEnabled()) { for (selected_items_t::const_iterator item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it) { // *TODO: only check folders and parent folders of items const LLFolderViewItem* item = (*item_it); const LLFolderViewModelItem* listener = item->getViewModelItem(); if(!listener || !listener->isClipboardPasteable()) { const LLFolderViewFolder* folderp = item->getParentFolder(); listener = folderp->getViewModelItem(); if (!listener || !listener->isClipboardPasteable()) { return false; } } } return true; } return false; } // paste selected item void LLFolderView::paste() { if(getVisible() && getEnabled()) { // find set of unique folders to paste into std::set folder_set; selected_items_t::iterator selected_it; for (selected_it = mSelectedItems.begin(); selected_it != mSelectedItems.end(); ++selected_it) { LLFolderViewItem* item = *selected_it; LLFolderViewFolder* folder = dynamic_cast(item); if (folder == NULL) { folder = item->getParentFolder(); } folder_set.insert(folder); } std::set::iterator set_iter; for(set_iter = folder_set.begin(); set_iter != folder_set.end(); ++set_iter) { LLFolderViewModelItem* listener = (*set_iter)->getViewModelItem(); if(listener && listener->isClipboardPasteable()) { listener->pasteFromClipboard(); } } } mSearchString.clear(); } // public rename functionality - can only start the process void LLFolderView::startRenamingSelectedItem( void ) { LL_DEBUGS("Inventory") << "Starting inventory renamer" << LL_ENDL; // make sure selection is visible scrollToShowSelection(); auto count = mSelectedItems.size(); LLFolderViewItem* item = NULL; if(count > 0) { item = mSelectedItems.front(); } if(getVisible() && getEnabled() && (count == 1) && item && item->getViewModelItem() && item->getViewModelItem()->isItemRenameable()) { mRenameItem = item; updateRenamerPosition(); mRenamer->setText(item->getName()); mRenamer->selectAll(); mRenamer->setVisible( true ); // set focus will fail unless item is visible mRenamer->setFocus( true ); if (!mRenamerTopLostSignalConnection.connected()) { mRenamerTopLostSignalConnection = mRenamer->setTopLostCallback(boost::bind(&LLFolderView::onRenamerLost, this)); } LLUI::getInstance()->addPopup(mRenamer); } } bool LLFolderView::handleKeyHere( KEY key, MASK mask ) { bool handled = false; // SL-51858: Key presses are not being passed to the Popup menu. // A proper fix is non-trivial so instead just close the menu. LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get(); if (menu && menu->isOpen()) { LLMenuGL::sMenuContainer->hideMenus(); } switch( key ) { case KEY_F2: mSearchString.clear(); startRenamingSelectedItem(); handled = true; break; case KEY_RETURN: if (mask == MASK_NONE) { if( mRenameItem && mRenamer->getVisible() ) { finishRenamingItem(); mSearchString.clear(); handled = true; } } break; case KEY_ESCAPE: if( mRenameItem && mRenamer->getVisible() ) { closeRenamer(); handled = true; } mSearchString.clear(); break; case KEY_PAGE_UP: mSearchString.clear(); if (mScrollContainer) { mScrollContainer->pageUp(30); } handled = true; break; case KEY_PAGE_DOWN: mSearchString.clear(); if (mScrollContainer) { mScrollContainer->pageDown(30); } handled = true; break; case KEY_HOME: mSearchString.clear(); if (mScrollContainer) { mScrollContainer->goToTop(); } handled = true; break; case KEY_END: mSearchString.clear(); if (mScrollContainer) { mScrollContainer->goToBottom(); } break; case KEY_DOWN: if((mSelectedItems.size() > 0) && mScrollContainer) { LLFolderViewItem* last_selected = getCurSelectedItem(); bool shift_select = mask & MASK_SHIFT; // don't shift select down to children of folders (they are implicitly selected through parent) LLFolderViewItem* next = last_selected->getNextOpenNode(!shift_select); if (!mKeyboardSelection || (!shift_select && (!next || next == last_selected))) { setSelection(last_selected, false, true); mKeyboardSelection = true; } if (shift_select) { if (next) { if (next->isSelected()) { // shrink selection changeSelection(last_selected, false); } else if (last_selected->getParentFolder() == next->getParentFolder()) { // grow selection changeSelection(next, true); } } } else { if( next ) { if (next == last_selected) { //special case for LLAccordionCtrl if(notifyParent(LLSD().with("action","select_next")) > 0 )//message was processed { clearSelection(); return true; } return false; } setSelection( next, false, true ); } else { //special case for LLAccordionCtrl if(notifyParent(LLSD().with("action","select_next")) > 0 )//message was processed { clearSelection(); return true; } return false; } } scrollToShowSelection(); mSearchString.clear(); handled = true; } break; case KEY_UP: if((mSelectedItems.size() > 0) && mScrollContainer) { LLFolderViewItem* last_selected = mSelectedItems.back(); bool shift_select = mask & MASK_SHIFT; // don't shift select down to children of folders (they are implicitly selected through parent) LLFolderViewItem* prev = last_selected->getPreviousOpenNode(!shift_select); if (!mKeyboardSelection || (!shift_select && prev == this)) { setSelection(last_selected, false, true); mKeyboardSelection = true; } if (shift_select) { if (prev) { if (prev->isSelected()) { // shrink selection changeSelection(last_selected, false); } else if (last_selected->getParentFolder() == prev->getParentFolder()) { // grow selection changeSelection(prev, true); } } } else { if( prev ) { if (prev == this) { // If case we are in accordion tab notify parent to go to the previous accordion if(notifyParent(LLSD().with("action","select_prev")) > 0 )//message was processed { clearSelection(); return true; } return false; } setSelection( prev, false, true ); } } scrollToShowSelection(); mSearchString.clear(); handled = true; } break; case KEY_RIGHT: if(mSelectedItems.size()) { LLFolderViewItem* last_selected = getCurSelectedItem(); last_selected->setOpen( true ); mSearchString.clear(); handled = true; } break; case KEY_LEFT: if(mSelectedItems.size()) { LLFolderViewItem* last_selected = getCurSelectedItem(); if(last_selected && last_selected->isSingleFolderMode()) { handled = false; break; } LLFolderViewItem* parent_folder = last_selected->getParentFolder(); if (!last_selected->isOpen() && parent_folder && parent_folder->getParentFolder()) { setSelection(parent_folder, false, true); } else { last_selected->setOpen( false ); } mSearchString.clear(); scrollToShowSelection(); handled = true; } break; } return handled; } bool LLFolderView::handleUnicodeCharHere(llwchar uni_char) { if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL { return false; } if (uni_char > 0x7f) { LL_WARNS() << "LLFolderView::handleUnicodeCharHere - Don't handle non-ascii yet, aborting" << LL_ENDL; return false; } bool handled = false; if (mParentPanel.get()->hasFocus()) { // SL-51858: Key presses are not being passed to the Popup menu. // A proper fix is non-trivial so instead just close the menu. LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get(); if (menu && menu->isOpen()) { LLMenuGL::sMenuContainer->hideMenus(); } //do text search if (mSearchTimer.getElapsedTimeF32() > LLUI::getInstance()->mSettingGroups["config"]->getF32("TypeAheadTimeout")) { mSearchString.clear(); } mSearchTimer.reset(); if (mSearchString.size() < 128) { mSearchString += uni_char; } search(getCurSelectedItem(), mSearchString, false); handled = true; } return handled; } bool LLFolderView::handleMouseDown( S32 x, S32 y, MASK mask ) { mKeyboardSelection = false; mSearchString.clear(); mParentPanel.get()->setFocus(true); LLEditMenuHandler::gEditMenuHandler = this; return LLView::handleMouseDown( x, y, mask ); } bool LLFolderView::search(LLFolderViewItem* first_item, const std::string &search_string, bool backward) { // get first selected item LLFolderViewItem* search_item = first_item; // make sure search string is upper case std::string upper_case_string = search_string; LLStringUtil::toUpper(upper_case_string); // if nothing selected, select first item in folder if (!search_item) { // start from first item search_item = getNextFromChild(NULL); } // search over all open nodes for first substring match (with wrapping) bool found = false; LLFolderViewItem* original_search_item = search_item; do { // wrap at end if (!search_item) { if (backward) { search_item = getPreviousFromChild(NULL); } else { search_item = getNextFromChild(NULL); } if (!search_item || search_item == original_search_item) { break; } } std::string current_item_label(search_item->getViewModelItem()->getSearchableName()); LLStringUtil::toUpper(current_item_label); auto search_string_length = llmin(upper_case_string.size(), current_item_label.size()); if (!current_item_label.compare(0, search_string_length, upper_case_string)) { found = true; break; } if (backward) { search_item = search_item->getPreviousOpenNode(); } else { search_item = search_item->getNextOpenNode(); } } while(search_item != original_search_item); if (found) { setSelection(search_item, false, true); scrollToShowSelection(); } return found; } bool LLFolderView::handleDoubleClick( S32 x, S32 y, MASK mask ) { // skip LLFolderViewFolder::handleDoubleClick() return LLView::handleDoubleClick( x, y, mask ); } bool LLFolderView::handleRightMouseDown( S32 x, S32 y, MASK mask ) { // all user operations move keyboard focus to inventory // this way, we know when to stop auto-updating a search mParentPanel.get()->setFocus(true); bool handled = childrenHandleRightMouseDown(x, y, mask) != NULL; auto count = mSelectedItems.size(); LLMenuGL* menu = static_cast(mPopupMenuHandle.get()); if (!menu) { if (mCallbackRegistrar) { mCallbackRegistrar->pushScope(); } if (mEnableRegistrar) { mEnableRegistrar->pushScope(); } llassert(LLMenuGL::sMenuContainer != NULL); menu = LLUICtrlFactory::getInstance()->createFromFile(mMenuFileName, LLMenuGL::sMenuContainer, LLMenuHolderGL::child_registry_t::instance()); if (!menu) { menu = LLUICtrlFactory::getDefaultWidget("inventory_menu"); } menu->setBackgroundColor(LLUIColorTable::instance().getColor("MenuPopupBgColor")); mPopupMenuHandle = menu->getHandle(); if (mEnableRegistrar) { mEnableRegistrar->popScope(); } if (mCallbackRegistrar) { mCallbackRegistrar->popScope(); } } bool item_clicked{ false }; for (const auto item : mSelectedItems) { item_clicked |= item->getRect().pointInRect(x, y); } if(!item_clicked && mSingleFolderMode) { clearSelection(); } bool hide_folder_menu = mSuppressFolderMenu && isFolderSelected(); if (menu && (mSingleFolderMode || (handled && ( count > 0 && (hasVisibleChildren()) ))) && // show menu only if selected items are visible !hide_folder_menu) { if (mCallbackRegistrar) { mCallbackRegistrar->pushScope(); } if (mEnableRegistrar) { mEnableRegistrar->pushScope(); } updateMenuOptions(menu); menu->updateParent(LLMenuGL::sMenuContainer); LLMenuGL::showPopup(this, menu, x, y); if (mEnableRegistrar) { mEnableRegistrar->popScope(); } if (mCallbackRegistrar) { mCallbackRegistrar->popScope(); } } else { if (menu && menu->getVisible()) { menu->setVisible(false); } setSelection(NULL, false, true); } return handled; } // Add "--no options--" if the menu is completely blank. bool LLFolderView::addNoOptions(LLMenuGL* menu) const { const std::string nooptions_str = "--no options--"; LLView *nooptions_item = NULL; const LLView::child_list_t *list = menu->getChildList(); for (LLView::child_list_t::const_iterator itor = list->begin(); itor != list->end(); ++itor) { LLView *menu_item = (*itor); if (menu_item->getVisible()) { return false; } std::string name = menu_item->getName(); if (menu_item->getName() == nooptions_str) { nooptions_item = menu_item; } } if (nooptions_item) { nooptions_item->setVisible(true); nooptions_item->setEnabled(false); return true; } return false; } bool LLFolderView::handleHover( S32 x, S32 y, MASK mask ) { return LLView::handleHover( x, y, mask ); } LLFolderViewItem* LLFolderView::getHoveredItem() const { return dynamic_cast(mHoveredItem.get()); } void LLFolderView::setHoveredItem(LLFolderViewItem* itemp) { if (mHoveredItem.get() != itemp) { if (itemp) mHoveredItem = itemp->getHandle(); else mHoveredItem.markDead(); } } bool LLFolderView::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg) { mDragAndDropThisFrame = true; // 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 folder which is the hierarchy root. if (!handled) { handled = LLFolderViewFolder::handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); } return handled; } void LLFolderView::deleteAllChildren() { mRenamerTopLostSignalConnection.disconnect(); if (mRenamer) { LLUI::getInstance()->removePopup(mRenamer); } if (mPopupMenuHandle.get()) mPopupMenuHandle.get()->die(); mPopupMenuHandle.markDead(); mScrollContainer = NULL; mRenameItem = NULL; mRenamer = NULL; mStatusTextBox = NULL; clearSelection(); LLView::deleteAllChildren(); } void LLFolderView::scrollToShowSelection() { if ( mSelectedItems.size() ) { mNeedsScroll = true; } } // If the parent is scroll container, scroll it to make the selection // is maximally visible. void LLFolderView::scrollToShowItem(LLFolderViewItem* item, const LLRect& constraint_rect) { if (!mScrollContainer) return; // don't scroll to items when mouse is being used to scroll/drag and drop if (gFocusMgr.childHasMouseCapture(mScrollContainer)) { mNeedsScroll = false; return; } // if item exists and is in visible portion of parent folder... if(item) { LLRect local_rect = item->getLocalRect(); S32 icon_height = mIcon.isNull() ? 0 : mIcon->getHeight(); S32 label_height = getLabelFont()->getLineHeight(); // when navigating with keyboard, only move top of opened folder on screen, otherwise show whole folder S32 max_height_to_show = item->isOpen() && mScrollContainer->hasFocus() ? (llmax( icon_height, label_height ) + item->getIconPad()) : local_rect.getHeight(); // get portion of item that we want to see... LLRect item_local_rect = LLRect(item->getIndentation(), local_rect.getHeight(), //+40 is supposed to include few first characters llmin(item->getLabelXPos() - item->getIndentation() + 40, local_rect.getWidth()), llmax(0, local_rect.getHeight() - max_height_to_show)); LLRect item_doc_rect; item->localRectToOtherView(item_local_rect, &item_doc_rect, this); mScrollContainer->scrollToShowRect( item_doc_rect, constraint_rect ); } } LLRect LLFolderView::getVisibleRect() { S32 visible_height = (mScrollContainer ? mScrollContainer->getRect().getHeight() : 0); S32 visible_width = (mScrollContainer ? mScrollContainer->getRect().getWidth() : 0); LLRect visible_rect; visible_rect.setLeftTopAndSize(-getRect().mLeft, visible_height - getRect().mBottom, visible_width, visible_height); return visible_rect; } bool LLFolderView::getShowSelectionContext() { if (mShowSelectionContext) { return true; } LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get(); if (menu && menu->getVisible()) { return true; } return false; } void LLFolderView::setShowSingleSelection(bool show) { if (show != mShowSingleSelection) { mMultiSelectionFadeTimer.reset(); mShowSingleSelection = show; } } static LLTrace::BlockTimerStatHandle FTM_INVENTORY("Inventory"); // Main idle routine void LLFolderView::update() { // If this is associated with the user's inventory, don't do anything // until that inventory is loaded up. LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; //LL_RECORD_BLOCK_TIME(FTM_INVENTORY); // If there's no model, the view is in suspended state (being deleted) and shouldn't be updated if (getFolderViewModel() == NULL) { return; } LLFolderViewFilter& filter_object = getFolderViewModel()->getFilter(); if (filter_object.isModified() && filter_object.isNotDefault() && mParentPanel.get()->getVisible()) { mNeedsAutoSelect = true; } // Filter to determine visibility before arranging filter(filter_object); // Clear the modified setting on the filter only if the filter finished after running the filter process // Note: if the filter count has timed out, that means the filter halted before completing the entire set of items bool filter_modified = filter_object.isModified(); if (filter_modified && (!filter_object.isTimedOut())) { filter_object.clearModified(); } // automatically show matching items, and select first one if we had a selection if (mNeedsAutoSelect) { // select new item only if a filtered item not currently selected and there was a selection LLFolderViewItem* selected_itemp = mSelectedItems.empty() ? NULL : mSelectedItems.back(); if (!mAutoSelectOverride && selected_itemp && !selected_itemp->getViewModelItem()->potentiallyVisible()) { // these are named variables to get around gcc not binding non-const references to rvalues // and functor application is inherently non-const to allow for stateful functors LLSelectFirstFilteredItem functor; applyFunctorRecursively(functor); } // Open filtered folders for folder views with mAutoSelectOverride=true. // Used by LLPlacesFolderView. if (filter_object.showAllResults()) { // these are named variables to get around gcc not binding non-const references to rvalues // and functor application is inherently non-const to allow for stateful functors LLOpenFilteredFolders functor; applyFunctorRecursively(functor); } scrollToShowSelection(); } bool filter_finished = mViewModel->contentsReady() && (getViewModelItem()->passedFilter() || ( getViewModelItem()->getLastFilterGeneration() >= filter_object.getFirstSuccessGeneration() && !filter_modified)); if (filter_finished || gFocusMgr.childHasKeyboardFocus(mParentPanel.get()) || gFocusMgr.childHasMouseCapture(mParentPanel.get())) { // finishing the filter process, giving focus to the folder view, or dragging the scrollbar all stop the auto select process mNeedsAutoSelect = false; } bool is_visible = isInVisibleChain() || mForceArrange; //Puts folders/items in proper positions // arrange() takes the model filter flag into account and call sort() if necessary (CHUI-849) // It also handles the open/close folder animation if ( is_visible ) { sanitizeSelection(); if( needsArrange() ) { S32 height = 0; S32 width = 0; S32 total_height = arrange( &width, &height ); notifyParent(LLSD().with("action", "size_changes").with("height", total_height)); } } // during filtering process, try to pin selected item's location on screen // this will happen when searching your inventory and when new items arrive if (!filter_finished) { // calculate rectangle to pin item to at start of animated rearrange if (!mPinningSelectedItem && !mSelectedItems.empty()) { // lets pin it! mPinningSelectedItem = true; //Computes visible area const LLRect visible_content_rect = (mScrollContainer ? mScrollContainer->getVisibleContentRect() : LLRect()); LLFolderViewItem* selected_item = mSelectedItems.back(); //Computes location of selected content, content outside visible area will be scrolled to using below code LLRect item_rect; selected_item->localRectToOtherView(selected_item->getLocalRect(), &item_rect, this); //Computes intersected region of the selected content and visible area LLRect overlap_rect(item_rect); overlap_rect.intersectWith(visible_content_rect); //Don't scroll when the selected content exists within the visible area if (overlap_rect.getHeight() >= selected_item->getItemHeight()) { // then attempt to keep it in same place on screen mScrollConstraintRect = item_rect; mScrollConstraintRect.translate(-visible_content_rect.mLeft, -visible_content_rect.mBottom); } //Scroll because the selected content is outside the visible area else { // otherwise we just want it onscreen somewhere LLRect content_rect = (mScrollContainer ? mScrollContainer->getContentWindowRect() : LLRect()); mScrollConstraintRect.setOriginAndSize(0, 0, content_rect.getWidth(), content_rect.getHeight()); } } } else { // stop pinning selected item after folders stop rearranging if (!needsArrange()) { mPinningSelectedItem = false; } } LLRect constraint_rect; if (mPinningSelectedItem) { // use last known constraint rect for pinned item constraint_rect = mScrollConstraintRect; } else { // during normal use (page up/page down, etc), just try to fit item on screen LLRect content_rect = (mScrollContainer ? mScrollContainer->getContentWindowRect() : LLRect()); constraint_rect.setOriginAndSize(0, 0, content_rect.getWidth(), content_rect.getHeight()); } if (mSelectedItems.size() && mNeedsScroll) { LLFolderViewItem* scroll_to_item = mSelectedItems.back(); scrollToShowItem(scroll_to_item, constraint_rect); // continue scrolling until animated layout change is done bool selected_filter_finished = getRoot()->getViewModelItem()->getLastFilterGeneration() >= filter_object.getFirstSuccessGeneration(); if (selected_filter_finished && scroll_to_item && scroll_to_item->getViewModelItem()) { selected_filter_finished = scroll_to_item->getViewModelItem()->getLastFilterGeneration() >= filter_object.getFirstSuccessGeneration(); } if (filter_finished && selected_filter_finished) { bool needs_arrange = needsArrange() || getRoot()->needsArrange(); if (mParentFolder) { needs_arrange |= (bool)mParentFolder->needsArrange(); } if (!needs_arrange || !is_visible) { mNeedsScroll = false; } } } if (mSelectedItems.size()) { LLFolderViewItem* item = mSelectedItems.back(); // If the goal is to show renamer, don't callback untill // item is visible or is no longer being scrolled to. // Otherwise renamer will be instantly closed // Todo: consider moving renamer out of selection callback if (!mNeedsAutoRename || !mNeedsScroll || item->getVisible()) { if (mSignalSelectCallback) { //RN: we use keyboard focus as a proxy for user-explicit actions bool take_keyboard_focus = (mSignalSelectCallback == SIGNAL_KEYBOARD_FOCUS); mSelectSignal(mSelectedItems, take_keyboard_focus); } mSignalSelectCallback = false; } } else { mSignalSelectCallback = false; } } void LLFolderView::dumpSelectionInformation() { LL_INFOS() << "LLFolderView::dumpSelectionInformation()" << LL_NEWLINE << "****************************************" << LL_ENDL; selected_items_t::iterator item_it; for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it) { LL_INFOS() << " " << (*item_it)->getName() << LL_ENDL; } LL_INFOS() << "****************************************" << LL_ENDL; } void LLFolderView::updateRenamerPosition() { if(mRenameItem) { // See also LLFolderViewItem::draw() S32 x = mRenameItem->getLabelXPos(); S32 y = mRenameItem->getRect().getHeight() - mRenameItem->getItemHeight() - RENAME_HEIGHT_PAD; mRenameItem->localPointToScreen( x, y, &x, &y ); screenPointToLocal( x, y, &x, &y ); mRenamer->setOrigin( x, y ); LLRect scroller_rect(0, 0, (S32)LLUI::getInstance()->getWindowSize().mV[VX], 0); if (mScrollContainer) { scroller_rect = mScrollContainer->getContentWindowRect(); } S32 width = llmax(llmin(mRenameItem->getRect().getWidth() - x, scroller_rect.getWidth() - x - getRect().mLeft), MINIMUM_RENAMER_WIDTH); S32 height = mRenameItem->getItemHeight() - RENAME_HEIGHT_PAD; mRenamer->reshape( width, height, true ); } } // Update visibility and availability (i.e. enabled/disabled) of context menu items. void LLFolderView::updateMenuOptions(LLMenuGL* menu) { const LLView::child_list_t *list = menu->getChildList(); LLView::child_list_t::const_iterator menu_itor; for (menu_itor = list->begin(); menu_itor != list->end(); ++menu_itor) { (*menu_itor)->setVisible(false); (*menu_itor)->pushVisible(true); (*menu_itor)->setEnabled(true); } // Successively filter out invalid options U32 multi_select_flag = (mSelectedItems.size() > 1 ? ITEM_IN_MULTI_SELECTION : 0x0); U32 flags = multi_select_flag | FIRST_SELECTED_ITEM; for (selected_items_t::iterator item_itor = mSelectedItems.begin(); item_itor != mSelectedItems.end(); ++item_itor) { LLFolderViewItem* selected_item = (*item_itor); selected_item->buildContextMenu(*menu, flags); flags = multi_select_flag; } if(mSingleFolderMode && (mSelectedItems.size() == 0)) { buildContextMenu(*menu, flags); } // This adds a check for restrictions based on the entire // selection set - for example, any one wearable may not push you // over the limit, but all wearables together still might. if (getFolderViewGroupedItemModel()) { getFolderViewGroupedItemModel()->groupFilterContextMenu(mSelectedItems,*menu); } addNoOptions(menu); } // Refresh the context menu (that is already shown). void LLFolderView::updateMenu() { LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get(); if (menu && menu->getVisible()) { updateMenuOptions(menu); menu->needsArrange(); // update menu height if needed } } bool LLFolderView::isFolderSelected() { selected_items_t::iterator item_iter; for (item_iter = mSelectedItems.begin(); item_iter != mSelectedItems.end(); ++item_iter) { LLFolderViewFolder* folder = dynamic_cast(*item_iter); if (folder != NULL) { return true; } } return false; } bool LLFolderView::selectFirstItem() { for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();++iter) { LLFolderViewFolder* folder = (*iter ); if (folder->getVisible()) { LLFolderViewItem* itemp = folder->getNextFromChild(0,true); if(itemp) setSelection(itemp,false,true); return true; } } for(items_t::iterator iit = mItems.begin(); iit != mItems.end(); ++iit) { LLFolderViewItem* itemp = (*iit); if (itemp->getVisible()) { setSelection(itemp,false,true); return true; } } return false; } bool LLFolderView::selectLastItem() { for(items_t::reverse_iterator iit = mItems.rbegin(); iit != mItems.rend(); ++iit) { LLFolderViewItem* itemp = (*iit); if (itemp->getVisible()) { setSelection(itemp,false,true); return true; } } for (folders_t::reverse_iterator iter = mFolders.rbegin(); iter != mFolders.rend();++iter) { LLFolderViewFolder* folder = (*iter); if (folder->getVisible()) { LLFolderViewItem* itemp = folder->getPreviousFromChild(0,true); if(itemp) setSelection(itemp,false,true); return true; } } return false; } S32 LLFolderView::notify(const LLSD& info) { if(info.has("action")) { std::string str_action = info["action"]; if(str_action == "select_first") { setFocus(true); selectFirstItem(); scrollToShowSelection(); return 1; } else if(str_action == "select_last") { setFocus(true); selectLastItem(); scrollToShowSelection(); return 1; } } return 0; } ///---------------------------------------------------------------------------- /// Local function definitions ///---------------------------------------------------------------------------- void LLFolderView::onRenamerLost() { if (mRenamer && mRenamer->getVisible()) { mRenamer->setVisible(false); // will commit current name (which could be same as original name) mRenamer->setFocus(false); } if( mRenameItem ) { setSelection( mRenameItem, true ); mRenameItem = NULL; } } LLFolderViewItem* LLFolderView::getNextUnselectedItem() { LLFolderViewItem* last_item = *mSelectedItems.rbegin(); LLFolderViewItem* new_selection = last_item->getNextOpenNode(false); while(new_selection && new_selection->isSelected()) { new_selection = new_selection->getNextOpenNode(false); } if (!new_selection) { new_selection = last_item->getPreviousOpenNode(false); while (new_selection && (new_selection->isInSelection())) { new_selection = new_selection->getPreviousOpenNode(false); } } return new_selection; } S32 LLFolderView::getItemHeight() const { if(!hasVisibleChildren()) { //We need to display status textbox, let's reserve some place for it return llmax(0, mStatusTextBox->getTextPixelHeight()); } return 0; }