phoenix-firestorm/indra/newview/llfolderview.cpp

4875 lines
118 KiB
C++

/**
* @file llfolderview.cpp
* @brief Implementation of the folder view collection of classes.
*
* Copyright (c) 2001-$CurrentYear$, Linden Research, Inc.
* $License$
*/
#include "llviewerprecompiledheaders.h"
#include "llfolderview.h"
#include <algorithm>
#include "llviewercontrol.h"
#include "lldbstrings.h"
#include "llfocusmgr.h"
#include "llfontgl.h"
#include "llgl.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 "llvieweruictrlfactory.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 "viewer.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;
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 top_view_lost( LLView* handler );
///----------------------------------------------------------------------------
/// 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;
// Default constructor
LLFolderViewItem::LLFolderViewItem( const LLString& name, LLViewerImage* 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 ),
mHasVisibleChildren(FALSE),
mIndentation(0),
mNumDescendantsSelected(0),
mFiltered(FALSE),
mLastFilterGeneration(-1),
mStringMatchOffset(LLString::npos),
mControlLabelRotation(0.f),
mRoot( root ),
mDragAndDropTarget(FALSE)
{
setIcon(icon);
if( !LLFolderViewItem::sFont )
{
LLFolderViewItem::sFont = gResMgr->getRes( LLFONT_SANSSERIF_SMALL );
}
if (!LLFolderViewItem::sSmallFont)
{
LLFolderViewItem::sSmallFont = gResMgr->getRes( LLFONT_SMALL );
}
// HACK: Can't be set above because gSavedSettings might not be constructed.
LLFolderViewItem::sFgColor = gColors.getColor( "MenuItemEnabledColor" );
LLFolderViewItem::sHighlightBgColor = gColors.getColor( "MenuItemHighlightBgColor" );
LLFolderViewItem::sHighlightFgColor = gColors.getColor( "MenuItemHighlightFgColor" );
LLFolderViewItem::sFilterBGColor = gColors.getColor( "FilterBackgroundColor" );
LLFolderViewItem::sFilterTextColor = gColors.getColor( "FilterTextColor" );
mArrowImage = gImageList.getImage(LLUUID(gViewerArt.getString("folder_arrow.tga")), MIPMAP_FALSE, TRUE);
mBoxImage = gImageList.getImage(LLUUID(gViewerArt.getString("rounded_square.tga")), MIPMAP_FALSE, TRUE);
refresh();
setTabStop(FALSE);
}
// Destroys the object
LLFolderViewItem::~LLFolderViewItem( void )
{
delete mListener;
mListener = NULL;
mArrowImage = NULL;
mBoxImage = 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;
}
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(LLViewerImage* icon)
{
mIcon = icon;
if (mIcon)
{
mIcon->setBoostLevel(LLViewerImage::BOOST_UI);
}
}
// refresh information from the listener
void LLFolderViewItem::refresh()
{
if(mListener)
{
const char* label = mListener->getDisplayName().c_str();
mLabel = label ? label : "";
setIcon(mListener->getIcon());
U32 creation_date = mListener->getCreationDate();
if (mCreationDate != creation_date)
{
mCreationDate = mListener->getCreationDate();
dirtyFilter();
}
mLabelStyle = mListener->getLabelStyle();
mLabelSuffix = mListener->getLabelSuffix();
LLString searchable_label(mLabel);
searchable_label.append(mLabelSuffix);
LLString::toUpper(searchable_label);
if (mSearchableLabel.compare(searchable_label))
{
mSearchableLabel.assign(searchable_label);
dirtyFilter();
// some part of label has changed, so overall width has potentially changed
if (mParentFolder)
{
mParentFolder->requestArrange();
}
}
S32 label_width = sFont->getWidth(mLabel);
if( mLabelSuffix.size() )
{
label_width += sFont->getWidth( mLabelSuffix );
}
mLabelWidth = ARROW_SIZE + TEXT_PAD + ICON_WIDTH + ICON_PAD + label_width;
}
}
void LLFolderViewItem::applyListenerFunctorRecursively(LLFolderViewListenerFunctor& functor)
{
functor(mListener);
}
// This function is called when items are added or view filters change. It's
// implemented here but called by derived classes when folding the
// views.
void LLFolderViewItem::filterFromRoot( void )
{
LLFolderViewItem* root = getRoot();
root->filter(*((LLFolderView*)root)->getFilter());
}
// This function is called when the folder view is dirty. It's
// implemented here but called by derived classes when folding the
// views.
void LLFolderViewItem::arrangeFromRoot()
{
LLFolderViewItem* root = getRoot();
S32 height = 0;
S32 width = 0;
root->arrange( &width, &height, 0 );
}
// This function clears the currently selected item, and records the
// specified selected item appropriately for display and use in the
// UI. If open is TRUE, then folders are opened up along the way to
// the selection.
void LLFolderViewItem::setSelectionFromRoot(LLFolderViewItem* selection,
BOOL open, /* Flawfinder: ignore */
BOOL take_keyboard_focus)
{
getRoot()->setSelection(selection, open, take_keyboard_focus); /* Flawfinder: ignore */
}
// 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);
}
EWidgetType LLFolderViewItem::getWidgetType() const
{
return WIDGET_TYPE_FOLDER_ITEM;
}
LLString LLFolderViewItem::getWidgetTag() const
{
return LL_FOLDER_VIEW_ITEM_TAG;
}
// 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 open,
BOOL take_keyboard_focus)
{
if( selection == this )
{
mIsSelected = TRUE;
if(mListener)
{
mListener->selectItem();
}
}
else
{
mIsSelected = FALSE;
}
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();
}
}
}
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::open( void ) /* Flawfinder: ignore */
{
if( mListener )
{
mListener->openItem();
}
}
void LLFolderViewItem::preview( void )
{
if (mListener)
{
mListener->previewItem();
}
}
void LLFolderViewItem::rename(const LLString& new_name)
{
if( !new_name.empty() )
{
mLabel = new_name.c_str();
BOOL is_renamed = TRUE;
if( mListener )
{
is_renamed = mListener->renameItem(new_name);
}
if(mParentFolder && is_renamed)
{
mParentFolder->resort(this);
}
//refresh();
}
}
const LLString& LLFolderViewItem::getSearchableLabel() const
{
return mSearchableLabel;
}
const LLString& LLFolderViewItem::getName( void ) const
{
if(mListener)
{
return mListener->getName();
}
return mLabel;
}
LLFolderViewFolder* LLFolderViewItem::getParentFolder( void )
{
return mParentFolder;
}
LLFolderViewEventListener* LLFolderViewItem::getListener( void )
{
return mListener;
}
// 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.
gViewerWindow->setMouseCapture( this, NULL );
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 );
gToolDragAndDrop->setDragStart( screen_x, screen_y );
}
return TRUE;
}
BOOL LLFolderViewItem::handleHover( S32 x, S32 y, MASK mask )
{
if( gViewerWindow->hasMouseCapture( this ) && isMovable() )
{
S32 screen_x;
S32 screen_y;
localPointToScreen(x, y, &screen_x, &screen_y );
BOOL can_drag = TRUE;
if( gToolDragAndDrop->isOverThreshold( screen_x, screen_y ) )
{
LLFolderView* root = getRoot();
if(root->getCurSelectedItem())
{
LLToolDragAndDrop::ESource src = LLToolDragAndDrop::SOURCE_WORLD;
// *TODO: push this into listener and remove
// dependency on llagent
if(mListener && gInventory.isObjectDescendentOf(mListener->getUUID(), gAgent.getInventoryRootID()))
{
src = LLToolDragAndDrop::SOURCE_AGENT;
}
else if (mListener && gInventory.isObjectDescendentOf(mListener->getUUID(), gInventoryLibraryRoot))
{
src = LLToolDragAndDrop::SOURCE_LIBRARY;
}
can_drag = root->startDrag(src);
if (can_drag)
{
// if (mListener) mListener->startDrag();
// RN: when starting drag and drop, clear out last auto-open
root->autoOpenTest(NULL);
root->setShowSelectionContext(TRUE);
// Release keyboard focus, so that if stuff is dropped into the
// world, pressing the delete key won't blow away the inventory
// item.
gViewerWindow->setKeyboardFocus(NULL, NULL);
return gToolDragAndDrop->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( gViewerWindow->hasMouseCapture( this ) )
{
getRoot()->setShowSelectionContext(FALSE);
gViewerWindow->setMouseCapture( NULL, NULL );
}
return TRUE;
}
BOOL LLFolderViewItem::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop,
EDragAndDropType cargo_type,
void* cargo_data,
EAcceptance* accept,
LLString& 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()
{
if( getVisible() )
{
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)
{
LLGLSTexture gls_texture;
if (mArrowImage)
{
gl_draw_scaled_rotated_image(mIndentation, mRect.getHeight() - ARROW_SIZE - TEXT_PAD,
ARROW_SIZE, ARROW_SIZE, mControlLabelRotation, mArrowImage, 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 )
{
LLGLSNoTexture gls_no_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,
mRect.getHeight(),
mRect.getWidth() - 2,
llfloor(mRect.getHeight() - sFont->getLineHeight() - ICON_PAD),
bg_color, filled);
if (mIsCurSelection)
{
gl_rect_2d(
0,
mRect.getHeight(),
mRect.getWidth() - 2,
llfloor(mRect.getHeight() - sFont->getLineHeight() - ICON_PAD),
sHighlightFgColor, FALSE);
}
if (mRect.getHeight() > llround(sFont->getLineHeight()) + ICON_PAD + 2)
{
gl_rect_2d(
0,
llfloor(mRect.getHeight() - sFont->getLineHeight() - ICON_PAD) - 2,
mRect.getWidth() - 2,
2,
sHighlightFgColor, FALSE);
if (show_context)
{
gl_rect_2d(
0,
llfloor(mRect.getHeight() - sFont->getLineHeight() - ICON_PAD) - 2,
mRect.getWidth() - 2,
2,
sHighlightBgColor, TRUE);
}
}
}
if (mDragAndDropTarget)
{
LLGLSNoTexture gls_no_texture;
gl_rect_2d(
0,
mRect.getHeight(),
mRect.getWidth() - 2,
llfloor(mRect.getHeight() - sFont->getLineHeight() - ICON_PAD),
sHighlightBgColor, FALSE);
if (mRect.getHeight() > llround(sFont->getLineHeight()) + ICON_PAD + 2)
{
gl_rect_2d(
0,
llfloor(mRect.getHeight() - sFont->getLineHeight() - ICON_PAD) - 2,
mRect.getWidth() - 2,
2,
sHighlightBgColor, FALSE);
}
mDragAndDropTarget = FALSE;
}
if(mIcon)
{
gl_draw_image(mIndentation + ARROW_SIZE + TEXT_PAD, mRect.getHeight() - mIcon->getHeight(), mIcon);
mIcon->addTextureStats( (F32)(mIcon->getWidth() * 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)mRect.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;
}
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, LLColor4(0.75f, 0.85f, 0.85f, 1.f),
LLFontGL::LEFT, LLFontGL::BOTTOM, mLabelStyle,
S32_MAX, S32_MAX, &right_x, FALSE );
}
if (mBoxImage.notNull() && mStringMatchOffset != LLString::npos)
{
// don't draw backgrounds for zero-length strings
S32 filter_string_length = mRoot->getFilterSubString().size();
if (filter_string_length > 0)
{
LLString combined_string = mLabel + mLabelSuffix;
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(mRect.getHeight() - sFont->getLineHeight() - 3);
S32 top = mRect.getHeight();
LLViewerImage::bindTexture(mBoxImage);
glColor4fv(sFilterBGColor.mV);
gl_segmented_rect_2d_tex(left, top, right, bottom, mBoxImage->getWidth(), mBoxImage->getHeight(), 16);
F32 match_string_left = text_left + sFont->getWidthF32(combined_string, 0, mStringMatchOffset);
F32 y = (F32)mRect.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();
}
}
else if (mStatusText.size())
{
// just draw status text
sFont->renderUTF8( mStatusText, 0, 0, 1, sFgColor, LLFontGL::LEFT, LLFontGL::BOTTOM, mLabelStyle, S32_MAX, S32_MAX, NULL, FALSE );
}
}
///----------------------------------------------------------------------------
/// Class LLFolderViewFolder
///----------------------------------------------------------------------------
// Default constructor
LLFolderViewFolder::LLFolderViewFolder( const LLString& name, LLViewerImage* icon,
LLFolderView* root,
LLFolderViewEventListener* listener ):
LLFolderViewItem( name, icon, 0, root, listener ), // 0 = no create time
mSortFunction(sort_item_name),
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 = "(folder)";
//mItems.setInsertBefore( &sort_item_name );
//mFolders.setInsertBefore( &folder_insert_before );
}
// 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()
//mItems.reset();
//mItems.removeAllNodes();
//mFolders.removeAllNodes();
}
EWidgetType LLFolderViewFolder::getWidgetType() const
{
return WIDGET_TYPE_FOLDER;
}
LLString LLFolderViewFolder::getWidgetTag() const
{
return LL_FOLDER_VIEW_FOLDER_TAG;
}
// 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 = mRect.getHeight();
folders_t::iterator fit = mFolders.begin();
folders_t::iterator fend = mFolders.end();
for(; fit < fend; ++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() );
}
}
items_t::iterator iit = mItems.begin();
items_t::iterator iend = mItems.end();
for(;iit < iend; ++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 (mRect.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 (mRect.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(mRect.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 (getRoot()->isFilterActive() && getFiltered(filter.getMinRequiredGeneration()) && !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();
}
// Passes selection information on to children and record selection
// information if necessary.
BOOL LLFolderViewFolder::setSelection(LLFolderViewItem* selection, BOOL open, /* Flawfinder: ignore */
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, open, take_keyboard_focus)) /* Flawfinder: ignore */
{
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, open, take_keyboard_focus)) /* Flawfinder: ignore */
{
rv = TRUE;
child_selected = TRUE;
mNumDescendantsSelected++;
}
}
if(open && child_selected) /* Flawfinder: ignore */
{
setOpenArrangeRecursively(TRUE);
}
return rv;
}
// This method is used to change the selection of an item. If
// selection is 'this', then note selection as true. Returns TRUE
// if this or a child is now selected.
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();
}
mFolders.clear();
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())
{
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)
{
return;
}
// deselect without traversing hierarchy
item->recursiveDeselect(TRUE);
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 = reinterpret_cast<LLFolderViewFolder*>(item);
folders_t::iterator ft;
ft = std::find(mFolders.begin(), mFolders.end(), f);
if(ft != mFolders.end())
{
mFolders.erase(ft);
}
}
else
{
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)
{
std::sort(mItems.begin(), mItems.end(), *mSortFunction);
std::sort(mFolders.begin(), mFolders.end(), *mSortFunction);
//if(mItems.removeData(item))
//{
// mItems.addDataSorted(item);
//}
//else
//{
// // 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 = reinterpret_cast<LLFolderViewFolder*>(item);
// if(mFolders.removeData(f))
// {
// mFolders.addDataSorted(f);
// }
//}
}
bool LLFolderViewFolder::isTrash()
{
if (mAmTrash == LLFolderViewFolder::UNKNOWN)
{
mAmTrash = mListener->getUUID() == gInventory.findCategoryUUIDForType(LLAssetType::AT_TRASH) ? LLFolderViewFolder::TRASH : LLFolderViewFolder::NOT_TRASH;
}
return mAmTrash == LLFolderViewFolder::TRASH;
}
void LLFolderViewFolder::sortBy(U32 order)
{
BOOL sort_order_changed = FALSE;
if (!(order & LLInventoryFilter::SO_DATE))
{
if (mSortFunction != sort_item_name)
{
mSortFunction = sort_item_name;
sort_order_changed = TRUE;
}
}
else
{
if (mSortFunction != sort_item_date)
{
mSortFunction = sort_item_date;
sort_order_changed = TRUE;
}
}
for (folders_t::iterator iter = mFolders.begin();
iter != mFolders.end();)
{
folders_t::iterator fit = iter++;
(*fit)->sortBy(order);
}
if (order & LLInventoryFilter::SO_FOLDERS_BY_NAME)
{
// sort folders by name if always by name
std::sort(mFolders.begin(), mFolders.end(), sort_item_name);
}
else
{
// sort folders by the default sort ordering
std::sort(mFolders.begin(), mFolders.end(), *mSortFunction);
// however, if we are at the root of the inventory and we are sorting by date
if (mListener->getUUID() == gAgent.getInventoryRootID() && order & LLInventoryFilter::SO_DATE)
{
// pull the trash folder and stick it on the end of the list
LLFolderViewFolder *t = NULL;
for (folders_t::iterator fit = mFolders.begin();
fit != mFolders.end(); ++fit)
{
if ((*fit)->isTrash())
{
t = *fit;
mFolders.erase(fit);
break;
}
}
if (t)
{
mFolders.push_back(t);
}
}
}
if (sort_order_changed)
{
std::sort(mItems.begin(), mItems.end(), *mSortFunction);
}
if (order & LLInventoryFilter::SO_DATE)
{
U32 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::setItemSortFunction(sort_order_f ordering)
{
mSortFunction = ordering;
for (folders_t::iterator iter = mFolders.begin();
iter != mFolders.end();)
{
folders_t::iterator fit = iter++;
(*fit)->setItemSortFunction(ordering);
}
std::sort(mFolders.begin(), mFolders.end(), *mSortFunction);
std::sort(mItems.begin(), mItems.end(), *mSortFunction);
}
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);
item->setRect(LLRect(0, 0, mRect.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);
folder->setOrigin(0, 0);
folder->reshape(mRect.getWidth(), 0);
folder->setVisible(FALSE);
addChild( folder );
folder->dirtyFilter();
requestArrange();
return TRUE;
}
void LLFolderViewFolder::requestArrange()
{
mLastArrangeGeneration = -1;
// flag all items up to root
if (mParentFolder)
{
mParentFolder->requestArrange();
}
}
void LLFolderViewFolder::toggleOpen()
{
setOpen(!mIsOpen);
}
// Force a folder open or closed
void LLFolderViewFolder::setOpen(BOOL open) /* Flawfinder: ignore */
{
setOpenArrangeRecursively(open); /* Flawfinder: ignore */
}
void LLFolderViewFolder::setOpenArrangeRecursively(BOOL open, ERecurseType recurse) /* Flawfinder: ignore */
{
BOOL was_open = mIsOpen;
mIsOpen = open; /* Flawfinder: ignore */
if(!was_open && open) /* Flawfinder: ignore */
{
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(open, RECURSE_DOWN); /* Flawfinder: ignore */
}
}
if (mParentFolder && (recurse == RECURSE_UP || recurse == RECURSE_UP_DOWN))
{
mParentFolder->setOpenArrangeRecursively(open, RECURSE_UP); /* Flawfinder: ignore */
}
if (was_open != mIsOpen)
{
requestArrange();
}
}
BOOL LLFolderViewFolder::handleDragAndDropFromChild(MASK mask,
BOOL drop,
EDragAndDropType c_type,
void* cargo_data,
EAcceptance* accept,
LLString& 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::open( void ) /* Flawfinder: ignore */
{
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,
LLString& 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;
if( getVisible() )
{
// 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 > mRect.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 )
{
if (!getVisible())
{
return FALSE;
}
BOOL rv = false;
if( mIsOpen )
{
rv = childrenHandleDoubleClick( x, y, mask ) != NULL;
}
if( !rv )
{
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();
}
return TRUE;
}
return rv;
}
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));
}
LLFolderViewItem::draw();
if( mIsOpen )
{
LLView::draw();
}
// if (mExpanderHighlighted)
// {
// gl_rect_2d(mIndentation - TEXT_PAD, llfloor(mRect.getHeight() - TEXT_PAD), mIndentation + sFont->getWidth(mControlLabel) + TEXT_PAD, llfloor(mRect.getHeight() - sFont->getLineHeight() - TEXT_PAD), sFgColor, FALSE);
// //sFont->renderUTF8( mControlLabel, 0, mIndentation, llfloor(mRect.getHeight() - sFont->getLineHeight() - TEXT_PAD), sFgColor, LLFontGL::LEFT, LLFontGL::BOTTOM, mLabelStyle, S32_MAX, S32_MAX, NULL, FALSE );
// }
mExpanderHighlighted = FALSE;
}
U32 LLFolderViewFolder::getCreationDate() const
{
return llmax<U32>(mCreationDate, mSubtreeCreationDate);
}
// 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(sort_order_f ordering)
: mSortFunction(ordering) {}
virtual ~LLSetItemSortFunction() {}
virtual void doFolder(LLFolderViewFolder* folder);
virtual void doItem(LLFolderViewItem* item);
sort_order_f mSortFunction;
};
// Set the sort order.
void LLSetItemSortFunction::doFolder(LLFolderViewFolder* folder)
{
folder->setItemSortFunction(mSortFunction);
}
// 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 LLString& name, LLViewerImage* 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( LLViewHandle::sDeadHandle ),
mAllowMultiSelect(TRUE),
mShowFolderHierarchy(FALSE),
mSourceID(source_id),
mRenameItem( NULL ),
mNeedsScroll( FALSE ),
mLastScrollItem( NULL ),
mNeedsAutoSelect( FALSE ),
mAutoSelectOverride(FALSE),
mDebugFilters(FALSE),
mSortOrder(LLInventoryFilter::SO_FOLDERS_BY_NAME), // This gets overridden by a pref immediately
mFilter(name),
mShowSelectionContext(FALSE),
mShowSingleSelection(FALSE),
mArrangeGeneration(0),
mSelectCallback(NULL),
mMinWidth(0),
mDragAndDropThisFrame(FALSE)
{
LLRect new_rect(rect.mLeft, rect.mBottom + mRect.getHeight(), rect.mLeft + mRect.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 = LLString::null;
mRenamer = new LLLineEditor("ren", mRect, "", sFont,
DB_INV_ITEM_NAME_STR_LEN,
&LLFolderView::commitRename,
NULL,
NULL,
this,
&LLLineEditor::prevalidatePrintableNotPipe,
LLViewBorder::BEVEL_NONE,
LLViewBorder::STYLE_LINE,
2);
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 = gUICtrlFactory->buildMenu("menu_inventory.xml", parent_view);
if (!menu)
{
menu = new LLMenuGL("");
}
menu->setBackgroundColor(gColors.getColor("MenuPopupBgColor"));
menu->setVisible(FALSE);
mPopupMenuHandle = menu->mViewHandle;
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(gViewerWindow->hasTopView(mRenamer))
{
gViewerWindow->setTopView(NULL, NULL);
}
mAutoOpenItems.removeAllNodes();
clearSelection();
mItems.clear();
mFolders.clear();
mItemMap.clear();
}
EWidgetType LLFolderView::getWidgetType() const
{
return WIDGET_TYPE_FOLDER_VIEW;
}
LLString LLFolderView::getWidgetTag() const
{
return LL_FOLDER_VIEW_TAG;
}
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);
}
folder->setOrigin(0, 0);
folder->reshape(mRect.getWidth(), 0);
folder->setVisible(FALSE);
addChild( folder );
folder->dirtyFilter();
return TRUE;
}
void LLFolderView::closeAllFolders()
{
// Close all the folders
setOpenArrangeRecursively(FALSE, LLFolderViewFolder::RECURSE_DOWN);
}
void LLFolderView::openFolder(const LLString& foldername)
{
LLFolderViewFolder* inv = (LLFolderViewFolder*)getChildByName(foldername);
if (inv)
{
setSelection(inv, FALSE, FALSE);
inv->setOpen(TRUE);
}
}
void LLFolderView::setOpenArrangeRecursively(BOOL open, ERecurseType recurse) /* Flawfinder: ignore */
{
// call base class to do proper recursion
LLFolderViewFolder::setOpenArrangeRecursively(open, recurse); /* Flawfinder: ignore */
// 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 = mRect.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 LLString 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 open, /* Flawfinder: ignore */
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, open, take_keyboard_focus); /* Flawfinder: ignore */
if(open) /* Flawfinder: ignore */
{
selection->getParentFolder()->requestArrange();
}
llassert(mSelectedItems.size() <= 1);
mSelectionChanged = TRUE;
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 (on_list && mSelectedItems.size() == 1)
{
// we are trying to select/deselect the only selected item
return FALSE;
}
if(selected && !on_list)
{
mNumDescendantsSelected++;
addToSelectionList(selection);
}
if(!selected && on_list)
{
mNumDescendantsSelected--;
removeFromSelectionList(selection);
}
rv = LLFolderViewFolder::changeSelection(selection, selected);
mSelectionChanged = TRUE;
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++;
}
mSelectionChanged = TRUE;
return rv;
}
void LLFolderView::sanitizeSelection()
{
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;
BOOL visible = item->getVisible();
LLFolderViewFolder* parent_folder = item->getParentFolder();
while(visible && parent_folder)
{
visible = visible && parent_folder->isOpen() && parent_folder->getVisible();
parent_folder = parent_folder->getParentFolder();
}
if (!visible || item->getNumSelectedDescendants() > 0)
{
// only deselect self if not visible
// check to see if item failed the filter but was checked against most recent generation
if ((!item->getFiltered() && item->getLastFilterGeneration() >= getFilter()->getMinRequiredGeneration())
|| (item->getParentFolder() && !item->getParentFolder()->isOpen()))
{
item->recursiveDeselect(TRUE);
items_to_remove.push_back(item);
}
else
{
item->recursiveDeselect(FALSE);
}
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;
LLFolderViewFolder* parent_folder = other_item->getParentFolder();
while (parent_folder)
{
if (parent_folder == item)
{
// this is a descendent of the current folder, remove from list
items_to_remove.push_back(other_item);
break;
}
parent_folder = parent_folder->getParentFolder();
}
}
}
}
std::vector<LLFolderViewItem*>::iterator item_it;
for (item_it = items_to_remove.begin(); item_it != items_to_remove.end(); ++item_it )
{
removeFromSelectionList(*item_it);
}
}
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);
}
gToolDragAndDrop->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)
{
LLString 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,
mRect.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(gViewerWindow->hasKeyboardFocus(this) && !getVisible())
{
gViewerWindow->setKeyboardFocus( NULL, NULL );
}
// while dragging, update selection rendering to reflect single/multi drag status
if (gToolDragAndDrop->hasMouseCapture())
{
EAcceptance last_accept = gToolDragAndDrop->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)
{
setStatusText("");
}
else
{
if (gInventory.backgroundFetchActive() || mCompletedFilterGeneration < mFilter.getMinRequiredGeneration())
{
setStatusText("Searching...");
sFont->renderUTF8(mStatusText, 0, 2, 1, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP, LLFontGL::NORMAL, S32_MAX, S32_MAX, NULL, FALSE );
}
else
{
setStatusText("No matching items found in inventory.");
sFont->renderUTF8(mStatusText, 0, 2, 1, LLColor4::white, 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().c_str() );
}
mRenamer->setCommitOnFocusLost( FALSE );
mRenamer->setFocus( FALSE );
mRenamer->setVisible( FALSE );
mRenamer->setCommitOnFocusLost( TRUE );
gViewerWindow->setTopView( NULL, 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::revertRenamingItem( void )
{
mRenamer->setCommitOnFocusLost( FALSE );
mRenamer->setFocus( FALSE );
mRenamer->setVisible( FALSE );
mRenamer->setCommitOnFocusLost( TRUE );
gViewerWindow->setTopView( NULL, NULL );
if( mRenameItem )
{
setSelectionFromRoot( mRenameItem, TRUE );
mRenameItem = NULL;
}
}
void LLFolderView::removeSelectedItems( void )
{
if(getVisible() && mEnabled)
{
// 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.
LLDynamicArray<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.put(item);
}
else
{
llinfos << "Cannot delete " << item->getName() << llendl;
return;
}
}
// iterate through the new container.
count = items.count();
LLUUID new_selection_id;
if(count == 1)
{
LLFolderViewItem* item_to_delete = items.get(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(), gViewerWindow->childHasKeyboardFocus(this));
}
else
{
setSelectionFromRoot(NULL, gViewerWindow->childHasKeyboardFocus(this));
}
}
}
arrangeAll();
}
else if (count > 1)
{
LLDynamicArray<LLFolderViewEventListener*> listeners;
LLFolderViewEventListener* listener;
LLFolderViewItem* last_item = items.get(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(), gViewerWindow->childHasKeyboardFocus(this));
}
else
{
setSelectionFromRoot(NULL, gViewerWindow->childHasKeyboardFocus(this));
}
for(S32 i = 0; i < count; ++i)
{
listener = items.get(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() && mEnabled)
{
if (mSelectedItems.size() == 1)
{
mSelectedItems.front()->open(); /* Flawfinder: ignore */
}
else
{
S32 left, top;
gFloaterView->getNewFloaterPosition(&left, &top);
LLMultiPreview* multi_previewp = new LLMultiPreview(LLRect(left, top, left + 300, top - 100));
LLFloater::setFloaterHost(multi_previewp);
selected_items_t::iterator item_it;
for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it)
{
(*item_it)->open(); /* Flawfinder: ignore */
}
LLFloater::setFloaterHost(NULL);
multi_previewp->open(); /* Flawfinder: ignore */
}
}
}
void LLFolderView::propertiesSelectedItems( void )
{
if(getVisible() && mEnabled)
{
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()
{
if (!(getVisible() && mEnabled && (mSelectedItems.size() > 0)))
{
return FALSE;
}
selected_items_t::iterator selected_it;
for (selected_it = mSelectedItems.begin(); selected_it != mSelectedItems.end(); ++selected_it)
{
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() && mEnabled && (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()
{
return FALSE;
}
void LLFolderView::cut()
{
// implement Windows-style cut-and-leave
}
BOOL LLFolderView::canPaste()
{
if (mSelectedItems.empty())
{
return FALSE;
}
if(getVisible() && mEnabled)
{
selected_items_t::iterator item_it;
for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it)
{
// *TODO: only check folders and parent folders of items
LLFolderViewItem* item = (*item_it);
LLFolderViewEventListener* listener = item->getListener();
if(!listener || !listener->isClipboardPasteable())
{
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() && mEnabled)
{
// 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() && mEnabled && (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 - mRect.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 );
gViewerWindow->setTopView( mRenamer, top_view_lost );
}
}
void LLFolderView::setFocus(BOOL focus)
{
if (focus)
{
// select "My Inventory" if nothing selected
if (!getCurSelectedItem())
{
LLFolderViewItem* itemp = getItemByID(gAgent.getInventoryRootID());
if (itemp)
{
setSelection(itemp, FALSE, FALSE);
}
}
if (mRenamer->getVisible())
{
//RN: commit rename changes when focus is moved, only revert on ESC
finishRenamingItem();
}
if(!hasFocus())
{
gEditMenuHandler = this;
}
}
LLFolderViewFolder::setFocus(focus);
}
BOOL LLFolderView::handleKeyHere( KEY key, MASK mask, BOOL called_from_parent )
{
BOOL handled = FALSE;
LLView *item = NULL;
if (getChildCount() > 0)
{
item = *(getChildList()->begin());
}
if( getVisible() && mEnabled && !called_from_parent )
{
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:
// mark flag don't commit
if( mRenameItem && mRenamer->getVisible() )
{
revertRenamingItem();
handled = TRUE;
}
else
{
if( gViewerWindow->childHasKeyboardFocus( this ) )
{
gViewerWindow->setKeyboardFocus( NULL, NULL );
}
}
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 && gFocusMgr.childHasKeyboardFocus(getRoot()))
{
if (key == KEY_BACKSPACE)
{
mSearchTimer.reset();
if (mSearchString.size())
{
mSearchString.erase(mSearchString.size() - 1, 1);
}
search(getCurSelectedItem(), mSearchString.c_str(), FALSE);
handled = TRUE;
}
}
return handled;
}
BOOL LLFolderView::handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent)
{
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()))
{
//do text search
if (mSearchTimer.getElapsedTimeF32() > gSavedSettings.getF32("TypeAheadTimeout"))
{
mSearchString.clear();
}
mSearchTimer.reset();
if (mSearchString.size() < 128)
{
mSearchString += uni_char;
}
search(getCurSelectedItem(), mSearchString.c_str(), FALSE);
handled = TRUE;
}
return handled;
}
BOOL LLFolderView::canDoDelete()
{
if (mSelectedItems.size() == 0) return FALSE;
selected_items_t::iterator item_it;
for (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;
}
}
BOOL LLFolderView::search(LLFolderViewItem* first_item, const LLString &search_string, BOOL backward)
{
// get first selected item
LLFolderViewItem* search_item = first_item;
// make sure search string is upper case
LLString upper_case_string = search_string;
LLString::toUpper(upper_case_string);
// if nothing selected, select first item in folder
if (!first_item)
{
// start from first item
first_item = getNextFromChild(NULL);
}
// search over all open nodes for first substring match (with wrapping)
BOOL found = FALSE;
LLFolderViewItem* original_search_item = search_item;
do
{
// wrap at end
if (!search_item)
{
if (backward)
{
search_item = getPreviousFromChild(NULL);
}
else
{
search_item = getNextFromChild(NULL);
}
if (!search_item || search_item == original_search_item)
{
break;
}
}
const LLString 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 )
{
if (!getVisible())
{
return FALSE;
}
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*)LLView::getViewByHandle(mPopupMenuHandle);
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,
LLString& 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(gViewerWindow->hasTopView(mRenamer))
{
gViewerWindow->setTopView(NULL, NULL);
}
LLView::deleteViewByHandle(mPopupMenuHandle);
mPopupMenuHandle = LLViewHandle::sDeadHandle;
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(-mRect.mLeft, visible_height - mRect.mBottom, visible_width, visible_height);
return visible_rect;
}
BOOL LLFolderView::getShowSelectionContext()
{
if (mShowSelectionContext)
{
return TRUE;
}
LLMenuGL* menu = (LLMenuGL*)LLView::getViewByHandle(mPopupMenuHandle);
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;
}
//static
void LLFolderView::idle(void* user_data)
{
LLFastTimer t2(LLFastTimer::FTM_INVENTORY);
LLFolderView* self = (LLFolderView*)user_data;
BOOL debug_filters = gSavedSettings.getBOOL("DebugInventoryFilters");
if (debug_filters != self->getDebugFilters())
{
self->mDebugFilters = debug_filters;
self->arrangeAll();
}
self->mFilter.clearModified();
BOOL filter_modified_and_active = self->mCompletedFilterGeneration < self->mFilter.getCurrentGeneration() &&
self->mFilter.isActive();
self->mNeedsAutoSelect = filter_modified_and_active &&
!(gFocusMgr.childHasKeyboardFocus(self) || gFocusMgr.getMouseCapture());
// filter to determine visiblity before arranging
self->filterFromRoot();
self->sanitizeSelection();
// 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 (self->mNeedsAutoSelect)
{
LLFastTimer t3(LLFastTimer::FTM_AUTO_SELECT);
// select new item only if a filtered item not currently selected
LLFolderViewItem* selected_itemp = self->mSelectedItems.empty() ? NULL : self->mSelectedItems.back();
if ((!selected_itemp || !selected_itemp->getFiltered()) && !self->mAutoSelectOverride)
{
// select first filtered item
LLSelectFirstFilteredItem filter;
self->applyFunctorRecursively(filter);
}
self->scrollToShowSelection();
}
if( self->needsArrange() && self->isInVisibleChain())
{
self->arrangeFromRoot();
}
if (self->mSelectedItems.size() && self->mNeedsScroll)
{
self->scrollToShowItem(self->mSelectedItems.back());
// continue scrolling until animated layout change is done
if (!self->needsArrange() || !self->isInVisibleChain())
{
self->mNeedsScroll = FALSE;
}
}
if (self->mSelectionChanged && self->mSelectCallback)
{
//RN: we use keyboard focus as a proxy for user-explicit actions
self->mSelectCallback(self->mSelectedItems, gFocusMgr.childHasKeyboardFocus(self), self->mUserData);
}
self->mSelectionChanged = FALSE;
}
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 sort_item_name(LLFolderViewItem* a, LLFolderViewItem* b)
{
S32 compare = LLString::compareDict(a->getLabel(), b->getLabel());
if (0 == compare)
{
return (a->getCreationDate() > b->getCreationDate());
}
else
{
return (compare < 0);
}
}
// BUG: This is very very slow. The getCreationDate() is log n in number
// of inventory items.
bool sort_item_date(LLFolderViewItem* a, LLFolderViewItem* b)
{
U32 first_create = a->getCreationDate();
U32 second_create = b->getCreationDate();
if (first_create == second_create)
{
return (LLString::compareDict(a->getLabel(), b->getLabel()) < 0);
}
else
{
return (first_create > second_create);
}
}
void top_view_lost( LLView* view )
{
if( view ) view->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 LLString& name) :
mName(name),
mModified(FALSE),
mNeedTextRebuild(TRUE)
{
mFilterOps.mFilterTypes = 0xffffffff;
mFilterOps.mMinDate = 0;
mFilterOps.mMaxDate = U32_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 = "";
mFilterGeneration = 0;
mMustPassGeneration = S32_MAX;
mMinRequiredGeneration = 0;
mNextFilterGeneration = mFilterGeneration + 1;
mLastLogoff = gSavedPerAccountSettings.getU32("LastLogoff");
}
LLInventoryFilter::~LLInventoryFilter()
{
}
BOOL LLInventoryFilter::check(LLFolderViewItem* item)
{
U32 earliest;
earliest = time_corrected() - mFilterOps.mHoursAgo * 3600;
if (mFilterOps.mMinDate && mFilterOps.mMinDate < earliest)
{
earliest = mFilterOps.mMinDate;
}
else if (!mFilterOps.mHoursAgo)
{
earliest = 0;
}
LLFolderViewEventListener* listener = item->getListener();
mSubStringMatchOffset = mFilterSubString.size() ? item->getSearchableLabel().find(mFilterSubString) : LLString::npos;
BOOL passed = (0x1 << listener->getInventoryType() & mFilterOps.mFilterTypes || listener->getInventoryType() == LLInventoryType::IT_NONE)
&& (mFilterSubString.size() == 0 || mSubStringMatchOffset != LLString::npos)
&& ((listener->getPermissionMask() & mFilterOps.mPermissions) == mFilterOps.mPermissions)
&& (listener->getCreationDate() >= earliest && listener->getCreationDate() <= mFilterOps.mMaxDate);
return passed;
}
const LLString LLInventoryFilter::getFilterSubString(BOOL trim)
{
return mFilterSubString;
}
std::string::size_type LLInventoryFilter::getStringMatchOffset() const
{
return mSubStringMatchOffset;
}
BOOL LLInventoryFilter::isActive()
{
return mFilterOps.mFilterTypes != 0xffffffff
|| mFilterSubString.size()
|| mFilterOps.mPermissions != PERM_NONE
|| mFilterOps.mMinDate != 0
|| mFilterOps.mMaxDate != U32_MAX
|| mFilterOps.mHoursAgo != 0;
}
BOOL LLInventoryFilter::isModified()
{
return mModified;
}
BOOL LLInventoryFilter::isModifiedAndClear()
{
BOOL ret = mModified;
mModified = FALSE;
return ret;
}
void LLInventoryFilter::setFilterTypes(U32 types)
{
if (mFilterOps.mFilterTypes != types)
{
// keep current items only if no type bits getting turned off
BOOL fewer_bits_set = (mFilterOps.mFilterTypes & ~types);
BOOL more_bits_set = (~mFilterOps.mFilterTypes & types);
mFilterOps.mFilterTypes = types;
if (more_bits_set && fewer_bits_set)
{
// neither less or more restrive, both simultaneously
// so we need to filter from scratch
setModified(FILTER_RESTART);
}
else if (more_bits_set)
{
// target is only one of all requested types so more type bits == less restrictive
setModified(FILTER_LESS_RESTRICTIVE);
}
else if (fewer_bits_set)
{
setModified(FILTER_MORE_RESTRICTIVE);
}
}
}
void LLInventoryFilter::setFilterSubString(const LLString& 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;
LLString::toUpper(mFilterSubString);
LLString::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(U32 min_date, U32 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, U32_MAX);
setModified();
}
if (!sl && isSinceLogoff())
{
setDateRange(0, U32_MAX);
setModified();
}
}
BOOL LLInventoryFilter::isSinceLogoff()
{
return mFilterOps.mMinDate == mLastLogoff && mFilterOps.mMaxDate == U32_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 == 0 && mFilterOps.mMaxDate == U32_MAX && hours > mFilterOps.mHoursAgo);
BOOL more_restrictive = (mFilterOps.mMinDate == 0 && mFilterOps.mMaxDate == U32_MAX && hours <= mFilterOps.mHoursAgo);
mFilterOps.mHoursAgo = hours;
mFilterOps.mMinDate = 0;
mFilterOps.mMaxDate = U32_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 (isActive())
{
// 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);
}
LLString LLInventoryFilter::getFilterText()
{
if (!mNeedTextRebuild)
{
return mFilterText;
}
mNeedTextRebuild = FALSE;
LLString filtered_types;
LLString not_filtered_types;
BOOL filtered_by_type = FALSE;
BOOL filtered_by_all_types = TRUE;
S32 num_filter_types = 0;
mFilterText = "";
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";
}
return mFilterText;
}