/** * @file llfolderview.cpp * @brief Implementation of the folder view collection of classes. * * $LicenseInfo:firstyear=2001&license=viewergpl$ * * Copyright (c) 2001-2009, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at * http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" #include "llfolderview.h" #include #include "llviewercontrol.h" #include "lldbstrings.h" #include "llfocusmgr.h" #include "llfontgl.h" #include "llgl.h" #include "llrender.h" #include "llinventory.h" #include "llcallbacklist.h" #include "llinventoryclipboard.h" // *TODO: remove this once hack below gone. #include "llinventoryview.h"// hacked in for the bonus context menu items. #include "llkeyboard.h" #include "lllineeditor.h" #include "llmenugl.h" #include "llresmgr.h" #include "llpreview.h" #include "llscrollcontainer.h" // hack to allow scrolling #include "lltooldraganddrop.h" #include "llui.h" #include "llviewerimage.h" #include "llviewerimagelist.h" #include "llviewerjointattachment.h" #include "llviewermenu.h" #include "lluictrlfactory.h" #include "llviewerwindow.h" #include "llvoavatar.h" #include "llfloaterproperties.h" // RN: HACK // We need these because some of the code below relies on things like // gAgent root folder. Remove them once the abstraction leak is fixed. #include "llagent.h" #include "llappviewer.h" ///---------------------------------------------------------------------------- /// Local function declarations, constants, enums, and typedefs ///---------------------------------------------------------------------------- const S32 LEFT_PAD = 5; const S32 LEFT_INDENTATION = 13; const S32 ICON_PAD = 2; const S32 ICON_WIDTH = 16; const S32 TEXT_PAD = 1; const S32 ARROW_SIZE = 12; const S32 RENAME_WIDTH_PAD = 4; const S32 RENAME_HEIGHT_PAD = 6; const S32 AUTO_OPEN_STACK_DEPTH = 16; const S32 MIN_ITEM_WIDTH_VISIBLE = ICON_WIDTH + ICON_PAD + ARROW_SIZE + TEXT_PAD + /*first few characters*/ 40; const S32 MINIMUM_RENAMER_WIDTH = 80; const F32 FOLDER_CLOSE_TIME_CONSTANT = 0.02f; const F32 FOLDER_OPEN_TIME_CONSTANT = 0.03f; const S32 MAX_FOLDER_ITEM_OVERLAP = 2; enum { SIGNAL_NO_KEYBOARD_FOCUS = 1, SIGNAL_KEYBOARD_FOCUS = 2 }; F32 LLFolderView::sAutoOpenTime = 1.f; void delete_selected_item(void* user_data); void copy_selected_item(void* user_data); void open_selected_items(void* user_data); void properties_selected_items(void* user_data); void paste_items(void* user_data); void renamer_focus_lost( LLFocusableElement* handler, void* user_data ); ///---------------------------------------------------------------------------- /// Class LLFolderViewItem ///---------------------------------------------------------------------------- // statics const LLFontGL* LLFolderViewItem::sFont = NULL; const LLFontGL* LLFolderViewItem::sSmallFont = NULL; LLColor4 LLFolderViewItem::sFgColor; LLColor4 LLFolderViewItem::sHighlightBgColor; LLColor4 LLFolderViewItem::sHighlightFgColor; LLColor4 LLFolderViewItem::sFilterBGColor; LLColor4 LLFolderViewItem::sFilterTextColor; LLColor4 LLFolderViewItem::sSuffixColor; LLColor4 LLFolderViewItem::sSearchStatusColor; LLUIImagePtr LLFolderViewItem::sArrowImage; LLUIImagePtr LLFolderViewItem::sBoxImage; //static void LLFolderViewItem::initClass() { sFont = LLResMgr::getInstance()->getRes( LLFONT_SANSSERIF_SMALL ); sSmallFont = LLResMgr::getInstance()->getRes( LLFONT_SMALL ); sFgColor = gColors.getColor( "MenuItemEnabledColor" ); sHighlightBgColor = gColors.getColor( "MenuItemHighlightBgColor" ); sHighlightFgColor = gColors.getColor( "MenuItemHighlightFgColor" ); sFilterBGColor = gColors.getColor( "FilterBackgroundColor" ); sFilterTextColor = gColors.getColor( "FilterTextColor" ); sSuffixColor = gColors.getColor( "InventoryItemSuffixColor" ); sSearchStatusColor = gColors.getColor( "InventorySearchStatusColor" ); sArrowImage = LLUI::getUIImage("folder_arrow.tga"); sBoxImage = LLUI::getUIImage("rounded_square.tga"); } //static void LLFolderViewItem::cleanupClass() { sArrowImage = NULL; sBoxImage = NULL; } // Default constructor // NOTE: Optimize this, we call it a *lot* when opening a large inventory LLFolderViewItem::LLFolderViewItem( const std::string& name, LLUIImagePtr icon, S32 creation_date, LLFolderView* root, LLFolderViewEventListener* listener ) : LLUICtrl( name, LLRect(0, 0, 0, 0), TRUE, NULL, NULL, FOLLOWS_LEFT|FOLLOWS_TOP|FOLLOWS_RIGHT), mLabel( name ), mLabelWidth(0), mCreationDate(creation_date), mParentFolder( NULL ), mListener( listener ), mIsSelected( FALSE ), mIsCurSelection( FALSE ), mSelectPending(FALSE), mLabelStyle( LLFontGL::NORMAL ), mIcon(icon), mHasVisibleChildren(FALSE), mIndentation(0), mFiltered(FALSE), mLastFilterGeneration(-1), mStringMatchOffset(std::string::npos), mControlLabelRotation(0.f), mRoot( root ), mDragAndDropTarget(FALSE), mIsLoading(FALSE) { refresh(); // possible opt: only call refreshFromListener() setTabStop(FALSE); } // Destroys the object LLFolderViewItem::~LLFolderViewItem( void ) { delete mListener; mListener = NULL; } LLFolderView* LLFolderViewItem::getRoot() { return mRoot; } // Returns true if this object is a child (or grandchild, etc.) of potential_ancestor. BOOL LLFolderViewItem::isDescendantOf( const LLFolderViewFolder* potential_ancestor ) { LLFolderViewItem* root = this; while( root->mParentFolder ) { if( root->mParentFolder == potential_ancestor ) { return TRUE; } root = root->mParentFolder; } return FALSE; } LLFolderViewItem* LLFolderViewItem::getNextOpenNode(BOOL include_children) { if (!mParentFolder) { return NULL; } LLFolderViewItem* itemp = mParentFolder->getNextFromChild( this, include_children ); while(itemp && !itemp->getVisible()) { LLFolderViewItem* next_itemp = itemp->mParentFolder->getNextFromChild( itemp, include_children ); if (itemp == next_itemp) { // hit last item return itemp->getVisible() ? itemp : this; } itemp = next_itemp; } return itemp; } LLFolderViewItem* LLFolderViewItem::getPreviousOpenNode(BOOL include_children) { if (!mParentFolder) { return NULL; } LLFolderViewItem* itemp = mParentFolder->getPreviousFromChild( this, include_children ); while(itemp && !itemp->getVisible()) { LLFolderViewItem* next_itemp = itemp->mParentFolder->getPreviousFromChild( itemp, include_children ); if (itemp == next_itemp) { // hit first item return itemp->getVisible() ? itemp : this; } itemp = next_itemp; } return itemp; } // is this item something we think we should be showing? // for example, if we haven't gotten around to filtering it yet, then the answer is yes // until we find out otherwise BOOL LLFolderViewItem::potentiallyVisible() { // we haven't been checked against min required filter // or we have and we passed return getLastFilterGeneration() < getRoot()->getFilter()->getMinRequiredGeneration() || getFiltered(); } BOOL LLFolderViewItem::getFiltered() { return mFiltered && mLastFilterGeneration >= mRoot->getFilter()->getMinRequiredGeneration(); } BOOL LLFolderViewItem::getFiltered(S32 filter_generation) { return mFiltered && mLastFilterGeneration >= filter_generation; } void LLFolderViewItem::setFiltered(BOOL filtered, S32 filter_generation) { mFiltered = filtered; mLastFilterGeneration = filter_generation; } void LLFolderViewItem::setIcon(LLUIImagePtr icon) { mIcon = icon; } // refresh information from the listener void LLFolderViewItem::refreshFromListener() { if(mListener) { //Super crazy hack to build the creator search label - RK LLInventoryItem* item = gInventory.getItem(mListener->getUUID()); std::string creator_name; if(item) { if(item->getCreatorUUID().notNull()) { gCacheName->getFullName(item->getCreatorUUID(), creator_name); } } mLabelCreator = creator_name; /*if(creator_name == "(Loading...)") mLabelCreator = ""; else mLabelCreator = creator_name;*/ //Label for desc search std::string desc; if(item) { if(!item->getDescription().empty()) { desc = item->getDescription(); } } mLabelDesc = desc; //Label for name search mLabel = mListener->getDisplayName(); //Build label for combined search - RK mLabelAll = mLabel + " " + mLabelCreator + " " + mLabelDesc; setIcon(mListener->getIcon()); time_t creation_date = mListener->getCreationDate(); if (mCreationDate != creation_date) { mCreationDate = mListener->getCreationDate(); dirtyFilter(); } mLabelStyle = mListener->getLabelStyle(); mLabelSuffix = mListener->getLabelSuffix(); } } void LLFolderViewItem::refresh() { refreshFromListener(); std::string searchable_label(mLabel); std::string searchable_label_creator(mLabelCreator); std::string searchable_label_desc(mLabelDesc); std::string searchable_label_all(mLabelAll); //add the (no modify), (no transfer) etc stuff to each label. searchable_label.append(mLabelSuffix); searchable_label_creator.append(mLabelSuffix); searchable_label_desc.append(mLabelSuffix); searchable_label_all.append(mLabelSuffix); //all labels need to be uppercase. LLStringUtil::toUpper(searchable_label); LLStringUtil::toUpper(searchable_label_creator); LLStringUtil::toUpper(searchable_label_desc); LLStringUtil::toUpper(searchable_label_all); if (mSearchableLabel.compare(searchable_label) || mSearchableLabelCreator.compare(searchable_label_creator) || mSearchableLabelDesc.compare(searchable_label_desc) || mSearchableLabelAll.compare(searchable_label_all)) { mSearchableLabel.assign(searchable_label); mSearchableLabelCreator.assign(searchable_label_creator); mSearchableLabelDesc.assign(searchable_label_desc); mSearchableLabelAll.assign(searchable_label_all); dirtyFilter(); // some part of label has changed, so overall width has potentially changed if (mParentFolder) mParentFolder->requestArrange(); } S32 label_width = sFont->getWidth(mLabel); if( mLabelSuffix.size() ) { label_width += sFont->getWidth( mLabelSuffix ); } mLabelWidth = ARROW_SIZE + TEXT_PAD + ICON_WIDTH + ICON_PAD + label_width; } void LLFolderViewItem::applyListenerFunctorRecursively(LLFolderViewListenerFunctor& functor) { functor(mListener); } // This function is called when items are added or view filters change. It's // implemented here but called by derived classes when folding the // views. void LLFolderViewItem::filterFromRoot( void ) { LLFolderViewItem* root = getRoot(); root->filter(*((LLFolderView*)root)->getFilter()); } // This function is called when the folder view is dirty. It's // implemented here but called by derived classes when folding the // views. void LLFolderViewItem::arrangeFromRoot() { LLFolderViewItem* root = getRoot(); S32 height = 0; S32 width = 0; root->arrange( &width, &height, 0 ); } // This function clears the currently selected item, and records the // specified selected item appropriately for display and use in the // UI. If open is TRUE, then folders are opened up along the way to // the selection. void LLFolderViewItem::setSelectionFromRoot(LLFolderViewItem* selection, BOOL openitem, BOOL take_keyboard_focus) { getRoot()->setSelection(selection, openitem, take_keyboard_focus); } // helper function to change the selection from the root. void LLFolderViewItem::changeSelectionFromRoot(LLFolderViewItem* selection, BOOL selected) { getRoot()->changeSelection(selection, selected); } void LLFolderViewItem::extendSelectionFromRoot(LLFolderViewItem* selection) { LLDynamicArray selected_items; getRoot()->extendSelection(selection, NULL, selected_items); } EInventorySortGroup LLFolderViewItem::getSortGroup() const { return SG_ITEM; } // addToFolder() returns TRUE if it succeeds. FALSE otherwise BOOL LLFolderViewItem::addToFolder(LLFolderViewFolder* folder, LLFolderView* root) { if (!folder) { return FALSE; } mParentFolder = folder; root->addItemID(getListener()->getUUID(), this); return folder->addItem(this); } // Finds width and height of this object and it's children. Also // makes sure that this view and it's children are the right size. S32 LLFolderViewItem::arrange( S32* width, S32* height, S32 filter_generation) { mIndentation = mParentFolder ? mParentFolder->getIndentation() + LEFT_INDENTATION : 0; *width = llmax(*width, mLabelWidth + mIndentation); *height = getItemHeight(); return *height; } S32 LLFolderViewItem::getItemHeight() { S32 icon_height = mIcon->getHeight(); S32 label_height = llround(sFont->getLineHeight()); return llmax( icon_height, label_height ) + ICON_PAD; } void LLFolderViewItem::filter( LLInventoryFilter& filter) { BOOL filtered = mListener && filter.check(this); // if our visibility will change as a result of this filter, then // we need to be rearranged in our parent folder if (getVisible() != filtered) { if (mParentFolder) { mParentFolder->requestArrange(); } } setFiltered(filtered, filter.getCurrentGeneration()); mStringMatchOffset = filter.getStringMatchOffset(); filter.decrementFilterCount(); if (getRoot()->getDebugFilters()) { mStatusText = llformat("%d", mLastFilterGeneration); } } void LLFolderViewItem::dirtyFilter() { mLastFilterGeneration = -1; // bubble up dirty flag all the way to root if (getParentFolder()) { getParentFolder()->setCompletedFilterGeneration(-1, TRUE); } } // *TODO: This can be optimized a lot by simply recording that it is // selected in the appropriate places, and assuming that set selection // means 'deselect' for a leaf item. Do this optimization after // multiple selection is implemented to make sure it all plays nice // together. BOOL LLFolderViewItem::setSelection(LLFolderViewItem* selection, BOOL openitem, BOOL take_keyboard_focus) { if (selection == this && !mIsSelected) { selectItem(); if(mListener) { mListener->selectItem(); } } else if (mIsSelected) // Deselect everything else. { deselectItem(); } return mIsSelected; } BOOL LLFolderViewItem::changeSelection(LLFolderViewItem* selection, BOOL selected) { if (selection == this && mIsSelected != selected) { if (mIsSelected) { deselectItem(); } else { selectItem(); } if(mListener) { mListener->selectItem(); } return TRUE; } return FALSE; } void LLFolderViewItem::deselectItem(void) { llassert(mIsSelected); mIsSelected = FALSE; // Update ancestors' count of selected descendents. LLFolderViewFolder* parent_folder = getParentFolder(); if (parent_folder) { parent_folder->recursiveIncrementNumDescendantsSelected(-1); } } void LLFolderViewItem::selectItem(void) { llassert(!mIsSelected); mIsSelected = TRUE; // Update ancestors' count of selected descendents. LLFolderViewFolder* parent_folder = getParentFolder(); if (parent_folder) { parent_folder->recursiveIncrementNumDescendantsSelected(1); } } BOOL LLFolderViewItem::isMovable() { if( mListener ) { return mListener->isItemMovable(); } else { return TRUE; } } BOOL LLFolderViewItem::isRemovable() { if( mListener ) { return mListener->isItemRemovable(); } else { return TRUE; } } void LLFolderViewItem::destroyView() { if (mParentFolder) { // removeView deletes me mParentFolder->removeView(this); } } // Call through to the viewed object and return true if it can be // removed. //BOOL LLFolderViewItem::removeRecursively(BOOL single_item) BOOL LLFolderViewItem::remove() { if(!isRemovable()) { return FALSE; } if(mListener) { return mListener->removeItem(); } return TRUE; } // Build an appropriate context menu for the item. void LLFolderViewItem::buildContextMenu(LLMenuGL& menu, U32 flags) { if(mListener) { mListener->buildContextMenu(menu, flags); } } void LLFolderViewItem::openItem( void ) { if( mListener ) { mListener->openItem(); } } void LLFolderViewItem::preview( void ) { if (mListener) { mListener->previewItem(); } } void LLFolderViewItem::rename(const std::string& new_name) { if( !new_name.empty() ) { mLabel = new_name; if( mListener ) { mListener->renameItem(new_name); if(mParentFolder) { mParentFolder->resort(this); } } } } const std::string& LLFolderViewItem::getSearchableLabel() const { U32 type = mRoot->getFilter()->getSearchType(); switch(type) { case 1: return mSearchableLabelCreator; case 2: return mSearchableLabelDesc; case 3: return mSearchableLabelAll; default: return mSearchableLabel; } } const std::string& LLFolderViewItem::getName( void ) const { if(mListener) { return mListener->getName(); } return mLabel; } // LLView functionality BOOL LLFolderViewItem::handleRightMouseDown( S32 x, S32 y, MASK mask ) { if(!mIsSelected) { setSelectionFromRoot(this, FALSE); } make_ui_sound("UISndClick"); return TRUE; } BOOL LLFolderViewItem::handleMouseDown( S32 x, S32 y, MASK mask ) { // No handler needed for focus lost since this class has no // state that depends on it. gFocusMgr.setMouseCapture( this ); if (!mIsSelected) { if(mask & MASK_CONTROL) { changeSelectionFromRoot(this, !mIsSelected); } else if (mask & MASK_SHIFT) { extendSelectionFromRoot(this); } else { setSelectionFromRoot(this, FALSE); } make_ui_sound("UISndClick"); } else { mSelectPending = TRUE; } if( isMovable() ) { S32 screen_x; S32 screen_y; localPointToScreen(x, y, &screen_x, &screen_y ); LLToolDragAndDrop::getInstance()->setDragStart( screen_x, screen_y ); } return TRUE; } BOOL LLFolderViewItem::handleHover( S32 x, S32 y, MASK mask ) { if( hasMouseCapture() && isMovable() ) { S32 screen_x; S32 screen_y; localPointToScreen(x, y, &screen_x, &screen_y ); BOOL can_drag = TRUE; if( LLToolDragAndDrop::getInstance()->isOverThreshold( screen_x, screen_y ) ) { LLFolderView* root = getRoot(); if(root->getCurSelectedItem()) { LLToolDragAndDrop::ESource src = LLToolDragAndDrop::SOURCE_WORLD; // *TODO: push this into listener and remove // dependency on llagent if(mListener && gInventory.isObjectDescendentOf(mListener->getUUID(), gAgent.getInventoryRootID())) { src = LLToolDragAndDrop::SOURCE_AGENT; } else if (mListener && gInventory.isObjectDescendentOf(mListener->getUUID(), gInventoryLibraryRoot)) { src = LLToolDragAndDrop::SOURCE_LIBRARY; } can_drag = root->startDrag(src); if (can_drag) { // if (mListener) mListener->startDrag(); // RN: when starting drag and drop, clear out last auto-open root->autoOpenTest(NULL); root->setShowSelectionContext(TRUE); // Release keyboard focus, so that if stuff is dropped into the // world, pressing the delete key won't blow away the inventory // item. gFocusMgr.setKeyboardFocus(NULL); return LLToolDragAndDrop::getInstance()->handleHover( x, y, mask ); } } } if (can_drag) { gViewerWindow->setCursor(UI_CURSOR_ARROW); } else { gViewerWindow->setCursor(UI_CURSOR_NOLOCKED); } return TRUE; } else { getRoot()->setShowSelectionContext(FALSE); gViewerWindow->setCursor(UI_CURSOR_ARROW); // let parent handle this then... return FALSE; } } BOOL LLFolderViewItem::handleDoubleClick( S32 x, S32 y, MASK mask ) { preview(); return TRUE; } BOOL LLFolderViewItem::handleScrollWheel(S32 x, S32 y, S32 clicks) { if (getParent()) { return getParent()->handleScrollWheel(x, y, clicks); } return FALSE; } BOOL LLFolderViewItem::handleMouseUp( S32 x, S32 y, MASK mask ) { // if mouse hasn't moved since mouse down... if ( pointInView(x, y) && mSelectPending ) { //...then select if(mask & MASK_CONTROL) { changeSelectionFromRoot(this, !mIsSelected); } else if (mask & MASK_SHIFT) { extendSelectionFromRoot(this); } else { setSelectionFromRoot(this, FALSE); } } mSelectPending = FALSE; if( hasMouseCapture() ) { getRoot()->setShowSelectionContext(FALSE); gFocusMgr.setMouseCapture( NULL ); } return TRUE; } BOOL LLFolderViewItem::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg) { BOOL accepted = FALSE; BOOL handled = FALSE; if(mListener) { accepted = mListener->dragOrDrop(mask,drop,cargo_type,cargo_data); handled = accepted; if (accepted) { mDragAndDropTarget = TRUE; *accept = ACCEPT_YES_MULTI; } else { *accept = ACCEPT_NO; } } if(mParentFolder && !handled) { handled = mParentFolder->handleDragAndDropFromChild(mask,drop,cargo_type,cargo_data,accept,tooltip_msg); } if (handled) { lldebugst(LLERR_USER_INPUT) << "dragAndDrop handled by LLFolderViewItem" << llendl; } return handled; } void LLFolderViewItem::draw() { bool possibly_has_children = false; bool up_to_date = mListener && mListener->isUpToDate(); if((up_to_date && hasVisibleChildren() ) || // we fetched our children and some of them have passed the filter... (!up_to_date && mListener && mListener->hasChildren())) // ...or we know we have children but haven't fetched them (doesn't obey filter) { possibly_has_children = true; } if(/*mControlLabel[0] != '\0' && */possibly_has_children) { if (sArrowImage) { gl_draw_scaled_rotated_image(mIndentation, getRect().getHeight() - ARROW_SIZE - TEXT_PAD, ARROW_SIZE, ARROW_SIZE, mControlLabelRotation, sArrowImage->getImage(), sFgColor); } } F32 text_left = (F32)(ARROW_SIZE + TEXT_PAD + ICON_WIDTH + ICON_PAD + mIndentation); // If we have keyboard focus, draw selection filled BOOL show_context = getRoot()->getShowSelectionContext(); BOOL filled = show_context || (gFocusMgr.getKeyboardFocus() == getRoot()); // always render "current" item, only render other selected items if // mShowSingleSelection is FALSE if( mIsSelected ) { gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); LLColor4 bg_color = sHighlightBgColor; //const S32 TRAILING_PAD = 5; // It just looks better with this. if (!mIsCurSelection) { // do time-based fade of extra objects F32 fade_time = getRoot()->getSelectionFadeElapsedTime(); if (getRoot()->getShowSingleSelection()) { // fading out bg_color.mV[VALPHA] = clamp_rescale(fade_time, 0.f, 0.4f, bg_color.mV[VALPHA], 0.f); } else { // fading in bg_color.mV[VALPHA] = clamp_rescale(fade_time, 0.f, 0.4f, 0.f, bg_color.mV[VALPHA]); } } gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth() - 2, llfloor(getRect().getHeight() - sFont->getLineHeight() - ICON_PAD), bg_color, filled); if (mIsCurSelection) { gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth() - 2, llfloor(getRect().getHeight() - sFont->getLineHeight() - ICON_PAD), sHighlightFgColor, FALSE); } if (getRect().getHeight() > llround(sFont->getLineHeight()) + ICON_PAD + 2) { gl_rect_2d( 0, llfloor(getRect().getHeight() - sFont->getLineHeight() - ICON_PAD) - 2, getRect().getWidth() - 2, 2, sHighlightFgColor, FALSE); if (show_context) { gl_rect_2d( 0, llfloor(getRect().getHeight() - sFont->getLineHeight() - ICON_PAD) - 2, getRect().getWidth() - 2, 2, sHighlightBgColor, TRUE); } } } if (mDragAndDropTarget) { gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth() - 2, llfloor(getRect().getHeight() - sFont->getLineHeight() - ICON_PAD), sHighlightBgColor, FALSE); if (getRect().getHeight() > llround(sFont->getLineHeight()) + ICON_PAD + 2) { gl_rect_2d( 0, llfloor(getRect().getHeight() - sFont->getLineHeight() - ICON_PAD) - 2, getRect().getWidth() - 2, 2, sHighlightBgColor, FALSE); } mDragAndDropTarget = FALSE; } if(mIcon) { mIcon->draw(mIndentation + ARROW_SIZE + TEXT_PAD, getRect().getHeight() - mIcon->getHeight()); } if (!mLabel.empty()) { // highlight filtered text BOOL debug_filters = getRoot()->getDebugFilters(); LLColor4 color = ( (mIsSelected && filled) ? sHighlightFgColor : sFgColor ); F32 right_x; F32 y = (F32)getRect().getHeight() - sFont->getLineHeight() - (F32)TEXT_PAD; if (debug_filters) { if (!getFiltered() && !possibly_has_children) { color.mV[VALPHA] *= 0.5f; } LLColor4 filter_color = mLastFilterGeneration >= getRoot()->getFilter()->getCurrentGeneration() ? LLColor4(0.5f, 0.8f, 0.5f, 1.f) : LLColor4(0.8f, 0.5f, 0.5f, 1.f); sSmallFont->renderUTF8(mStatusText, 0, text_left, y, filter_color, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, S32_MAX, S32_MAX, &right_x, FALSE ); text_left = right_x; } if ( mIsLoading && mTimeSinceRequestStart.getElapsedTimeF32() >= gSavedSettings.getF32("FolderLoadingMessageWaitTime") ) { // *TODO: Translate sFont->renderUTF8( std::string("Loading... "), 0, text_left, y, sSearchStatusColor, LLFontGL::LEFT, LLFontGL::BOTTOM, mLabelStyle, S32_MAX, S32_MAX, &right_x, FALSE); text_left = right_x; } sFont->renderUTF8( mLabel, 0, text_left, y, color, LLFontGL::LEFT, LLFontGL::BOTTOM, mLabelStyle, S32_MAX, S32_MAX, &right_x, FALSE ); if (!mLabelSuffix.empty()) { sFont->renderUTF8( mLabelSuffix, 0, right_x, y, sSuffixColor, LLFontGL::LEFT, LLFontGL::BOTTOM, mLabelStyle, S32_MAX, S32_MAX, &right_x, FALSE ); } if (sBoxImage.notNull() && mStringMatchOffset != std::string::npos) { // don't draw backgrounds for zero-length strings S32 filter_string_length = mRoot->getFilterSubString().size(); std::string combined_string = mLabel + mLabelSuffix; //fix so that highlighting works properly again - rkeast std::string check = combined_string; LLStringUtil::toUpper(check); if ((filter_string_length > 0) && (check.find(mRoot->getFilterSubString()) != -1)) { // llinfos << "mLabel " << mLabel<< " mLabelSuffix " << mLabelSuffix << " mLabel " << mLabel << " mLabel " << mLabel << llendl; S32 left = llround(text_left) + sFont->getWidth(combined_string, 0, mStringMatchOffset) - 1; S32 right = left + sFont->getWidth(combined_string, mStringMatchOffset, filter_string_length) + 2; S32 bottom = llfloor(getRect().getHeight() - sFont->getLineHeight() - 3); S32 top = getRect().getHeight(); LLRect box_rect(left, top, right, bottom); sBoxImage->draw(box_rect, sFilterBGColor); F32 match_string_left = text_left + sFont->getWidthF32(combined_string, 0, mStringMatchOffset); F32 y = (F32)getRect().getHeight() - sFont->getLineHeight() - (F32)TEXT_PAD; sFont->renderUTF8( combined_string, mStringMatchOffset, match_string_left, y, sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, mLabelStyle, filter_string_length, S32_MAX, &right_x, FALSE ); } } } if( sDebugRects ) { drawDebugRect(); } } ///---------------------------------------------------------------------------- /// Class LLFolderViewFolder ///---------------------------------------------------------------------------- // Default constructor LLFolderViewFolder::LLFolderViewFolder( const std::string& name, LLUIImagePtr icon, LLFolderView* root, LLFolderViewEventListener* listener ): LLFolderViewItem( name, icon, 0, root, listener ), // 0 = no create time mNumDescendantsSelected(0), mIsOpen(FALSE), mExpanderHighlighted(FALSE), mCurHeight(0.f), mTargetHeight(0.f), mAutoOpenCountdown(0.f), mSubtreeCreationDate(0), mAmTrash(LLFolderViewFolder::UNKNOWN), mLastArrangeGeneration( -1 ), mLastCalculatedWidth(0), mCompletedFilterGeneration(-1), mMostFilteredDescendantGeneration(-1) { mType = std::string("(folder)"); } // Destroys the object LLFolderViewFolder::~LLFolderViewFolder( void ) { // The LLView base class takes care of object destruction. make sure that we // don't have mouse or keyboard focus gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit() } // addToFolder() returns TRUE if it succeeds. FALSE otherwise BOOL LLFolderViewFolder::addToFolder(LLFolderViewFolder* folder, LLFolderView* root) { if (!folder) { return FALSE; } mParentFolder = folder; root->addItemID(getListener()->getUUID(), this); return folder->addFolder(this); } // Finds width and height of this object and it's children. Also // makes sure that this view and it's children are the right size. S32 LLFolderViewFolder::arrange( S32* width, S32* height, S32 filter_generation) { mHasVisibleChildren = hasFilteredDescendants(filter_generation); LLInventoryFilter::EFolderShow show_folder_state = getRoot()->getShowFolderState(); // calculate height as a single item (without any children), and reshapes rectangle to match LLFolderViewItem::arrange( width, height, filter_generation ); // clamp existing animated height so as to never get smaller than a single item mCurHeight = llmax((F32)*height, mCurHeight); // initialize running height value as height of single item in case we have no children *height = getItemHeight(); F32 running_height = (F32)*height; F32 target_height = (F32)*height; // are my children visible? if (needsArrange()) { // set last arrange generation first, in case children are animating // and need to be arranged again mLastArrangeGeneration = mRoot->getArrangeGeneration(); if (mIsOpen) { // Add sizes of children S32 parent_item_height = getRect().getHeight(); for(folders_t::iterator fit = mFolders.begin(); fit != mFolders.end(); ++fit) { LLFolderViewFolder* folderp = (*fit); if (getRoot()->getDebugFilters()) { folderp->setVisible(TRUE); } else { folderp->setVisible(show_folder_state == LLInventoryFilter::SHOW_ALL_FOLDERS || // always show folders? (folderp->getFiltered(filter_generation) || folderp->hasFilteredDescendants(filter_generation))); // passed filter or has descendants that passed filter } if (folderp->getVisible()) { S32 child_width = *width; S32 child_height = 0; S32 child_top = parent_item_height - llround(running_height); target_height += folderp->arrange( &child_width, &child_height, filter_generation ); running_height += (F32)child_height; *width = llmax(*width, child_width); folderp->setOrigin( 0, child_top - folderp->getRect().getHeight() ); } } for(items_t::iterator iit = mItems.begin(); iit != mItems.end(); ++iit) { LLFolderViewItem* itemp = (*iit); if (getRoot()->getDebugFilters()) { itemp->setVisible(TRUE); } else { itemp->setVisible(itemp->getFiltered(filter_generation)); } if (itemp->getVisible()) { S32 child_width = *width; S32 child_height = 0; S32 child_top = parent_item_height - llround(running_height); target_height += itemp->arrange( &child_width, &child_height, filter_generation ); // don't change width, as this item is as wide as its parent folder by construction itemp->reshape( itemp->getRect().getWidth(), child_height); running_height += (F32)child_height; *width = llmax(*width, child_width); itemp->setOrigin( 0, child_top - itemp->getRect().getHeight() ); } } } mTargetHeight = target_height; // cache this width so next time we can just return it mLastCalculatedWidth = *width; } else { // just use existing width *width = mLastCalculatedWidth; } // animate current height towards target height if (llabs(mCurHeight - mTargetHeight) > 1.f) { mCurHeight = lerp(mCurHeight, mTargetHeight, LLCriticalDamp::getInterpolant(mIsOpen ? FOLDER_OPEN_TIME_CONSTANT : FOLDER_CLOSE_TIME_CONSTANT)); requestArrange(); // hide child elements that fall out of current animated height for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; // number of pixels that bottom of folder label is from top of parent folder if (getRect().getHeight() - (*fit)->getRect().mTop + (*fit)->getItemHeight() > llround(mCurHeight) + MAX_FOLDER_ITEM_OVERLAP) { // hide if beyond current folder height (*fit)->setVisible(FALSE); } } for (items_t::iterator iter = mItems.begin(); iter != mItems.end();) { items_t::iterator iit = iter++; // number of pixels that bottom of item label is from top of parent folder if (getRect().getHeight() - (*iit)->getRect().mBottom > llround(mCurHeight) + MAX_FOLDER_ITEM_OVERLAP) { (*iit)->setVisible(FALSE); } } } else { mCurHeight = mTargetHeight; } // don't change width as this item is already as wide as its parent folder reshape(getRect().getWidth(),llround(mCurHeight)); // pass current height value back to parent *height = llround(mCurHeight); return llround(mTargetHeight); } BOOL LLFolderViewFolder::needsArrange() { return mLastArrangeGeneration < mRoot->getArrangeGeneration(); } void LLFolderViewFolder::setCompletedFilterGeneration(S32 generation, BOOL recurse_up) { mMostFilteredDescendantGeneration = llmin(mMostFilteredDescendantGeneration, generation); mCompletedFilterGeneration = generation; // only aggregate up if we are a lower (older) value if (recurse_up && mParentFolder && generation < mParentFolder->getCompletedFilterGeneration()) { mParentFolder->setCompletedFilterGeneration(generation, TRUE); } } void LLFolderViewFolder::filter( LLInventoryFilter& filter) { S32 filter_generation = filter.getCurrentGeneration(); // if failed to pass filter newer than must_pass_generation // you will automatically fail this time, so we only // check against items that have passed the filter S32 must_pass_generation = filter.getMustPassGeneration(); // if we have already been filtered against this generation, skip out if (getCompletedFilterGeneration() >= filter_generation) { return; } // filter folder itself if (getLastFilterGeneration() < filter_generation) { if (getLastFilterGeneration() >= must_pass_generation && // folder has been compared to a valid precursor filter !mFiltered) // and did not pass the filter { // go ahead and flag this folder as done mLastFilterGeneration = filter_generation; } else { // filter self only on first pass through LLFolderViewItem::filter( filter ); } } if (getRoot()->getDebugFilters()) { mStatusText = llformat("%d", mLastFilterGeneration); mStatusText += llformat("(%d)", mCompletedFilterGeneration); mStatusText += llformat("+%d", mMostFilteredDescendantGeneration); } // all descendants have been filtered later than must pass generation // but none passed if(getCompletedFilterGeneration() >= must_pass_generation && !hasFilteredDescendants(must_pass_generation)) { // don't traverse children if we've already filtered them since must_pass_generation // and came back with nothing return; } // we entered here with at least one filter iteration left // check to see if we have any more before continuing on to children if (filter.getFilterCount() < 0) { return; } // when applying a filter, matching folders get their contents downloaded first if (filter.isNotDefault() && getFiltered(filter.getMinRequiredGeneration()) && (mListener && !gInventory.isCategoryComplete(mListener->getUUID()))) { gInventory.startBackgroundFetch(mListener->getUUID()); } // now query children for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; // have we run out of iterations this frame? if (filter.getFilterCount() < 0) { break; } // mMostFilteredDescendantGeneration might have been reset // in which case we need to update it even for folders that // don't need to be filtered anymore if ((*fit)->getCompletedFilterGeneration() >= filter_generation) { // track latest generation to pass any child items if ((*fit)->getFiltered() || (*fit)->hasFilteredDescendants(filter.getMinRequiredGeneration())) { mMostFilteredDescendantGeneration = filter_generation; if (mRoot->needsAutoSelect()) { (*fit)->setOpenArrangeRecursively(TRUE); } } // just skip it, it has already been filtered continue; } // update this folders filter status (and children) (*fit)->filter( filter ); // track latest generation to pass any child items if ((*fit)->getFiltered() || (*fit)->hasFilteredDescendants(filter_generation)) { mMostFilteredDescendantGeneration = filter_generation; if (mRoot->needsAutoSelect()) { (*fit)->setOpenArrangeRecursively(TRUE); } } } for (items_t::iterator iter = mItems.begin(); iter != mItems.end();) { items_t::iterator iit = iter++; if (filter.getFilterCount() < 0) { break; } if ((*iit)->getLastFilterGeneration() >= filter_generation) { if ((*iit)->getFiltered()) { mMostFilteredDescendantGeneration = filter_generation; } continue; } if ((*iit)->getLastFilterGeneration() >= must_pass_generation && !(*iit)->getFiltered(must_pass_generation)) { // failed to pass an earlier filter that was a subset of the current one // go ahead and flag this item as done (*iit)->setFiltered(FALSE, filter_generation); continue; } (*iit)->filter( filter ); if ((*iit)->getFiltered(filter.getMinRequiredGeneration())) { mMostFilteredDescendantGeneration = filter_generation; } } // if we didn't use all filter iterations // that means we filtered all of our descendants // instead of exhausting the filter count for this frame if (filter.getFilterCount() > 0) { // flag this folder as having completed filter pass for all descendants setCompletedFilterGeneration(filter_generation, FALSE/*dont recurse up to root*/); } } void LLFolderViewFolder::setFiltered(BOOL filtered, S32 filter_generation) { // if this folder is now filtered, but wasn't before // (it just passed) if (filtered && !mFiltered) { // reset current height, because last time we drew it // it might have had more visible items than now mCurHeight = 0.f; } LLFolderViewItem::setFiltered(filtered, filter_generation); } void LLFolderViewFolder::dirtyFilter() { // we're a folder, so invalidate our completed generation setCompletedFilterGeneration(-1, FALSE); LLFolderViewItem::dirtyFilter(); } BOOL LLFolderViewFolder::hasFilteredDescendants() { return mMostFilteredDescendantGeneration >= mRoot->getFilter()->getCurrentGeneration(); } void LLFolderViewFolder::recursiveIncrementNumDescendantsSelected(S32 increment) { LLFolderViewFolder* parent_folder = this; do { parent_folder->mNumDescendantsSelected += increment; // Make sure we don't have negative values. llassert(parent_folder->mNumDescendantsSelected >= 0); parent_folder = parent_folder->getParentFolder(); } while(parent_folder); } // Passes selection information on to children and record selection // information if necessary. BOOL LLFolderViewFolder::setSelection(LLFolderViewItem* selection, BOOL openitem, BOOL take_keyboard_focus) { BOOL rv = FALSE; if( selection == this ) { if (!isSelected()) { selectItem(); } if(mListener) { mListener->selectItem(); } rv = TRUE; } else { if (isSelected()) { deselectItem(); } rv = FALSE; } BOOL child_selected = FALSE; for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; if((*fit)->setSelection(selection, openitem, take_keyboard_focus)) { rv = TRUE; child_selected = TRUE; } } for (items_t::iterator iter = mItems.begin(); iter != mItems.end();) { items_t::iterator iit = iter++; if((*iit)->setSelection(selection, openitem, take_keyboard_focus)) { rv = TRUE; child_selected = TRUE; } } if(openitem && child_selected) { setOpenArrangeRecursively(TRUE); } return rv; } // This method is used to change the selection of an item. // Recursively traverse all children; if 'selection' is 'this' then change // the select status if necessary. // Returns TRUE if the selection state of this folder, or of a child, was changed. BOOL LLFolderViewFolder::changeSelection(LLFolderViewItem* selection, BOOL selected) { BOOL rv = FALSE; if(selection == this) { if (isSelected() != selected) { rv = TRUE; if (selected) { selectItem(); } else { deselectItem(); } } if(mListener && selected) { mListener->selectItem(); } } for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; if ((*fit)->changeSelection(selection, selected)) { rv = TRUE; } } for (items_t::iterator iter = mItems.begin(); iter != mItems.end();) { items_t::iterator iit = iter++; if ((*iit)->changeSelection(selection, selected)) { rv = TRUE; } } return rv; } void LLFolderViewFolder::extendSelection(LLFolderViewItem* selection, LLFolderViewItem* last_selected, LLDynamicArray& selected_items) { // pass on to child folders first for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; (*fit)->extendSelection(selection, last_selected, selected_items); } // handle selection of our immediate children... BOOL reverse_select = FALSE; BOOL found_last_selected = FALSE; BOOL found_selection = FALSE; LLDynamicArray items_to_select; LLFolderViewItem* item; //...folders first... for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; item = (*fit); if(item == selection) { found_selection = TRUE; } else if (item == last_selected) { found_last_selected = TRUE; if (found_selection) { reverse_select = TRUE; } } if (found_selection || found_last_selected) { // deselect currently selected items so they can be pushed back on queue if (item->isSelected()) { item->changeSelection(item, FALSE); } items_to_select.put(item); } if (found_selection && found_last_selected) { break; } } if (!(found_selection && found_last_selected)) { //,,,then items for (items_t::iterator iter = mItems.begin(); iter != mItems.end();) { items_t::iterator iit = iter++; item = (*iit); if(item == selection) { found_selection = TRUE; } else if (item == last_selected) { found_last_selected = TRUE; if (found_selection) { reverse_select = TRUE; } } if (found_selection || found_last_selected) { // deselect currently selected items so they can be pushed back on queue if (item->isSelected()) { item->changeSelection(item, FALSE); } items_to_select.put(item); } if (found_selection && found_last_selected) { break; } } } if (found_last_selected && found_selection) { // we have a complete selection inside this folder for (S32 index = reverse_select ? items_to_select.getLength() - 1 : 0; reverse_select ? index >= 0 : index < items_to_select.getLength(); reverse_select ? index-- : index++) { LLFolderViewItem* item = items_to_select[index]; if (item->changeSelection(item, TRUE)) { selected_items.put(item); } } } else if (found_selection) { // last selection was not in this folder....go ahead and select just the new item if (selection->changeSelection(selection, TRUE)) { selected_items.put(selection); } } } void LLFolderViewFolder::recursiveDeselect(BOOL deselect_self) { if (isSelected() && deselect_self) { deselectItem(); } if (0 == mNumDescendantsSelected) { return; } // Deselect all items in this folder. for (items_t::iterator iter = mItems.begin(); iter != mItems.end();) { items_t::iterator iit = iter++; LLFolderViewItem* item = (*iit); if (item->isSelected()) { item->deselectItem(); } } // Recursively deselect all folders in this folder. for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; LLFolderViewFolder* folder = (*fit); folder->recursiveDeselect(TRUE); } } void LLFolderViewFolder::destroyView() { for (items_t::iterator iter = mItems.begin(); iter != mItems.end();) { items_t::iterator iit = iter++; LLFolderViewItem* item = (*iit); getRoot()->removeItemID(item->getListener()->getUUID()); } std::for_each(mItems.begin(), mItems.end(), DeletePointer()); mItems.clear(); while (!mFolders.empty()) { LLFolderViewFolder *folderp = mFolders.back(); folderp->destroyView(); // removes entry from mFolders } deleteAllChildren(); if (mParentFolder) { mParentFolder->removeView(this); } } // remove the specified item (and any children) if possible. Return // TRUE if the item was deleted. BOOL LLFolderViewFolder::removeItem(LLFolderViewItem* item) { if(item->remove()) { //RN: this seem unneccessary as remove() moves to trash //removeView(item); return TRUE; } return FALSE; } // simply remove the view (and any children) Don't bother telling the // listeners. void LLFolderViewFolder::removeView(LLFolderViewItem* item) { if (!item || item->getParentFolder() != this) { return; } // deselect without traversing hierarchy if (item->isSelected()) { item->deselectItem(); } getRoot()->removeFromSelectionList(item); extractItem(item); delete item; } // extractItem() removes the specified item from the folder, but // doesn't delete it. void LLFolderViewFolder::extractItem( LLFolderViewItem* item ) { items_t::iterator it = std::find(mItems.begin(), mItems.end(), item); if(it == mItems.end()) { // This is an evil downcast. However, it's only doing // pointer comparison to find if (which it should be ) the // item is in the container, so it's pretty safe. LLFolderViewFolder* f = static_cast(item); folders_t::iterator ft; ft = std::find(mFolders.begin(), mFolders.end(), f); if(ft != mFolders.end()) { if ((*ft)->numSelected()) { recursiveIncrementNumDescendantsSelected(-(*ft)->numSelected()); } mFolders.erase(ft); } } else { if ((*it)->isSelected()) { recursiveIncrementNumDescendantsSelected(-1); } mItems.erase(it); } //item has been removed, need to update filter dirtyFilter(); //because an item is going away regardless of filter status, force rearrange requestArrange(); getRoot()->removeItemID(item->getListener()->getUUID()); removeChild(item); } // This function is called by a child that needs to be resorted. // This is only called for renaming an object because it won't work for date void LLFolderViewFolder::resort(LLFolderViewItem* item) { mItems.sort(mSortFunction); mFolders.sort(mSortFunction); } bool LLFolderViewFolder::isTrash() const { if (mAmTrash == LLFolderViewFolder::UNKNOWN) { mAmTrash = mListener->getUUID() == gInventory.findCategoryUUIDForType(LLAssetType::AT_TRASH, false) ? LLFolderViewFolder::TRASH : LLFolderViewFolder::NOT_TRASH; } return mAmTrash == LLFolderViewFolder::TRASH; } void LLFolderViewFolder::sortBy(U32 order) { if (!mSortFunction.updateSort(order)) { // No changes. return; } // Propegate this change to sub folders for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; (*fit)->sortBy(order); } mFolders.sort(mSortFunction); mItems.sort(mSortFunction); if (order & LLInventoryFilter::SO_DATE) { time_t latest = 0; if (!mItems.empty()) { LLFolderViewItem* item = *(mItems.begin()); latest = item->getCreationDate(); } if (!mFolders.empty()) { LLFolderViewFolder* folder = *(mFolders.begin()); if (folder->getCreationDate() > latest) { latest = folder->getCreationDate(); } } mSubtreeCreationDate = latest; } } void LLFolderViewFolder::setItemSortOrder(U32 ordering) { if (mSortFunction.updateSort(ordering)) { for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; (*fit)->setItemSortOrder(ordering); } mFolders.sort(mSortFunction); mItems.sort(mSortFunction); } } EInventorySortGroup LLFolderViewFolder::getSortGroup() const { if (isTrash()) { return SG_TRASH_FOLDER; } // Folders that can't be moved are 'system' folders. if( mListener ) { if( !(mListener->isItemMovable()) ) { return SG_SYSTEM_FOLDER; } } return SG_NORMAL_FOLDER; } BOOL LLFolderViewFolder::isMovable() { if( mListener ) { if( !(mListener->isItemMovable()) ) { return FALSE; } for (items_t::iterator iter = mItems.begin(); iter != mItems.end();) { items_t::iterator iit = iter++; if(!(*iit)->isMovable()) { return FALSE; } } for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; if(!(*fit)->isMovable()) { return FALSE; } } } return TRUE; } BOOL LLFolderViewFolder::isRemovable() { if( mListener ) { if( !(mListener->isItemRemovable()) ) { return FALSE; } for (items_t::iterator iter = mItems.begin(); iter != mItems.end();) { items_t::iterator iit = iter++; if(!(*iit)->isRemovable()) { return FALSE; } } for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; if(!(*fit)->isRemovable()) { return FALSE; } } } return TRUE; } // this is an internal method used for adding items to folders. BOOL LLFolderViewFolder::addItem(LLFolderViewItem* item) { items_t::iterator it = std::lower_bound( mItems.begin(), mItems.end(), item, mSortFunction); mItems.insert(it,item); if (item->isSelected()) { recursiveIncrementNumDescendantsSelected(1); } item->setRect(LLRect(0, 0, getRect().getWidth(), 0)); item->setVisible(FALSE); addChild( item ); item->dirtyFilter(); requestArrange(); return TRUE; } // this is an internal method used for adding items to folders. BOOL LLFolderViewFolder::addFolder(LLFolderViewFolder* folder) { folders_t::iterator it = std::lower_bound( mFolders.begin(), mFolders.end(), folder, mSortFunction); mFolders.insert(it,folder); if (folder->numSelected()) { recursiveIncrementNumDescendantsSelected(folder->numSelected()); } folder->setOrigin(0, 0); folder->reshape(getRect().getWidth(), 0); folder->setVisible(FALSE); addChild( folder ); folder->dirtyFilter(); // rearrange all descendants too, as our indentation level might have changed folder->requestArrange(TRUE); return TRUE; } void LLFolderViewFolder::requestArrange(BOOL include_descendants) { mLastArrangeGeneration = -1; // flag all items up to root if (mParentFolder) { mParentFolder->requestArrange(); } if (include_descendants) { for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end(); ++iter) { (*iter)->requestArrange(TRUE); } } } void LLFolderViewFolder::toggleOpen() { setOpen(!mIsOpen); } // Force a folder open or closed void LLFolderViewFolder::setOpen(BOOL openitem) { setOpenArrangeRecursively(openitem); } void LLFolderViewFolder::setOpenArrangeRecursively(BOOL openitem, ERecurseType recurse) { BOOL was_open = mIsOpen; mIsOpen = openitem; if(!was_open && openitem) { if(mListener) { mListener->openItem(); } } if (recurse == RECURSE_DOWN || recurse == RECURSE_UP_DOWN) { for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; (*fit)->setOpenArrangeRecursively(openitem, RECURSE_DOWN); /* Flawfinder: ignore */ } } if (mParentFolder && (recurse == RECURSE_UP || recurse == RECURSE_UP_DOWN)) { mParentFolder->setOpenArrangeRecursively(openitem, RECURSE_UP); } if (was_open != mIsOpen) { requestArrange(); } } BOOL LLFolderViewFolder::handleDragAndDropFromChild(MASK mask, BOOL drop, EDragAndDropType c_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg) { BOOL accepted = mListener && mListener->dragOrDrop(mask,drop,c_type,cargo_data); if (accepted) { mDragAndDropTarget = TRUE; *accept = ACCEPT_YES_MULTI; } else { *accept = ACCEPT_NO; } // drag and drop to child item, so clear pending auto-opens getRoot()->autoOpenTest(NULL); return TRUE; } void LLFolderViewFolder::openItem( void ) { toggleOpen(); } void LLFolderViewFolder::applyFunctorRecursively(LLFolderViewFunctor& functor) { functor.doFolder(this); for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; (*fit)->applyFunctorRecursively(functor); } for (items_t::iterator iter = mItems.begin(); iter != mItems.end();) { items_t::iterator iit = iter++; functor.doItem((*iit)); } } void LLFolderViewFolder::applyListenerFunctorRecursively(LLFolderViewListenerFunctor& functor) { functor(mListener); for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; (*fit)->applyListenerFunctorRecursively(functor); } for (items_t::iterator iter = mItems.begin(); iter != mItems.end();) { items_t::iterator iit = iter++; (*iit)->applyListenerFunctorRecursively(functor); } } // LLView functionality BOOL LLFolderViewFolder::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg) { LLFolderView* root_view = getRoot(); BOOL handled = FALSE; if(mIsOpen) { handled = childrenHandleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg) != NULL; } if (!handled) { BOOL accepted = mListener && mListener->dragOrDrop(mask, drop,cargo_type,cargo_data); if (accepted) { mDragAndDropTarget = TRUE; *accept = ACCEPT_YES_MULTI; } else { *accept = ACCEPT_NO; } if (!drop && accepted) { root_view->autoOpenTest(this); } lldebugst(LLERR_USER_INPUT) << "dragAndDrop handled by LLFolderViewFolder" << llendl; } return TRUE; } BOOL LLFolderViewFolder::handleRightMouseDown( S32 x, S32 y, MASK mask ) { BOOL handled = FALSE; // fetch contents of this folder, as context menu can depend on contents // still, user would have to open context menu again to see the changes gInventory.fetchDescendentsOf(mListener->getUUID()); if( mIsOpen ) { handled = childrenHandleRightMouseDown( x, y, mask ) != NULL; } if (!handled) { handled = LLFolderViewItem::handleRightMouseDown( x, y, mask ); } return handled; } BOOL LLFolderViewFolder::handleHover(S32 x, S32 y, MASK mask) { BOOL handled = LLView::handleHover(x, y, mask); if (!handled) { // this doesn't do child processing handled = LLFolderViewItem::handleHover(x, y, mask); } //if(x < LEFT_INDENTATION + mIndentation && x > mIndentation - LEFT_PAD && y > getRect().getHeight() - ) //{ // gViewerWindow->setCursor(UI_CURSOR_ARROW); // mExpanderHighlighted = TRUE; // handled = TRUE; //} return handled; } BOOL LLFolderViewFolder::handleMouseDown( S32 x, S32 y, MASK mask ) { BOOL handled = FALSE; if( mIsOpen ) { handled = childrenHandleMouseDown(x,y,mask) != NULL; } if( !handled ) { if(x < LEFT_INDENTATION + mIndentation && x > mIndentation - LEFT_PAD) { toggleOpen(); handled = TRUE; } else { // do normal selection logic handled = LLFolderViewItem::handleMouseDown(x, y, mask); } } return handled; } BOOL LLFolderViewFolder::handleDoubleClick( S32 x, S32 y, MASK mask ) { BOOL handled = FALSE; if( mIsOpen ) { handled = childrenHandleDoubleClick( x, y, mask ) != NULL; } if( !handled ) { if(x < LEFT_INDENTATION + mIndentation && x > mIndentation - LEFT_PAD) { // don't select when user double-clicks plus sign // so as not to contradict single-click behavior toggleOpen(); } else { setSelectionFromRoot(this, FALSE); toggleOpen(); } handled = TRUE; } return handled; } void LLFolderViewFolder::draw() { if (mAutoOpenCountdown != 0.f) { mControlLabelRotation = mAutoOpenCountdown * -90.f; } else if (mIsOpen) { mControlLabelRotation = lerp(mControlLabelRotation, -90.f, LLCriticalDamp::getInterpolant(0.04f)); } else { mControlLabelRotation = lerp(mControlLabelRotation, 0.f, LLCriticalDamp::getInterpolant(0.025f)); } bool possibly_has_children = false; bool up_to_date = mListener && mListener->isUpToDate(); if(!up_to_date && mListener && mListener->hasChildren()) // we know we have children but haven't fetched them (doesn't obey filter) { possibly_has_children = true; } BOOL loading = ( mIsOpen && possibly_has_children && !up_to_date ); if ( loading && !mIsLoading ) { // Measure how long we've been in the loading state mTimeSinceRequestStart.reset(); } mIsLoading = loading; LLFolderViewItem::draw(); // draw children if root folder, or any other folder that is open or animating to closed state if( getRoot() == this || (mIsOpen || mCurHeight != mTargetHeight )) { LLView::draw(); } mExpanderHighlighted = FALSE; } time_t LLFolderViewFolder::getCreationDate() const { return llmax(mCreationDate, mSubtreeCreationDate); } BOOL LLFolderViewFolder::potentiallyVisible() { // folder should be visible by it's own filter status return LLFolderViewItem::potentiallyVisible() // or one or more of its descendants have passed the minimum filter requirement || hasFilteredDescendants(mRoot->getFilter()->getMinRequiredGeneration()) // or not all of its descendants have been checked against minimum filter requirement || getCompletedFilterGeneration() < getRoot()->getFilter()->getMinRequiredGeneration(); } // this does prefix traversal, as folders are listed above their contents LLFolderViewItem* LLFolderViewFolder::getNextFromChild( LLFolderViewItem* item, BOOL include_children ) { BOOL found_item = FALSE; LLFolderViewItem* result = NULL; // when not starting from a given item, start at beginning if(item == NULL) { found_item = TRUE; } // find current item among children folders_t::iterator fit = mFolders.begin(); folders_t::iterator fend = mFolders.end(); items_t::iterator iit = mItems.begin(); items_t::iterator iend = mItems.end(); // if not trivially starting at the beginning, we have to find the current item if (!found_item) { // first, look among folders, since they are always above items for(; fit != fend; ++fit) { if(item == (*fit)) { found_item = TRUE; // if we are on downwards traversal if (include_children && (*fit)->isOpen()) { // look for first descendant return (*fit)->getNextFromChild(NULL, TRUE); } // otherwise advance to next folder ++fit; include_children = TRUE; break; } } // didn't find in folders? Check items... if (!found_item) { for(; iit != iend; ++iit) { if(item == (*iit)) { found_item = TRUE; // point to next item ++iit; break; } } } } if (!found_item) { // you should never call this method with an item that isn't a child // so we should always find something llassert(FALSE); return NULL; } // at this point, either iit or fit point to a candidate "next" item // if both are out of range, we need to punt up to our parent // now, starting from found folder, continue through folders // searching for next visible folder while(fit != fend && !(*fit)->getVisible()) { // turn on downwards traversal for next folder ++fit; } if (fit != fend) { result = (*fit); } else { // otherwise, scan for next visible item while(iit != iend && !(*iit)->getVisible()) { ++iit; } // check to see if we have a valid item if (iit != iend) { result = (*iit); } } if( !result && mParentFolder ) { // If there are no siblings or children to go to, recurse up one level in the tree // and skip children for this folder, as we've already discounted them result = mParentFolder->getNextFromChild(this, FALSE); } return result; } // this does postfix traversal, as folders are listed above their contents LLFolderViewItem* LLFolderViewFolder::getPreviousFromChild( LLFolderViewItem* item, BOOL include_children ) { BOOL found_item = FALSE; LLFolderViewItem* result = NULL; // when not starting from a given item, start at end if(item == NULL) { found_item = TRUE; } // find current item among children folders_t::reverse_iterator fit = mFolders.rbegin(); folders_t::reverse_iterator fend = mFolders.rend(); items_t::reverse_iterator iit = mItems.rbegin(); items_t::reverse_iterator iend = mItems.rend(); // if not trivially starting at the end, we have to find the current item if (!found_item) { // first, look among items, since they are always below the folders for(; iit != iend; ++iit) { if(item == (*iit)) { found_item = TRUE; // point to next item ++iit; break; } } // didn't find in items? Check folders... if (!found_item) { for(; fit != fend; ++fit) { if(item == (*fit)) { found_item = TRUE; // point to next folder ++fit; break; } } } } if (!found_item) { // you should never call this method with an item that isn't a child // so we should always find something llassert(FALSE); return NULL; } // at this point, either iit or fit point to a candidate "next" item // if both are out of range, we need to punt up to our parent // now, starting from found item, continue through items // searching for next visible item while(iit != iend && !(*iit)->getVisible()) { ++iit; } if (iit != iend) { // we found an appropriate item result = (*iit); } else { // otherwise, scan for next visible folder while(fit != fend && !(*fit)->getVisible()) { ++fit; } // check to see if we have a valid folder if (fit != fend) { // try selecting child element of this folder if ((*fit)->isOpen()) { result = (*fit)->getPreviousFromChild(NULL); } else { result = (*fit); } } } if( !result ) { // If there are no siblings or children to go to, recurse up one level in the tree // which gets back to this folder, which will only be visited if it is a valid, visible item result = this; } return result; } //--------------------------------------------------------------------------- // Tells all folders in a folderview to sort their items // (and only their items, not folders) by a certain function. class LLSetItemSortFunction : public LLFolderViewFunctor { public: LLSetItemSortFunction(U32 ordering) : mSortOrder(ordering) {} virtual ~LLSetItemSortFunction() {} virtual void doFolder(LLFolderViewFolder* folder); virtual void doItem(LLFolderViewItem* item); U32 mSortOrder; }; // Set the sort order. void LLSetItemSortFunction::doFolder(LLFolderViewFolder* folder) { folder->setItemSortOrder(mSortOrder); } // Do nothing. void LLSetItemSortFunction::doItem(LLFolderViewItem* item) { return; } //--------------------------------------------------------------------------- // 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; }; // Set the sort order. void LLCloseAllFoldersFunctor::doFolder(LLFolderViewFolder* folder) { folder->setOpenArrangeRecursively(mOpen); } // Do nothing. void LLCloseAllFoldersFunctor::doItem(LLFolderViewItem* item) { } ///---------------------------------------------------------------------------- /// Class LLFolderView ///---------------------------------------------------------------------------- // Default constructor LLFolderView::LLFolderView( const std::string& name, LLUIImagePtr root_folder_icon, const LLRect& rect, const LLUUID& source_id, LLView *parent_view ) : #if LL_WINDOWS #pragma warning( push ) #pragma warning( disable : 4355 ) // warning C4355: 'this' : used in base member initializer list #endif LLFolderViewFolder( name, root_folder_icon, this, NULL ), #if LL_WINDOWS #pragma warning( pop ) #endif mScrollContainer( NULL ), mPopupMenuHandle(), mAllowMultiSelect(TRUE), mShowFolderHierarchy(FALSE), mSourceID(source_id), mRenameItem( NULL ), mNeedsScroll( FALSE ), mLastScrollItem( NULL ), mNeedsAutoSelect( FALSE ), mAutoSelectOverride(FALSE), mNeedsAutoRename(FALSE), mDebugFilters(FALSE), mSortOrder(LLInventoryFilter::SO_FOLDERS_BY_NAME), // This gets overridden by a pref immediately mFilter(name), mShowSelectionContext(FALSE), mShowSingleSelection(FALSE), mArrangeGeneration(0), mUserData(NULL), mSelectCallback(NULL), mSignalSelectCallback(0), mMinWidth(0), mDragAndDropThisFrame(FALSE) { LLRect new_rect(rect.mLeft, rect.mBottom + getRect().getHeight(), rect.mLeft + getRect().getWidth(), rect.mBottom); setRect( rect ); reshape(rect.getWidth(), rect.getHeight()); mIsOpen = TRUE; // this view is always open. mAutoOpenItems.setDepth(AUTO_OPEN_STACK_DEPTH); mAutoOpenCandidate = NULL; mAutoOpenTimer.stop(); mKeyboardSelection = FALSE; mIndentation = -LEFT_INDENTATION; // children start at indentation 0 gIdleCallbacks.addFunction(idle, this); //clear label // go ahead and render root folder as usual // just make sure the label ("Inventory Folder") never shows up mLabel = LLStringUtil::null; mRenamer = new LLLineEditor(std::string("ren"), getRect(), LLStringUtil::null, sFont, DB_INV_ITEM_NAME_STR_LEN, &LLFolderView::commitRename, NULL, NULL, this, &LLLineEditor::prevalidatePrintableNotPipe); //mRenamer->setWriteableBgColor(LLColor4::white); // Escape is handled by reverting the rename, not commiting it (default behavior) mRenamer->setCommitOnFocusLost(TRUE); mRenamer->setVisible(FALSE); addChild(mRenamer); // make the popup menu available LLMenuGL* menu = LLUICtrlFactory::getInstance()->buildMenu("menu_inventory.xml", parent_view); if (!menu) { menu = new LLMenuGL(LLStringUtil::null); } menu->setBackgroundColor(gColors.getColor("MenuPopupBgColor")); menu->setVisible(FALSE); mPopupMenuHandle = menu->getHandle(); setTabStop(TRUE); } // Destroys the object LLFolderView::~LLFolderView( void ) { // 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; gFocusMgr.releaseFocusIfNeeded( this ); if( gEditMenuHandler == this ) { gEditMenuHandler = NULL; } mAutoOpenItems.removeAllNodes(); gIdleCallbacks.deleteFunction(idle, this); LLView::deleteViewByHandle(mPopupMenuHandle); if(mRenamer == gFocusMgr.getTopCtrl()) { gFocusMgr.setTopCtrl(NULL); } mAutoOpenItems.removeAllNodes(); clearSelection(); mItems.clear(); mFolders.clear(); mItemMap.clear(); } BOOL LLFolderView::canFocusChildren() const { return FALSE; } void LLFolderView::checkTreeResortForModelChanged() { if (mSortOrder & LLInventoryFilter::SO_DATE && !(mSortOrder & LLInventoryFilter::SO_FOLDERS_BY_NAME)) { // This is the case where something got added or removed. If we are date sorting // everything including folders, then we need to rebuild the whole tree. // Just set to something not SO_DATE to force the folder most resent date resort. mSortOrder = mSortOrder & ~LLInventoryFilter::SO_DATE; setSortOrder(mSortOrder | LLInventoryFilter::SO_DATE); } } void LLFolderView::setSortOrder(U32 order) { if (order != mSortOrder) { LLFastTimer t(LLFastTimer::FTM_SORT); mSortOrder = order; for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; (*fit)->sortBy(order); } arrangeAll(); } } U32 LLFolderView::getSortOrder() const { return mSortOrder; } BOOL LLFolderView::addFolder( LLFolderViewFolder* folder) { // enforce sort order of My Inventory followed by Library if (folder->getListener()->getUUID() == gInventoryLibraryRoot) { mFolders.push_back(folder); } else { mFolders.insert(mFolders.begin(), folder); } if (folder->numSelected()) { recursiveIncrementNumDescendantsSelected(folder->numSelected()); } folder->setOrigin(0, 0); folder->reshape(getRect().getWidth(), 0); folder->setVisible(FALSE); addChild( folder ); folder->dirtyFilter(); folder->requestArrange(); return TRUE; } void LLFolderView::closeAllFolders() { // Close all the folders setOpenArrangeRecursively(FALSE, LLFolderViewFolder::RECURSE_DOWN); } void LLFolderView::openFolder(const std::string& foldername) { LLFolderViewFolder* inv = getChild(foldername); if (inv) { setSelection(inv, FALSE, FALSE); inv->setOpen(TRUE); } } void LLFolderView::setOpenArrangeRecursively(BOOL openitem, ERecurseType recurse) { // call base class to do proper recursion LLFolderViewFolder::setOpenArrangeRecursively(openitem, recurse); // make sure root folder is always open mIsOpen = TRUE; } // This view grows and shinks to enclose all of its children items and folders. S32 LLFolderView::arrange( S32* unused_width, S32* unused_height, S32 filter_generation ) { LLFastTimer t2(LLFastTimer::FTM_ARRANGE); filter_generation = mFilter.getMinRequiredGeneration(); mMinWidth = 0; mHasVisibleChildren = hasFilteredDescendants(filter_generation); // arrange always finishes, so optimistically set the arrange generation to the most current mLastArrangeGeneration = mRoot->getArrangeGeneration(); LLInventoryFilter::EFolderShow show_folder_state = getRoot()->getShowFolderState(); S32 total_width = LEFT_PAD; S32 running_height = mDebugFilters ? llceil(sSmallFont->getLineHeight()) : 0; S32 target_height = running_height; S32 parent_item_height = getRect().getHeight(); for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; LLFolderViewFolder* folderp = (*fit); if (getDebugFilters()) { folderp->setVisible(TRUE); } else { folderp->setVisible(show_folder_state == LLInventoryFilter::SHOW_ALL_FOLDERS || // always show folders? (folderp->getFiltered(filter_generation) || folderp->hasFilteredDescendants(filter_generation))); // passed filter or has descendants that passed filter } if (folderp->getVisible()) { S32 child_height = 0; S32 child_width = 0; S32 child_top = parent_item_height - running_height; target_height += folderp->arrange( &child_width, &child_height, filter_generation ); mMinWidth = llmax(mMinWidth, child_width); total_width = llmax( total_width, child_width ); running_height += child_height; folderp->setOrigin( ICON_PAD, child_top - (*fit)->getRect().getHeight() ); } } for (items_t::iterator iter = mItems.begin(); iter != mItems.end();) { items_t::iterator iit = iter++; LLFolderViewItem* itemp = (*iit); itemp->setVisible(itemp->getFiltered(filter_generation)); if (itemp->getVisible()) { S32 child_width = 0; S32 child_height = 0; S32 child_top = parent_item_height - running_height; target_height += itemp->arrange( &child_width, &child_height, filter_generation ); itemp->reshape(itemp->getRect().getWidth(), child_height); mMinWidth = llmax(mMinWidth, child_width); total_width = llmax( total_width, child_width ); running_height += child_height; itemp->setOrigin( ICON_PAD, child_top - itemp->getRect().getHeight() ); } } S32 dummy_s32; BOOL dummy_bool; S32 min_width; mScrollContainer->calcVisibleSize( &min_width, &dummy_s32, &dummy_bool, &dummy_bool); reshape( llmax(min_width, total_width), running_height ); S32 new_min_width; mScrollContainer->calcVisibleSize( &new_min_width, &dummy_s32, &dummy_bool, &dummy_bool); if (new_min_width != min_width) { reshape( llmax(min_width, total_width), running_height ); } mTargetHeight = (F32)target_height; return llround(mTargetHeight); } const std::string LLFolderView::getFilterSubString(BOOL trim) { return mFilter.getFilterSubString(trim); } void LLFolderView::filter( LLInventoryFilter& filter ) { LLFastTimer t2(LLFastTimer::FTM_FILTER); static S32 *sFilterItemsPerFrame = rebind_llcontrol("FilterItemsPerFrame", &gSavedSettings, true); filter.setFilterCount(llclamp(*sFilterItemsPerFrame, 1, 5000)); if (getCompletedFilterGeneration() < filter.getCurrentGeneration()) { mFiltered = FALSE; mMinWidth = 0; LLFolderViewFolder::filter(filter); } } void LLFolderView::reshape(S32 width, S32 height, BOOL called_from_parent) { S32 min_width = 0; S32 dummy_height; BOOL dummy_bool; if (mScrollContainer) { mScrollContainer->calcVisibleSize( &min_width, &dummy_height, &dummy_bool, &dummy_bool); } width = llmax(mMinWidth, min_width); LLView::reshape(width, height, called_from_parent); } 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; } // Record the selected item and pass it down the hierachy. BOOL LLFolderView::setSelection(LLFolderViewItem* selection, BOOL openitem, BOOL take_keyboard_focus) { if( selection == this ) { return FALSE; } if( selection && take_keyboard_focus) { 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); mSignalSelectCallback = take_keyboard_focus ? SIGNAL_KEYBOARD_FOCUS : SIGNAL_NO_KEYBOARD_FOCUS; 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::extendSelection(LLFolderViewItem* selection, LLFolderViewItem* last_selected, LLDynamicArray& items) { // now store resulting selection if (mAllowMultiSelect) { LLFolderViewItem *cur_selection = getCurSelectedItem(); LLFolderViewFolder::extendSelection(selection, cur_selection, items); for (S32 i = 0; i < items.count(); i++) { addToSelectionList(items[i]); } } else { setSelection(selection, FALSE, FALSE); } mSignalSelectCallback = SIGNAL_KEYBOARD_FOCUS; } void LLFolderView::sanitizeSelection() { // store off current item in case it is automatically deselected // and we want to preserve context LLFolderViewItem* original_selected_item = getCurSelectedItem(); // Cache "Show all folders" filter setting BOOL show_all_folders = (getRoot()->getShowFolderState() == LLInventoryFilter::SHOW_ALL_FOLDERS); 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 = item->potentiallyVisible(); // initialize from filter state for this item // modify with parent open and filters states LLFolderViewFolder* parent_folder = item->getParentFolder(); if ( parent_folder ) { if ( show_all_folders ) { // "Show all folders" is on, so this folder is visible visible = TRUE; } else { // Move up through parent folders and see what's visible while(parent_folder) { visible = visible && parent_folder->isOpen() && parent_folder->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; } } } } 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, or "My Inventory" otherwise 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->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 { // nothing selected to start with, so pick "My Inventory" as best guess new_selection = getItemByID(gAgent.getInventoryRootID()); } if (new_selection) { setSelection(new_selection, FALSE, FALSE); } } } void LLFolderView::clearSelection() { if (mSelectedItems.size() > 0) { recursiveDeselect(FALSE); mSelectedItems.clear(); } } BOOL LLFolderView::getSelectionList(std::set &selection) { selected_items_t::iterator item_it; for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it) { selection.insert((*item_it)->getListener()->getUUID()); } return (selection.size() != 0); } BOOL LLFolderView::startDrag(LLToolDragAndDrop::ESource source) { std::vector types; std::vector cargo_ids; selected_items_t::iterator item_it; BOOL can_drag = TRUE; if (!mSelectedItems.empty()) { for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it) { EDragAndDropType type = DAD_NONE; LLUUID id = LLUUID::null; can_drag = can_drag && (*item_it)->getListener()->startDrag(&type, &id); types.push_back(type); cargo_ids.push_back(id); } LLToolDragAndDrop::getInstance()->beginMultiDrag(types, cargo_ids, source, mSourceID); } return can_drag; } void LLFolderView::commitRename( LLUICtrl* renamer, void* user_data ) { LLFolderView* root = reinterpret_cast(user_data); if( root ) { root->finishRenamingItem(); } } void LLFolderView::draw() { if (mDebugFilters) { std::string current_filter_string = llformat("Current Filter: %d, Least Filter: %d, Auto-accept Filter: %d", mFilter.getCurrentGeneration(), mFilter.getMinRequiredGeneration(), mFilter.getMustPassGeneration()); sSmallFont->renderUTF8(current_filter_string, 0, 2, getRect().getHeight() - sSmallFont->getLineHeight(), LLColor4(0.5f, 0.5f, 0.8f, 1.f), LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, S32_MAX, S32_MAX, NULL, FALSE ); } // if cursor has moved off of me during drag and drop // close all auto opened folders if (!mDragAndDropThisFrame) { closeAutoOpenedFolders(); } if(this == gFocusMgr.getKeyboardFocus() && !getVisible()) { gFocusMgr.setKeyboardFocus( NULL ); } // while dragging, update selection rendering to reflect single/multi drag status if (LLToolDragAndDrop::getInstance()->hasMouseCapture()) { EAcceptance last_accept = LLToolDragAndDrop::getInstance()->getLastAccept(); if (last_accept == ACCEPT_YES_SINGLE || last_accept == ACCEPT_YES_COPY_SINGLE) { setShowSingleSelection(TRUE); } else { setShowSingleSelection(FALSE); } } else { setShowSingleSelection(FALSE); } static F32 *sTypeAheadTimeout = rebind_llcontrol("TypeAheadTimeout", &gSavedSettings, true); if (mSearchTimer.getElapsedTimeF32() > *sTypeAheadTimeout || !mSearchString.size()) { mSearchString.clear(); } if (hasVisibleChildren() || getShowFolderState() == LLInventoryFilter::SHOW_ALL_FOLDERS) { mStatusText.clear(); } else { if (gInventory.backgroundFetchActive() || mCompletedFilterGeneration < mFilter.getMinRequiredGeneration()) { mStatusText = std::string("Searching..."); // *TODO:translate sFont->renderUTF8(mStatusText, 0, 2, 1, sSearchStatusColor, LLFontGL::LEFT, LLFontGL::TOP, LLFontGL::NORMAL, S32_MAX, S32_MAX, NULL, FALSE ); } else { mStatusText = std::string("No matching items found in inventory."); // *TODO:translate sFont->renderUTF8(mStatusText, 0, 2, 1, sSearchStatusColor, LLFontGL::LEFT, LLFontGL::TOP, LLFontGL::NORMAL, S32_MAX, S32_MAX, NULL, FALSE ); } } LLFolderViewFolder::draw(); mDragAndDropThisFrame = FALSE; } void LLFolderView::finishRenamingItem( void ) { if(!mRenamer) { return; } if( mRenameItem ) { mRenameItem->rename( mRenamer->getText() ); } mRenamer->setCommitOnFocusLost( FALSE ); mRenamer->setFocus( FALSE ); mRenamer->setVisible( FALSE ); mRenamer->setCommitOnFocusLost( TRUE ); gFocusMgr.setTopCtrl( NULL ); if( mRenameItem ) { setSelectionFromRoot( mRenameItem, TRUE ); mRenameItem = NULL; } // List is re-sorted alphabeticly, so scroll to make sure the selected item is visible. scrollToShowSelection(); } void LLFolderView::closeRenamer( void ) { // will commit current name (which could be same as original name) mRenamer->setFocus( FALSE ); mRenamer->setVisible( FALSE ); gFocusMgr.setTopCtrl( NULL ); if( mRenameItem ) { setSelectionFromRoot( mRenameItem, TRUE ); mRenameItem = NULL; } } void LLFolderView::removeSelectedItems( void ) { 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; S32 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->isRemovable()) { items.push_back(item); } else { llinfos << "Cannot delete " << item->getName() << llendl; return; } } // iterate through the new container. count = items.size(); LLUUID new_selection_id; if(count == 1) { LLFolderViewItem* item_to_delete = items[0]; LLFolderViewFolder* parent = item_to_delete->getParentFolder(); LLFolderViewItem* new_selection = item_to_delete->getNextOpenNode(FALSE); if (!new_selection) { new_selection = item_to_delete->getPreviousOpenNode(FALSE); } if(parent) { if (parent->removeItem(item_to_delete)) { // change selection on successful delete if (new_selection) { setSelectionFromRoot(new_selection, new_selection->isOpen(), gFocusMgr.childHasKeyboardFocus(this)); } else { setSelectionFromRoot(NULL, gFocusMgr.childHasKeyboardFocus(this)); } } } arrangeAll(); } else if (count > 1) { LLDynamicArray listeners; LLFolderViewEventListener* listener; LLFolderViewItem* last_item = items[count - 1]; 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->isSelected()) { new_selection = new_selection->getPreviousOpenNode(FALSE); } } if (new_selection) { setSelectionFromRoot(new_selection, new_selection->isOpen(), gFocusMgr.childHasKeyboardFocus(this)); } else { setSelectionFromRoot(NULL, gFocusMgr.childHasKeyboardFocus(this)); } for(S32 i = 0; i < count; ++i) { listener = items[i]->getListener(); if(listener && (listeners.find(listener) == LLDynamicArray::FAIL)) { listeners.put(listener); } } listener = listeners.get(0); if(listener) { listener->removeBatch(listeners); } } arrangeAll(); scrollToShowSelection(); } } // open the selected item. void LLFolderView::openSelectedItems( void ) { if(getVisible() && getEnabled()) { if (mSelectedItems.size() == 1) { mSelectedItems.front()->openItem(); } else { S32 left, top; gFloaterView->getNewFloaterPosition(&left, &top); LLMultiPreview* multi_previewp = new LLMultiPreview(LLRect(left, top, left + 300, top - 100)); gFloaterView->getNewFloaterPosition(&left, &top); LLMultiProperties* multi_propertiesp = new LLMultiProperties(LLRect(left, top, left + 300, top - 100)); selected_items_t::iterator item_it; for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it) { // IT_{OBJECT,ATTACHMENT} creates LLProperties // floaters; others create LLPreviews. Put // each one in the right type of container. LLFolderViewEventListener* listener = (*item_it)->getListener(); bool is_prop = listener && (listener->getInventoryType() == LLInventoryType::IT_OBJECT || listener->getInventoryType() == LLInventoryType::IT_ATTACHMENT); if (is_prop) LLFloater::setFloaterHost(multi_propertiesp); else LLFloater::setFloaterHost(multi_previewp); (*item_it)->openItem(); } LLFloater::setFloaterHost(NULL); // *NOTE: LLMulti* will safely auto-delete when open'd // without any children. multi_previewp->open(); multi_propertiesp->open(); } } } void LLFolderView::propertiesSelectedItems( void ) { if(getVisible() && getEnabled()) { if (mSelectedItems.size() == 1) { LLFolderViewItem* folder_item = mSelectedItems.front(); if(!folder_item) return; folder_item->getListener()->showProperties(); } else { S32 left, top; gFloaterView->getNewFloaterPosition(&left, &top); LLMultiProperties* multi_propertiesp = new LLMultiProperties(LLRect(left, top, left + 100, top - 100)); LLFloater::setFloaterHost(multi_propertiesp); selected_items_t::iterator item_it; for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it) { (*item_it)->getListener()->showProperties(); } LLFloater::setFloaterHost(NULL); multi_propertiesp->open(); /* Flawfinder: ignore */ } } } void LLFolderView::autoOpenItem( LLFolderViewFolder* item ) { if (mAutoOpenItems.check() == item || mAutoOpenItems.getDepth() >= (U32)AUTO_OPEN_STACK_DEPTH) { 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); scrollToShowItem(item); } 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->getListener()->isItemCopyable()) { return FALSE; } } return TRUE; } // copy selected item void LLFolderView::copy() { // *NOTE: total hack to clear the inventory clipboard LLInventoryClipboard::instance().reset(); S32 count = mSelectedItems.size(); if(getVisible() && getEnabled() && (count > 0)) { LLFolderViewEventListener* listener = NULL; selected_items_t::iterator item_it; for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it) { listener = (*item_it)->getListener(); if(listener) { listener->copyToClipboard(); } } } mSearchString.clear(); } BOOL LLFolderView::canCut() const { return FALSE; } void LLFolderView::cut() { // implement Windows-style cut-and-leave } 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 LLFolderViewEventListener* listener = item->getListener(); if(!listener || !listener->isClipboardPasteable()) { const LLFolderViewFolder* folderp = item->getParentFolder(); listener = folderp->getListener(); 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; LLFolderViewEventListener* listener = item->getListener(); if (listener->getInventoryType() != LLInventoryType::IT_CATEGORY) { item = item->getParentFolder(); } folder_set.insert(item); } std::set::iterator set_iter; for(set_iter = folder_set.begin(); set_iter != folder_set.end(); ++set_iter) { LLFolderViewEventListener* listener = (*set_iter)->getListener(); if(listener && listener->isClipboardPasteable()) { listener->pasteFromClipboard(); } } } mSearchString.clear(); } // public rename functionality - can only start the process void LLFolderView::startRenamingSelectedItem( void ) { // make sure selection is visible scrollToShowSelection(); S32 count = mSelectedItems.size(); LLFolderViewItem* item = NULL; if(count > 0) { item = mSelectedItems.front(); } if(getVisible() && getEnabled() && (count == 1) && item && item->getListener() && item->getListener()->isItemRenameable()) { mRenameItem = item; S32 x = ARROW_SIZE + TEXT_PAD + ICON_WIDTH + ICON_PAD - 1 + item->getIndentation(); S32 y = llfloor(item->getRect().getHeight()-sFont->getLineHeight()-2); item->localPointToScreen( x, y, &x, &y ); screenPointToLocal( x, y, &x, &y ); mRenamer->setOrigin( x, y ); S32 scroller_height = 0; S32 scroller_width = gViewerWindow->getWindowWidth(); BOOL dummy_bool; if (mScrollContainer) { mScrollContainer->calcVisibleSize( &scroller_width, &scroller_height, &dummy_bool, &dummy_bool); } S32 width = llmax(llmin(item->getRect().getWidth() - x, scroller_width - x - getRect().mLeft), MINIMUM_RENAMER_WIDTH); S32 height = llfloor(sFont->getLineHeight() + RENAME_HEIGHT_PAD); mRenamer->reshape( width, height, TRUE ); mRenamer->setText(item->getName()); mRenamer->selectAll(); mRenamer->setVisible( TRUE ); // set focus will fail unless item is visible mRenamer->setFocus( TRUE ); mRenamer->setLostTopCallback(onRenamerLost); gFocusMgr.setTopCtrl( mRenamer ); } } void LLFolderView::setFocus(BOOL focus) { if (focus) { if(!hasFocus()) { gEditMenuHandler = this; } } LLFolderViewFolder::setFocus(focus); } 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; } else { LLFolderView::openSelectedItems(); handled = TRUE; } } break; case KEY_ESCAPE: if( mRenameItem && mRenamer->getVisible() ) { closeRenamer(); handled = TRUE; } mSearchString.clear(); break; case KEY_PAGE_UP: mSearchString.clear(); mScrollContainer->pageUp(30); handled = TRUE; break; case KEY_PAGE_DOWN: mSearchString.clear(); mScrollContainer->pageDown(30); handled = TRUE; break; case KEY_HOME: mSearchString.clear(); mScrollContainer->goToTop(); handled = TRUE; break; case KEY_END: mSearchString.clear(); mScrollContainer->goToBottom(); break; case KEY_DOWN: if((mSelectedItems.size() > 0) && mScrollContainer) { LLFolderViewItem* last_selected = getCurSelectedItem(); if (!mKeyboardSelection) { setSelection(last_selected, FALSE, TRUE); mKeyboardSelection = TRUE; } LLFolderViewItem* next = NULL; if (mask & MASK_SHIFT) { // don't shift select down to children of folders (they are implicitly selected through parent) next = last_selected->getNextOpenNode(FALSE); if (next) { if (next->isSelected()) { // shrink selection changeSelectionFromRoot(last_selected, FALSE); } else if (last_selected->getParentFolder() == next->getParentFolder()) { // grow selection changeSelectionFromRoot(next, TRUE); } } } else { next = last_selected->getNextOpenNode(); if( next ) { if (next == last_selected) { return FALSE; } setSelection( next, FALSE, TRUE ); } } scrollToShowSelection(); mSearchString.clear(); handled = TRUE; } break; case KEY_UP: if((mSelectedItems.size() > 0) && mScrollContainer) { LLFolderViewItem* last_selected = mSelectedItems.back(); if (!mKeyboardSelection) { setSelection(last_selected, FALSE, TRUE); mKeyboardSelection = TRUE; } LLFolderViewItem* prev = NULL; if (mask & MASK_SHIFT) { // don't shift select down to children of folders (they are implicitly selected through parent) prev = last_selected->getPreviousOpenNode(FALSE); if (prev) { if (prev->isSelected()) { // shrink selection changeSelectionFromRoot(last_selected, FALSE); } else if (last_selected->getParentFolder() == prev->getParentFolder()) { // grow selection changeSelectionFromRoot(prev, TRUE); } } } else { prev = last_selected->getPreviousOpenNode(); if( prev ) { if (prev == this) { 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(); 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; } if (!handled && hasFocus()) { if (key == KEY_BACKSPACE) { mSearchTimer.reset(); if (mSearchString.size()) { mSearchString.erase(mSearchString.size() - 1, 1); } search(getCurSelectedItem(), mSearchString, FALSE); handled = TRUE; } } 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) { llwarns << "LLFolderView::handleUnicodeCharHere - Don't handle non-ascii yet, aborting" << llendl; return FALSE; } BOOL handled = FALSE; if (gFocusMgr.childHasKeyboardFocus(getRoot())) { // 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(); } static F32 *sTypeAheadTimeout = rebind_llcontrol("TypeAheadTimeout", &gSavedSettings, true); //do text search if (mSearchTimer.getElapsedTimeF32() > *sTypeAheadTimeout) { mSearchString.clear(); } mSearchTimer.reset(); if (mSearchString.size() < 128) { mSearchString += uni_char; } search(getCurSelectedItem(), mSearchString, FALSE); handled = TRUE; } return handled; } BOOL LLFolderView::canDoDelete() const { if (mSelectedItems.size() == 0) return FALSE; for (selected_items_t::const_iterator item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it) { if (!(*item_it)->getListener()->isItemRemovable()) { return FALSE; } } return TRUE; } void LLFolderView::doDelete() { if(mSelectedItems.size() > 0) { removeSelectedItems(); } } BOOL LLFolderView::handleMouseDown( S32 x, S32 y, MASK mask ) { mKeyboardSelection = FALSE; mSearchString.clear(); setFocus(TRUE); return LLView::handleMouseDown( x, y, mask ); } void LLFolderView::onFocusLost( ) { if( gEditMenuHandler == this ) { gEditMenuHandler = NULL; } LLUICtrl::onFocusLost(); } 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; } } const std::string current_item_label(search_item->getSearchableLabel()); S32 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 setFocus(TRUE); BOOL handled = childrenHandleRightMouseDown(x, y, mask) != NULL; S32 count = mSelectedItems.size(); LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get(); if(handled && (count > 0) && menu) { //menu->empty(); 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(TRUE); (*menu_itor)->setEnabled(TRUE); } // Successively filter out invalid options selected_items_t::iterator item_itor; U32 flags = FIRST_SELECTED_ITEM; for (item_itor = mSelectedItems.begin(); item_itor != mSelectedItems.end(); ++item_itor) { (*item_itor)->buildContextMenu(*menu, flags); flags = 0x0; } menu->arrange(); menu->updateParent(LLMenuGL::sMenuContainer); LLMenuGL::showPopup(this, menu, x, y); } else { if(menu && menu->getVisible()) { menu->setVisible(FALSE); } setSelection(NULL, FALSE, TRUE); } return handled; } BOOL LLFolderView::handleHover( S32 x, S32 y, MASK mask ) { return LLView::handleHover( x, y, mask ); } 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; BOOL handled = LLView::handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); if (handled) { lldebugst(LLERR_USER_INPUT) << "dragAndDrop handled by LLFolderView" << llendl; } return handled; } BOOL LLFolderView::handleScrollWheel(S32 x, S32 y, S32 clicks) { if (mScrollContainer) { return mScrollContainer->handleScrollWheel(x, y, clicks); } return FALSE; } void LLFolderView::deleteAllChildren() { if(mRenamer == gFocusMgr.getTopCtrl()) { gFocusMgr.setTopCtrl(NULL); } LLView::deleteViewByHandle(mPopupMenuHandle); mPopupMenuHandle = LLHandle(); mRenamer = NULL; mRenameItem = NULL; clearSelection(); LLView::deleteAllChildren(); } void LLFolderView::scrollToShowSelection() { if (mSelectedItems.size()) { mNeedsScroll = TRUE; } } // If the parent is scroll containter, scroll it to make the selection // is maximally visible. void LLFolderView::scrollToShowItem(LLFolderViewItem* item) { // don't scroll to items when mouse is being used to scroll/drag and drop if (gFocusMgr.childHasMouseCapture(mScrollContainer)) { mNeedsScroll = FALSE; return; } if(item && mScrollContainer) { LLRect local_rect = item->getRect(); LLRect item_scrolled_rect; // item position relative to display area of scroller S32 icon_height = mIcon.isNull() ? 0 : mIcon->getHeight(); S32 label_height = llround(sFont->getLineHeight()); // when navigating with keyboard, only move top of folders on screen, otherwise show whole folder S32 max_height_to_show = gFocusMgr.childHasKeyboardFocus(this) ? (llmax( icon_height, label_height ) + ICON_PAD) : local_rect.getHeight(); item->localPointToOtherView(item->getIndentation(), llmax(0, local_rect.getHeight() - max_height_to_show), &item_scrolled_rect.mLeft, &item_scrolled_rect.mBottom, mScrollContainer); item->localPointToOtherView(local_rect.getWidth(), local_rect.getHeight(), &item_scrolled_rect.mRight, &item_scrolled_rect.mTop, mScrollContainer); item_scrolled_rect.mRight = llmin(item_scrolled_rect.mLeft + MIN_ITEM_WIDTH_VISIBLE, item_scrolled_rect.mRight); LLCoordGL scroll_offset(-mScrollContainer->getBorderWidth() - item_scrolled_rect.mLeft, mScrollContainer->getRect().getHeight() - item_scrolled_rect.mTop - 1); S32 max_scroll_offset = getVisibleRect().getHeight() - item_scrolled_rect.getHeight(); if (item != mLastScrollItem || // if we're scrolling to focus on a new item // or the item has just appeared on screen and it wasn't onscreen before (scroll_offset.mY > 0 && scroll_offset.mY < max_scroll_offset && (mLastScrollOffset.mY < 0 || mLastScrollOffset.mY > max_scroll_offset))) { // we now have a position on screen that we want to keep stable // offset of selection relative to top of visible area mLastScrollOffset = scroll_offset; mLastScrollItem = item; } mScrollContainer->scrollToShowRect( item_scrolled_rect, mLastScrollOffset ); // after scrolling, store new offset // in case we don't have room to maintain the original position LLCoordGL new_item_left_top; item->localPointToOtherView(item->getIndentation(), item->getRect().getHeight(), &new_item_left_top.mX, &new_item_left_top.mY, mScrollContainer); mLastScrollOffset.set(-mScrollContainer->getBorderWidth() - new_item_left_top.mX, mScrollContainer->getRect().getHeight() - new_item_left_top.mY - 1); } } LLRect LLFolderView::getVisibleRect() { S32 visible_height = mScrollContainer->getRect().getHeight(); S32 visible_width = mScrollContainer->getRect().getWidth(); 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; } } void LLFolderView::addItemID(const LLUUID& id, LLFolderViewItem* itemp) { mItemMap[id] = itemp; } void LLFolderView::removeItemID(const LLUUID& id) { mItemMap.erase(id); } LLFolderViewItem* LLFolderView::getItemByID(const LLUUID& id) { if (id.isNull()) { return this; } std::map::iterator map_it; map_it = mItemMap.find(id); if (map_it != mItemMap.end()) { return map_it->second; } return NULL; } // Main idle routine void LLFolderView::doIdle() { LLFastTimer t2(LLFastTimer::FTM_INVENTORY); static BOOL* debug_filters = rebind_llcontrol("DebugInventoryFilters", &gSavedSettings, true); if (*debug_filters != getDebugFilters()) { mDebugFilters = *debug_filters; arrangeAll(); } mFilter.clearModified(); BOOL filter_modified_and_active = mCompletedFilterGeneration < mFilter.getCurrentGeneration() && mFilter.isNotDefault(); mNeedsAutoSelect = filter_modified_and_active && !(gFocusMgr.childHasKeyboardFocus(this) || gFocusMgr.getMouseCapture()); // filter to determine visiblity before arranging filterFromRoot(); // automatically show matching items, and select first one // do this every frame until user puts keyboard focus into the inventory window // signaling the end of the automatic update // only do this when mNeedsFilter is set, meaning filtered items have // potentially changed if (mNeedsAutoSelect) { LLFastTimer t3(LLFastTimer::FTM_AUTO_SELECT); // select new item only if a filtered item not currently selected LLFolderViewItem* selected_itemp = mSelectedItems.empty() ? NULL : mSelectedItems.back(); if ((!selected_itemp || !selected_itemp->getFiltered()) && !mAutoSelectOverride) { // select first filtered item LLSelectFirstFilteredItem filter; applyFunctorRecursively(filter); } scrollToShowSelection(); } BOOL is_visible = isInVisibleChain(); if ( is_visible ) { sanitizeSelection(); if( needsArrange() ) { arrangeFromRoot(); } } if (mSelectedItems.size() && mNeedsScroll) { scrollToShowItem(mSelectedItems.back()); // continue scrolling until animated layout change is done if (getCompletedFilterGeneration() >= mFilter.getMinRequiredGeneration() && (!needsArrange() || !is_visible)) { mNeedsScroll = FALSE; } } if (mSignalSelectCallback && mSelectCallback) { //RN: we use keyboard focus as a proxy for user-explicit actions BOOL take_keyboard_focus = (mSignalSelectCallback == SIGNAL_KEYBOARD_FOCUS); mSelectCallback(mSelectedItems, take_keyboard_focus, mUserData); } mSignalSelectCallback = FALSE; } //static void LLFolderView::idle(void* user_data) { LLFolderView* self = (LLFolderView*)user_data; if ( self ) { // Do the real idle self->doIdle(); } } void LLFolderView::dumpSelectionInformation() { llinfos << "LLFolderView::dumpSelectionInformation()" << llendl; llinfos << "****************************************" << llendl; selected_items_t::iterator item_it; for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it) { llinfos << " " << (*item_it)->getName() << llendl; } llinfos << "****************************************" << llendl; } ///---------------------------------------------------------------------------- /// Local function definitions ///---------------------------------------------------------------------------- bool LLInventorySort::updateSort(U32 order) { if (order != mSortOrder) { mSortOrder = order; mByDate = (order & LLInventoryFilter::SO_DATE); mSystemToTop = (order & LLInventoryFilter::SO_SYSTEM_FOLDERS_TO_TOP); mFoldersByName = (order & LLInventoryFilter::SO_FOLDERS_BY_NAME); return true; } return false; } bool LLInventorySort::operator()(const LLFolderViewItem* const& a, const LLFolderViewItem* const& b) { // We sort by name if we aren't sorting by date // OR if these are folders and we are sorting folders by name. bool by_name = (!mByDate || (mFoldersByName && (a->getSortGroup() != SG_ITEM))); if (a->getSortGroup() != b->getSortGroup()) { if (mSystemToTop) { // Group order is System Folders, Trash, Normal Folders, Items return (a->getSortGroup() < b->getSortGroup()); } else if (mByDate) { // Trash needs to go to the bottom if we are sorting by date if ( (a->getSortGroup() == SG_TRASH_FOLDER) || (b->getSortGroup() == SG_TRASH_FOLDER)) { return (b->getSortGroup() == SG_TRASH_FOLDER); } } } if (by_name) { S32 compare = LLStringUtil::compareDict(a->getLabel(), b->getLabel()); if (0 == compare) { return (a->getCreationDate() > b->getCreationDate()); } else { return (compare < 0); } } else { // BUG: This is very very slow. The getCreationDate() is log n in number // of inventory items. time_t first_create = a->getCreationDate(); time_t second_create = b->getCreationDate(); if (first_create == second_create) { return (LLStringUtil::compareDict(a->getLabel(), b->getLabel()) < 0); } else { return (first_create > second_create); } } } //static void LLFolderView::onRenamerLost( LLUICtrl* renamer, void* user_data) { renamer->setVisible(FALSE); } void delete_selected_item(void* user_data) { if(user_data) { LLFolderView* fv = reinterpret_cast(user_data); fv->removeSelectedItems(); } } void copy_selected_item(void* user_data) { if(user_data) { LLFolderView* fv = reinterpret_cast(user_data); fv->copy(); } } void paste_items(void* user_data) { if(user_data) { LLFolderView* fv = reinterpret_cast(user_data); fv->paste(); } } void open_selected_items(void* user_data) { if(user_data) { LLFolderView* fv = reinterpret_cast(user_data); fv->openSelectedItems(); } } void properties_selected_items(void* user_data) { if(user_data) { LLFolderView* fv = reinterpret_cast(user_data); fv->propertiesSelectedItems(); } } ///---------------------------------------------------------------------------- /// Class LLFolderViewEventListener ///---------------------------------------------------------------------------- void LLFolderViewEventListener::arrangeAndSet(LLFolderViewItem* focus, BOOL set_selection, BOOL take_keyboard_focus) { if(!focus) return; LLFolderView* root = focus->getRoot(); focus->getParentFolder()->requestArrange(); if(set_selection) { focus->setSelectionFromRoot(focus, TRUE, take_keyboard_focus); if(root) { root->scrollToShowSelection(); } } } ///---------------------------------------------------------------------------- /// Class LLInventoryFilter ///---------------------------------------------------------------------------- LLInventoryFilter::LLInventoryFilter(const std::string& name) : mName(name), mModified(FALSE), mNeedTextRebuild(TRUE) { //fix to get rid of gSavedSettings use - rkeast mSearchType = 0; mFilterOps.mFilterTypes = LLInventoryType::NIT_ALL; mFilterOps.mMinDate = time_min(); mFilterOps.mMaxDate = time_max(); mFilterOps.mHoursAgo = 0; mFilterOps.mShowFolderState = SHOW_NON_EMPTY_FOLDERS; mFilterOps.mPermissions = PERM_NONE; mOrder = SO_FOLDERS_BY_NAME; // This gets overridden by a pref immediately mSubStringMatchOffset = 0; mFilterSubString.clear(); mFilterWorn = false; mFilterGeneration = 0; mMustPassGeneration = S32_MAX; mMinRequiredGeneration = 0; mFilterCount = 0; mNextFilterGeneration = mFilterGeneration + 1; mLastLogoff = gSavedPerAccountSettings.getU32("LastLogoff"); mFilterBehavior = FILTER_NONE; // copy mFilterOps into mDefaultFilterOps markDefault(); } LLInventoryFilter::~LLInventoryFilter() { } BOOL LLInventoryFilter::check(LLFolderViewItem* item) { time_t earliest; earliest = time_corrected() - mFilterOps.mHoursAgo * 3600; if (mFilterOps.mMinDate > time_min() && mFilterOps.mMinDate < earliest) { earliest = mFilterOps.mMinDate; } else if (!mFilterOps.mHoursAgo) { earliest = 0; } LLFolderViewEventListener* listener = item->getListener(); const LLUUID& item_id = listener->getUUID(); //When searching for all labels, we need to explode the filter string //Into an array, and then compare each string to the label seperately //Otherwise the filter substring needs to be //formatted in the same order as the label - rkeast BOOL passed; //Added ability to toggle this type of searching for all labels cause it's convienient - RKeast if(mSearchType == 3) { std::istringstream i(mFilterSubString); std::string blah; LLDynamicArray search_array; while(i >> blah) { search_array.put(blah); } BOOL subStringMatch = true; for(int i = 0; i < search_array.getLength(); i++) { mSubStringMatchOffset = (search_array.get(i)).size() ? item->getSearchableLabel().find(search_array.get(i)) : std::string::npos; subStringMatch = subStringMatch && ((search_array.get(i)).size() == 0 || mSubStringMatchOffset != std::string::npos); } passed = (listener->getNInventoryType() & mFilterOps.mFilterTypes || listener->getNInventoryType() == LLInventoryType::NIT_NONE) && (subStringMatch) && (mFilterWorn == false || gAgent.isWearingItem(item_id) || (gAgent.getAvatarObject() && gAgent.getAvatarObject()->isWearingAttachment(item_id))) && ((listener->getPermissionMask() & mFilterOps.mPermissions) == mFilterOps.mPermissions) && (listener->getCreationDate() >= earliest && listener->getCreationDate() <= mFilterOps.mMaxDate); } else { mSubStringMatchOffset = mFilterSubString.size() ? item->getSearchableLabel().find(mFilterSubString) : std::string::npos; passed = (listener->getNInventoryType() & mFilterOps.mFilterTypes || listener->getNInventoryType() == LLInventoryType::NIT_NONE) && (mFilterSubString.size() == 0 || mSubStringMatchOffset != std::string::npos) && (mFilterWorn == false || gAgent.isWearingItem(item_id) || (gAgent.getAvatarObject() && gAgent.getAvatarObject()->isWearingAttachment(item_id))) && ((listener->getPermissionMask() & mFilterOps.mPermissions) == mFilterOps.mPermissions) && (listener->getCreationDate() >= earliest && listener->getCreationDate() <= mFilterOps.mMaxDate); } return passed; } const std::string LLInventoryFilter::getFilterSubString(BOOL trim) { return mFilterSubString; } std::string::size_type LLInventoryFilter::getStringMatchOffset() const { return mSubStringMatchOffset; } // has user modified default filter params? BOOL LLInventoryFilter::isNotDefault() { return mFilterOps.mFilterTypes != mDefaultFilterOps.mFilterTypes || mFilterSubString.size() || mFilterWorn || mFilterOps.mPermissions != mDefaultFilterOps.mPermissions || mFilterOps.mMinDate != mDefaultFilterOps.mMinDate || mFilterOps.mMaxDate != mDefaultFilterOps.mMaxDate || mFilterOps.mHoursAgo != mDefaultFilterOps.mHoursAgo; } BOOL LLInventoryFilter::isActive() { return mFilterOps.mFilterTypes != LLInventoryType::NIT_ALL || mFilterSubString.size() || mFilterWorn || mFilterOps.mPermissions != PERM_NONE || mFilterOps.mMinDate != time_min() || mFilterOps.mMaxDate != time_max() || mFilterOps.mHoursAgo != 0; } BOOL LLInventoryFilter::isModified() { return mModified; } BOOL LLInventoryFilter::isModifiedAndClear() { BOOL ret = mModified; mModified = FALSE; return ret; } //fix to get rid of gSavedSettings use - rkeast void LLInventoryFilter::setSearchType(U32 type) { mSearchType = type; } //fix to get rid of gSavedSettings use - rkeast U32 LLInventoryFilter::getSearchType() { return mSearchType; } void LLInventoryFilter::setFilterTypes(U32 types) { if (mFilterOps.mFilterTypes != types) { // keep current items only if no type bits getting turned off BOOL fewer_bits_set = (mFilterOps.mFilterTypes & ~types); BOOL more_bits_set = (~mFilterOps.mFilterTypes & types); mFilterOps.mFilterTypes = types; if (more_bits_set && fewer_bits_set) { // neither less or more restrive, both simultaneously // so we need to filter from scratch setModified(FILTER_RESTART); } else if (more_bits_set) { // target is only one of all requested types so more type bits == less restrictive setModified(FILTER_LESS_RESTRICTIVE); } else if (fewer_bits_set) { setModified(FILTER_MORE_RESTRICTIVE); } } } void LLInventoryFilter::setFilterSubString(const std::string& string) { if (mFilterSubString != string) { // hitting BACKSPACE, for example BOOL less_restrictive = mFilterSubString.size() >= string.size() && !mFilterSubString.substr(0, string.size()).compare(string); // appending new characters BOOL more_restrictive = mFilterSubString.size() < string.size() && !string.substr(0, mFilterSubString.size()).compare(mFilterSubString); mFilterSubString = string; LLStringUtil::toUpper(mFilterSubString); LLStringUtil::trimHead(mFilterSubString); if (less_restrictive) { setModified(FILTER_LESS_RESTRICTIVE); } else if (more_restrictive) { setModified(FILTER_MORE_RESTRICTIVE); } else { setModified(FILTER_RESTART); } } } void LLInventoryFilter::setFilterPermissions(PermissionMask perms) { if (mFilterOps.mPermissions != perms) { // keep current items only if no perm bits getting turned off BOOL fewer_bits_set = (mFilterOps.mPermissions & ~perms); BOOL more_bits_set = (~mFilterOps.mPermissions & perms); mFilterOps.mPermissions = perms; if (more_bits_set && fewer_bits_set) { setModified(FILTER_RESTART); } else if (more_bits_set) { // target must have all requested permission bits, so more bits == more restrictive setModified(FILTER_MORE_RESTRICTIVE); } else if (fewer_bits_set) { setModified(FILTER_LESS_RESTRICTIVE); } } } void LLInventoryFilter::setDateRange(time_t min_date, time_t max_date) { mFilterOps.mHoursAgo = 0; if (mFilterOps.mMinDate != min_date) { mFilterOps.mMinDate = min_date; setModified(); } if (mFilterOps.mMaxDate != llmax(mFilterOps.mMinDate, max_date)) { mFilterOps.mMaxDate = llmax(mFilterOps.mMinDate, max_date); setModified(); } } void LLInventoryFilter::setDateRangeLastLogoff(BOOL sl) { if (sl && !isSinceLogoff()) { setDateRange(mLastLogoff, time_max()); setModified(); } if (!sl && isSinceLogoff()) { setDateRange(0, time_max()); setModified(); } } BOOL LLInventoryFilter::isSinceLogoff() { return (mFilterOps.mMinDate == mLastLogoff) && (mFilterOps.mMaxDate == time_max()); } void LLInventoryFilter::setHoursAgo(U32 hours) { if (mFilterOps.mHoursAgo != hours) { // *NOTE: need to cache last filter time, in case filter goes stale BOOL less_restrictive = (mFilterOps.mMinDate == time_min() && mFilterOps.mMaxDate == time_max() && hours > mFilterOps.mHoursAgo); BOOL more_restrictive = (mFilterOps.mMinDate == time_min() && mFilterOps.mMaxDate == time_max() && hours <= mFilterOps.mHoursAgo); mFilterOps.mHoursAgo = hours; mFilterOps.mMinDate = time_min(); mFilterOps.mMaxDate = time_max(); if (less_restrictive) { setModified(FILTER_LESS_RESTRICTIVE); } else if (more_restrictive) { setModified(FILTER_MORE_RESTRICTIVE); } else { setModified(FILTER_RESTART); } } } void LLInventoryFilter::setShowFolderState(EFolderShow state) { if (mFilterOps.mShowFolderState != state) { mFilterOps.mShowFolderState = state; if (state == SHOW_NON_EMPTY_FOLDERS) { // showing fewer folders than before setModified(FILTER_MORE_RESTRICTIVE); } else if (state == SHOW_ALL_FOLDERS) { // showing same folders as before and then some setModified(FILTER_LESS_RESTRICTIVE); } else { setModified(); } } } void LLInventoryFilter::setSortOrder(U32 order) { if (mOrder != order) { mOrder = order; setModified(); } } void LLInventoryFilter::markDefault() { mDefaultFilterOps = mFilterOps; } void LLInventoryFilter::resetDefault() { mFilterOps = mDefaultFilterOps; setModified(); } void LLInventoryFilter::setModified(EFilterBehavior behavior) { mModified = TRUE; mNeedTextRebuild = TRUE; mFilterGeneration = mNextFilterGeneration++; if (mFilterBehavior == FILTER_NONE) { mFilterBehavior = behavior; } else if (mFilterBehavior != behavior) { // trying to do both less restrictive and more restrictive filter // basically means restart from scratch mFilterBehavior = FILTER_RESTART; } if (isNotDefault()) { // if not keeping current filter results, update last valid as well switch(mFilterBehavior) { case FILTER_RESTART: mMustPassGeneration = mFilterGeneration; mMinRequiredGeneration = mFilterGeneration; break; case FILTER_LESS_RESTRICTIVE: mMustPassGeneration = mFilterGeneration; break; case FILTER_MORE_RESTRICTIVE: mMinRequiredGeneration = mFilterGeneration; // must have passed either current filter generation (meaningless, as it hasn't been run yet) // or some older generation, so keep the value mMustPassGeneration = llmin(mMustPassGeneration, mFilterGeneration); break; default: llerrs << "Bad filter behavior specified" << llendl; } } else { // shortcut disabled filters to show everything immediately mMinRequiredGeneration = 0; mMustPassGeneration = S32_MAX; } } BOOL LLInventoryFilter::isFilterWith(LLInventoryType::NType t) { return mFilterOps.mFilterTypes & t; } std::string LLInventoryFilter::getFilterText() { if (!mNeedTextRebuild) { return mFilterText; } return rebuildFilterText(); } std::string LLInventoryFilter::rebuildFilterText() { mNeedTextRebuild = FALSE; std::string filtered_types; std::string not_filtered_types; BOOL filtered_by_type = FALSE; BOOL filtered_by_all_types = TRUE; S32 num_filter_types = 0; mFilterText.clear(); if (isFilterWith(LLInventoryType::NIT_ANIMATION)) { filtered_types += " Animations,"; filtered_by_type = TRUE; num_filter_types++; } else { not_filtered_types += " Animations,"; filtered_by_all_types = FALSE; } if (isFilterWith(LLInventoryType::NIT_BODYPART)) { filtered_types += " Body Parts,"; filtered_by_type = TRUE; num_filter_types++; } else { not_filtered_types += " Body Parts,"; filtered_by_all_types = FALSE; } if (isFilterWith(LLInventoryType::NIT_CALLCARD)) { filtered_types += " Calling Cards,"; filtered_by_type = TRUE; num_filter_types++; } else { not_filtered_types += " Calling Cards,"; filtered_by_all_types = FALSE; } if (isFilterWith(LLInventoryType::NIT_CLOTHING)) { filtered_types += " Clothing,"; filtered_by_type = TRUE; num_filter_types++; } else { not_filtered_types += " Clothing,"; filtered_by_all_types = FALSE; } if (isFilterWith(LLInventoryType::NIT_GESTURE)) { filtered_types += " Gestures,"; filtered_by_type = TRUE; num_filter_types++; } else { not_filtered_types += " Gestures,"; filtered_by_all_types = FALSE; } if (isFilterWith(LLInventoryType::NIT_LANDMARK)) { filtered_types += " Landmarks,"; filtered_by_type = TRUE; num_filter_types++; } else { not_filtered_types += " Landmarks,"; filtered_by_all_types = FALSE; } if (isFilterWith(LLInventoryType::NIT_NOTECARD)) { filtered_types += " Notecards,"; filtered_by_type = TRUE; num_filter_types++; } else { not_filtered_types += " Notecards,"; filtered_by_all_types = FALSE; } if (isFilterWith(LLInventoryType::NIT_OBJECT)) { filtered_types += " Objects,"; filtered_by_type = TRUE; num_filter_types++; } else { not_filtered_types += " Objects,"; filtered_by_all_types = FALSE; } if (isFilterWith(LLInventoryType::NIT_SCRIPT_LSL2)) { filtered_types += " Scripts,"; filtered_by_type = TRUE; num_filter_types++; } else { not_filtered_types += " Scripts,"; filtered_by_all_types = FALSE; } if (isFilterWith(LLInventoryType::NIT_SOUND)) { filtered_types += " Sounds,"; filtered_by_type = TRUE; num_filter_types++; } else { not_filtered_types += " Sounds,"; filtered_by_all_types = FALSE; } if (isFilterWith(LLInventoryType::NIT_TEXTURE)) { filtered_types += " Textures,"; filtered_by_type = TRUE; num_filter_types++; } else { not_filtered_types += " Textures,"; filtered_by_all_types = FALSE; } if (isFilterWith(LLInventoryType::NIT_SNAPSHOT)) { filtered_types += " Snapshots,"; filtered_by_type = TRUE; num_filter_types++; } else { not_filtered_types += " Snapshots,"; filtered_by_all_types = FALSE; } if (!gInventory.backgroundFetchActive() && filtered_by_type && !filtered_by_all_types) { mFilterText += " - "; if (num_filter_types < 5) { mFilterText += filtered_types; } else { mFilterText += "No "; mFilterText += not_filtered_types; } // remove the ',' at the end mFilterText.erase(mFilterText.size() - 1, 1); } if (isSinceLogoff()) { mFilterText += " - Since Logoff"; } if (getFilterWorn()) { mFilterText += " - Worn"; } return mFilterText; } void LLInventoryFilter::toLLSD(LLSD& data) { data["filter_types"] = (LLSD::Integer)getFilterTypes(); data["min_date"] = (LLSD::Integer)getMinDate(); data["max_date"] = (LLSD::Integer)getMaxDate(); data["hours_ago"] = (LLSD::Integer)getHoursAgo(); data["show_folder_state"] = (LLSD::Integer)getShowFolderState(); data["permissions"] = (LLSD::Integer)getFilterPermissions(); data["substring"] = (LLSD::String)getFilterSubString(); data["sort_order"] = (LLSD::Integer)getSortOrder(); data["since_logoff"] = (LLSD::Boolean)isSinceLogoff(); } void LLInventoryFilter::fromLLSD(LLSD& data) { if(data.has("filter_types")) { setFilterTypes((U32)data["filter_types"].asInteger()); } if(data.has("min_date") && data.has("max_date")) { setDateRange(data["min_date"].asInteger(), data["max_date"].asInteger()); } if(data.has("hours_ago")) { setHoursAgo((U32)data["hours_ago"].asInteger()); } if(data.has("show_folder_state")) { setShowFolderState((EFolderShow)data["show_folder_state"].asInteger()); } if(data.has("permissions")) { setFilterPermissions((PermissionMask)data["permissions"].asInteger()); } if(data.has("substring")) { setFilterSubString(std::string(data["substring"].asString())); } if(data.has("sort_order")) { setSortOrder((U32)data["sort_order"].asInteger()); } if(data.has("since_logoff")) { setDateRangeLastLogoff((bool)data["since_logoff"].asBoolean()); } }