Need to test: localassetbrowser preview related floaters hgfloatertexteditor maps media textures! Currently very hacky web browser alpha masks on avatars bumpmaps Are all sky components appearing? LLViewerDynamicTexture (texture baking, browser, animated textures, anim previews, etc) Snapshot related features Customize avatar vfs floater UI textures in general Texture priority issues
5207 lines
127 KiB
C++
5207 lines
127 KiB
C++
/**
|
|
* @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 <algorithm>
|
|
|
|
#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 "llviewertexturelist.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;
|
|
|
|
// This is used to keep track of existing folder view items and
|
|
// avoid a crash bug due to a race condition (see in doIdle()).
|
|
static std::set<LLFolderViewItem*> sFolderViewItems;
|
|
|
|
//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)
|
|
{
|
|
sFolderViewItems.insert(this);
|
|
refresh(); // possible opt: only call refreshFromListener()
|
|
setTabStop(FALSE);
|
|
}
|
|
|
|
// Destroys the object
|
|
LLFolderViewItem::~LLFolderViewItem( void )
|
|
{
|
|
sFolderViewItems.erase(this);
|
|
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)
|
|
{
|
|
mLabel = mListener->getDisplayName();
|
|
setIcon(mListener->getIcon());
|
|
time_t creation_date = mListener->getCreationDate();
|
|
if (mCreationDate != creation_date)
|
|
{
|
|
mCreationDate = mListener->getCreationDate();
|
|
dirtyFilter();
|
|
}
|
|
mLabelStyle = mListener->getLabelStyle();
|
|
mLabelSuffix = mListener->getLabelSuffix();
|
|
|
|
LLInventoryItem* item = gInventory.getItem(mListener->getUUID());
|
|
|
|
std::string desc;
|
|
if (item)
|
|
{
|
|
if (!item->getDescription().empty())
|
|
{
|
|
desc = item->getDescription();
|
|
LLStringUtil::toUpper(desc);
|
|
}
|
|
}
|
|
mSearchableLabelDesc = desc;
|
|
|
|
std::string creator_name;
|
|
if (item)
|
|
{
|
|
if (item->getCreatorUUID().notNull())
|
|
{
|
|
gCacheName->getFullName(item->getCreatorUUID(), creator_name);
|
|
LLStringUtil::toUpper(creator_name);
|
|
}
|
|
}
|
|
mSearchableLabelCreator = creator_name;
|
|
}
|
|
}
|
|
|
|
void LLFolderViewItem::refresh()
|
|
{
|
|
refreshFromListener();
|
|
|
|
std::string searchable_label(mLabel);
|
|
searchable_label.append(mLabelSuffix);
|
|
LLStringUtil::toUpper(searchable_label);
|
|
|
|
if (mSearchableLabel.compare(searchable_label))
|
|
{
|
|
mSearchableLabel.assign(searchable_label);
|
|
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<LLFolderViewItem*> 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string& LLFolderViewItem::getSearchableLabel()
|
|
{
|
|
mSearchable = "";
|
|
U32 flags = mRoot->getSearchType();
|
|
if (flags == 0 || (flags & 1))
|
|
{
|
|
mSearchable = mSearchableLabel;
|
|
}
|
|
if (flags & 2)
|
|
{
|
|
if (mSearchable.length())
|
|
{
|
|
mSearchable += " ";
|
|
}
|
|
mSearchable += mSearchableLabelDesc;
|
|
}
|
|
if (flags & 4)
|
|
{
|
|
if (mSearchable.length())
|
|
{
|
|
mSearchable += " ";
|
|
}
|
|
mSearchable += mSearchableLabelCreator;
|
|
}
|
|
return mSearchable;
|
|
}
|
|
|
|
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
|
|
std::string combined_string = mLabel + mLabelSuffix;
|
|
S32 filter_string_length = mRoot->getFilterSubString().size();
|
|
std::string combined_string_upper = combined_string;
|
|
LLStringUtil::toUpper(combined_string_upper);
|
|
if (filter_string_length > 0 && (mRoot->getSearchType() & 1) &&
|
|
combined_string_upper.find(mRoot->getFilterSubString()) == mStringMatchOffset)
|
|
{
|
|
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<LLFolderViewItem*>& 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<LLFolderViewItem*> 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<LLFolderViewFolder*>(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<time_t>(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
|
|
mSearchType(1),
|
|
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;
|
|
}
|
|
|
|
U32 LLFolderView::toggleSearchType(std::string toggle)
|
|
{
|
|
if (toggle == "name")
|
|
{
|
|
if (mSearchType & 1)
|
|
{
|
|
mSearchType &= 6;
|
|
}
|
|
else
|
|
{
|
|
mSearchType |= 1;
|
|
}
|
|
}
|
|
else if (toggle == "description")
|
|
{
|
|
if (mSearchType & 2)
|
|
{
|
|
mSearchType &= 5;
|
|
}
|
|
else
|
|
{
|
|
mSearchType |= 2;
|
|
}
|
|
}
|
|
else if (toggle == "creator")
|
|
{
|
|
if (mSearchType & 4)
|
|
{
|
|
mSearchType &= 3;
|
|
}
|
|
else
|
|
{
|
|
mSearchType |= 4;
|
|
}
|
|
}
|
|
if (mSearchType == 0)
|
|
{
|
|
mSearchType = 1;
|
|
}
|
|
|
|
if (getFilterSubString().length())
|
|
{
|
|
mFilter.setModified(LLInventoryFilter::FILTER_RESTART);
|
|
}
|
|
|
|
return mSearchType;
|
|
}
|
|
|
|
U32 LLFolderView::getSearchType() const
|
|
{
|
|
return mSearchType;
|
|
}
|
|
|
|
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<LLFolderViewFolder>(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);
|
|
filter.setFilterCount(llclamp(gSavedSettings.getS32("FilterItemsPerFrame"), 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<LLFolderViewItem*>& 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<LLFolderViewItem*> 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<LLFolderViewItem*>::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<LLUUID> &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<EDragAndDropType> types;
|
|
std::vector<LLUUID> 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<LLFolderView*>(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);
|
|
}
|
|
|
|
|
|
if (mSearchTimer.getElapsedTimeF32() > gSavedSettings.getF32("TypeAheadTimeout") || !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<LLFolderViewItem*> 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<LLFolderViewEventListener*> 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<LLFolderViewEventListener*>::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<LLFolderViewItem*> 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<LLFolderViewItem*>::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();
|
|
}
|
|
|
|
LLView *item = NULL;
|
|
if (getChildCount() > 0)
|
|
{
|
|
item = *(getChildList()->begin());
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
//do text search
|
|
if (mSearchTimer.getElapsedTimeF32() > gSavedSettings.getF32("TypeAheadTimeout"))
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
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<LLView>();
|
|
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<LLUUID, LLFolderViewItem*>::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);
|
|
|
|
BOOL debug_filters = gSavedSettings.getBOOL("DebugInventoryFilters");
|
|
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 != NULL && sFolderViewItems.count(selected_itemp) == 0)
|
|
{
|
|
// There is a crash bug due to a race condition: when a folder view item is
|
|
// destroyed, its address may still appear in mSelectedItems a couple of doIdle()
|
|
// later, even if you explicitely clear this list and dirty the filters in the
|
|
// destructor...
|
|
// This code avoids the crash bug.
|
|
llwarns << "Invalid folder view item (" << selected_itemp << ") in selection: clearing the latter." << llendl;
|
|
dirtyFilter();
|
|
clearSelection();
|
|
requestArrange();
|
|
}
|
|
else if (!mAutoSelectOverride && (!selected_itemp || !selected_itemp->getFiltered()))
|
|
{
|
|
// 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<LLFolderView*>(user_data);
|
|
fv->removeSelectedItems();
|
|
}
|
|
}
|
|
|
|
void copy_selected_item(void* user_data)
|
|
{
|
|
if(user_data)
|
|
{
|
|
LLFolderView* fv = reinterpret_cast<LLFolderView*>(user_data);
|
|
fv->copy();
|
|
}
|
|
}
|
|
|
|
void paste_items(void* user_data)
|
|
{
|
|
if(user_data)
|
|
{
|
|
LLFolderView* fv = reinterpret_cast<LLFolderView*>(user_data);
|
|
fv->paste();
|
|
}
|
|
}
|
|
|
|
void open_selected_items(void* user_data)
|
|
{
|
|
if(user_data)
|
|
{
|
|
LLFolderView* fv = reinterpret_cast<LLFolderView*>(user_data);
|
|
fv->openSelectedItems();
|
|
}
|
|
}
|
|
|
|
void properties_selected_items(void* user_data)
|
|
{
|
|
if(user_data)
|
|
{
|
|
LLFolderView* fv = reinterpret_cast<LLFolderView*>(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)
|
|
{
|
|
mFilterOps.mFilterTypes = 0xffffffff;
|
|
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)
|
|
{
|
|
LLFolderViewEventListener* listener = item->getListener();
|
|
const LLUUID& item_id = listener->getUUID();
|
|
const LLInventoryObject *obj = gInventory.getObject(item_id);
|
|
if (isActive() && obj && obj->getIsLinkType())
|
|
{
|
|
// When filtering is active, omit links.
|
|
return FALSE;
|
|
}
|
|
|
|
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;
|
|
}
|
|
mSubStringMatchOffset = mFilterSubString.size() ? item->getSearchableLabel().find(mFilterSubString) : std::string::npos;
|
|
BOOL passed = (0x1 << listener->getInventoryType() & mFilterOps.mFilterTypes || listener->getInventoryType() == LLInventoryType::IT_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 != 0xffffffff
|
|
|| 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;
|
|
}
|
|
|
|
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::EType t)
|
|
{
|
|
return mFilterOps.mFilterTypes & (0x01 << t);
|
|
}
|
|
|
|
std::string LLInventoryFilter::getFilterText()
|
|
{
|
|
if (!mNeedTextRebuild)
|
|
{
|
|
return mFilterText;
|
|
}
|
|
|
|
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::IT_ANIMATION))
|
|
{
|
|
filtered_types += " Animations,";
|
|
filtered_by_type = TRUE;
|
|
num_filter_types++;
|
|
}
|
|
else
|
|
{
|
|
not_filtered_types += " Animations,";
|
|
filtered_by_all_types = FALSE;
|
|
}
|
|
|
|
if (isFilterWith(LLInventoryType::IT_CALLINGCARD))
|
|
{
|
|
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::IT_WEARABLE))
|
|
{
|
|
filtered_types += " Clothing,";
|
|
filtered_by_type = TRUE;
|
|
num_filter_types++;
|
|
}
|
|
else
|
|
{
|
|
not_filtered_types += " Clothing,";
|
|
filtered_by_all_types = FALSE;
|
|
}
|
|
|
|
if (isFilterWith(LLInventoryType::IT_GESTURE))
|
|
{
|
|
filtered_types += " Gestures,";
|
|
filtered_by_type = TRUE;
|
|
num_filter_types++;
|
|
}
|
|
else
|
|
{
|
|
not_filtered_types += " Gestures,";
|
|
filtered_by_all_types = FALSE;
|
|
}
|
|
|
|
if (isFilterWith(LLInventoryType::IT_LANDMARK))
|
|
{
|
|
filtered_types += " Landmarks,";
|
|
filtered_by_type = TRUE;
|
|
num_filter_types++;
|
|
}
|
|
else
|
|
{
|
|
not_filtered_types += " Landmarks,";
|
|
filtered_by_all_types = FALSE;
|
|
}
|
|
|
|
if (isFilterWith(LLInventoryType::IT_NOTECARD))
|
|
{
|
|
filtered_types += " Notecards,";
|
|
filtered_by_type = TRUE;
|
|
num_filter_types++;
|
|
}
|
|
else
|
|
{
|
|
not_filtered_types += " Notecards,";
|
|
filtered_by_all_types = FALSE;
|
|
}
|
|
|
|
if (isFilterWith(LLInventoryType::IT_OBJECT) && isFilterWith(LLInventoryType::IT_ATTACHMENT))
|
|
{
|
|
filtered_types += " Objects,";
|
|
filtered_by_type = TRUE;
|
|
num_filter_types++;
|
|
}
|
|
else
|
|
{
|
|
not_filtered_types += " Objects,";
|
|
filtered_by_all_types = FALSE;
|
|
}
|
|
|
|
if (isFilterWith(LLInventoryType::IT_LSL))
|
|
{
|
|
filtered_types += " Scripts,";
|
|
filtered_by_type = TRUE;
|
|
num_filter_types++;
|
|
}
|
|
else
|
|
{
|
|
not_filtered_types += " Scripts,";
|
|
filtered_by_all_types = FALSE;
|
|
}
|
|
|
|
if (isFilterWith(LLInventoryType::IT_SOUND))
|
|
{
|
|
filtered_types += " Sounds,";
|
|
filtered_by_type = TRUE;
|
|
num_filter_types++;
|
|
}
|
|
else
|
|
{
|
|
not_filtered_types += " Sounds,";
|
|
filtered_by_all_types = FALSE;
|
|
}
|
|
|
|
if (isFilterWith(LLInventoryType::IT_TEXTURE))
|
|
{
|
|
filtered_types += " Textures,";
|
|
filtered_by_type = TRUE;
|
|
num_filter_types++;
|
|
}
|
|
else
|
|
{
|
|
not_filtered_types += " Textures,";
|
|
filtered_by_all_types = FALSE;
|
|
}
|
|
|
|
if (isFilterWith(LLInventoryType::IT_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());
|
|
}
|
|
}
|