-Color Fixies- Fixed Gemini skin not looking as it originally did. We now use DefaultListText to color all otherwise uncolored text in scroll list cells. All of our dark skins have been updated to use white here, as dark skins are intended to have white text... This includes the Dark we ship with. LLFloaterActiveSpeakers no longer uses hard coded font colors, it uses SpeakersInactive, DefaultListText, and SpeakersGhost. LLFloaterAvatarList no longer uses hard coded font colors, it uses DefaultListText, RadarTextChatRange, RadarTextShoutRange, and RadarTextDrawDist, in place of previously hard coded font colors Since the tooltip defines color meaning, these new colors should only be skinned to change shade. DefaultListText defaults to black; SpeaksInactive, gray(grey4); SpeakersGhost, Red; RadarTextChatRange, Red; RadarTextShoutRange, Yellow(yellow1); RadarTextDrawDist, Green(green2). -Translation update and fixies- Partial credit to viewer-development, thanks for hanging onto old strings! Also, updated to look more like v-d in translated areas. Punctuation strings can be used quite a lot. Brought them all in, just in case. AscentPrefs*: Drag and Drop points now use CurrentlySetTo, CurrentlyNotSet, AnItemNotOnThisAccount, and NotLoggedIn. (applies, as well, to FloaterAO) Power User message is now built from PowerUser1, PowerUser2, Unlocked:, PowerUser3, RightClick, PowerUser4 and PowerUser5; this should give translators enough space to explain any tough to translate, and make the message easier to alter in the future, if necessary. LLCompileQueue: Now uses translation strings from upstream Starting, Done, Resetting, Running, and NotRunning in its xml. CompileQueueTitle, CompileQueueStart, CompileQueueDownloadedCompiling, CompileQueueScriptNotFound, CompileQueueProblemDownloading, CompileQueueInsufficientPermDownload, CompileQueueInsufficientPermFor, CompileQueueUnknownFailure, ResetQueueTitle, ResetQueueStart, NotRunQueueTitle, and NotRunQueueStart from strings. LLFloaterAvatarList(floater_radar) now uses has_entered, has_left, the_sim, draw_distance, shout_range, and chat_range in its xml for translatable alerts. LLFloaterLand(floater_about_land) now uses minutes, 1_minute, 1_second, seconds, and remaining from its xml. LLFloaterLand, LLFolderView, LLPanelDirBrowser now make use of InventoryNoMatchingItems, NoneFound, and Searching in their searches. LLFolderView was brought closer to v-d in translation. LLGroupNotify now uses GroupNotifyGroupNotice, GroupNotifySentBy, GroupNotifyAttached, next (also now used by LLNotify), ok, GroupNotifyGroupNotices, GroupNotifyViewPastNotices, GroupNotifyOpenAttachment, and GroupNotifySaveAttachment. LLInventoryFilter synced with V-D for translation: Now uses Animations, Calling Cards, Clothing, Gestures, Landmarks, Notecards, Objects, Scripts, Sounds, Textures, Snapshots, No Filters, Since Logoff, and Worn. LLManipRotate now uses Direction_Forward, Direction_Left, Direction_Right, Direction_Back, Direction_North, Direction_South, Direction_West, Direction_East, Direction_Up, and Direction_Down, like upstream v-d. LLPanelAvatar(panel_avatar) now uses None string in its xml for when there are no groups, also removed cruft. Though the None has not been showing up for quite some time, anyway... LLPanelObjectInventory now uses Buy, LoadingContents and NoContents, however the last two strings did not seem to show up anyway... thanks to Latif Khalifa, Jean Horten, theGenius Indigo and Cubbi Bearcat for confirming this happens across many different versions and on both Windows and linux(32 and 64). LLPreviewScript now uses CompileSuccessful, SaveComplete, ObjectOutOfRange, and CompileSuccessfulSaving. LLScrollingPanelParam now translates Less and More. Avatar Shape Information strings now used for customize appearance panels. LLTextureCtrl now uses multiple_textures. LLToolpie has been updated to use v-d include order and now uses UnmuteAvatar, MuteAvatar, UnmuteObject, and MuteObject2 strings. LLVOAvatarSelf now uses BodyParts* strings for translation of toolpie entries pertaining to bodyparts. LLViewerMenuFile now uses UnknownFileExtension, and UploadingCosts. LLViewerMessage now uses Cancel(also used by LLViewerDisplay), AcquiredItems, Saved_message, IM_autoresponse_sent_item, IM_autoresponded_to, IM_announce_incoming, InvOfferDecline, InvOfferGaveYou, InvOfferOwnedByUnknownUser, InvOfferAnObjectNamed, InvOfferOwnedBy, InvOfferOwnedByUnknownGroup, and InvOfferOwnedByGroup. -AvatarRadar Update- AvatarRadar enhanced with code from both Cool VL Viewer and some avian corpse. e_radar_alert_type has been reworked to allow bitwise usage in the future. Cool stuff: gSavedSettings for radar are now CachedControls, entering boolean is now checked before going into the switchcase, Style changes, yay C++ style. Avian Flu: Distance for shout range corrected to be 96. handleKeyHere: If the user hits enter with an avatar on the radar selected, focus camera on selected avatar; ctrl-enter, teleport to selected avatar. Otherwise: Tiny spelling fixies, and a suggestive comment.
2543 lines
63 KiB
C++
2543 lines
63 KiB
C++
/**
|
|
* @file llfolderview.cpp
|
|
* @brief Implementation of the folder view collection of classes.
|
|
*
|
|
* $LicenseInfo:firstyear=2001&license=viewergpl$
|
|
*
|
|
* Copyright (c) 2001-2009, Linden Research, Inc.
|
|
*
|
|
* Second Life Viewer Source Code
|
|
* The source code in this file ("Source Code") is provided by Linden Lab
|
|
* to you under the terms of the GNU General Public License, version 2.0
|
|
* ("GPL"), unless you have obtained a separate licensing agreement
|
|
* ("Other License"), formally executed by you and Linden Lab. Terms of
|
|
* the GPL can be found in doc/GPL-license.txt in this distribution, or
|
|
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
|
|
*
|
|
* There are special exceptions to the terms and conditions of the GPL as
|
|
* it is applied to this Source Code. View the full text of the exception
|
|
* in the file doc/FLOSS-exception.txt in this software distribution, or
|
|
* online at
|
|
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
|
|
*
|
|
* By copying, modifying or distributing this software, you acknowledge
|
|
* that you have read and understood your obligations described above,
|
|
* and agree to abide by those obligations.
|
|
*
|
|
* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
|
|
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
|
|
* COMPLETENESS OR PERFORMANCE.
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#include "llviewerprecompiledheaders.h"
|
|
|
|
#include "llfolderview.h"
|
|
|
|
#include "llcallbacklist.h"
|
|
#include "llinventorybridge.h"
|
|
#include "llinventoryclipboard.h" // *TODO: remove this once hack below gone.
|
|
#include "llinventoryfilter.h"
|
|
#include "llinventoryfunctions.h"
|
|
#include "llinventorymodelbackgroundfetch.h"
|
|
#include "llinventorypanel.h"
|
|
#include "llfoldertype.h"
|
|
#include "llfloaterinventory.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 "lltrans.h"
|
|
#include "llui.h"
|
|
#include "llviewertexture.h"
|
|
#include "llviewertexturelist.h"
|
|
#include "llviewerjointattachment.h"
|
|
#include "llviewermenu.h"
|
|
#include "lluictrlfactory.h"
|
|
#include "llviewercontrol.h"
|
|
#include "llviewerfoldertype.h"
|
|
#include "llviewerwindow.h"
|
|
#include "llvoavatar.h"
|
|
#include "llfloaterproperties.h"
|
|
#include "llnotificationsutil.h"
|
|
|
|
// Linden library includes
|
|
#include "lldbstrings.h"
|
|
#include "llfocusmgr.h"
|
|
#include "llfontgl.h"
|
|
#include "llgl.h"
|
|
#include "llrender.h"
|
|
#include "llinventory.h"
|
|
|
|
// Third-party library includes
|
|
#include <algorithm>
|
|
|
|
///----------------------------------------------------------------------------
|
|
/// Local function declarations, constants, enums, and typedefs
|
|
///----------------------------------------------------------------------------
|
|
|
|
const S32 RENAME_WIDTH_PAD = 4;
|
|
const S32 RENAME_HEIGHT_PAD = 1;
|
|
const S32 AUTO_OPEN_STACK_DEPTH = 16;
|
|
const S32 MIN_ITEM_WIDTH_VISIBLE = LLFolderViewItem::ICON_WIDTH
|
|
+ LLFolderViewItem::ICON_PAD
|
|
+ LLFolderViewItem::ARROW_SIZE
|
|
+ LLFolderViewItem::TEXT_PAD
|
|
+ /*first few characters*/ 40;
|
|
const S32 MINIMUM_RENAMER_WIDTH = 80;
|
|
|
|
// *TODO: move in params in xml if necessary. Requires modification of LLFolderView & LLInventoryPanel Params.
|
|
const S32 STATUS_TEXT_HPAD = 6;
|
|
const S32 STATUS_TEXT_VPAD = 8;
|
|
|
|
enum {
|
|
SIGNAL_NO_KEYBOARD_FOCUS = 1,
|
|
SIGNAL_KEYBOARD_FOCUS = 2
|
|
};
|
|
|
|
F32 LLFolderView::sAutoOpenTime = 1.f;
|
|
|
|
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);
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// Tells all folders in a folderview to sort their items
|
|
// (and only their items, not folders) by a certain function.
|
|
class LLSetItemSortFunction : public LLFolderViewFunctor
|
|
{
|
|
public:
|
|
LLSetItemSortFunction(U32 ordering)
|
|
: mSortOrder(ordering) {}
|
|
virtual ~LLSetItemSortFunction() {}
|
|
virtual void doFolder(LLFolderViewFolder* folder);
|
|
virtual void doItem(LLFolderViewItem* item);
|
|
|
|
U32 mSortOrder;
|
|
};
|
|
|
|
|
|
// Set the sort order.
|
|
void LLSetItemSortFunction::doFolder(LLFolderViewFolder* folder)
|
|
{
|
|
folder->setItemSortOrder(mSortOrder);
|
|
}
|
|
|
|
// Do nothing.
|
|
void LLSetItemSortFunction::doItem(LLFolderViewItem* item)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// Tells all folders in a folderview to close themselves
|
|
// For efficiency, calls setOpenArrangeRecursively().
|
|
// The calling function must then call:
|
|
// LLFolderView* root = getRoot();
|
|
// if( root )
|
|
// {
|
|
// root->arrange( NULL, NULL );
|
|
// root->scrollToShowSelection();
|
|
// }
|
|
// to patch things up.
|
|
class LLCloseAllFoldersFunctor : public LLFolderViewFunctor
|
|
{
|
|
public:
|
|
LLCloseAllFoldersFunctor(BOOL close) { mOpen = !close; }
|
|
virtual ~LLCloseAllFoldersFunctor() {}
|
|
virtual void doFolder(LLFolderViewFolder* folder);
|
|
virtual void doItem(LLFolderViewItem* item);
|
|
|
|
BOOL mOpen;
|
|
};
|
|
|
|
|
|
// Set the sort order.
|
|
void LLCloseAllFoldersFunctor::doFolder(LLFolderViewFolder* folder)
|
|
{
|
|
folder->setOpenArrangeRecursively(mOpen);
|
|
}
|
|
|
|
// Do nothing.
|
|
void LLCloseAllFoldersFunctor::doItem(LLFolderViewItem* item)
|
|
{ }
|
|
|
|
///----------------------------------------------------------------------------
|
|
/// Class LLFolderView
|
|
///----------------------------------------------------------------------------
|
|
|
|
// Default constructor
|
|
LLFolderView::LLFolderView( const std::string& name,
|
|
const LLRect& rect, const LLUUID& source_id, LLPanel *parent_view, LLFolderViewEventListener* listener ) :
|
|
#if LL_WINDOWS
|
|
#pragma warning( push )
|
|
#pragma warning( disable : 4355 ) // warning C4355: 'this' : used in base member initializer list
|
|
#endif
|
|
LLFolderViewFolder( name, NULL, NULL, NULL, this, listener ),
|
|
#if LL_WINDOWS
|
|
#pragma warning( pop )
|
|
#endif
|
|
mRunningHeight(0),
|
|
mScrollContainer( NULL ),
|
|
mPopupMenuHandle(),
|
|
mAllowMultiSelect(TRUE),
|
|
mShowEmptyMessage(TRUE),
|
|
mShowFolderHierarchy(FALSE),
|
|
mSourceID(source_id),
|
|
mRenameItem( NULL ),
|
|
mNeedsScroll( FALSE ),
|
|
mUseLabelSuffix( TRUE ),
|
|
mPinningSelectedItem(FALSE),
|
|
mNeedsAutoSelect( FALSE ),
|
|
mAutoSelectOverride(FALSE),
|
|
mNeedsAutoRename(FALSE),
|
|
mDebugFilters(FALSE),
|
|
mSortOrder(LLInventoryFilter::SO_FOLDERS_BY_NAME), // This gets overridden by a pref immediately
|
|
mFilter( new LLInventoryFilter(name) ),
|
|
mShowSelectionContext(FALSE),
|
|
mShowSingleSelection(FALSE),
|
|
mArrangeGeneration(0),
|
|
mSignalSelectCallback(0),
|
|
mMinWidth(0),
|
|
mDragAndDropThisFrame(FALSE),
|
|
mParentPanel(parent_view),
|
|
mUseEllipses(FALSE),
|
|
mDraggingOverItem(NULL),
|
|
mStatusTextBox(NULL),
|
|
mSearchType(1)
|
|
{
|
|
mRoot = this;
|
|
|
|
mShowLoadStatus = TRUE;
|
|
LLRect new_rect(rect.mLeft, rect.mBottom + getRect().getHeight(), rect.mLeft + getRect().getWidth(), rect.mBottom);
|
|
setRect( rect );
|
|
reshape(rect.getWidth(), rect.getHeight());
|
|
mIsOpen = TRUE; // this view is always open.
|
|
mAutoOpenItems.setDepth(AUTO_OPEN_STACK_DEPTH);
|
|
mAutoOpenCandidate = NULL;
|
|
mAutoOpenTimer.stop();
|
|
mKeyboardSelection = FALSE;
|
|
mIndentation = -LEFT_INDENTATION; // children start at indentation 0
|
|
gIdleCallbacks.addFunction(idle, this);
|
|
|
|
//clear label
|
|
// go ahead and render root folder as usual
|
|
// just make sure the label ("Inventory Folder") never shows up
|
|
mLabel = LLStringUtil::null;
|
|
|
|
mRenamer = new LLLineEditor(std::string("ren"), getRect(), LLStringUtil::null, getLabelFontForStyle(LLFontGL::NORMAL),
|
|
DB_INV_ITEM_NAME_STR_LEN,
|
|
&LLFolderView::commitRename,
|
|
NULL,
|
|
NULL,
|
|
this,
|
|
&LLLineEditor::prevalidatePrintableNotPipe);
|
|
//mRenamer->setWriteableBgColor(LLColor4::white);
|
|
// Escape is handled by reverting the rename, not commiting it (default behavior)
|
|
mRenamer->setCommitOnFocusLost(TRUE);
|
|
mRenamer->setVisible(FALSE);
|
|
addChild(mRenamer);
|
|
|
|
LLFontGL* font = getLabelFontForStyle(mLabelStyle);
|
|
LLRect new_r = LLRect(rect.mLeft + ICON_PAD,
|
|
rect.mTop - TEXT_PAD,
|
|
rect.mRight,
|
|
rect.mTop - TEXT_PAD - llfloor(font->getLineHeight()));
|
|
mStatusTextBox = new LLTextBox(name, new_r, std::string(), font, false);
|
|
mStatusTextBox->setVisible(true);
|
|
mStatusTextBox->setHPad(STATUS_TEXT_HPAD);
|
|
mStatusTextBox->setVPad(STATUS_TEXT_VPAD);
|
|
mStatusTextBox->setFollows(FOLLOWS_LEFT|FOLLOWS_TOP);
|
|
// make the popup menu available
|
|
LLMenuGL* menu = LLUICtrlFactory::getInstance()->buildMenu("menu_inventory.xml", parent_view);
|
|
if (!menu)
|
|
{
|
|
menu = new LLMenuGL(LLStringUtil::null);
|
|
}
|
|
menu->setBackgroundColor(gColors.getColor("MenuPopupBgColor"));
|
|
menu->setVisible(FALSE);
|
|
mPopupMenuHandle = menu->getHandle();
|
|
|
|
setTabStop(TRUE);
|
|
|
|
mListener->openItem();
|
|
}
|
|
|
|
// Destroys the object
|
|
LLFolderView::~LLFolderView( void )
|
|
{
|
|
closeRenamer();
|
|
|
|
// The release focus call can potentially call the
|
|
// scrollcontainer, which can potentially be called with a partly
|
|
// destroyed scollcontainer. Just null it out here, and no worries
|
|
// about calling into the invalid scroll container.
|
|
// Same with the renamer.
|
|
mScrollContainer = NULL;
|
|
mRenameItem = NULL;
|
|
mRenamer = NULL;
|
|
mStatusTextBox = NULL;
|
|
|
|
mAutoOpenItems.removeAllNodes();
|
|
gIdleCallbacks.deleteFunction(idle, this);
|
|
|
|
if (mPopupMenuHandle.get()) mPopupMenuHandle.get()->die();
|
|
|
|
mAutoOpenItems.removeAllNodes();
|
|
clearSelection();
|
|
mItems.clear();
|
|
mFolders.clear();
|
|
|
|
mItemMap.clear();
|
|
|
|
delete mFilter;
|
|
mFilter = NULL;
|
|
}
|
|
|
|
BOOL LLFolderView::canFocusChildren() const
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
static LLFastTimer::DeclareTimer FTM_SORT("Sort Inventory");
|
|
|
|
void LLFolderView::setSortOrder(U32 order)
|
|
{
|
|
if (order != mSortOrder)
|
|
{
|
|
LLFastTimer t(FTM_SORT);
|
|
|
|
mSortOrder = order;
|
|
|
|
sortBy(order);
|
|
arrangeAll();
|
|
}
|
|
}
|
|
|
|
|
|
U32 LLFolderView::getSortOrder() const
|
|
{
|
|
return mSortOrder;
|
|
}
|
|
|
|
U32 LLFolderView::toggleSearchType(std::string toggle)
|
|
{
|
|
if (toggle == "name")
|
|
{
|
|
if (mSearchType & 1)
|
|
{
|
|
mSearchType &= 6;
|
|
}
|
|
else
|
|
{
|
|
mSearchType |= 1;
|
|
}
|
|
}
|
|
else if (toggle == "description")
|
|
{
|
|
if (mSearchType & 2)
|
|
{
|
|
mSearchType &= 5;
|
|
}
|
|
else
|
|
{
|
|
mSearchType |= 2;
|
|
}
|
|
}
|
|
else if (toggle == "creator")
|
|
{
|
|
if (mSearchType & 4)
|
|
{
|
|
mSearchType &= 3;
|
|
}
|
|
else
|
|
{
|
|
mSearchType |= 4;
|
|
}
|
|
}
|
|
if (mSearchType == 0)
|
|
{
|
|
mSearchType = 1;
|
|
}
|
|
|
|
if (getFilterSubString().length())
|
|
{
|
|
mFilter->setModified(LLInventoryFilter::FILTER_RESTART);
|
|
}
|
|
|
|
return mSearchType;
|
|
}
|
|
|
|
U32 LLFolderView::getSearchType() const
|
|
{
|
|
return mSearchType;
|
|
}
|
|
|
|
BOOL LLFolderView::addFolder( LLFolderViewFolder* folder)
|
|
{
|
|
// enforce sort order of My Inventory followed by Library
|
|
if (folder->getListener()->getUUID() == gInventory.getLibraryRootFolderID())
|
|
{
|
|
mFolders.push_back(folder);
|
|
}
|
|
else
|
|
{
|
|
mFolders.insert(mFolders.begin(), folder);
|
|
}
|
|
folder->setShowLoadStatus(mShowLoadStatus);
|
|
folder->setOrigin(0, 0);
|
|
folder->reshape(getRect().getWidth(), 0);
|
|
folder->setVisible(FALSE);
|
|
addChild( folder );
|
|
folder->dirtyFilter();
|
|
folder->requestArrange();
|
|
return TRUE;
|
|
}
|
|
|
|
void LLFolderView::closeAllFolders()
|
|
{
|
|
// Close all the folders
|
|
setOpenArrangeRecursively(FALSE, LLFolderViewFolder::RECURSE_DOWN);
|
|
arrangeAll();
|
|
}
|
|
|
|
void LLFolderView::openTopLevelFolders()
|
|
{
|
|
for (folders_t::iterator iter = mFolders.begin();
|
|
iter != mFolders.end();)
|
|
{
|
|
folders_t::iterator fit = iter++;
|
|
(*fit)->setOpen(TRUE);
|
|
}
|
|
}
|
|
|
|
void LLFolderView::setOpenArrangeRecursively(BOOL openitem, ERecurseType recurse)
|
|
{
|
|
// call base class to do proper recursion
|
|
LLFolderViewFolder::setOpenArrangeRecursively(openitem, recurse);
|
|
// make sure root folder is always open
|
|
mIsOpen = TRUE;
|
|
}
|
|
|
|
static LLFastTimer::DeclareTimer FTM_ARRANGE("Arrange");
|
|
|
|
// 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 )
|
|
{
|
|
if(!mScrollContainer)
|
|
return 1;
|
|
if (getListener()->getUUID().notNull())
|
|
{
|
|
if (mNeedsSort)
|
|
{
|
|
mFolders.sort(mSortFunction);
|
|
mItems.sort(mSortFunction);
|
|
mNeedsSort = false;
|
|
}
|
|
}
|
|
|
|
LLFastTimer t2(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 = getRoot()->getArrangeGeneration();
|
|
|
|
LLInventoryFilter::EFolderShow show_folder_state =
|
|
getRoot()->getFilter()->getShowFolderState();
|
|
|
|
S32 total_width = LEFT_PAD;
|
|
S32 running_height = mDebugFilters ? llceil(LLFontGL::getFontMonospace()->getLineHeight()) : 0;
|
|
S32 target_height = running_height;
|
|
S32 parent_item_height = getRect().getHeight();
|
|
|
|
for (folders_t::iterator iter = mFolders.begin();
|
|
iter != mFolders.end();)
|
|
{
|
|
folders_t::iterator fit = iter++;
|
|
LLFolderViewFolder* folderp = (*fit);
|
|
if (getDebugFilters())
|
|
{
|
|
folderp->setVisible(TRUE);
|
|
}
|
|
else
|
|
{
|
|
folderp->setVisible(show_folder_state == LLInventoryFilter::SHOW_ALL_FOLDERS || // always show folders?
|
|
(folderp->getFiltered(filter_generation) || folderp->hasFilteredDescendants(filter_generation))); // passed filter or has descendants that passed filter
|
|
}
|
|
|
|
if (folderp->getVisible())
|
|
{
|
|
S32 child_height = 0;
|
|
S32 child_width = 0;
|
|
S32 child_top = parent_item_height - running_height;
|
|
|
|
target_height += folderp->arrange( &child_width, &child_height, filter_generation );
|
|
|
|
mMinWidth = llmax(mMinWidth, child_width);
|
|
total_width = llmax( total_width, child_width );
|
|
running_height += child_height;
|
|
folderp->setOrigin( ICON_PAD, child_top - (*fit)->getRect().getHeight() );
|
|
}
|
|
}
|
|
|
|
for (items_t::iterator iter = mItems.begin();
|
|
iter != mItems.end();)
|
|
{
|
|
items_t::iterator iit = iter++;
|
|
LLFolderViewItem* itemp = (*iit);
|
|
itemp->setVisible(itemp->getFiltered(filter_generation));
|
|
|
|
if (itemp->getVisible())
|
|
{
|
|
S32 child_width = 0;
|
|
S32 child_height = 0;
|
|
S32 child_top = parent_item_height - running_height;
|
|
|
|
target_height += itemp->arrange( &child_width, &child_height, filter_generation );
|
|
itemp->reshape(itemp->getRect().getWidth(), child_height);
|
|
|
|
mMinWidth = llmax(mMinWidth, child_width);
|
|
total_width = llmax( total_width, child_width );
|
|
running_height += child_height;
|
|
itemp->setOrigin( ICON_PAD, child_top - itemp->getRect().getHeight() );
|
|
}
|
|
}
|
|
|
|
if(!mHasVisibleChildren)// is there any filtered items ?
|
|
{
|
|
//Nope. We need to display status textbox, let's reserve some place for it
|
|
running_height = mStatusTextBox->getTextPixelHeight();
|
|
target_height = running_height;
|
|
}
|
|
|
|
mRunningHeight = running_height;
|
|
LLRect scroll_rect = mScrollContainer->getContentWindowRect();
|
|
reshape( llmax(scroll_rect.getWidth(), total_width), running_height );
|
|
|
|
LLRect new_scroll_rect = mScrollContainer->getContentWindowRect();
|
|
if (new_scroll_rect.getWidth() != scroll_rect.getWidth())
|
|
{
|
|
reshape( llmax(scroll_rect.getWidth(), total_width), running_height );
|
|
}
|
|
|
|
// move item renamer text field to item's new position
|
|
updateRenamerPosition();
|
|
|
|
mTargetHeight = (F32)target_height;
|
|
return llround(mTargetHeight);
|
|
}
|
|
|
|
const std::string LLFolderView::getFilterSubString(BOOL trim)
|
|
{
|
|
return mFilter->getFilterSubString(trim);
|
|
}
|
|
|
|
static LLFastTimer::DeclareTimer FTM_FILTER("Filter Inventory");
|
|
|
|
void LLFolderView::filter( LLInventoryFilter& filter )
|
|
{
|
|
LLFastTimer t2(FTM_FILTER);
|
|
filter.setFilterCount(llclamp(gSavedSettings.getS32("FilterItemsPerFrame"), 1, 5000));
|
|
|
|
if (getCompletedFilterGeneration() < filter.getCurrentGeneration())
|
|
{
|
|
mPassedFilter = FALSE;
|
|
mMinWidth = 0;
|
|
LLFolderViewFolder::filter(filter);
|
|
}
|
|
else
|
|
{
|
|
mPassedFilter = TRUE;
|
|
}
|
|
}
|
|
|
|
void LLFolderView::reshape(S32 width, S32 height, BOOL called_from_parent)
|
|
{
|
|
LLRect scroll_rect;
|
|
if (mScrollContainer)
|
|
{
|
|
LLView::reshape(width, height, called_from_parent);
|
|
scroll_rect = mScrollContainer->getContentWindowRect();
|
|
}
|
|
width = llmax(mMinWidth, scroll_rect.getWidth());
|
|
height = llmax(mRunningHeight, scroll_rect.getHeight());
|
|
|
|
// restrict width with scroll container's width
|
|
if (mUseEllipses)
|
|
width = scroll_rect.getWidth();
|
|
|
|
LLView::reshape(width, height, called_from_parent);
|
|
|
|
mReshapeSignal(mSelectedItems, FALSE);
|
|
}
|
|
|
|
void LLFolderView::addToSelectionList(LLFolderViewItem* item)
|
|
{
|
|
if (item->isSelected())
|
|
{
|
|
removeFromSelectionList(item);
|
|
}
|
|
if (mSelectedItems.size())
|
|
{
|
|
mSelectedItems.back()->setIsCurSelection(FALSE);
|
|
}
|
|
item->setIsCurSelection(TRUE);
|
|
mSelectedItems.push_back(item);
|
|
}
|
|
|
|
void LLFolderView::removeFromSelectionList(LLFolderViewItem* item)
|
|
{
|
|
if (mSelectedItems.size())
|
|
{
|
|
mSelectedItems.back()->setIsCurSelection(FALSE);
|
|
}
|
|
|
|
selected_items_t::iterator item_iter;
|
|
for (item_iter = mSelectedItems.begin(); item_iter != mSelectedItems.end();)
|
|
{
|
|
if (*item_iter == item)
|
|
{
|
|
item_iter = mSelectedItems.erase(item_iter);
|
|
}
|
|
else
|
|
{
|
|
++item_iter;
|
|
}
|
|
}
|
|
if (mSelectedItems.size())
|
|
{
|
|
mSelectedItems.back()->setIsCurSelection(TRUE);
|
|
}
|
|
}
|
|
|
|
LLFolderViewItem* LLFolderView::getCurSelectedItem( void )
|
|
{
|
|
if(mSelectedItems.size())
|
|
{
|
|
LLFolderViewItem* itemp = mSelectedItems.back();
|
|
llassert(itemp->getIsCurSelection());
|
|
return itemp;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// Record the selected item and pass it down the hierachy.
|
|
BOOL LLFolderView::setSelection(LLFolderViewItem* selection, BOOL openitem,
|
|
BOOL take_keyboard_focus)
|
|
{
|
|
mSignalSelectCallback = take_keyboard_focus ? SIGNAL_KEYBOARD_FOCUS : SIGNAL_NO_KEYBOARD_FOCUS;
|
|
|
|
if( selection == this )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if( selection && take_keyboard_focus)
|
|
{
|
|
mParentPanel->setFocus(TRUE);
|
|
}
|
|
|
|
// clear selection down here because change of keyboard focus can potentially
|
|
// affect selection
|
|
clearSelection();
|
|
|
|
if(selection)
|
|
{
|
|
addToSelectionList(selection);
|
|
}
|
|
|
|
BOOL rv = LLFolderViewFolder::setSelection(selection, openitem, take_keyboard_focus);
|
|
if(openitem && selection)
|
|
{
|
|
selection->getParentFolder()->requestArrange();
|
|
}
|
|
|
|
llassert(mSelectedItems.size() <= 1);
|
|
|
|
return rv;
|
|
}
|
|
|
|
void LLFolderView::setSelectionByID(const LLUUID& obj_id, BOOL take_keyboard_focus)
|
|
{
|
|
LLFolderViewItem* itemp = getItemByID(obj_id);
|
|
if(itemp && itemp->getListener())
|
|
{
|
|
itemp->arrangeAndSet(TRUE, take_keyboard_focus);
|
|
mSelectThisID.setNull();
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// save the desired item to be selected later (if/when ready)
|
|
mSelectThisID = obj_id;
|
|
}
|
|
}
|
|
|
|
void LLFolderView::updateSelection()
|
|
{
|
|
if (mSelectThisID.notNull())
|
|
{
|
|
setSelectionByID(mSelectThisID, false);
|
|
}
|
|
}
|
|
|
|
BOOL LLFolderView::changeSelection(LLFolderViewItem* selection, BOOL selected)
|
|
{
|
|
BOOL rv = FALSE;
|
|
|
|
// can't select root folder
|
|
if(!selection || selection == this)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (!mAllowMultiSelect)
|
|
{
|
|
clearSelection();
|
|
}
|
|
|
|
selected_items_t::iterator item_iter;
|
|
for (item_iter = mSelectedItems.begin(); item_iter != mSelectedItems.end(); ++item_iter)
|
|
{
|
|
if (*item_iter == selection)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
BOOL on_list = (item_iter != mSelectedItems.end());
|
|
|
|
if(selected && !on_list)
|
|
{
|
|
addToSelectionList(selection);
|
|
}
|
|
if(!selected && on_list)
|
|
{
|
|
removeFromSelectionList(selection);
|
|
}
|
|
|
|
rv = LLFolderViewFolder::changeSelection(selection, selected);
|
|
|
|
mSignalSelectCallback = SIGNAL_KEYBOARD_FOCUS;
|
|
|
|
return rv;
|
|
}
|
|
|
|
static LLFastTimer::DeclareTimer FTM_SANITIZE_SELECTION("Sanitize Selection");
|
|
void LLFolderView::sanitizeSelection()
|
|
{
|
|
LLFastTimer _(FTM_SANITIZE_SELECTION);
|
|
// store off current item in case it is automatically deselected
|
|
// and we want to preserve context
|
|
LLFolderViewItem* original_selected_item = getCurSelectedItem();
|
|
|
|
// Cache "Show all folders" filter setting
|
|
BOOL show_all_folders = (getRoot()->getFilter()->getShowFolderState() == LLInventoryFilter::SHOW_ALL_FOLDERS);
|
|
|
|
std::vector<LLFolderViewItem*> items_to_remove;
|
|
selected_items_t::iterator item_iter;
|
|
for (item_iter = mSelectedItems.begin(); item_iter != mSelectedItems.end(); ++item_iter)
|
|
{
|
|
LLFolderViewItem* item = *item_iter;
|
|
|
|
// ensure that each ancestor is open and potentially passes filtering
|
|
BOOL visible = item->potentiallyVisible(); // initialize from filter state for this item
|
|
// modify with parent open and filters states
|
|
LLFolderViewFolder* parent_folder = item->getParentFolder();
|
|
if ( parent_folder )
|
|
{
|
|
if ( show_all_folders )
|
|
{ // "Show all folders" is on, so this folder is visible
|
|
visible = TRUE;
|
|
}
|
|
else
|
|
{ // Move up through parent folders and see what's visible
|
|
while(parent_folder)
|
|
{
|
|
visible = visible && parent_folder->isOpen() && parent_folder->potentiallyVisible();
|
|
parent_folder = parent_folder->getParentFolder();
|
|
}
|
|
}
|
|
}
|
|
|
|
// deselect item if any ancestor is closed or didn't pass filter requirements.
|
|
if (!visible)
|
|
{
|
|
items_to_remove.push_back(item);
|
|
}
|
|
|
|
// disallow nested selections (i.e. folder items plus one or more ancestors)
|
|
// could check cached mum selections count and only iterate if there are any
|
|
// but that may be a premature optimization.
|
|
selected_items_t::iterator other_item_iter;
|
|
for (other_item_iter = mSelectedItems.begin(); other_item_iter != mSelectedItems.end(); ++other_item_iter)
|
|
{
|
|
LLFolderViewItem* other_item = *other_item_iter;
|
|
for( parent_folder = other_item->getParentFolder(); parent_folder; parent_folder = parent_folder->getParentFolder())
|
|
{
|
|
if (parent_folder == item)
|
|
{
|
|
// this is a descendent of the current folder, remove from list
|
|
items_to_remove.push_back(other_item);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Don't allow invisible items (such as root folders) to be selected.
|
|
if (item == getRoot())
|
|
{
|
|
items_to_remove.push_back(item);
|
|
}
|
|
}
|
|
|
|
std::vector<LLFolderViewItem*>::iterator item_it;
|
|
for (item_it = items_to_remove.begin(); item_it != items_to_remove.end(); ++item_it )
|
|
{
|
|
changeSelection(*item_it, FALSE); // toggle selection (also removes from list)
|
|
}
|
|
|
|
// if nothing selected after prior constraints...
|
|
if (mSelectedItems.empty())
|
|
{
|
|
// ...select first available parent of original selection, or "My Inventory" otherwise
|
|
LLFolderViewItem* new_selection = NULL;
|
|
if (original_selected_item)
|
|
{
|
|
for(LLFolderViewFolder* parent_folder = original_selected_item->getParentFolder();
|
|
parent_folder;
|
|
parent_folder = parent_folder->getParentFolder())
|
|
{
|
|
if (parent_folder->potentiallyVisible())
|
|
{
|
|
// give initial selection to first ancestor folder that potentially passes the filter
|
|
if (!new_selection)
|
|
{
|
|
new_selection = parent_folder;
|
|
}
|
|
|
|
// if any ancestor folder of original item is closed, move the selection up
|
|
// to the highest closed
|
|
if (!parent_folder->isOpen())
|
|
{
|
|
new_selection = parent_folder;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
new_selection = NULL;
|
|
}
|
|
|
|
if (new_selection)
|
|
{
|
|
setSelection(new_selection, FALSE, FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLFolderView::clearSelection()
|
|
{
|
|
for (selected_items_t::const_iterator item_it = mSelectedItems.begin();
|
|
item_it != mSelectedItems.end();
|
|
++item_it)
|
|
{
|
|
(*item_it)->setUnselected();
|
|
}
|
|
|
|
mSelectedItems.clear();
|
|
mSelectThisID.setNull();
|
|
}
|
|
|
|
std::set<LLUUID> LLFolderView::getSelectionList() const
|
|
{
|
|
std::set<LLUUID> selection;
|
|
for (selected_items_t::const_iterator item_it = mSelectedItems.begin();
|
|
item_it != mSelectedItems.end();
|
|
++item_it)
|
|
{
|
|
selection.insert((*item_it)->getListener()->getUUID());
|
|
}
|
|
return selection;
|
|
}
|
|
|
|
BOOL LLFolderView::startDrag(LLToolDragAndDrop::ESource source)
|
|
{
|
|
std::vector<EDragAndDropType> types;
|
|
uuid_vec_t cargo_ids;
|
|
selected_items_t::iterator item_it;
|
|
BOOL can_drag = TRUE;
|
|
if (!mSelectedItems.empty())
|
|
{
|
|
for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it)
|
|
{
|
|
EDragAndDropType type = DAD_NONE;
|
|
LLUUID id = LLUUID::null;
|
|
can_drag = can_drag && (*item_it)->getListener()->startDrag(&type, &id);
|
|
|
|
types.push_back(type);
|
|
cargo_ids.push_back(id);
|
|
}
|
|
|
|
LLToolDragAndDrop::getInstance()->beginMultiDrag(types, cargo_ids, source, mSourceID);
|
|
}
|
|
return can_drag;
|
|
}
|
|
|
|
void LLFolderView::commitRename( LLUICtrl* renamer, void* user_data )
|
|
{
|
|
LLFolderView* root = reinterpret_cast<LLFolderView*>(user_data);
|
|
if( root )
|
|
{
|
|
root->finishRenamingItem();
|
|
}
|
|
}
|
|
|
|
void LLFolderView::draw()
|
|
{
|
|
if (mDebugFilters)
|
|
{
|
|
std::string current_filter_string = llformat("Current Filter: %d, Least Filter: %d, Auto-accept Filter: %d",
|
|
mFilter->getCurrentGeneration(), mFilter->getMinRequiredGeneration(), mFilter->getMustPassGeneration());
|
|
LLFontGL::getFontMonospace()->renderUTF8(current_filter_string, 0, 2,
|
|
getRect().getHeight() - LLFontGL::getFontMonospace()->getLineHeight(), LLColor4(0.5f, 0.5f, 0.8f, 1.f),
|
|
LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, 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();
|
|
}
|
|
|
|
// while dragging, update selection rendering to reflect single/multi drag status
|
|
if (LLToolDragAndDrop::getInstance()->hasMouseCapture())
|
|
{
|
|
EAcceptance last_accept = LLToolDragAndDrop::getInstance()->getLastAccept();
|
|
if (last_accept == ACCEPT_YES_SINGLE || last_accept == ACCEPT_YES_COPY_SINGLE)
|
|
{
|
|
setShowSingleSelection(TRUE);
|
|
}
|
|
else
|
|
{
|
|
setShowSingleSelection(FALSE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
setShowSingleSelection(FALSE);
|
|
}
|
|
|
|
|
|
if (mSearchTimer.getElapsedTimeF32() > gSavedSettings.getF32("TypeAheadTimeout") || !mSearchString.size())
|
|
{
|
|
mSearchString.clear();
|
|
}
|
|
|
|
if (hasVisibleChildren()
|
|
|| mFilter->getShowFolderState() == LLInventoryFilter::SHOW_ALL_FOLDERS)
|
|
{
|
|
mStatusText.clear();
|
|
mStatusTextBox->setVisible( FALSE );
|
|
}
|
|
else if (mShowEmptyMessage)
|
|
{
|
|
static LLCachedControl<LLColor4> sSearchStatusColor(gColors, "InventorySearchStatusColor", LLColor4::white );
|
|
if (LLInventoryModelBackgroundFetch::instance().folderFetchActive() || mCompletedFilterGeneration < mFilter->getMinRequiredGeneration())
|
|
{
|
|
mStatusText = LLTrans::getString("Searching");
|
|
}
|
|
else
|
|
{
|
|
// if(getFilter())
|
|
// {
|
|
// LLStringUtil::format_map_t args;
|
|
// args["[SEARCH_TERM]"] = LLURI::escape(getFilter()->getFilterSubStringOrig());
|
|
mStatusText = LLTrans::getString("InventoryNoMatchingItems"); //, args);
|
|
// }
|
|
}
|
|
mStatusTextBox->setWrappedText(mStatusText);
|
|
mStatusTextBox->setVisible( TRUE );
|
|
|
|
// firstly reshape message textbox with current size. This is necessary to
|
|
// LLTextBox::getTextPixelHeight works properly
|
|
const LLRect local_rect = getLocalRect();
|
|
mStatusTextBox->setShape(local_rect);
|
|
|
|
// get preferable text height...
|
|
S32 pixel_height = mStatusTextBox->getTextPixelHeight();
|
|
bool height_changed = local_rect.getHeight() != pixel_height;
|
|
if (height_changed)
|
|
{
|
|
// ... if it does not match current height, lets rearrange current view.
|
|
// This will indirectly call ::arrange and reshape of the status textbox.
|
|
// We should call this method to also notify parent about required rect.
|
|
// See EXT-7564, EXT-7047.
|
|
arrangeFromRoot();
|
|
}
|
|
}
|
|
|
|
LLView::draw();
|
|
|
|
mDragAndDropThisFrame = FALSE;
|
|
}
|
|
|
|
void LLFolderView::finishRenamingItem( void )
|
|
{
|
|
if(!mRenamer)
|
|
{
|
|
return;
|
|
}
|
|
if( mRenameItem )
|
|
{
|
|
mRenameItem->rename( mRenamer->getText() );
|
|
}
|
|
|
|
closeRenamer();
|
|
|
|
// List is re-sorted alphabeticly, so scroll to make sure the selected item is visible.
|
|
scrollToShowSelection();
|
|
}
|
|
|
|
void LLFolderView::closeRenamer( void )
|
|
{
|
|
if (mRenamer && mRenamer->getVisible())
|
|
{
|
|
// Triggers onRenamerLost() that actually closes the renamer.
|
|
gFocusMgr.setTopCtrl( NULL );
|
|
}
|
|
}
|
|
|
|
bool isDescendantOfASelectedItem(LLFolderViewItem* item, const std::vector<LLFolderViewItem*>& selectedItems)
|
|
{
|
|
LLFolderViewItem* item_parent = dynamic_cast<LLFolderViewItem*>(item->getParent());
|
|
|
|
if (item_parent)
|
|
{
|
|
for(std::vector<LLFolderViewItem*>::const_iterator it = selectedItems.begin(); it != selectedItems.end(); ++it)
|
|
{
|
|
const LLFolderViewItem* const selected_item = (*it);
|
|
|
|
LLFolderViewItem* parent = item_parent;
|
|
|
|
while (parent)
|
|
{
|
|
if (selected_item == parent)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
parent = dynamic_cast<LLFolderViewItem*>(parent->getParent());
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void LLFolderView::removeSelectedItems( void )
|
|
{
|
|
if(getVisible() && getEnabled())
|
|
{
|
|
// just in case we're removing the renaming item.
|
|
mRenameItem = NULL;
|
|
|
|
// create a temporary structure which we will use to remove
|
|
// items, since the removal will futz with internal data
|
|
// structures.
|
|
std::vector<LLFolderViewItem*> items;
|
|
S32 count = mSelectedItems.size();
|
|
if(count == 0) return;
|
|
LLFolderViewItem* item = NULL;
|
|
selected_items_t::iterator item_it;
|
|
for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it)
|
|
{
|
|
item = *item_it;
|
|
if (item && item->isRemovable())
|
|
{
|
|
items.push_back(item);
|
|
}
|
|
else
|
|
{
|
|
llinfos << "Cannot delete " << item->getName() << llendl;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// iterate through the new container.
|
|
count = items.size();
|
|
LLUUID new_selection_id;
|
|
if(count == 1)
|
|
{
|
|
LLFolderViewItem* item_to_delete = items[0];
|
|
LLFolderViewFolder* parent = item_to_delete->getParentFolder();
|
|
LLFolderViewItem* new_selection = item_to_delete->getNextOpenNode(FALSE);
|
|
if (!new_selection)
|
|
{
|
|
new_selection = item_to_delete->getPreviousOpenNode(FALSE);
|
|
}
|
|
if(parent)
|
|
{
|
|
if (parent->removeItem(item_to_delete))
|
|
{
|
|
// change selection on successful delete
|
|
if (new_selection)
|
|
{
|
|
setSelectionFromRoot(new_selection, new_selection->isOpen(), mParentPanel->hasFocus());
|
|
}
|
|
else
|
|
{
|
|
setSelectionFromRoot(NULL, mParentPanel->hasFocus());
|
|
}
|
|
}
|
|
}
|
|
arrangeAll();
|
|
}
|
|
else if (count > 1)
|
|
{
|
|
LLDynamicArray<LLFolderViewEventListener*> listeners;
|
|
LLFolderViewEventListener* listener;
|
|
LLFolderViewItem* last_item = items[count - 1];
|
|
LLFolderViewItem* new_selection = last_item->getNextOpenNode(FALSE);
|
|
while(new_selection && new_selection->isSelected())
|
|
{
|
|
new_selection = new_selection->getNextOpenNode(FALSE);
|
|
}
|
|
if (!new_selection)
|
|
{
|
|
new_selection = last_item->getPreviousOpenNode(FALSE);
|
|
while (new_selection && (new_selection->isSelected() || isDescendantOfASelectedItem(new_selection, items)))
|
|
{
|
|
new_selection = new_selection->getPreviousOpenNode(FALSE);
|
|
}
|
|
}
|
|
if (new_selection)
|
|
{
|
|
setSelectionFromRoot(new_selection, new_selection->isOpen(), mParentPanel->hasFocus());
|
|
}
|
|
else
|
|
{
|
|
setSelectionFromRoot(NULL, mParentPanel->hasFocus());
|
|
}
|
|
|
|
for(S32 i = 0; i < count; ++i)
|
|
{
|
|
listener = items[i]->getListener();
|
|
if(listener && (listeners.find(listener) == LLDynamicArray<LLFolderViewEventListener*>::FAIL))
|
|
{
|
|
listeners.put(listener);
|
|
}
|
|
}
|
|
listener = listeners.get(0);
|
|
if(listener)
|
|
{
|
|
listener->removeBatch(listeners);
|
|
}
|
|
}
|
|
arrangeAll();
|
|
scrollToShowSelection();
|
|
}
|
|
}
|
|
|
|
// open the selected item.
|
|
void LLFolderView::openSelectedItems( void )
|
|
{
|
|
if(getVisible() && getEnabled())
|
|
{
|
|
if (mSelectedItems.size() == 1)
|
|
{
|
|
mSelectedItems.front()->openItem();
|
|
}
|
|
else
|
|
{
|
|
S32 left, top;
|
|
gFloaterView->getNewFloaterPosition(&left, &top);
|
|
LLMultiPreview* multi_previewp = new LLMultiPreview(LLRect(left, top, left + 300, top - 100));
|
|
gFloaterView->getNewFloaterPosition(&left, &top);
|
|
LLMultiProperties* multi_propertiesp = new LLMultiProperties(LLRect(left, top, left + 300, top - 100));
|
|
|
|
selected_items_t::iterator item_it;
|
|
for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it)
|
|
{
|
|
// IT_{OBJECT,ATTACHMENT} creates LLProperties
|
|
// floaters; others create LLPreviews. Put
|
|
// each one in the right type of container.
|
|
LLFolderViewEventListener* listener = (*item_it)->getListener();
|
|
bool is_prop = listener && (listener->getInventoryType() == LLInventoryType::IT_OBJECT || listener->getInventoryType() == LLInventoryType::IT_ATTACHMENT);
|
|
if (is_prop)
|
|
LLFloater::setFloaterHost(multi_propertiesp);
|
|
else
|
|
LLFloater::setFloaterHost(multi_previewp);
|
|
(*item_it)->openItem();
|
|
}
|
|
|
|
LLFloater::setFloaterHost(NULL);
|
|
// *NOTE: LLMulti* will safely auto-delete when open'd
|
|
// without any children.
|
|
multi_previewp->open();
|
|
multi_propertiesp->open();
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLFolderView::propertiesSelectedItems( void )
|
|
{
|
|
if(getVisible() && getEnabled())
|
|
{
|
|
if (mSelectedItems.size() == 1)
|
|
{
|
|
LLFolderViewItem* folder_item = mSelectedItems.front();
|
|
if(!folder_item) return;
|
|
folder_item->getListener()->showProperties();
|
|
}
|
|
else
|
|
{
|
|
S32 left, top;
|
|
gFloaterView->getNewFloaterPosition(&left, &top);
|
|
|
|
LLMultiProperties* multi_propertiesp = new LLMultiProperties(LLRect(left, top, left + 100, top - 100));
|
|
|
|
LLFloater::setFloaterHost(multi_propertiesp);
|
|
|
|
selected_items_t::iterator item_it;
|
|
for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it)
|
|
{
|
|
(*item_it)->getListener()->showProperties();
|
|
}
|
|
|
|
LLFloater::setFloaterHost(NULL);
|
|
multi_propertiesp->open(); /* Flawfinder: ignore */
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLFolderView::changeType(LLInventoryModel *model, LLFolderType::EType new_folder_type)
|
|
{
|
|
LLFolderBridge *folder_bridge = LLFolderBridge::sSelf.get();
|
|
|
|
if (!folder_bridge) return;
|
|
LLViewerInventoryCategory *cat = folder_bridge->getCategory();
|
|
if (!cat) return;
|
|
cat->changeType(new_folder_type);
|
|
}
|
|
void LLFolderView::autoOpenItem( LLFolderViewFolder* item )
|
|
{
|
|
if ((mAutoOpenItems.check() == item) ||
|
|
(mAutoOpenItems.getDepth() >= (U32)AUTO_OPEN_STACK_DEPTH) ||
|
|
item->isOpen())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// close auto-opened folders
|
|
LLFolderViewFolder* close_item = mAutoOpenItems.check();
|
|
while (close_item && close_item != item->getParentFolder())
|
|
{
|
|
mAutoOpenItems.pop();
|
|
close_item->setOpenArrangeRecursively(FALSE);
|
|
close_item = mAutoOpenItems.check();
|
|
}
|
|
|
|
item->requestArrange();
|
|
|
|
mAutoOpenItems.push(item);
|
|
|
|
item->setOpen(TRUE);
|
|
LLRect content_rect = mScrollContainer->getContentWindowRect();
|
|
LLRect constraint_rect(0,content_rect.getHeight(), content_rect.getWidth(), 0);
|
|
scrollToShowItem(item, constraint_rect);
|
|
}
|
|
|
|
void LLFolderView::closeAutoOpenedFolders()
|
|
{
|
|
while (mAutoOpenItems.check())
|
|
{
|
|
LLFolderViewFolder* close_item = mAutoOpenItems.pop();
|
|
close_item->setOpen(FALSE);
|
|
}
|
|
|
|
if (mAutoOpenCandidate)
|
|
{
|
|
mAutoOpenCandidate->setAutoOpenCountdown(0.f);
|
|
}
|
|
mAutoOpenCandidate = NULL;
|
|
mAutoOpenTimer.stop();
|
|
}
|
|
|
|
BOOL LLFolderView::autoOpenTest(LLFolderViewFolder* folder)
|
|
{
|
|
if (folder && mAutoOpenCandidate == folder)
|
|
{
|
|
if (mAutoOpenTimer.getStarted())
|
|
{
|
|
if (!mAutoOpenCandidate->isOpen())
|
|
{
|
|
mAutoOpenCandidate->setAutoOpenCountdown(clamp_rescale(mAutoOpenTimer.getElapsedTimeF32(), 0.f, sAutoOpenTime, 0.f, 1.f));
|
|
}
|
|
if (mAutoOpenTimer.getElapsedTimeF32() > sAutoOpenTime)
|
|
{
|
|
autoOpenItem(folder);
|
|
mAutoOpenTimer.stop();
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
// otherwise new candidate, restart timer
|
|
if (mAutoOpenCandidate)
|
|
{
|
|
mAutoOpenCandidate->setAutoOpenCountdown(0.f);
|
|
}
|
|
mAutoOpenCandidate = folder;
|
|
mAutoOpenTimer.start();
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL LLFolderView::canCopy() const
|
|
{
|
|
if (!(getVisible() && getEnabled() && (mSelectedItems.size() > 0)))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
for (selected_items_t::const_iterator selected_it = mSelectedItems.begin(); selected_it != mSelectedItems.end(); ++selected_it)
|
|
{
|
|
const LLFolderViewItem* item = *selected_it;
|
|
if (!item->getListener()->isItemCopyable())
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
// copy selected item
|
|
void LLFolderView::copy()
|
|
{
|
|
// *NOTE: total hack to clear the inventory clipboard
|
|
LLInventoryClipboard::instance().reset();
|
|
S32 count = mSelectedItems.size();
|
|
if(getVisible() && getEnabled() && (count > 0))
|
|
{
|
|
LLFolderViewEventListener* listener = NULL;
|
|
selected_items_t::iterator item_it;
|
|
for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it)
|
|
{
|
|
listener = (*item_it)->getListener();
|
|
if(listener)
|
|
{
|
|
listener->copyToClipboard();
|
|
}
|
|
}
|
|
}
|
|
mSearchString.clear();
|
|
}
|
|
|
|
BOOL LLFolderView::canCut() const
|
|
{
|
|
if (!(getVisible() && getEnabled() && (mSelectedItems.size() > 0)))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
for (selected_items_t::const_iterator selected_it = mSelectedItems.begin(); selected_it != mSelectedItems.end(); ++selected_it)
|
|
{
|
|
const LLFolderViewItem* item = *selected_it;
|
|
const LLFolderViewEventListener* listener = item->getListener();
|
|
|
|
if (!listener || !listener->isItemRemovable())
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
void LLFolderView::cut()
|
|
{
|
|
// clear the inventory clipboard
|
|
LLInventoryClipboard::instance().reset();
|
|
S32 count = mSelectedItems.size();
|
|
if(getVisible() && getEnabled() && (count > 0))
|
|
{
|
|
LLFolderViewEventListener* listener = NULL;
|
|
selected_items_t::iterator item_it;
|
|
for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it)
|
|
{
|
|
listener = (*item_it)->getListener();
|
|
if(listener)
|
|
{
|
|
listener->cutToClipboard();
|
|
}
|
|
}
|
|
}
|
|
mSearchString.clear();
|
|
}
|
|
|
|
BOOL LLFolderView::canPaste() const
|
|
{
|
|
if (mSelectedItems.empty())
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if(getVisible() && getEnabled())
|
|
{
|
|
for (selected_items_t::const_iterator item_it = mSelectedItems.begin();
|
|
item_it != mSelectedItems.end(); ++item_it)
|
|
{
|
|
// *TODO: only check folders and parent folders of items
|
|
const LLFolderViewItem* item = (*item_it);
|
|
const LLFolderViewEventListener* listener = item->getListener();
|
|
if(!listener || !listener->isClipboardPasteable())
|
|
{
|
|
const LLFolderViewFolder* folderp = item->getParentFolder();
|
|
listener = folderp->getListener();
|
|
if (!listener || !listener->isClipboardPasteable())
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
// paste selected item
|
|
void LLFolderView::paste()
|
|
{
|
|
if(getVisible() && getEnabled())
|
|
{
|
|
// find set of unique folders to paste into
|
|
std::set<LLFolderViewItem*> folder_set;
|
|
|
|
selected_items_t::iterator selected_it;
|
|
for (selected_it = mSelectedItems.begin(); selected_it != mSelectedItems.end(); ++selected_it)
|
|
{
|
|
LLFolderViewItem* item = *selected_it;
|
|
LLFolderViewEventListener* listener = item->getListener();
|
|
if (listener->getInventoryType() != LLInventoryType::IT_CATEGORY)
|
|
{
|
|
item = item->getParentFolder();
|
|
}
|
|
folder_set.insert(item);
|
|
}
|
|
|
|
std::set<LLFolderViewItem*>::iterator set_iter;
|
|
for(set_iter = folder_set.begin(); set_iter != folder_set.end(); ++set_iter)
|
|
{
|
|
LLFolderViewEventListener* listener = (*set_iter)->getListener();
|
|
if(listener && listener->isClipboardPasteable())
|
|
{
|
|
listener->pasteFromClipboard();
|
|
}
|
|
}
|
|
}
|
|
mSearchString.clear();
|
|
}
|
|
|
|
// public rename functionality - can only start the process
|
|
void LLFolderView::startRenamingSelectedItem( void )
|
|
{
|
|
// make sure selection is visible
|
|
scrollToShowSelection();
|
|
|
|
S32 count = mSelectedItems.size();
|
|
LLFolderViewItem* item = NULL;
|
|
if(count > 0)
|
|
{
|
|
item = mSelectedItems.front();
|
|
}
|
|
if(getVisible() && getEnabled() && (count == 1) && item && item->getListener() &&
|
|
item->getListener()->isItemRenameable())
|
|
{
|
|
mRenameItem = item;
|
|
|
|
updateRenamerPosition();
|
|
|
|
|
|
mRenamer->setText(item->getName());
|
|
mRenamer->selectAll();
|
|
mRenamer->setVisible( TRUE );
|
|
// set focus will fail unless item is visible
|
|
mRenamer->setFocus( TRUE );
|
|
mRenamer->setTopLostCallback(boost::bind(&LLFolderView::onRenamerLost, this));
|
|
gFocusMgr.setTopCtrl( mRenamer );
|
|
}
|
|
}
|
|
|
|
BOOL LLFolderView::handleKeyHere( KEY key, MASK mask )
|
|
{
|
|
BOOL handled = FALSE;
|
|
|
|
// SL-51858: Key presses are not being passed to the Popup menu.
|
|
// A proper fix is non-trivial so instead just close the menu.
|
|
LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get();
|
|
if (menu && menu->isOpen())
|
|
{
|
|
LLMenuGL::sMenuContainer->hideMenus();
|
|
}
|
|
|
|
LLView *item = NULL;
|
|
if (getChildCount() > 0)
|
|
{
|
|
item = *(getChildList()->begin());
|
|
}
|
|
|
|
switch( key )
|
|
{
|
|
case KEY_F2:
|
|
mSearchString.clear();
|
|
startRenamingSelectedItem();
|
|
handled = TRUE;
|
|
break;
|
|
|
|
case KEY_RETURN:
|
|
if (mask == MASK_NONE)
|
|
{
|
|
if( mRenameItem && mRenamer->getVisible() )
|
|
{
|
|
finishRenamingItem();
|
|
mSearchString.clear();
|
|
handled = TRUE;
|
|
}
|
|
else
|
|
{
|
|
LLFolderView::openSelectedItems();
|
|
handled = TRUE;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case KEY_ESCAPE:
|
|
if( mRenameItem && mRenamer->getVisible() )
|
|
{
|
|
closeRenamer();
|
|
handled = TRUE;
|
|
}
|
|
mSearchString.clear();
|
|
break;
|
|
|
|
case KEY_PAGE_UP:
|
|
mSearchString.clear();
|
|
mScrollContainer->pageUp(30);
|
|
handled = TRUE;
|
|
break;
|
|
|
|
case KEY_PAGE_DOWN:
|
|
mSearchString.clear();
|
|
mScrollContainer->pageDown(30);
|
|
handled = TRUE;
|
|
break;
|
|
|
|
case KEY_HOME:
|
|
mSearchString.clear();
|
|
mScrollContainer->goToTop();
|
|
handled = TRUE;
|
|
break;
|
|
|
|
case KEY_END:
|
|
mSearchString.clear();
|
|
mScrollContainer->goToBottom();
|
|
break;
|
|
|
|
case KEY_DOWN:
|
|
if((mSelectedItems.size() > 0) && mScrollContainer)
|
|
{
|
|
LLFolderViewItem* last_selected = getCurSelectedItem();
|
|
|
|
if (!mKeyboardSelection)
|
|
{
|
|
setSelection(last_selected, FALSE, TRUE);
|
|
mKeyboardSelection = TRUE;
|
|
}
|
|
|
|
LLFolderViewItem* next = NULL;
|
|
if (mask & MASK_SHIFT)
|
|
{
|
|
// don't shift select down to children of folders (they are implicitly selected through parent)
|
|
next = last_selected->getNextOpenNode(FALSE);
|
|
if (next)
|
|
{
|
|
if (next->isSelected())
|
|
{
|
|
// shrink selection
|
|
changeSelectionFromRoot(last_selected, FALSE);
|
|
}
|
|
else if (last_selected->getParentFolder() == next->getParentFolder())
|
|
{
|
|
// grow selection
|
|
changeSelectionFromRoot(next, TRUE);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
next = last_selected->getNextOpenNode();
|
|
if( next )
|
|
{
|
|
if (next == last_selected)
|
|
{
|
|
//special case for LLAccordionCtrl
|
|
if(notifyParent(LLSD().with("action","select_next")) > 0 )//message was processed
|
|
{
|
|
clearSelection();
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
setSelection( next, FALSE, TRUE );
|
|
}
|
|
else
|
|
{
|
|
//special case for LLAccordionCtrl
|
|
if(notifyParent(LLSD().with("action","select_next")) > 0 )//message was processed
|
|
{
|
|
clearSelection();
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
}
|
|
scrollToShowSelection();
|
|
mSearchString.clear();
|
|
handled = TRUE;
|
|
}
|
|
break;
|
|
|
|
case KEY_UP:
|
|
if((mSelectedItems.size() > 0) && mScrollContainer)
|
|
{
|
|
LLFolderViewItem* last_selected = mSelectedItems.back();
|
|
|
|
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)
|
|
{
|
|
// If case we are in accordion tab notify parent to go to the previous accordion
|
|
if(notifyParent(LLSD().with("action","select_prev")) > 0 )//message was processed
|
|
{
|
|
clearSelection();
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
setSelection( prev, FALSE, TRUE );
|
|
}
|
|
}
|
|
scrollToShowSelection();
|
|
mSearchString.clear();
|
|
|
|
handled = TRUE;
|
|
}
|
|
break;
|
|
|
|
case KEY_RIGHT:
|
|
if(mSelectedItems.size())
|
|
{
|
|
LLFolderViewItem* last_selected = getCurSelectedItem();
|
|
last_selected->setOpen( TRUE );
|
|
mSearchString.clear();
|
|
handled = TRUE;
|
|
}
|
|
break;
|
|
|
|
case KEY_LEFT:
|
|
if(mSelectedItems.size())
|
|
{
|
|
LLFolderViewItem* last_selected = getCurSelectedItem();
|
|
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 && mParentPanel->hasFocus())
|
|
{
|
|
if (key == KEY_BACKSPACE)
|
|
{
|
|
mSearchTimer.reset();
|
|
if (mSearchString.size())
|
|
{
|
|
mSearchString.erase(mSearchString.size() - 1, 1);
|
|
}
|
|
search(getCurSelectedItem(), mSearchString, FALSE);
|
|
handled = TRUE;
|
|
}
|
|
}
|
|
|
|
return handled;
|
|
}
|
|
|
|
|
|
BOOL LLFolderView::handleUnicodeCharHere(llwchar uni_char)
|
|
{
|
|
if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (uni_char > 0x7f)
|
|
{
|
|
llwarns << "LLFolderView::handleUnicodeCharHere - Don't handle non-ascii yet, aborting" << llendl;
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL handled = FALSE;
|
|
if (mParentPanel->hasFocus())
|
|
{
|
|
// SL-51858: Key presses are not being passed to the Popup menu.
|
|
// A proper fix is non-trivial so instead just close the menu.
|
|
LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get();
|
|
if (menu && menu->isOpen())
|
|
{
|
|
LLMenuGL::sMenuContainer->hideMenus();
|
|
}
|
|
|
|
//do text search
|
|
if (mSearchTimer.getElapsedTimeF32() > gSavedSettings.getF32("TypeAheadTimeout"))
|
|
{
|
|
mSearchString.clear();
|
|
}
|
|
mSearchTimer.reset();
|
|
if (mSearchString.size() < 128)
|
|
{
|
|
mSearchString += uni_char;
|
|
}
|
|
search(getCurSelectedItem(), mSearchString, FALSE);
|
|
|
|
handled = TRUE;
|
|
}
|
|
|
|
return handled;
|
|
}
|
|
|
|
|
|
BOOL LLFolderView::canDoDelete() const
|
|
{
|
|
if (mSelectedItems.size() == 0) return FALSE;
|
|
|
|
for (selected_items_t::const_iterator item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it)
|
|
{
|
|
if (!(*item_it)->getListener()->isItemRemovable())
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
void LLFolderView::doDelete()
|
|
{
|
|
if(mSelectedItems.size() > 0)
|
|
{
|
|
removeSelectedItems();
|
|
}
|
|
}
|
|
|
|
|
|
BOOL LLFolderView::handleMouseDown( S32 x, S32 y, MASK mask )
|
|
{
|
|
mKeyboardSelection = FALSE;
|
|
mSearchString.clear();
|
|
|
|
mParentPanel->setFocus(TRUE);
|
|
|
|
LLEditMenuHandler::gEditMenuHandler = this;
|
|
|
|
return LLView::handleMouseDown( x, y, mask );
|
|
}
|
|
|
|
BOOL LLFolderView::search(LLFolderViewItem* first_item, const std::string &search_string, BOOL backward)
|
|
{
|
|
// get first selected item
|
|
LLFolderViewItem* search_item = first_item;
|
|
|
|
// make sure search string is upper case
|
|
std::string upper_case_string = search_string;
|
|
LLStringUtil::toUpper(upper_case_string);
|
|
|
|
// if nothing selected, select first item in folder
|
|
if (!search_item)
|
|
{
|
|
// start from first item
|
|
search_item = getNextFromChild(NULL);
|
|
}
|
|
|
|
// search over all open nodes for first substring match (with wrapping)
|
|
BOOL found = FALSE;
|
|
LLFolderViewItem* original_search_item = search_item;
|
|
do
|
|
{
|
|
// wrap at end
|
|
if (!search_item)
|
|
{
|
|
if (backward)
|
|
{
|
|
search_item = getPreviousFromChild(NULL);
|
|
}
|
|
else
|
|
{
|
|
search_item = getNextFromChild(NULL);
|
|
}
|
|
if (!search_item || search_item == original_search_item)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
std::string current_item_label(search_item->getSearchableLabel());
|
|
S32 search_string_length = llmin(upper_case_string.size(), current_item_label.size());
|
|
if (!current_item_label.compare(0, search_string_length, upper_case_string))
|
|
{
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
if (backward)
|
|
{
|
|
search_item = search_item->getPreviousOpenNode();
|
|
}
|
|
else
|
|
{
|
|
search_item = search_item->getNextOpenNode();
|
|
}
|
|
|
|
} while(search_item != original_search_item);
|
|
|
|
|
|
if (found)
|
|
{
|
|
setSelection(search_item, FALSE, TRUE);
|
|
scrollToShowSelection();
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
BOOL LLFolderView::handleDoubleClick( S32 x, S32 y, MASK mask )
|
|
{
|
|
// skip LLFolderViewFolder::handleDoubleClick()
|
|
return LLView::handleDoubleClick( x, y, mask );
|
|
}
|
|
|
|
BOOL LLFolderView::handleRightMouseDown( S32 x, S32 y, MASK mask )
|
|
{
|
|
// all user operations move keyboard focus to inventory
|
|
// this way, we know when to stop auto-updating a search
|
|
mParentPanel->setFocus(TRUE);
|
|
|
|
BOOL handled = childrenHandleRightMouseDown(x, y, mask) != NULL;
|
|
S32 count = mSelectedItems.size();
|
|
LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get();
|
|
if ( handled
|
|
&& ( count > 0 && (hasVisibleChildren() || mFilter->getShowFolderState() == LLInventoryFilter::SHOW_ALL_FOLDERS) ) // show menu only if selected items are visible
|
|
&& menu )
|
|
{
|
|
updateMenuOptions(menu);
|
|
|
|
menu->updateParent(LLMenuGL::sMenuContainer);
|
|
LLMenuGL::showPopup(this, menu, x, y);
|
|
//menu->needsArrange(); // update menu height if needed
|
|
}
|
|
else
|
|
{
|
|
if(menu && menu->getVisible())
|
|
{
|
|
menu->setVisible(FALSE);
|
|
}
|
|
setSelection(NULL, FALSE, TRUE);
|
|
}
|
|
return handled;
|
|
}
|
|
|
|
// Add "--no options--" if the menu is completely blank.
|
|
BOOL LLFolderView::addNoOptions(LLMenuGL* menu) const
|
|
{
|
|
const std::string nooptions_str = "--no options--";
|
|
LLView *nooptions_item = NULL;
|
|
|
|
const LLView::child_list_t *list = menu->getChildList();
|
|
for (LLView::child_list_t::const_iterator itor = list->begin();
|
|
itor != list->end();
|
|
++itor)
|
|
{
|
|
LLView *menu_item = (*itor);
|
|
if (menu_item->getVisible())
|
|
{
|
|
return FALSE;
|
|
}
|
|
std::string name = menu_item->getName();
|
|
if (menu_item->getName() == nooptions_str)
|
|
{
|
|
nooptions_item = menu_item;
|
|
}
|
|
}
|
|
if (nooptions_item)
|
|
{
|
|
nooptions_item->setVisible(TRUE);
|
|
nooptions_item->setEnabled(FALSE);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL LLFolderView::handleHover( S32 x, S32 y, MASK mask )
|
|
{
|
|
return LLView::handleHover( x, y, mask );
|
|
}
|
|
|
|
BOOL LLFolderView::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop,
|
|
EDragAndDropType cargo_type,
|
|
void* cargo_data,
|
|
EAcceptance* accept,
|
|
std::string& tooltip_msg)
|
|
{
|
|
mDragAndDropThisFrame = TRUE;
|
|
// have children handle it first
|
|
BOOL handled = LLView::handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data,
|
|
accept, tooltip_msg);
|
|
|
|
// when drop is not handled by child, it should be handled
|
|
// by the folder which is the hierarchy root.
|
|
if (!handled)
|
|
{
|
|
if (getListener()->getUUID().notNull())
|
|
{
|
|
handled = LLFolderViewFolder::handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg);
|
|
}
|
|
else
|
|
{
|
|
if (!mFolders.empty())
|
|
{
|
|
// dispatch to last folder as a hack to support "Contents" folder in object inventory
|
|
handled = mFolders.back()->handleDragAndDropFromChild(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()
|
|
{
|
|
closeRenamer();
|
|
if (mPopupMenuHandle.get()) mPopupMenuHandle.get()->die();
|
|
mPopupMenuHandle = LLHandle<LLView>();
|
|
mScrollContainer = NULL;
|
|
mRenameItem = NULL;
|
|
mRenamer = NULL;
|
|
mStatusTextBox = NULL;
|
|
|
|
clearSelection();
|
|
LLView::deleteAllChildren();
|
|
}
|
|
|
|
void LLFolderView::scrollToShowSelection()
|
|
{
|
|
// If items are filtered while background fetch is in progress
|
|
// scrollbar resets to the first filtered item. See EXT-3981.
|
|
// However we allow scrolling for folder views with mAutoSelectOverride
|
|
// (used in Places SP) as an exception because the selection in them
|
|
// is not reset during items filtering. See STORM-133.
|
|
if ( (!LLInventoryModelBackgroundFetch::instance().folderFetchActive() || mAutoSelectOverride)
|
|
&& mSelectedItems.size() )
|
|
{
|
|
mNeedsScroll = TRUE;
|
|
}
|
|
}
|
|
|
|
// If the parent is scroll containter, scroll it to make the selection
|
|
// is maximally visible.
|
|
void LLFolderView::scrollToShowItem(LLFolderViewItem* item, const LLRect& constraint_rect)
|
|
{
|
|
if (!mScrollContainer) return;
|
|
|
|
// don't scroll to items when mouse is being used to scroll/drag and drop
|
|
if (gFocusMgr.childHasMouseCapture(mScrollContainer))
|
|
{
|
|
mNeedsScroll = FALSE;
|
|
return;
|
|
}
|
|
|
|
// if item exists and is in visible portion of parent folder...
|
|
if(item)
|
|
{
|
|
LLRect local_rect = item->getLocalRect();
|
|
LLRect item_scrolled_rect; // item position relative to display area of scroller
|
|
LLRect visible_doc_rect = mScrollContainer->getVisibleContentRect();
|
|
|
|
S32 icon_height = mIcon.isNull() ? 0 : mIcon->getHeight();
|
|
S32 label_height = llround(getLabelFontForStyle(mLabelStyle)->getLineHeight());
|
|
// when navigating with keyboard, only move top of opened folder on screen, otherwise show whole folder
|
|
S32 max_height_to_show = item->isOpen() && mScrollContainer->hasFocus() ? (llmax( icon_height, label_height ) + ICON_PAD) : local_rect.getHeight();
|
|
|
|
// get portion of item that we want to see...
|
|
LLRect item_local_rect = LLRect(item->getIndentation(),
|
|
local_rect.getHeight(),
|
|
llmin(MIN_ITEM_WIDTH_VISIBLE, local_rect.getWidth()),
|
|
llmax(0, local_rect.getHeight() - max_height_to_show));
|
|
|
|
LLRect item_doc_rect;
|
|
|
|
item->localRectToOtherView(item_local_rect, &item_doc_rect, this);
|
|
|
|
mScrollContainer->scrollToShowRect( item_doc_rect, constraint_rect );
|
|
|
|
}
|
|
}
|
|
|
|
LLRect LLFolderView::getVisibleRect()
|
|
{
|
|
S32 visible_height = mScrollContainer->getRect().getHeight();
|
|
S32 visible_width = mScrollContainer->getRect().getWidth();
|
|
LLRect visible_rect;
|
|
visible_rect.setLeftTopAndSize(-getRect().mLeft, visible_height - getRect().mBottom, visible_width, visible_height);
|
|
return visible_rect;
|
|
}
|
|
|
|
BOOL LLFolderView::getShowSelectionContext()
|
|
{
|
|
if (mShowSelectionContext)
|
|
{
|
|
return TRUE;
|
|
}
|
|
LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get();
|
|
if (menu && menu->getVisible())
|
|
{
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
void LLFolderView::setShowSingleSelection(BOOL show)
|
|
{
|
|
if (show != mShowSingleSelection)
|
|
{
|
|
mMultiSelectionFadeTimer.reset();
|
|
mShowSingleSelection = show;
|
|
}
|
|
}
|
|
|
|
void LLFolderView::addItemID(const LLUUID& id, LLFolderViewItem* itemp)
|
|
{
|
|
mItemMap[id] = itemp;
|
|
}
|
|
|
|
void LLFolderView::removeItemID(const LLUUID& id)
|
|
{
|
|
mItemMap.erase(id);
|
|
}
|
|
|
|
LLFastTimer::DeclareTimer FTM_GET_ITEM_BY_ID("Get FolderViewItem by ID");
|
|
LLFolderViewItem* LLFolderView::getItemByID(const LLUUID& id)
|
|
{
|
|
LLFastTimer _(FTM_GET_ITEM_BY_ID);
|
|
if (id == getListener()->getUUID())
|
|
{
|
|
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;
|
|
}
|
|
|
|
LLFolderViewFolder* LLFolderView::getFolderByID(const LLUUID& id)
|
|
{
|
|
if (id == getListener()->getUUID())
|
|
{
|
|
return this;
|
|
}
|
|
|
|
for (folders_t::iterator iter = mFolders.begin();
|
|
iter != mFolders.end();
|
|
++iter)
|
|
{
|
|
LLFolderViewFolder *folder = (*iter);
|
|
if (folder->getListener()->getUUID() == id)
|
|
{
|
|
return folder;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static LLFastTimer::DeclareTimer FTM_AUTO_SELECT("Open and Select");
|
|
static LLFastTimer::DeclareTimer FTM_INVENTORY("Inventory");
|
|
// Main idle routine
|
|
void LLFolderView::doIdle()
|
|
{
|
|
// If this is associated with the user's inventory, don't do anything
|
|
// until that inventory is loaded up.
|
|
const LLInventoryPanel *inventory_panel = dynamic_cast<LLInventoryPanel*>(mParentPanel);
|
|
if (inventory_panel && !inventory_panel->getIsViewsInitialized())
|
|
{
|
|
return;
|
|
}
|
|
|
|
LLFastTimer t2(FTM_INVENTORY);
|
|
|
|
BOOL debug_filters = gSavedSettings.getBOOL("DebugInventoryFilters");
|
|
if (debug_filters != getDebugFilters())
|
|
{
|
|
mDebugFilters = debug_filters;
|
|
arrangeAll();
|
|
}
|
|
|
|
mFilter->clearModified();
|
|
BOOL filter_modified_and_active = mCompletedFilterGeneration < mFilter->getCurrentGeneration() &&
|
|
mFilter->isNotDefault();
|
|
mNeedsAutoSelect = filter_modified_and_active &&
|
|
!(gFocusMgr.childHasKeyboardFocus(this) || gFocusMgr.getMouseCapture());
|
|
|
|
// filter to determine visiblity before arranging
|
|
filterFromRoot();
|
|
|
|
// automatically show matching items, and select first one
|
|
// do this every frame until user puts keyboard focus into the inventory window
|
|
// signaling the end of the automatic update
|
|
// only do this when mNeedsFilter is set, meaning filtered items have
|
|
// potentially changed
|
|
if (mNeedsAutoSelect)
|
|
{
|
|
LLFastTimer t3(FTM_AUTO_SELECT);
|
|
// select new item only if a filtered item not currently selected
|
|
LLFolderViewItem* selected_itemp = mSelectedItems.empty() ? NULL : mSelectedItems.back();
|
|
if ((selected_itemp && !selected_itemp->getFiltered()) && !mAutoSelectOverride)
|
|
{
|
|
// select first filtered item
|
|
LLSelectFirstFilteredItem filter;
|
|
applyFunctorRecursively(filter);
|
|
}
|
|
|
|
// Open filtered folders for folder views with mAutoSelectOverride=TRUE.
|
|
// Used by LLPlacesFolderView.
|
|
if (mAutoSelectOverride && !mFilter->getFilterSubString().empty())
|
|
{
|
|
LLOpenFilteredFolders filter;
|
|
applyFunctorRecursively(filter);
|
|
}
|
|
|
|
scrollToShowSelection();
|
|
}
|
|
|
|
// during filtering process, try to pin selected item's location on screen
|
|
// this will happen when searching your inventory and when new items arrive
|
|
if (filter_modified_and_active)
|
|
{
|
|
// calculate rectangle to pin item to at start of animated rearrange
|
|
if (!mPinningSelectedItem && !mSelectedItems.empty())
|
|
{
|
|
// lets pin it!
|
|
mPinningSelectedItem = TRUE;
|
|
|
|
LLRect visible_content_rect = mScrollContainer->getVisibleContentRect();
|
|
LLFolderViewItem* selected_item = mSelectedItems.back();
|
|
|
|
LLRect item_rect;
|
|
selected_item->localRectToOtherView(selected_item->getLocalRect(), &item_rect, this);
|
|
// if item is visible in scrolled region
|
|
if (visible_content_rect.overlaps(item_rect))
|
|
{
|
|
// then attempt to keep it in same place on screen
|
|
mScrollConstraintRect = item_rect;
|
|
mScrollConstraintRect.translate(-visible_content_rect.mLeft, -visible_content_rect.mBottom);
|
|
}
|
|
else
|
|
{
|
|
// otherwise we just want it onscreen somewhere
|
|
LLRect content_rect = mScrollContainer->getContentWindowRect();
|
|
mScrollConstraintRect.setOriginAndSize(0, 0, content_rect.getWidth(), content_rect.getHeight());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// stop pinning selected item after folders stop rearranging
|
|
if (!needsArrange())
|
|
{
|
|
mPinningSelectedItem = FALSE;
|
|
}
|
|
}
|
|
|
|
LLRect constraint_rect;
|
|
if (mPinningSelectedItem)
|
|
{
|
|
// use last known constraint rect for pinned item
|
|
constraint_rect = mScrollConstraintRect;
|
|
}
|
|
else
|
|
{
|
|
// during normal use (page up/page down, etc), just try to fit item on screen
|
|
LLRect content_rect = mScrollContainer->getContentWindowRect();
|
|
constraint_rect.setOriginAndSize(0, 0, content_rect.getWidth(), content_rect.getHeight());
|
|
}
|
|
|
|
|
|
BOOL is_visible = isInVisibleChain();
|
|
|
|
if ( is_visible )
|
|
{
|
|
sanitizeSelection();
|
|
if( needsArrange() )
|
|
{
|
|
arrangeFromRoot();
|
|
}
|
|
}
|
|
|
|
if (mSelectedItems.size() && mNeedsScroll)
|
|
{
|
|
scrollToShowItem(mSelectedItems.back(), constraint_rect);
|
|
// continue scrolling until animated layout change is done
|
|
if (!filter_modified_and_active
|
|
&& (!needsArrange() || !is_visible))
|
|
{
|
|
mNeedsScroll = FALSE;
|
|
}
|
|
}
|
|
|
|
if (mSignalSelectCallback)
|
|
{
|
|
//RN: we use keyboard focus as a proxy for user-explicit actions
|
|
BOOL take_keyboard_focus = (mSignalSelectCallback == SIGNAL_KEYBOARD_FOCUS);
|
|
mSelectSignal(mSelectedItems, take_keyboard_focus);
|
|
}
|
|
mSignalSelectCallback = FALSE;
|
|
}
|
|
|
|
|
|
//static
|
|
void LLFolderView::idle(void* user_data)
|
|
{
|
|
LLFolderView* self = (LLFolderView*)user_data;
|
|
if ( self )
|
|
{ // Do the real idle
|
|
self->doIdle();
|
|
}
|
|
}
|
|
|
|
void LLFolderView::dumpSelectionInformation()
|
|
{
|
|
llinfos << "LLFolderView::dumpSelectionInformation()" << llendl;
|
|
llinfos << "****************************************" << llendl;
|
|
selected_items_t::iterator item_it;
|
|
for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it)
|
|
{
|
|
llinfos << " " << (*item_it)->getName() << llendl;
|
|
}
|
|
llinfos << "****************************************" << llendl;
|
|
}
|
|
|
|
void LLFolderView::updateRenamerPosition()
|
|
{
|
|
if(mRenameItem)
|
|
{
|
|
// See also LLFolderViewItem::draw()
|
|
S32 x = ARROW_SIZE + TEXT_PAD + ICON_WIDTH + ICON_PAD + mRenameItem->getIndentation();
|
|
S32 y = mRenameItem->getRect().getHeight() - mRenameItem->getItemHeight() - RENAME_HEIGHT_PAD;
|
|
mRenameItem->localPointToScreen( x, y, &x, &y );
|
|
screenPointToLocal( x, y, &x, &y );
|
|
mRenamer->setOrigin( x, y );
|
|
|
|
LLRect scroller_rect(0, 0, gViewerWindow->getWindowWidthScaled(), 0);
|
|
if (mScrollContainer)
|
|
{
|
|
scroller_rect = mScrollContainer->getContentWindowRect();
|
|
}
|
|
|
|
S32 width = llmax(llmin(mRenameItem->getRect().getWidth() - x, scroller_rect.getWidth() - x - getRect().mLeft), MINIMUM_RENAMER_WIDTH);
|
|
S32 height = mRenameItem->getItemHeight() - RENAME_HEIGHT_PAD;
|
|
mRenamer->reshape( width, height, TRUE );
|
|
}
|
|
}
|
|
|
|
// Update visibility and availability (i.e. enabled/disabled) of context menu items.
|
|
void LLFolderView::updateMenuOptions(LLMenuGL* menu)
|
|
{
|
|
const LLView::child_list_t *list = menu->getChildList();
|
|
|
|
LLView::child_list_t::const_iterator menu_itor;
|
|
for (menu_itor = list->begin(); menu_itor != list->end(); ++menu_itor)
|
|
{
|
|
(*menu_itor)->setVisible(FALSE);
|
|
(*menu_itor)->pushVisible(TRUE);
|
|
(*menu_itor)->setEnabled(TRUE);
|
|
}
|
|
|
|
// Successively filter out invalid options
|
|
|
|
U32 flags = FIRST_SELECTED_ITEM;
|
|
for (selected_items_t::iterator item_itor = mSelectedItems.begin();
|
|
item_itor != mSelectedItems.end();
|
|
++item_itor)
|
|
{
|
|
LLFolderViewItem* selected_item = (*item_itor);
|
|
selected_item->buildContextMenu(*menu, flags);
|
|
flags = 0x0;
|
|
}
|
|
|
|
addNoOptions(menu);
|
|
}
|
|
|
|
// Refresh the context menu (that is already shown).
|
|
void LLFolderView::updateMenu()
|
|
{
|
|
LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get();
|
|
if (menu && menu->getVisible())
|
|
{
|
|
updateMenuOptions(menu);
|
|
menu->needsArrange(); // update menu height if needed
|
|
}
|
|
}
|
|
|
|
bool LLFolderView::selectFirstItem()
|
|
{
|
|
for (folders_t::iterator iter = mFolders.begin();
|
|
iter != mFolders.end();++iter)
|
|
{
|
|
LLFolderViewFolder* folder = (*iter );
|
|
if (folder->getVisible())
|
|
{
|
|
LLFolderViewItem* itemp = folder->getNextFromChild(0,true);
|
|
if(itemp)
|
|
setSelection(itemp,FALSE,TRUE);
|
|
return true;
|
|
}
|
|
|
|
}
|
|
for(items_t::iterator iit = mItems.begin();
|
|
iit != mItems.end(); ++iit)
|
|
{
|
|
LLFolderViewItem* itemp = (*iit);
|
|
if (itemp->getVisible())
|
|
{
|
|
setSelection(itemp,FALSE,TRUE);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
bool LLFolderView::selectLastItem()
|
|
{
|
|
for(items_t::reverse_iterator iit = mItems.rbegin();
|
|
iit != mItems.rend(); ++iit)
|
|
{
|
|
LLFolderViewItem* itemp = (*iit);
|
|
if (itemp->getVisible())
|
|
{
|
|
setSelection(itemp,FALSE,TRUE);
|
|
return true;
|
|
}
|
|
}
|
|
for (folders_t::reverse_iterator iter = mFolders.rbegin();
|
|
iter != mFolders.rend();++iter)
|
|
{
|
|
LLFolderViewFolder* folder = (*iter);
|
|
if (folder->getVisible())
|
|
{
|
|
LLFolderViewItem* itemp = folder->getPreviousFromChild(0,true);
|
|
if(itemp)
|
|
setSelection(itemp,FALSE,TRUE);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
S32 LLFolderView::notify(const LLSD& info)
|
|
{
|
|
if(info.has("action"))
|
|
{
|
|
std::string str_action = info["action"];
|
|
if(str_action == "select_first")
|
|
{
|
|
setFocus(true);
|
|
selectFirstItem();
|
|
scrollToShowSelection();
|
|
return 1;
|
|
|
|
}
|
|
else if(str_action == "select_last")
|
|
{
|
|
setFocus(true);
|
|
selectLastItem();
|
|
scrollToShowSelection();
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
///----------------------------------------------------------------------------
|
|
/// Local function definitions
|
|
///----------------------------------------------------------------------------
|
|
|
|
void LLFolderView::onRenamerLost()
|
|
{
|
|
if (mRenamer && mRenamer->getVisible())
|
|
{
|
|
mRenamer->setVisible(FALSE);
|
|
|
|
// will commit current name (which could be same as original name)
|
|
mRenamer->setFocus(FALSE);
|
|
}
|
|
|
|
if( mRenameItem )
|
|
{
|
|
setSelectionFromRoot( mRenameItem, TRUE );
|
|
mRenameItem = NULL;
|
|
}
|
|
}
|
|
|
|
LLInventoryFilter* LLFolderView::getFilter()
|
|
{
|
|
return mFilter;
|
|
}
|
|
|
|
void LLFolderView::setFilterPermMask( PermissionMask filter_perm_mask )
|
|
{
|
|
mFilter->setFilterPermissions(filter_perm_mask);
|
|
}
|
|
|
|
bool LLFolderView::getFilterWorn() const
|
|
{
|
|
return mFilter->getFilterWorn();
|
|
}
|
|
|
|
U32 LLFolderView::getFilterObjectTypes() const
|
|
{
|
|
return mFilter->getFilterObjectTypes();
|
|
}
|
|
|
|
PermissionMask LLFolderView::getFilterPermissions() const
|
|
{
|
|
return mFilter->getFilterPermissions();
|
|
}
|
|
|
|
BOOL LLFolderView::isFilterModified()
|
|
{
|
|
return mFilter->isNotDefault();
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|