Holy hell, this was an interesting one to implement. I wish I understood it better. Unfortunately I think this marks the end of trying to re-implement the Local Inventory for Temp and Local textures. It's just not feasible now that the entire inventory system has been whipped into a code shitstorm. Signed-off-by: Beeks <HgDelirium@gmail.com>
5277 lines
130 KiB
C++
5277 lines
130 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 "llviewerimage.h"
|
|
#include "llviewerimagelist.h"
|
|
#include "llviewerjointattachment.h"
|
|
#include "llviewermenu.h"
|
|
#include "lluictrlfactory.h"
|
|
#include "llviewerwindow.h"
|
|
#include "llvoavatar.h"
|
|
#include "llfloaterproperties.h"
|
|
|
|
// RN: HACK
|
|
// We need these because some of the code below relies on things like
|
|
// gAgent root folder. Remove them once the abstraction leak is fixed.
|
|
#include "llagent.h"
|
|
#include "llappviewer.h"
|
|
|
|
///----------------------------------------------------------------------------
|
|
/// Local function declarations, constants, enums, and typedefs
|
|
///----------------------------------------------------------------------------
|
|
|
|
const S32 LEFT_PAD = 5;
|
|
const S32 LEFT_INDENTATION = 13;
|
|
const S32 ICON_PAD = 2;
|
|
const S32 ICON_WIDTH = 16;
|
|
const S32 TEXT_PAD = 1;
|
|
const S32 ARROW_SIZE = 12;
|
|
const S32 RENAME_WIDTH_PAD = 4;
|
|
const S32 RENAME_HEIGHT_PAD = 6;
|
|
const S32 AUTO_OPEN_STACK_DEPTH = 16;
|
|
const S32 MIN_ITEM_WIDTH_VISIBLE = ICON_WIDTH + ICON_PAD + ARROW_SIZE + TEXT_PAD + /*first few characters*/ 40;
|
|
const S32 MINIMUM_RENAMER_WIDTH = 80;
|
|
const F32 FOLDER_CLOSE_TIME_CONSTANT = 0.02f;
|
|
const F32 FOLDER_OPEN_TIME_CONSTANT = 0.03f;
|
|
const S32 MAX_FOLDER_ITEM_OVERLAP = 2;
|
|
|
|
enum {
|
|
SIGNAL_NO_KEYBOARD_FOCUS = 1,
|
|
SIGNAL_KEYBOARD_FOCUS = 2
|
|
};
|
|
|
|
F32 LLFolderView::sAutoOpenTime = 1.f;
|
|
|
|
void delete_selected_item(void* user_data);
|
|
void copy_selected_item(void* user_data);
|
|
void open_selected_items(void* user_data);
|
|
void properties_selected_items(void* user_data);
|
|
void paste_items(void* user_data);
|
|
void renamer_focus_lost( LLFocusableElement* handler, void* user_data );
|
|
|
|
///----------------------------------------------------------------------------
|
|
/// Class LLFolderViewItem
|
|
///----------------------------------------------------------------------------
|
|
|
|
// statics
|
|
const LLFontGL* LLFolderViewItem::sFont = NULL;
|
|
const LLFontGL* LLFolderViewItem::sSmallFont = NULL;
|
|
LLColor4 LLFolderViewItem::sFgColor;
|
|
LLColor4 LLFolderViewItem::sHighlightBgColor;
|
|
LLColor4 LLFolderViewItem::sHighlightFgColor;
|
|
LLColor4 LLFolderViewItem::sFilterBGColor;
|
|
LLColor4 LLFolderViewItem::sFilterTextColor;
|
|
LLColor4 LLFolderViewItem::sSuffixColor;
|
|
LLColor4 LLFolderViewItem::sSearchStatusColor;
|
|
LLUIImagePtr LLFolderViewItem::sArrowImage;
|
|
LLUIImagePtr LLFolderViewItem::sBoxImage;
|
|
|
|
//static
|
|
void LLFolderViewItem::initClass()
|
|
{
|
|
sFont = LLResMgr::getInstance()->getRes( LLFONT_SANSSERIF_SMALL );
|
|
sSmallFont = LLResMgr::getInstance()->getRes( LLFONT_SMALL );
|
|
sFgColor = gColors.getColor( "MenuItemEnabledColor" );
|
|
sHighlightBgColor = gColors.getColor( "MenuItemHighlightBgColor" );
|
|
sHighlightFgColor = gColors.getColor( "MenuItemHighlightFgColor" );
|
|
sFilterBGColor = gColors.getColor( "FilterBackgroundColor" );
|
|
sFilterTextColor = gColors.getColor( "FilterTextColor" );
|
|
sSuffixColor = gColors.getColor( "InventoryItemSuffixColor" );
|
|
sSearchStatusColor = gColors.getColor( "InventorySearchStatusColor" );
|
|
sArrowImage = LLUI::getUIImage("folder_arrow.tga");
|
|
sBoxImage = LLUI::getUIImage("rounded_square.tga");
|
|
}
|
|
|
|
//static
|
|
void LLFolderViewItem::cleanupClass()
|
|
{
|
|
sArrowImage = NULL;
|
|
sBoxImage = NULL;
|
|
}
|
|
|
|
// Default constructor
|
|
// NOTE: Optimize this, we call it a *lot* when opening a large inventory
|
|
LLFolderViewItem::LLFolderViewItem( const std::string& name, LLUIImagePtr icon,
|
|
S32 creation_date,
|
|
LLFolderView* root,
|
|
LLFolderViewEventListener* listener ) :
|
|
LLUICtrl( name, LLRect(0, 0, 0, 0), TRUE, NULL, NULL, FOLLOWS_LEFT|FOLLOWS_TOP|FOLLOWS_RIGHT),
|
|
mLabel( name ),
|
|
mLabelWidth(0),
|
|
mCreationDate(creation_date),
|
|
mParentFolder( NULL ),
|
|
mListener( listener ),
|
|
mIsSelected( FALSE ),
|
|
mIsCurSelection( FALSE ),
|
|
mSelectPending(FALSE),
|
|
mLabelStyle( LLFontGL::NORMAL ),
|
|
mIcon(icon),
|
|
mHasVisibleChildren(FALSE),
|
|
mIndentation(0),
|
|
mNumDescendantsSelected(0),
|
|
mFiltered(FALSE),
|
|
mLastFilterGeneration(-1),
|
|
mStringMatchOffset(std::string::npos),
|
|
mControlLabelRotation(0.f),
|
|
mRoot( root ),
|
|
mDragAndDropTarget(FALSE),
|
|
mIsLoading(FALSE)
|
|
{
|
|
refresh(); // possible opt: only call refreshFromListener()
|
|
setTabStop(FALSE);
|
|
}
|
|
|
|
// Destroys the object
|
|
LLFolderViewItem::~LLFolderViewItem( void )
|
|
{
|
|
delete mListener;
|
|
mListener = NULL;
|
|
}
|
|
|
|
LLFolderView* LLFolderViewItem::getRoot()
|
|
{
|
|
return mRoot;
|
|
}
|
|
|
|
// Returns true if this object is a child (or grandchild, etc.) of potential_ancestor.
|
|
BOOL LLFolderViewItem::isDescendantOf( const LLFolderViewFolder* potential_ancestor )
|
|
{
|
|
LLFolderViewItem* root = this;
|
|
while( root->mParentFolder )
|
|
{
|
|
if( root->mParentFolder == potential_ancestor )
|
|
{
|
|
return TRUE;
|
|
}
|
|
root = root->mParentFolder;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
LLFolderViewItem* LLFolderViewItem::getNextOpenNode(BOOL include_children)
|
|
{
|
|
if (!mParentFolder)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
LLFolderViewItem* itemp = mParentFolder->getNextFromChild( this, include_children );
|
|
while(itemp && !itemp->getVisible())
|
|
{
|
|
LLFolderViewItem* next_itemp = itemp->mParentFolder->getNextFromChild( itemp, include_children );
|
|
if (itemp == next_itemp)
|
|
{
|
|
// hit last item
|
|
return itemp->getVisible() ? itemp : this;
|
|
}
|
|
itemp = next_itemp;
|
|
}
|
|
|
|
return itemp;
|
|
}
|
|
|
|
LLFolderViewItem* LLFolderViewItem::getPreviousOpenNode(BOOL include_children)
|
|
{
|
|
if (!mParentFolder)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
LLFolderViewItem* itemp = mParentFolder->getPreviousFromChild( this, include_children );
|
|
while(itemp && !itemp->getVisible())
|
|
{
|
|
LLFolderViewItem* next_itemp = itemp->mParentFolder->getPreviousFromChild( itemp, include_children );
|
|
if (itemp == next_itemp)
|
|
{
|
|
// hit first item
|
|
return itemp->getVisible() ? itemp : this;
|
|
}
|
|
itemp = next_itemp;
|
|
}
|
|
|
|
return itemp;
|
|
}
|
|
|
|
// is this item something we think we should be showing?
|
|
// for example, if we haven't gotten around to filtering it yet, then the answer is yes
|
|
// until we find out otherwise
|
|
BOOL LLFolderViewItem::potentiallyVisible()
|
|
{
|
|
// we haven't been checked against min required filter
|
|
// or we have and we passed
|
|
return getLastFilterGeneration() < getRoot()->getFilter()->getMinRequiredGeneration() || getFiltered();
|
|
}
|
|
|
|
BOOL LLFolderViewItem::getFiltered()
|
|
{
|
|
return mFiltered && mLastFilterGeneration >= mRoot->getFilter()->getMinRequiredGeneration();
|
|
}
|
|
|
|
BOOL LLFolderViewItem::getFiltered(S32 filter_generation)
|
|
{
|
|
return mFiltered && mLastFilterGeneration >= filter_generation;
|
|
}
|
|
|
|
void LLFolderViewItem::setFiltered(BOOL filtered, S32 filter_generation)
|
|
{
|
|
mFiltered = filtered;
|
|
mLastFilterGeneration = filter_generation;
|
|
}
|
|
|
|
void LLFolderViewItem::setIcon(LLUIImagePtr icon)
|
|
{
|
|
mIcon = icon;
|
|
}
|
|
|
|
// refresh information from the listener
|
|
void LLFolderViewItem::refreshFromListener()
|
|
{
|
|
if(mListener)
|
|
{
|
|
//Super crazy hack to build the creator search label - RK
|
|
LLInventoryItem* item = gInventory.getItem(mListener->getUUID());
|
|
std::string creator_name;
|
|
if(item)
|
|
{
|
|
if(item->getCreatorUUID().notNull())
|
|
{
|
|
gCacheName->getFullName(item->getCreatorUUID(), creator_name);
|
|
}
|
|
}
|
|
mLabelCreator = creator_name;
|
|
/*if(creator_name == "(Loading...)")
|
|
mLabelCreator = "";
|
|
else
|
|
mLabelCreator = creator_name;*/
|
|
|
|
//Label for desc search
|
|
std::string desc;
|
|
if (item)
|
|
{
|
|
if (!item->getDescription().empty())
|
|
{
|
|
desc = item->getDescription();
|
|
LLStringUtil::toUpper(desc);
|
|
}
|
|
}
|
|
mLabelDesc = desc;
|
|
|
|
//Label for name search
|
|
mLabel = mListener->getDisplayName();
|
|
|
|
//Build label for combined search - RK
|
|
mLabelAll = mLabel + " " + mLabelCreator + " " + mLabelDesc;
|
|
|
|
setIcon(mListener->getIcon());
|
|
time_t creation_date = mListener->getCreationDate();
|
|
if (mCreationDate != creation_date)
|
|
{
|
|
mCreationDate = mListener->getCreationDate();
|
|
dirtyFilter();
|
|
}
|
|
mLabelStyle = mListener->getLabelStyle();
|
|
mLabelSuffix = mListener->getLabelSuffix();
|
|
}
|
|
}
|
|
|
|
void LLFolderViewItem::refresh()
|
|
{
|
|
refreshFromListener();
|
|
|
|
std::string searchable_label(mLabel);
|
|
std::string searchable_label_creator(mLabelCreator);
|
|
std::string searchable_label_desc(mLabelDesc);
|
|
std::string searchable_label_all(mLabelAll);
|
|
|
|
//add the (no modify), (no transfer) etc stuff to each label.
|
|
searchable_label.append(mLabelSuffix);
|
|
searchable_label_creator.append(mLabelSuffix);
|
|
searchable_label_desc.append(mLabelSuffix);
|
|
searchable_label_all.append(mLabelSuffix);
|
|
|
|
//all labels need to be uppercase.
|
|
LLStringUtil::toUpper(searchable_label);
|
|
LLStringUtil::toUpper(searchable_label_creator);
|
|
LLStringUtil::toUpper(searchable_label_desc);
|
|
LLStringUtil::toUpper(searchable_label_all);
|
|
|
|
if (mSearchableLabel.compare(searchable_label) ||
|
|
mSearchableLabelCreator.compare(searchable_label_creator) ||
|
|
mSearchableLabelDesc.compare(searchable_label_desc) ||
|
|
mSearchableLabelAll.compare(searchable_label_all))
|
|
{
|
|
mSearchableLabel.assign(searchable_label);
|
|
mSearchableLabelCreator.assign(searchable_label_creator);
|
|
mSearchableLabelDesc.assign(searchable_label_desc);
|
|
mSearchableLabelAll.assign(searchable_label_all);
|
|
|
|
dirtyFilter();
|
|
// some part of label has changed, so overall width has potentially changed
|
|
if (mParentFolder)
|
|
{
|
|
mParentFolder->requestArrange();
|
|
}
|
|
}
|
|
|
|
S32 label_width = sFont->getWidth(mLabel);
|
|
if( mLabelSuffix.size() )
|
|
{
|
|
label_width += sFont->getWidth( mLabelSuffix );
|
|
}
|
|
|
|
mLabelWidth = ARROW_SIZE + TEXT_PAD + ICON_WIDTH + ICON_PAD + label_width;
|
|
}
|
|
|
|
void LLFolderViewItem::applyListenerFunctorRecursively(LLFolderViewListenerFunctor& functor)
|
|
{
|
|
functor(mListener);
|
|
}
|
|
|
|
// This function is called when items are added or view filters change. It's
|
|
// implemented here but called by derived classes when folding the
|
|
// views.
|
|
void LLFolderViewItem::filterFromRoot( void )
|
|
{
|
|
LLFolderViewItem* root = getRoot();
|
|
|
|
root->filter(*((LLFolderView*)root)->getFilter());
|
|
}
|
|
|
|
// This function is called when the folder view is dirty. It's
|
|
// implemented here but called by derived classes when folding the
|
|
// views.
|
|
void LLFolderViewItem::arrangeFromRoot()
|
|
{
|
|
LLFolderViewItem* root = getRoot();
|
|
|
|
S32 height = 0;
|
|
S32 width = 0;
|
|
root->arrange( &width, &height, 0 );
|
|
}
|
|
|
|
// This function clears the currently selected item, and records the
|
|
// specified selected item appropriately for display and use in the
|
|
// UI. If open is TRUE, then folders are opened up along the way to
|
|
// the selection.
|
|
void LLFolderViewItem::setSelectionFromRoot(LLFolderViewItem* selection,
|
|
BOOL openitem,
|
|
BOOL take_keyboard_focus)
|
|
{
|
|
getRoot()->setSelection(selection, openitem, take_keyboard_focus);
|
|
}
|
|
|
|
// helper function to change the selection from the root.
|
|
void LLFolderViewItem::changeSelectionFromRoot(LLFolderViewItem* selection, BOOL selected)
|
|
{
|
|
getRoot()->changeSelection(selection, selected);
|
|
}
|
|
|
|
void LLFolderViewItem::extendSelectionFromRoot(LLFolderViewItem* selection)
|
|
{
|
|
LLDynamicArray<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)
|
|
{
|
|
mIsSelected = selected;
|
|
if(mListener)
|
|
{
|
|
mListener->selectItem();
|
|
}
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
void LLFolderViewItem::recursiveDeselect(BOOL deselect_self)
|
|
{
|
|
if (mIsSelected && deselect_self)
|
|
{
|
|
mIsSelected = FALSE;
|
|
|
|
// update ancestors' count of selected descendents
|
|
LLFolderViewFolder* parent_folder = getParentFolder();
|
|
while(parent_folder)
|
|
{
|
|
parent_folder->mNumDescendantsSelected--;
|
|
parent_folder = parent_folder->getParentFolder();
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLFolderViewItem::deselectItem(void)
|
|
{
|
|
llassert(mIsSelected);
|
|
|
|
mIsSelected = FALSE;
|
|
|
|
// Update ancestors' count of selected descendents.
|
|
LLFolderViewFolder* parent_folder = getParentFolder();
|
|
if (parent_folder)
|
|
{
|
|
parent_folder->recursiveIncrementNumDescendantsSelected(-1);
|
|
}
|
|
}
|
|
|
|
void LLFolderViewItem::selectItem(void)
|
|
{
|
|
llassert(!mIsSelected);
|
|
|
|
mIsSelected = TRUE;
|
|
|
|
// Update ancestors' count of selected descendents.
|
|
LLFolderViewFolder* parent_folder = getParentFolder();
|
|
if (parent_folder)
|
|
{
|
|
parent_folder->recursiveIncrementNumDescendantsSelected(1);
|
|
}
|
|
}
|
|
|
|
BOOL LLFolderViewItem::isMovable()
|
|
{
|
|
if( mListener )
|
|
{
|
|
return mListener->isItemMovable();
|
|
}
|
|
else
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
BOOL LLFolderViewItem::isRemovable()
|
|
{
|
|
if( mListener )
|
|
{
|
|
return mListener->isItemRemovable();
|
|
}
|
|
else
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
void LLFolderViewItem::destroyView()
|
|
{
|
|
if (mParentFolder)
|
|
{
|
|
// removeView deletes me
|
|
mParentFolder->removeView(this);
|
|
}
|
|
}
|
|
|
|
// Call through to the viewed object and return true if it can be
|
|
// removed.
|
|
//BOOL LLFolderViewItem::removeRecursively(BOOL single_item)
|
|
BOOL LLFolderViewItem::remove()
|
|
{
|
|
if(!isRemovable())
|
|
{
|
|
return FALSE;
|
|
}
|
|
if(mListener)
|
|
{
|
|
return mListener->removeItem();
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
// Build an appropriate context menu for the item.
|
|
void LLFolderViewItem::buildContextMenu(LLMenuGL& menu, U32 flags)
|
|
{
|
|
if(mListener)
|
|
{
|
|
mListener->buildContextMenu(menu, flags);
|
|
}
|
|
}
|
|
|
|
void LLFolderViewItem::openItem( void )
|
|
{
|
|
if( mListener )
|
|
{
|
|
mListener->openItem();
|
|
}
|
|
}
|
|
|
|
void LLFolderViewItem::preview( void )
|
|
{
|
|
if (mListener)
|
|
{
|
|
mListener->previewItem();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void LLFolderViewItem::rename(const std::string& new_name)
|
|
{
|
|
if( !new_name.empty() )
|
|
{
|
|
mLabel = new_name;
|
|
if( mListener )
|
|
{
|
|
mListener->renameItem(new_name);
|
|
|
|
if(mParentFolder)
|
|
{
|
|
mParentFolder->resort(this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const std::string& LLFolderViewItem::getSearchableLabel() const
|
|
{
|
|
U32 type = mRoot->getFilter()->getSearchType();
|
|
switch(type)
|
|
{
|
|
case 1:
|
|
return mSearchableLabelCreator;
|
|
case 2:
|
|
return mSearchableLabelDesc;
|
|
case 3:
|
|
return mSearchableLabelAll;
|
|
default:
|
|
return mSearchableLabel;
|
|
}
|
|
}
|
|
|
|
const std::string& LLFolderViewItem::getName( void ) const
|
|
{
|
|
if(mListener)
|
|
{
|
|
return mListener->getName();
|
|
}
|
|
return mLabel;
|
|
}
|
|
|
|
// LLView functionality
|
|
BOOL LLFolderViewItem::handleRightMouseDown( S32 x, S32 y, MASK mask )
|
|
{
|
|
if(!mIsSelected)
|
|
{
|
|
setSelectionFromRoot(this, FALSE);
|
|
}
|
|
make_ui_sound("UISndClick");
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL LLFolderViewItem::handleMouseDown( S32 x, S32 y, MASK mask )
|
|
{
|
|
// No handler needed for focus lost since this class has no
|
|
// state that depends on it.
|
|
gFocusMgr.setMouseCapture( this );
|
|
|
|
if (!mIsSelected)
|
|
{
|
|
if(mask & MASK_CONTROL)
|
|
{
|
|
changeSelectionFromRoot(this, !mIsSelected);
|
|
}
|
|
else if (mask & MASK_SHIFT)
|
|
{
|
|
extendSelectionFromRoot(this);
|
|
}
|
|
else
|
|
{
|
|
setSelectionFromRoot(this, FALSE);
|
|
}
|
|
make_ui_sound("UISndClick");
|
|
}
|
|
else
|
|
{
|
|
mSelectPending = TRUE;
|
|
}
|
|
|
|
if( isMovable() )
|
|
{
|
|
S32 screen_x;
|
|
S32 screen_y;
|
|
localPointToScreen(x, y, &screen_x, &screen_y );
|
|
LLToolDragAndDrop::getInstance()->setDragStart( screen_x, screen_y );
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL LLFolderViewItem::handleHover( S32 x, S32 y, MASK mask )
|
|
{
|
|
if( hasMouseCapture() && isMovable() )
|
|
{
|
|
S32 screen_x;
|
|
S32 screen_y;
|
|
localPointToScreen(x, y, &screen_x, &screen_y );
|
|
BOOL can_drag = TRUE;
|
|
if( LLToolDragAndDrop::getInstance()->isOverThreshold( screen_x, screen_y ) )
|
|
{
|
|
LLFolderView* root = getRoot();
|
|
|
|
if(root->getCurSelectedItem())
|
|
{
|
|
LLToolDragAndDrop::ESource src = LLToolDragAndDrop::SOURCE_WORLD;
|
|
|
|
// *TODO: push this into listener and remove
|
|
// dependency on llagent
|
|
if(mListener && gInventory.isObjectDescendentOf(mListener->getUUID(), gAgent.getInventoryRootID()))
|
|
{
|
|
src = LLToolDragAndDrop::SOURCE_AGENT;
|
|
}
|
|
else if (mListener && gInventory.isObjectDescendentOf(mListener->getUUID(), gInventoryLibraryRoot))
|
|
{
|
|
src = LLToolDragAndDrop::SOURCE_LIBRARY;
|
|
}
|
|
// <edit>
|
|
else if(mListener && gInventory.isObjectDescendentOf(mListener->getUUID(), gLocalInventoryRoot))
|
|
{ // Note: this is only ok if all future pretend folders are subcategories of Pretend Inventory
|
|
src = LLToolDragAndDrop::SOURCE_LIBRARY;
|
|
}
|
|
// </edit>
|
|
|
|
can_drag = root->startDrag(src);
|
|
if (can_drag)
|
|
{
|
|
// if (mListener) mListener->startDrag();
|
|
// RN: when starting drag and drop, clear out last auto-open
|
|
root->autoOpenTest(NULL);
|
|
root->setShowSelectionContext(TRUE);
|
|
|
|
// Release keyboard focus, so that if stuff is dropped into the
|
|
// world, pressing the delete key won't blow away the inventory
|
|
// item.
|
|
gFocusMgr.setKeyboardFocus(NULL);
|
|
|
|
return LLToolDragAndDrop::getInstance()->handleHover( x, y, mask );
|
|
}
|
|
}
|
|
}
|
|
|
|
if (can_drag)
|
|
{
|
|
gViewerWindow->setCursor(UI_CURSOR_ARROW);
|
|
}
|
|
else
|
|
{
|
|
gViewerWindow->setCursor(UI_CURSOR_NOLOCKED);
|
|
}
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
getRoot()->setShowSelectionContext(FALSE);
|
|
gViewerWindow->setCursor(UI_CURSOR_ARROW);
|
|
// let parent handle this then...
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
BOOL LLFolderViewItem::handleDoubleClick( S32 x, S32 y, MASK mask )
|
|
{
|
|
preview();
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL LLFolderViewItem::handleScrollWheel(S32 x, S32 y, S32 clicks)
|
|
{
|
|
if (getParent())
|
|
{
|
|
return getParent()->handleScrollWheel(x, y, clicks);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL LLFolderViewItem::handleMouseUp( S32 x, S32 y, MASK mask )
|
|
{
|
|
// if mouse hasn't moved since mouse down...
|
|
if ( pointInView(x, y) && mSelectPending )
|
|
{
|
|
//...then select
|
|
if(mask & MASK_CONTROL)
|
|
{
|
|
changeSelectionFromRoot(this, !mIsSelected);
|
|
}
|
|
else if (mask & MASK_SHIFT)
|
|
{
|
|
extendSelectionFromRoot(this);
|
|
}
|
|
else
|
|
{
|
|
setSelectionFromRoot(this, FALSE);
|
|
}
|
|
}
|
|
|
|
mSelectPending = FALSE;
|
|
|
|
if( hasMouseCapture() )
|
|
{
|
|
getRoot()->setShowSelectionContext(FALSE);
|
|
gFocusMgr.setMouseCapture( NULL );
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL LLFolderViewItem::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop,
|
|
EDragAndDropType cargo_type,
|
|
void* cargo_data,
|
|
EAcceptance* accept,
|
|
std::string& tooltip_msg)
|
|
{
|
|
BOOL accepted = FALSE;
|
|
BOOL handled = FALSE;
|
|
if(mListener)
|
|
{
|
|
accepted = mListener->dragOrDrop(mask,drop,cargo_type,cargo_data);
|
|
handled = accepted;
|
|
if (accepted)
|
|
{
|
|
mDragAndDropTarget = TRUE;
|
|
*accept = ACCEPT_YES_MULTI;
|
|
}
|
|
else
|
|
{
|
|
*accept = ACCEPT_NO;
|
|
}
|
|
}
|
|
if(mParentFolder && !handled)
|
|
{
|
|
handled = mParentFolder->handleDragAndDropFromChild(mask,drop,cargo_type,cargo_data,accept,tooltip_msg);
|
|
}
|
|
if (handled)
|
|
{
|
|
lldebugst(LLERR_USER_INPUT) << "dragAndDrop handled by LLFolderViewItem" << llendl;
|
|
}
|
|
|
|
return handled;
|
|
}
|
|
|
|
|
|
void LLFolderViewItem::draw()
|
|
{
|
|
bool possibly_has_children = false;
|
|
bool up_to_date = mListener && mListener->isUpToDate();
|
|
if((up_to_date && hasVisibleChildren() ) || // we fetched our children and some of them have passed the filter...
|
|
(!up_to_date && mListener && mListener->hasChildren())) // ...or we know we have children but haven't fetched them (doesn't obey filter)
|
|
{
|
|
possibly_has_children = true;
|
|
}
|
|
if(/*mControlLabel[0] != '\0' && */possibly_has_children)
|
|
{
|
|
if (sArrowImage)
|
|
{
|
|
gl_draw_scaled_rotated_image(mIndentation, getRect().getHeight() - ARROW_SIZE - TEXT_PAD,
|
|
ARROW_SIZE, ARROW_SIZE, mControlLabelRotation, sArrowImage->getImage(), sFgColor);
|
|
}
|
|
}
|
|
|
|
F32 text_left = (F32)(ARROW_SIZE + TEXT_PAD + ICON_WIDTH + ICON_PAD + mIndentation);
|
|
|
|
// If we have keyboard focus, draw selection filled
|
|
BOOL show_context = getRoot()->getShowSelectionContext();
|
|
BOOL filled = show_context || (gFocusMgr.getKeyboardFocus() == getRoot());
|
|
|
|
// always render "current" item, only render other selected items if
|
|
// mShowSingleSelection is FALSE
|
|
if( mIsSelected )
|
|
{
|
|
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
LLColor4 bg_color = sHighlightBgColor;
|
|
//const S32 TRAILING_PAD = 5; // It just looks better with this.
|
|
if (!mIsCurSelection)
|
|
{
|
|
// do time-based fade of extra objects
|
|
F32 fade_time = getRoot()->getSelectionFadeElapsedTime();
|
|
if (getRoot()->getShowSingleSelection())
|
|
{
|
|
// fading out
|
|
bg_color.mV[VALPHA] = clamp_rescale(fade_time, 0.f, 0.4f, bg_color.mV[VALPHA], 0.f);
|
|
}
|
|
else
|
|
{
|
|
// fading in
|
|
bg_color.mV[VALPHA] = clamp_rescale(fade_time, 0.f, 0.4f, 0.f, bg_color.mV[VALPHA]);
|
|
}
|
|
}
|
|
|
|
gl_rect_2d(
|
|
0,
|
|
getRect().getHeight(),
|
|
getRect().getWidth() - 2,
|
|
llfloor(getRect().getHeight() - sFont->getLineHeight() - ICON_PAD),
|
|
bg_color, filled);
|
|
if (mIsCurSelection)
|
|
{
|
|
gl_rect_2d(
|
|
0,
|
|
getRect().getHeight(),
|
|
getRect().getWidth() - 2,
|
|
llfloor(getRect().getHeight() - sFont->getLineHeight() - ICON_PAD),
|
|
sHighlightFgColor, FALSE);
|
|
}
|
|
if (getRect().getHeight() > llround(sFont->getLineHeight()) + ICON_PAD + 2)
|
|
{
|
|
gl_rect_2d(
|
|
0,
|
|
llfloor(getRect().getHeight() - sFont->getLineHeight() - ICON_PAD) - 2,
|
|
getRect().getWidth() - 2,
|
|
2,
|
|
sHighlightFgColor, FALSE);
|
|
if (show_context)
|
|
{
|
|
gl_rect_2d(
|
|
0,
|
|
llfloor(getRect().getHeight() - sFont->getLineHeight() - ICON_PAD) - 2,
|
|
getRect().getWidth() - 2,
|
|
2,
|
|
sHighlightBgColor, TRUE);
|
|
}
|
|
}
|
|
}
|
|
if (mDragAndDropTarget)
|
|
{
|
|
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
gl_rect_2d(
|
|
0,
|
|
getRect().getHeight(),
|
|
getRect().getWidth() - 2,
|
|
llfloor(getRect().getHeight() - sFont->getLineHeight() - ICON_PAD),
|
|
sHighlightBgColor, FALSE);
|
|
|
|
if (getRect().getHeight() > llround(sFont->getLineHeight()) + ICON_PAD + 2)
|
|
{
|
|
gl_rect_2d(
|
|
0,
|
|
llfloor(getRect().getHeight() - sFont->getLineHeight() - ICON_PAD) - 2,
|
|
getRect().getWidth() - 2,
|
|
2,
|
|
sHighlightBgColor, FALSE);
|
|
}
|
|
mDragAndDropTarget = FALSE;
|
|
}
|
|
|
|
|
|
if(mIcon)
|
|
{
|
|
mIcon->draw(mIndentation + ARROW_SIZE + TEXT_PAD, getRect().getHeight() - mIcon->getHeight());
|
|
}
|
|
|
|
if (!mLabel.empty())
|
|
{
|
|
// highlight filtered text
|
|
BOOL debug_filters = getRoot()->getDebugFilters();
|
|
LLColor4 color = ( (mIsSelected && filled) ? sHighlightFgColor : sFgColor );
|
|
F32 right_x;
|
|
F32 y = (F32)getRect().getHeight() - sFont->getLineHeight() - (F32)TEXT_PAD;
|
|
|
|
if (debug_filters)
|
|
{
|
|
if (!getFiltered() && !possibly_has_children)
|
|
{
|
|
color.mV[VALPHA] *= 0.5f;
|
|
}
|
|
|
|
LLColor4 filter_color = mLastFilterGeneration >= getRoot()->getFilter()->getCurrentGeneration() ? LLColor4(0.5f, 0.8f, 0.5f, 1.f) : LLColor4(0.8f, 0.5f, 0.5f, 1.f);
|
|
sSmallFont->renderUTF8(mStatusText, 0, text_left, y, filter_color,
|
|
LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL,
|
|
S32_MAX, S32_MAX, &right_x, FALSE );
|
|
text_left = right_x;
|
|
}
|
|
|
|
|
|
if ( mIsLoading && mTimeSinceRequestStart.getElapsedTimeF32() >= gSavedSettings.getF32("FolderLoadingMessageWaitTime") )
|
|
{
|
|
// *TODO: Translate
|
|
sFont->renderUTF8( std::string("Loading... "), 0, text_left, y, sSearchStatusColor,
|
|
LLFontGL::LEFT, LLFontGL::BOTTOM, mLabelStyle, S32_MAX, S32_MAX, &right_x, FALSE);
|
|
text_left = right_x;
|
|
}
|
|
|
|
sFont->renderUTF8( mLabel, 0, text_left, y, color,
|
|
LLFontGL::LEFT, LLFontGL::BOTTOM, mLabelStyle,
|
|
S32_MAX, S32_MAX, &right_x, FALSE );
|
|
if (!mLabelSuffix.empty())
|
|
{
|
|
sFont->renderUTF8( mLabelSuffix, 0, right_x, y, sSuffixColor,
|
|
LLFontGL::LEFT, LLFontGL::BOTTOM, mLabelStyle,
|
|
S32_MAX, S32_MAX, &right_x, FALSE );
|
|
}
|
|
|
|
if (sBoxImage.notNull() && mStringMatchOffset != std::string::npos)
|
|
{
|
|
// don't draw backgrounds for zero-length strings
|
|
S32 filter_string_length = mRoot->getFilterSubString().size();
|
|
std::string combined_string = mLabel + mLabelSuffix;
|
|
|
|
//fix so that highlighting works properly again - rkeast
|
|
std::string check = combined_string;
|
|
LLStringUtil::toUpper(check);
|
|
|
|
if ((filter_string_length > 0) && (check.find(mRoot->getFilterSubString()) != -1))
|
|
{
|
|
// llinfos << "mLabel " << mLabel<< " mLabelSuffix " << mLabelSuffix << " mLabel " << mLabel << " mLabel " << mLabel << llendl;
|
|
S32 left = llround(text_left) + sFont->getWidth(combined_string, 0, mStringMatchOffset) - 1;
|
|
S32 right = left + sFont->getWidth(combined_string, mStringMatchOffset, filter_string_length) + 2;
|
|
S32 bottom = llfloor(getRect().getHeight() - sFont->getLineHeight() - 3);
|
|
S32 top = getRect().getHeight();
|
|
|
|
LLRect box_rect(left, top, right, bottom);
|
|
sBoxImage->draw(box_rect, sFilterBGColor);
|
|
F32 match_string_left = text_left + sFont->getWidthF32(combined_string, 0, mStringMatchOffset);
|
|
F32 y = (F32)getRect().getHeight() - sFont->getLineHeight() - (F32)TEXT_PAD;
|
|
sFont->renderUTF8( combined_string, mStringMatchOffset, match_string_left, y,
|
|
sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, mLabelStyle,
|
|
filter_string_length, S32_MAX, &right_x, FALSE );
|
|
}
|
|
}
|
|
}
|
|
|
|
if( sDebugRects )
|
|
{
|
|
drawDebugRect();
|
|
}
|
|
}
|
|
|
|
|
|
///----------------------------------------------------------------------------
|
|
/// Class LLFolderViewFolder
|
|
///----------------------------------------------------------------------------
|
|
|
|
// Default constructor
|
|
LLFolderViewFolder::LLFolderViewFolder( const std::string& name, LLUIImagePtr icon,
|
|
LLFolderView* root,
|
|
LLFolderViewEventListener* listener ):
|
|
LLFolderViewItem( name, icon, 0, root, listener ), // 0 = no create time
|
|
mNumDescendantsSelected(0),
|
|
mIsOpen(FALSE),
|
|
mExpanderHighlighted(FALSE),
|
|
mCurHeight(0.f),
|
|
mTargetHeight(0.f),
|
|
mAutoOpenCountdown(0.f),
|
|
mSubtreeCreationDate(0),
|
|
mAmTrash(LLFolderViewFolder::UNKNOWN),
|
|
mLastArrangeGeneration( -1 ),
|
|
mLastCalculatedWidth(0),
|
|
mCompletedFilterGeneration(-1),
|
|
mMostFilteredDescendantGeneration(-1)
|
|
{
|
|
mType = std::string("(folder)");
|
|
}
|
|
|
|
// Destroys the object
|
|
LLFolderViewFolder::~LLFolderViewFolder( void )
|
|
{
|
|
// The LLView base class takes care of object destruction. make sure that we
|
|
// don't have mouse or keyboard focus
|
|
gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit()
|
|
}
|
|
|
|
// addToFolder() returns TRUE if it succeeds. FALSE otherwise
|
|
BOOL LLFolderViewFolder::addToFolder(LLFolderViewFolder* folder, LLFolderView* root)
|
|
{
|
|
if (!folder)
|
|
{
|
|
return FALSE;
|
|
}
|
|
mParentFolder = folder;
|
|
root->addItemID(getListener()->getUUID(), this);
|
|
return folder->addFolder(this);
|
|
}
|
|
|
|
// Finds width and height of this object and it's children. Also
|
|
// makes sure that this view and it's children are the right size.
|
|
S32 LLFolderViewFolder::arrange( S32* width, S32* height, S32 filter_generation)
|
|
{
|
|
mHasVisibleChildren = hasFilteredDescendants(filter_generation);
|
|
|
|
LLInventoryFilter::EFolderShow show_folder_state = getRoot()->getShowFolderState();
|
|
|
|
// calculate height as a single item (without any children), and reshapes rectangle to match
|
|
LLFolderViewItem::arrange( width, height, filter_generation );
|
|
|
|
// clamp existing animated height so as to never get smaller than a single item
|
|
mCurHeight = llmax((F32)*height, mCurHeight);
|
|
|
|
// initialize running height value as height of single item in case we have no children
|
|
*height = getItemHeight();
|
|
F32 running_height = (F32)*height;
|
|
F32 target_height = (F32)*height;
|
|
|
|
// are my children visible?
|
|
if (needsArrange())
|
|
{
|
|
// set last arrange generation first, in case children are animating
|
|
// and need to be arranged again
|
|
mLastArrangeGeneration = mRoot->getArrangeGeneration();
|
|
if (mIsOpen)
|
|
{
|
|
// Add sizes of children
|
|
S32 parent_item_height = getRect().getHeight();
|
|
|
|
for(folders_t::iterator fit = mFolders.begin(); fit != mFolders.end(); ++fit)
|
|
{
|
|
LLFolderViewFolder* folderp = (*fit);
|
|
if (getRoot()->getDebugFilters())
|
|
{
|
|
folderp->setVisible(TRUE);
|
|
}
|
|
else
|
|
{
|
|
folderp->setVisible(show_folder_state == LLInventoryFilter::SHOW_ALL_FOLDERS || // always show folders?
|
|
(folderp->getFiltered(filter_generation) || folderp->hasFilteredDescendants(filter_generation))); // passed filter or has descendants that passed filter
|
|
}
|
|
|
|
if (folderp->getVisible())
|
|
{
|
|
S32 child_width = *width;
|
|
S32 child_height = 0;
|
|
S32 child_top = parent_item_height - llround(running_height);
|
|
|
|
target_height += folderp->arrange( &child_width, &child_height, filter_generation );
|
|
|
|
running_height += (F32)child_height;
|
|
*width = llmax(*width, child_width);
|
|
folderp->setOrigin( 0, child_top - folderp->getRect().getHeight() );
|
|
}
|
|
}
|
|
for(items_t::iterator iit = mItems.begin();
|
|
iit != mItems.end(); ++iit)
|
|
{
|
|
LLFolderViewItem* itemp = (*iit);
|
|
if (getRoot()->getDebugFilters())
|
|
{
|
|
itemp->setVisible(TRUE);
|
|
}
|
|
else
|
|
{
|
|
itemp->setVisible(itemp->getFiltered(filter_generation));
|
|
}
|
|
|
|
if (itemp->getVisible())
|
|
{
|
|
S32 child_width = *width;
|
|
S32 child_height = 0;
|
|
S32 child_top = parent_item_height - llround(running_height);
|
|
|
|
target_height += itemp->arrange( &child_width, &child_height, filter_generation );
|
|
// don't change width, as this item is as wide as its parent folder by construction
|
|
itemp->reshape( itemp->getRect().getWidth(), child_height);
|
|
|
|
running_height += (F32)child_height;
|
|
*width = llmax(*width, child_width);
|
|
itemp->setOrigin( 0, child_top - itemp->getRect().getHeight() );
|
|
}
|
|
}
|
|
}
|
|
|
|
mTargetHeight = target_height;
|
|
// cache this width so next time we can just return it
|
|
mLastCalculatedWidth = *width;
|
|
}
|
|
else
|
|
{
|
|
// just use existing width
|
|
*width = mLastCalculatedWidth;
|
|
}
|
|
|
|
// animate current height towards target height
|
|
if (llabs(mCurHeight - mTargetHeight) > 1.f)
|
|
{
|
|
mCurHeight = lerp(mCurHeight, mTargetHeight, LLCriticalDamp::getInterpolant(mIsOpen ? FOLDER_OPEN_TIME_CONSTANT : FOLDER_CLOSE_TIME_CONSTANT));
|
|
|
|
requestArrange();
|
|
|
|
// hide child elements that fall out of current animated height
|
|
for (folders_t::iterator iter = mFolders.begin();
|
|
iter != mFolders.end();)
|
|
{
|
|
folders_t::iterator fit = iter++;
|
|
// number of pixels that bottom of folder label is from top of parent folder
|
|
if (getRect().getHeight() - (*fit)->getRect().mTop + (*fit)->getItemHeight()
|
|
> llround(mCurHeight) + MAX_FOLDER_ITEM_OVERLAP)
|
|
{
|
|
// hide if beyond current folder height
|
|
(*fit)->setVisible(FALSE);
|
|
}
|
|
}
|
|
|
|
for (items_t::iterator iter = mItems.begin();
|
|
iter != mItems.end();)
|
|
{
|
|
items_t::iterator iit = iter++;
|
|
// number of pixels that bottom of item label is from top of parent folder
|
|
if (getRect().getHeight() - (*iit)->getRect().mBottom
|
|
> llround(mCurHeight) + MAX_FOLDER_ITEM_OVERLAP)
|
|
{
|
|
(*iit)->setVisible(FALSE);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mCurHeight = mTargetHeight;
|
|
}
|
|
|
|
// don't change width as this item is already as wide as its parent folder
|
|
reshape(getRect().getWidth(),llround(mCurHeight));
|
|
|
|
// pass current height value back to parent
|
|
*height = llround(mCurHeight);
|
|
|
|
return llround(mTargetHeight);
|
|
}
|
|
|
|
BOOL LLFolderViewFolder::needsArrange()
|
|
{
|
|
return mLastArrangeGeneration < mRoot->getArrangeGeneration();
|
|
}
|
|
|
|
void LLFolderViewFolder::setCompletedFilterGeneration(S32 generation, BOOL recurse_up)
|
|
{
|
|
mMostFilteredDescendantGeneration = llmin(mMostFilteredDescendantGeneration, generation);
|
|
mCompletedFilterGeneration = generation;
|
|
// only aggregate up if we are a lower (older) value
|
|
if (recurse_up && mParentFolder && generation < mParentFolder->getCompletedFilterGeneration())
|
|
{
|
|
mParentFolder->setCompletedFilterGeneration(generation, TRUE);
|
|
}
|
|
}
|
|
|
|
void LLFolderViewFolder::filter( LLInventoryFilter& filter)
|
|
{
|
|
S32 filter_generation = filter.getCurrentGeneration();
|
|
// if failed to pass filter newer than must_pass_generation
|
|
// you will automatically fail this time, so we only
|
|
// check against items that have passed the filter
|
|
S32 must_pass_generation = filter.getMustPassGeneration();
|
|
|
|
// if we have already been filtered against this generation, skip out
|
|
if (getCompletedFilterGeneration() >= filter_generation)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// filter folder itself
|
|
if (getLastFilterGeneration() < filter_generation)
|
|
{
|
|
if (getLastFilterGeneration() >= must_pass_generation && // folder has been compared to a valid precursor filter
|
|
!mFiltered) // and did not pass the filter
|
|
{
|
|
// go ahead and flag this folder as done
|
|
mLastFilterGeneration = filter_generation;
|
|
}
|
|
else
|
|
{
|
|
// filter self only on first pass through
|
|
LLFolderViewItem::filter( filter );
|
|
}
|
|
}
|
|
|
|
if (getRoot()->getDebugFilters())
|
|
{
|
|
mStatusText = llformat("%d", mLastFilterGeneration);
|
|
mStatusText += llformat("(%d)", mCompletedFilterGeneration);
|
|
mStatusText += llformat("+%d", mMostFilteredDescendantGeneration);
|
|
}
|
|
|
|
// all descendants have been filtered later than must pass generation
|
|
// but none passed
|
|
if(getCompletedFilterGeneration() >= must_pass_generation && !hasFilteredDescendants(must_pass_generation))
|
|
{
|
|
// don't traverse children if we've already filtered them since must_pass_generation
|
|
// and came back with nothing
|
|
return;
|
|
}
|
|
|
|
// we entered here with at least one filter iteration left
|
|
// check to see if we have any more before continuing on to children
|
|
if (filter.getFilterCount() < 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// when applying a filter, matching folders get their contents downloaded first
|
|
if (filter.isNotDefault() && getFiltered(filter.getMinRequiredGeneration()) && (mListener && !gInventory.isCategoryComplete(mListener->getUUID())))
|
|
{
|
|
gInventory.startBackgroundFetch(mListener->getUUID());
|
|
}
|
|
|
|
// now query children
|
|
for (folders_t::iterator iter = mFolders.begin();
|
|
iter != mFolders.end();)
|
|
{
|
|
folders_t::iterator fit = iter++;
|
|
// have we run out of iterations this frame?
|
|
if (filter.getFilterCount() < 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// mMostFilteredDescendantGeneration might have been reset
|
|
// in which case we need to update it even for folders that
|
|
// don't need to be filtered anymore
|
|
if ((*fit)->getCompletedFilterGeneration() >= filter_generation)
|
|
{
|
|
// track latest generation to pass any child items
|
|
if ((*fit)->getFiltered() || (*fit)->hasFilteredDescendants(filter.getMinRequiredGeneration()))
|
|
{
|
|
mMostFilteredDescendantGeneration = filter_generation;
|
|
if (mRoot->needsAutoSelect())
|
|
{
|
|
(*fit)->setOpenArrangeRecursively(TRUE);
|
|
}
|
|
}
|
|
// just skip it, it has already been filtered
|
|
continue;
|
|
}
|
|
|
|
// update this folders filter status (and children)
|
|
(*fit)->filter( filter );
|
|
|
|
// track latest generation to pass any child items
|
|
if ((*fit)->getFiltered() || (*fit)->hasFilteredDescendants(filter_generation))
|
|
{
|
|
mMostFilteredDescendantGeneration = filter_generation;
|
|
if (mRoot->needsAutoSelect())
|
|
{
|
|
(*fit)->setOpenArrangeRecursively(TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (items_t::iterator iter = mItems.begin();
|
|
iter != mItems.end();)
|
|
{
|
|
items_t::iterator iit = iter++;
|
|
if (filter.getFilterCount() < 0)
|
|
{
|
|
break;
|
|
}
|
|
if ((*iit)->getLastFilterGeneration() >= filter_generation)
|
|
{
|
|
if ((*iit)->getFiltered())
|
|
{
|
|
mMostFilteredDescendantGeneration = filter_generation;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if ((*iit)->getLastFilterGeneration() >= must_pass_generation &&
|
|
!(*iit)->getFiltered(must_pass_generation))
|
|
{
|
|
// failed to pass an earlier filter that was a subset of the current one
|
|
// go ahead and flag this item as done
|
|
(*iit)->setFiltered(FALSE, filter_generation);
|
|
continue;
|
|
}
|
|
|
|
(*iit)->filter( filter );
|
|
|
|
if ((*iit)->getFiltered(filter.getMinRequiredGeneration()))
|
|
{
|
|
mMostFilteredDescendantGeneration = filter_generation;
|
|
}
|
|
}
|
|
|
|
// if we didn't use all filter iterations
|
|
// that means we filtered all of our descendants
|
|
// instead of exhausting the filter count for this frame
|
|
if (filter.getFilterCount() > 0)
|
|
{
|
|
// flag this folder as having completed filter pass for all descendants
|
|
setCompletedFilterGeneration(filter_generation, FALSE/*dont recurse up to root*/);
|
|
}
|
|
}
|
|
|
|
void LLFolderViewFolder::setFiltered(BOOL filtered, S32 filter_generation)
|
|
{
|
|
// if this folder is now filtered, but wasn't before
|
|
// (it just passed)
|
|
if (filtered && !mFiltered)
|
|
{
|
|
// reset current height, because last time we drew it
|
|
// it might have had more visible items than now
|
|
mCurHeight = 0.f;
|
|
}
|
|
|
|
LLFolderViewItem::setFiltered(filtered, filter_generation);
|
|
}
|
|
|
|
void LLFolderViewFolder::dirtyFilter()
|
|
{
|
|
// we're a folder, so invalidate our completed generation
|
|
setCompletedFilterGeneration(-1, FALSE);
|
|
LLFolderViewItem::dirtyFilter();
|
|
}
|
|
|
|
BOOL LLFolderViewFolder::hasFilteredDescendants()
|
|
{
|
|
return mMostFilteredDescendantGeneration >= mRoot->getFilter()->getCurrentGeneration();
|
|
}
|
|
|
|
void LLFolderViewFolder::recursiveIncrementNumDescendantsSelected(S32 increment)
|
|
{
|
|
LLFolderViewFolder* parent_folder = this;
|
|
do
|
|
{
|
|
parent_folder->mNumDescendantsSelected += increment;
|
|
|
|
// Make sure we don't have negative values.
|
|
llassert(parent_folder->mNumDescendantsSelected >= 0);
|
|
|
|
parent_folder = parent_folder->getParentFolder();
|
|
}
|
|
while(parent_folder);
|
|
}
|
|
|
|
// Passes selection information on to children and record selection
|
|
// information if necessary.
|
|
BOOL LLFolderViewFolder::setSelection(LLFolderViewItem* selection, BOOL openitem,
|
|
BOOL take_keyboard_focus)
|
|
{
|
|
BOOL rv = FALSE;
|
|
if( selection == this )
|
|
{
|
|
mIsSelected = TRUE;
|
|
if(mListener)
|
|
{
|
|
mListener->selectItem();
|
|
}
|
|
rv = TRUE;
|
|
}
|
|
else
|
|
{
|
|
mIsSelected = FALSE;
|
|
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;
|
|
mNumDescendantsSelected++;
|
|
}
|
|
}
|
|
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;
|
|
mNumDescendantsSelected++;
|
|
}
|
|
}
|
|
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)
|
|
{
|
|
mIsSelected = selected;
|
|
if(mListener && selected)
|
|
{
|
|
mListener->selectItem();
|
|
}
|
|
rv = TRUE;
|
|
}
|
|
|
|
for (folders_t::iterator iter = mFolders.begin();
|
|
iter != mFolders.end();)
|
|
{
|
|
folders_t::iterator fit = iter++;
|
|
if((*fit)->changeSelection(selection, selected))
|
|
{
|
|
if (selected)
|
|
{
|
|
mNumDescendantsSelected++;
|
|
}
|
|
else
|
|
{
|
|
mNumDescendantsSelected--;
|
|
}
|
|
rv = TRUE;
|
|
}
|
|
}
|
|
for (items_t::iterator iter = mItems.begin();
|
|
iter != mItems.end();)
|
|
{
|
|
items_t::iterator iit = iter++;
|
|
if((*iit)->changeSelection(selection, selected))
|
|
{
|
|
if (selected)
|
|
{
|
|
mNumDescendantsSelected++;
|
|
}
|
|
else
|
|
{
|
|
mNumDescendantsSelected--;
|
|
}
|
|
rv = TRUE;
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
S32 LLFolderViewFolder::extendSelection(LLFolderViewItem* selection, LLFolderViewItem* last_selected, LLDynamicArray<LLFolderViewItem*>& selected_items)
|
|
{
|
|
S32 num_selected = 0;
|
|
|
|
// pass on to child folders first
|
|
for (folders_t::iterator iter = mFolders.begin();
|
|
iter != mFolders.end();)
|
|
{
|
|
folders_t::iterator fit = iter++;
|
|
num_selected += (*fit)->extendSelection(selection, last_selected, selected_items);
|
|
mNumDescendantsSelected += num_selected;
|
|
}
|
|
|
|
// 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);
|
|
mNumDescendantsSelected++;
|
|
num_selected++;
|
|
}
|
|
}
|
|
}
|
|
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);
|
|
mNumDescendantsSelected++;
|
|
num_selected++;
|
|
}
|
|
}
|
|
|
|
return num_selected;
|
|
}
|
|
|
|
void LLFolderViewFolder::recursiveDeselect(BOOL deselect_self)
|
|
{
|
|
// make sure we don't have negative values
|
|
llassert(mNumDescendantsSelected >= 0);
|
|
|
|
if (mIsSelected && deselect_self)
|
|
{
|
|
mIsSelected = FALSE;
|
|
|
|
// update ancestors' count of selected descendents
|
|
LLFolderViewFolder* parent_folder = getParentFolder();
|
|
while(parent_folder)
|
|
{
|
|
parent_folder->mNumDescendantsSelected--;
|
|
parent_folder = parent_folder->getParentFolder();
|
|
}
|
|
}
|
|
|
|
if (0 == mNumDescendantsSelected)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (items_t::iterator iter = mItems.begin();
|
|
iter != mItems.end();)
|
|
{
|
|
items_t::iterator iit = iter++;
|
|
LLFolderViewItem* item = (*iit);
|
|
item->recursiveDeselect(TRUE);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
S32 LLFolderView::extendSelection(LLFolderViewItem* selection, LLFolderViewItem* last_selected, LLDynamicArray<LLFolderViewItem*>& items)
|
|
{
|
|
S32 rv = 0;
|
|
|
|
// now store resulting selection
|
|
if (mAllowMultiSelect)
|
|
{
|
|
LLFolderViewItem *cur_selection = getCurSelectedItem();
|
|
rv = LLFolderViewFolder::extendSelection(selection, cur_selection, items);
|
|
for (S32 i = 0; i < items.count(); i++)
|
|
{
|
|
addToSelectionList(items[i]);
|
|
rv++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
setSelection(selection, FALSE, FALSE);
|
|
rv++;
|
|
}
|
|
|
|
mSignalSelectCallback = SIGNAL_KEYBOARD_FOCUS;
|
|
return rv;
|
|
}
|
|
|
|
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 (mask == MASK_NONE)
|
|
{
|
|
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 || !selected_itemp->getFiltered()) && !mAutoSelectOverride)
|
|
{
|
|
// select first filtered item
|
|
LLSelectFirstFilteredItem filter;
|
|
applyFunctorRecursively(filter);
|
|
}
|
|
scrollToShowSelection();
|
|
}
|
|
|
|
BOOL is_visible = isInVisibleChain();
|
|
|
|
if ( is_visible )
|
|
{
|
|
sanitizeSelection();
|
|
if( needsArrange() )
|
|
{
|
|
arrangeFromRoot();
|
|
}
|
|
}
|
|
|
|
if (mSelectedItems.size() && mNeedsScroll)
|
|
{
|
|
scrollToShowItem(mSelectedItems.back());
|
|
// continue scrolling until animated layout change is done
|
|
if (getCompletedFilterGeneration() >= mFilter.getMinRequiredGeneration() &&
|
|
(!needsArrange() || !is_visible))
|
|
{
|
|
mNeedsScroll = FALSE;
|
|
}
|
|
}
|
|
|
|
if (mSignalSelectCallback && mSelectCallback)
|
|
{
|
|
//RN: we use keyboard focus as a proxy for user-explicit actions
|
|
BOOL take_keyboard_focus = (mSignalSelectCallback == SIGNAL_KEYBOARD_FOCUS);
|
|
mSelectCallback(mSelectedItems, take_keyboard_focus, mUserData);
|
|
}
|
|
mSignalSelectCallback = FALSE;
|
|
}
|
|
|
|
|
|
//static
|
|
void LLFolderView::idle(void* user_data)
|
|
{
|
|
LLFolderView* self = (LLFolderView*)user_data;
|
|
if ( self )
|
|
{ // Do the real idle
|
|
self->doIdle();
|
|
}
|
|
}
|
|
|
|
|
|
void LLFolderView::dumpSelectionInformation()
|
|
{
|
|
llinfos << "LLFolderView::dumpSelectionInformation()" << llendl;
|
|
llinfos << "****************************************" << llendl;
|
|
selected_items_t::iterator item_it;
|
|
for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it)
|
|
{
|
|
llinfos << " " << (*item_it)->getName() << llendl;
|
|
}
|
|
llinfos << "****************************************" << llendl;
|
|
}
|
|
|
|
///----------------------------------------------------------------------------
|
|
/// Local function definitions
|
|
///----------------------------------------------------------------------------
|
|
bool LLInventorySort::updateSort(U32 order)
|
|
{
|
|
if (order != mSortOrder)
|
|
{
|
|
mSortOrder = order;
|
|
mByDate = (order & LLInventoryFilter::SO_DATE);
|
|
mSystemToTop = (order & LLInventoryFilter::SO_SYSTEM_FOLDERS_TO_TOP);
|
|
mFoldersByName = (order & LLInventoryFilter::SO_FOLDERS_BY_NAME);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool LLInventorySort::operator()(const LLFolderViewItem* const& a, const LLFolderViewItem* const& b)
|
|
{
|
|
// We sort by name if we aren't sorting by date
|
|
// OR if these are folders and we are sorting folders by name.
|
|
bool by_name = (!mByDate
|
|
|| (mFoldersByName
|
|
&& (a->getSortGroup() != SG_ITEM)));
|
|
|
|
if (a->getSortGroup() != b->getSortGroup())
|
|
{
|
|
if (mSystemToTop)
|
|
{
|
|
// Group order is System Folders, Trash, Normal Folders, Items
|
|
return (a->getSortGroup() < b->getSortGroup());
|
|
}
|
|
else if (mByDate)
|
|
{
|
|
// Trash needs to go to the bottom if we are sorting by date
|
|
if ( (a->getSortGroup() == SG_TRASH_FOLDER)
|
|
|| (b->getSortGroup() == SG_TRASH_FOLDER))
|
|
{
|
|
return (b->getSortGroup() == SG_TRASH_FOLDER);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (by_name)
|
|
{
|
|
S32 compare = LLStringUtil::compareDict(a->getLabel(), b->getLabel());
|
|
if (0 == compare)
|
|
{
|
|
return (a->getCreationDate() > b->getCreationDate());
|
|
}
|
|
else
|
|
{
|
|
return (compare < 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// BUG: This is very very slow. The getCreationDate() is log n in number
|
|
// of inventory items.
|
|
time_t first_create = a->getCreationDate();
|
|
time_t second_create = b->getCreationDate();
|
|
if (first_create == second_create)
|
|
{
|
|
return (LLStringUtil::compareDict(a->getLabel(), b->getLabel()) < 0);
|
|
}
|
|
else
|
|
{
|
|
return (first_create > second_create);
|
|
}
|
|
}
|
|
}
|
|
|
|
//static
|
|
void LLFolderView::onRenamerLost( LLUICtrl* renamer, void* user_data)
|
|
{
|
|
renamer->setVisible(FALSE);
|
|
}
|
|
|
|
void delete_selected_item(void* user_data)
|
|
{
|
|
if(user_data)
|
|
{
|
|
LLFolderView* fv = reinterpret_cast<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)
|
|
{
|
|
//fix to get rid of gSavedSettings use - rkeast
|
|
mSearchType = 0;
|
|
mPartialSearch = false;
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
//When searching for all labels, we need to explode the filter string
|
|
//Into an array, and then compare each string to the label seperately
|
|
//Otherwise the filter substring needs to be
|
|
//formatted in the same order as the label - rkeast
|
|
|
|
BOOL passed;
|
|
//Added ability to toggle this type of searching for all labels cause it's convienient - RKeast
|
|
if(mSearchType == 3 || mPartialSearch)
|
|
{
|
|
std::istringstream i(mFilterSubString);
|
|
std::string blah;
|
|
|
|
LLDynamicArray<std::string> search_array;
|
|
|
|
while(i >> blah)
|
|
{
|
|
search_array.put(blah);
|
|
}
|
|
|
|
BOOL subStringMatch = true;
|
|
for(int i = 0; i < search_array.getLength(); i++)
|
|
{
|
|
mSubStringMatchOffset = (search_array.get(i)).size() ? item->getSearchableLabel().find(search_array.get(i)) : std::string::npos;
|
|
subStringMatch = subStringMatch && ((search_array.get(i)).size() == 0 || mSubStringMatchOffset != std::string::npos);
|
|
}
|
|
|
|
passed = (0x1 << listener->getInventoryType() & mFilterOps.mFilterTypes || listener->getInventoryType() == LLInventoryType::IT_NONE)
|
|
&& (subStringMatch)
|
|
&& (mFilterWorn == false || gAgent.isWearingItem(item_id) ||
|
|
(gAgent.getAvatarObject() && gAgent.getAvatarObject()->isWearingAttachment(item_id)))
|
|
&& ((listener->getPermissionMask() & mFilterOps.mPermissions) == mFilterOps.mPermissions)
|
|
&& (listener->getCreationDate() >= earliest && listener->getCreationDate() <= mFilterOps.mMaxDate);
|
|
}
|
|
else
|
|
{
|
|
mSubStringMatchOffset = mFilterSubString.size() ? item->getSearchableLabel().find(mFilterSubString) : std::string::npos;
|
|
passed = (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;
|
|
}
|
|
|
|
//fix to get rid of gSavedSettings use - rkeast
|
|
void LLInventoryFilter::setPartialSearch(bool toggle)
|
|
{
|
|
|
|
mPartialSearch = toggle;
|
|
}
|
|
|
|
//fix to get rid of gSavedSettings use - rkeast
|
|
bool LLInventoryFilter::getPartialSearch()
|
|
{
|
|
return mPartialSearch;
|
|
}
|
|
|
|
//fix to get rid of gSavedSettings use - rkeast
|
|
void LLInventoryFilter::setSearchType(U32 type)
|
|
{
|
|
mSearchType = type;
|
|
}
|
|
|
|
//fix to get rid of gSavedSettings use - rkeast
|
|
U32 LLInventoryFilter::getSearchType()
|
|
{
|
|
return mSearchType;
|
|
}
|
|
|
|
void LLInventoryFilter::setFilterTypes(U32 types)
|
|
{
|
|
if (mFilterOps.mFilterTypes != types)
|
|
{
|
|
// keep current items only if no type bits getting turned off
|
|
BOOL fewer_bits_set = (mFilterOps.mFilterTypes & ~types);
|
|
BOOL more_bits_set = (~mFilterOps.mFilterTypes & types);
|
|
|
|
mFilterOps.mFilterTypes = types;
|
|
if (more_bits_set && fewer_bits_set)
|
|
{
|
|
// neither less or more restrive, both simultaneously
|
|
// so we need to filter from scratch
|
|
setModified(FILTER_RESTART);
|
|
}
|
|
else if (more_bits_set)
|
|
{
|
|
// target is only one of all requested types so more type bits == less restrictive
|
|
setModified(FILTER_LESS_RESTRICTIVE);
|
|
}
|
|
else if (fewer_bits_set)
|
|
{
|
|
setModified(FILTER_MORE_RESTRICTIVE);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void LLInventoryFilter::setFilterSubString(const std::string& string)
|
|
{
|
|
if (mFilterSubString != string)
|
|
{
|
|
// hitting BACKSPACE, for example
|
|
BOOL less_restrictive = mFilterSubString.size() >= string.size() && !mFilterSubString.substr(0, string.size()).compare(string);
|
|
// appending new characters
|
|
BOOL more_restrictive = mFilterSubString.size() < string.size() && !string.substr(0, mFilterSubString.size()).compare(mFilterSubString);
|
|
mFilterSubString = string;
|
|
LLStringUtil::toUpper(mFilterSubString);
|
|
LLStringUtil::trimHead(mFilterSubString);
|
|
|
|
if (less_restrictive)
|
|
{
|
|
setModified(FILTER_LESS_RESTRICTIVE);
|
|
}
|
|
else if (more_restrictive)
|
|
{
|
|
setModified(FILTER_MORE_RESTRICTIVE);
|
|
}
|
|
else
|
|
{
|
|
setModified(FILTER_RESTART);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLInventoryFilter::setFilterPermissions(PermissionMask perms)
|
|
{
|
|
if (mFilterOps.mPermissions != perms)
|
|
{
|
|
// keep current items only if no perm bits getting turned off
|
|
BOOL fewer_bits_set = (mFilterOps.mPermissions & ~perms);
|
|
BOOL more_bits_set = (~mFilterOps.mPermissions & perms);
|
|
mFilterOps.mPermissions = perms;
|
|
|
|
if (more_bits_set && fewer_bits_set)
|
|
{
|
|
setModified(FILTER_RESTART);
|
|
}
|
|
else if (more_bits_set)
|
|
{
|
|
// target must have all requested permission bits, so more bits == more restrictive
|
|
setModified(FILTER_MORE_RESTRICTIVE);
|
|
}
|
|
else if (fewer_bits_set)
|
|
{
|
|
setModified(FILTER_LESS_RESTRICTIVE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLInventoryFilter::setDateRange(time_t min_date, time_t max_date)
|
|
{
|
|
mFilterOps.mHoursAgo = 0;
|
|
if (mFilterOps.mMinDate != min_date)
|
|
{
|
|
mFilterOps.mMinDate = min_date;
|
|
setModified();
|
|
}
|
|
if (mFilterOps.mMaxDate != llmax(mFilterOps.mMinDate, max_date))
|
|
{
|
|
mFilterOps.mMaxDate = llmax(mFilterOps.mMinDate, max_date);
|
|
setModified();
|
|
}
|
|
}
|
|
|
|
void LLInventoryFilter::setDateRangeLastLogoff(BOOL sl)
|
|
{
|
|
if (sl && !isSinceLogoff())
|
|
{
|
|
setDateRange(mLastLogoff, time_max());
|
|
setModified();
|
|
}
|
|
if (!sl && isSinceLogoff())
|
|
{
|
|
setDateRange(0, time_max());
|
|
setModified();
|
|
}
|
|
}
|
|
|
|
BOOL LLInventoryFilter::isSinceLogoff()
|
|
{
|
|
return (mFilterOps.mMinDate == mLastLogoff) &&
|
|
(mFilterOps.mMaxDate == time_max());
|
|
}
|
|
|
|
void LLInventoryFilter::setHoursAgo(U32 hours)
|
|
{
|
|
if (mFilterOps.mHoursAgo != hours)
|
|
{
|
|
// *NOTE: need to cache last filter time, in case filter goes stale
|
|
BOOL less_restrictive = (mFilterOps.mMinDate == time_min() && mFilterOps.mMaxDate == time_max() && hours > mFilterOps.mHoursAgo);
|
|
BOOL more_restrictive = (mFilterOps.mMinDate == time_min() && mFilterOps.mMaxDate == time_max() && hours <= mFilterOps.mHoursAgo);
|
|
mFilterOps.mHoursAgo = hours;
|
|
mFilterOps.mMinDate = time_min();
|
|
mFilterOps.mMaxDate = time_max();
|
|
if (less_restrictive)
|
|
{
|
|
setModified(FILTER_LESS_RESTRICTIVE);
|
|
}
|
|
else if (more_restrictive)
|
|
{
|
|
setModified(FILTER_MORE_RESTRICTIVE);
|
|
}
|
|
else
|
|
{
|
|
setModified(FILTER_RESTART);
|
|
}
|
|
}
|
|
}
|
|
void LLInventoryFilter::setShowFolderState(EFolderShow state)
|
|
{
|
|
if (mFilterOps.mShowFolderState != state)
|
|
{
|
|
mFilterOps.mShowFolderState = state;
|
|
if (state == SHOW_NON_EMPTY_FOLDERS)
|
|
{
|
|
// showing fewer folders than before
|
|
setModified(FILTER_MORE_RESTRICTIVE);
|
|
}
|
|
else if (state == SHOW_ALL_FOLDERS)
|
|
{
|
|
// showing same folders as before and then some
|
|
setModified(FILTER_LESS_RESTRICTIVE);
|
|
}
|
|
else
|
|
{
|
|
setModified();
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLInventoryFilter::setSortOrder(U32 order)
|
|
{
|
|
if (mOrder != order)
|
|
{
|
|
mOrder = order;
|
|
setModified();
|
|
}
|
|
}
|
|
|
|
void LLInventoryFilter::markDefault()
|
|
{
|
|
mDefaultFilterOps = mFilterOps;
|
|
}
|
|
|
|
void LLInventoryFilter::resetDefault()
|
|
{
|
|
mFilterOps = mDefaultFilterOps;
|
|
setModified();
|
|
}
|
|
|
|
void LLInventoryFilter::setModified(EFilterBehavior behavior)
|
|
{
|
|
mModified = TRUE;
|
|
mNeedTextRebuild = TRUE;
|
|
mFilterGeneration = mNextFilterGeneration++;
|
|
|
|
if (mFilterBehavior == FILTER_NONE)
|
|
{
|
|
mFilterBehavior = behavior;
|
|
}
|
|
else if (mFilterBehavior != behavior)
|
|
{
|
|
// trying to do both less restrictive and more restrictive filter
|
|
// basically means restart from scratch
|
|
mFilterBehavior = FILTER_RESTART;
|
|
}
|
|
|
|
if (isNotDefault())
|
|
{
|
|
// if not keeping current filter results, update last valid as well
|
|
switch(mFilterBehavior)
|
|
{
|
|
case FILTER_RESTART:
|
|
mMustPassGeneration = mFilterGeneration;
|
|
mMinRequiredGeneration = mFilterGeneration;
|
|
break;
|
|
case FILTER_LESS_RESTRICTIVE:
|
|
mMustPassGeneration = mFilterGeneration;
|
|
break;
|
|
case FILTER_MORE_RESTRICTIVE:
|
|
mMinRequiredGeneration = mFilterGeneration;
|
|
// must have passed either current filter generation (meaningless, as it hasn't been run yet)
|
|
// or some older generation, so keep the value
|
|
mMustPassGeneration = llmin(mMustPassGeneration, mFilterGeneration);
|
|
break;
|
|
default:
|
|
llerrs << "Bad filter behavior specified" << llendl;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// shortcut disabled filters to show everything immediately
|
|
mMinRequiredGeneration = 0;
|
|
mMustPassGeneration = S32_MAX;
|
|
}
|
|
}
|
|
|
|
BOOL LLInventoryFilter::isFilterWith(LLInventoryType::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());
|
|
}
|
|
}
|