summaryrefslogtreecommitdiff
path: root/indra/llui
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llui')
-rw-r--r--indra/llui/CMakeLists.txt6
-rw-r--r--indra/llui/llfolderview.cpp2078
-rw-r--r--indra/llui/llfolderview.h388
-rw-r--r--indra/llui/llfolderviewitem.cpp2032
-rw-r--r--indra/llui/llfolderviewitem.h420
-rw-r--r--indra/llui/llfolderviewmodel.cpp53
-rw-r--r--indra/llui/llfolderviewmodel.h353
7 files changed, 5330 insertions, 0 deletions
diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt
index b50ed2342d..a9ad0a3c0b 100644
--- a/indra/llui/CMakeLists.txt
+++ b/indra/llui/CMakeLists.txt
@@ -54,6 +54,9 @@ set(llui_SOURCE_FILES
llfloaterreglistener.cpp
llflyoutbutton.cpp
llfocusmgr.cpp
+ llfolderview.cpp
+ llfolderviewitem.cpp
+ llfolderviewmodel.cpp
llfunctorregistry.cpp
lliconctrl.cpp
llkeywords.cpp
@@ -154,6 +157,9 @@ set(llui_HEADER_FILES
llfloaterreglistener.h
llflyoutbutton.h
llfocusmgr.h
+ llfolderview.h
+ llfolderviewitem.h
+ llfolderviewmodel.h
llfunctorregistry.h
llhandle.h
llhelp.h
diff --git a/indra/llui/llfolderview.cpp b/indra/llui/llfolderview.cpp
new file mode 100644
index 0000000000..0d3bc44ae4
--- /dev/null
+++ b/indra/llui/llfolderview.cpp
@@ -0,0 +1,2078 @@
+/**
+ * @file llfolderview.cpp
+ * @brief Implementation of the folder view collection of classes.
+ *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "linden_common.h"
+
+#include "llfolderview.h"
+#include "llfolderviewmodel.h"
+#include "llclipboard.h" // *TODO: remove this once hack below gone.
+#include "llkeyboard.h"
+#include "lllineeditor.h"
+#include "llmenugl.h"
+#include "llpanel.h"
+#include "llscrollcontainer.h" // hack to allow scrolling
+#include "lltextbox.h"
+#include "lltrans.h"
+#include "llui.h"
+#include "lluictrlfactory.h"
+
+// Linden library includes
+#include "lldbstrings.h"
+#include "llfocusmgr.h"
+#include "llfontgl.h"
+#include "llgl.h"
+#include "llrender.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;
+
+//---------------------------------------------------------------------------
+
+// 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;
+};
+
+
+void LLCloseAllFoldersFunctor::doFolder(LLFolderViewFolder* folder)
+{
+ folder->setOpenArrangeRecursively(mOpen);
+}
+
+// Do nothing.
+void LLCloseAllFoldersFunctor::doItem(LLFolderViewItem* item)
+{ }
+
+///----------------------------------------------------------------------------
+/// Class LLFolderViewScrollContainer
+///----------------------------------------------------------------------------
+
+// virtual
+const LLRect LLFolderViewScrollContainer::getScrolledViewRect() const
+{
+ LLRect rect = LLRect::null;
+ if (mScrolledView)
+ {
+ LLFolderView* folder_view = dynamic_cast<LLFolderView*>(mScrolledView);
+ if (folder_view)
+ {
+ S32 height = folder_view->getRect().getHeight();
+
+ rect = mScrolledView->getRect();
+ rect.setLeftTopAndSize(rect.mLeft, rect.mTop, rect.getWidth(), height);
+ }
+ }
+
+ return rect;
+}
+
+LLFolderViewScrollContainer::LLFolderViewScrollContainer(const LLScrollContainer::Params& p)
+: LLScrollContainer(p)
+{}
+
+///----------------------------------------------------------------------------
+/// Class LLFolderView
+///----------------------------------------------------------------------------
+LLFolderView::Params::Params()
+: title("title"),
+ use_label_suffix("use_label_suffix"),
+ allow_multiselect("allow_multiselect", true),
+ show_empty_message("show_empty_message", true),
+ use_ellipses("use_ellipses", false)
+{
+ folder_indentation = -4;
+}
+
+
+// Default constructor
+LLFolderView::LLFolderView(const Params& p)
+: LLFolderViewFolder(p),
+ mScrollContainer( NULL ),
+ mPopupMenuHandle(),
+ mAllowMultiSelect(p.allow_multiselect),
+ mShowEmptyMessage(p.show_empty_message),
+ mShowFolderHierarchy(FALSE),
+ mRenameItem( NULL ),
+ mNeedsScroll( FALSE ),
+ mUseLabelSuffix(p.use_label_suffix),
+ mPinningSelectedItem(FALSE),
+ mNeedsAutoSelect( FALSE ),
+ mAutoSelectOverride(FALSE),
+ mNeedsAutoRename(FALSE),
+ mShowSelectionContext(FALSE),
+ mShowSingleSelection(FALSE),
+ mArrangeGeneration(0),
+ mSignalSelectCallback(0),
+ mMinWidth(0),
+ mDragAndDropThisFrame(FALSE),
+ mCallbackRegistrar(NULL),
+ mParentPanel(p.parent_panel),
+ mUseEllipses(p.use_ellipses),
+ mDraggingOverItem(NULL),
+ mStatusTextBox(NULL),
+ mShowItemLinkOverlays(p.show_item_link_overlays),
+ mViewModel(p.view_model)
+{
+ mViewModel->setFolderView(this);
+ mRoot = this;
+
+ LLRect rect = p.rect;
+ LLRect new_rect(rect.mLeft, rect.mBottom + getRect().getHeight(), rect.mLeft + getRect().getWidth(), rect.mBottom);
+ setRect( rect );
+ reshape(rect.getWidth(), rect.getHeight());
+ mAutoOpenItems.setDepth(AUTO_OPEN_STACK_DEPTH);
+ mAutoOpenCandidate = NULL;
+ mAutoOpenTimer.stop();
+ mKeyboardSelection = FALSE;
+ mIndentation = p.folder_indentation;
+
+ //clear label
+ // go ahead and render root folder as usual
+ // just make sure the label ("Inventory Folder") never shows up
+ mLabel = LLStringUtil::null;
+
+ // Escape is handled by reverting the rename, not commiting it (default behavior)
+ LLLineEditor::Params params;
+ params.name("ren");
+ params.rect(rect);
+ params.font(getLabelFontForStyle(LLFontGL::NORMAL));
+ params.max_length.bytes(DB_INV_ITEM_NAME_STR_LEN);
+ params.commit_callback.function(boost::bind(&LLFolderView::commitRename, this, _2));
+ params.prevalidate_callback(&LLTextValidate::validateASCIIPrintableNoPipe);
+ params.commit_on_focus_lost(true);
+ params.visible(false);
+ mRenamer = LLUICtrlFactory::create<LLLineEditor> (params);
+ addChild(mRenamer);
+
+ // Textbox
+ LLTextBox::Params text_p;
+ LLFontGL* font = getLabelFontForStyle(mLabelStyle);
+ LLRect new_r = LLRect(rect.mLeft + ICON_PAD,
+ rect.mTop - TEXT_PAD,
+ rect.mRight,
+ rect.mTop - TEXT_PAD - font->getLineHeight());
+ text_p.rect(new_r);
+ text_p.name(std::string(p.name));
+ text_p.font(font);
+ text_p.visible(false);
+ text_p.parse_urls(true);
+ text_p.wrap(true); // allow multiline text. See EXT-7564, EXT-7047
+ // set text padding the same as in People panel. EXT-7047, EXT-4837
+ text_p.h_pad(STATUS_TEXT_HPAD);
+ text_p.v_pad(STATUS_TEXT_VPAD);
+ mStatusTextBox = LLUICtrlFactory::create<LLTextBox> (text_p);
+ mStatusTextBox->setFollowsLeft();
+ mStatusTextBox->setFollowsTop();
+ //addChild(mStatusTextBox);
+
+
+ // make the popup menu available
+ LLMenuGL* menu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>("menu_inventory.xml", LLMenuGL::sMenuContainer, LLMenuHolderGL::child_registry_t::instance());
+ if (!menu)
+ {
+ menu = LLUICtrlFactory::getDefaultWidget<LLMenuGL>("inventory_menu");
+ }
+ menu->setBackgroundColor(LLUIColorTable::instance().getColor("MenuPopupBgColor"));
+ mPopupMenuHandle = menu->getHandle();
+
+ mViewModelItem->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();
+
+ if (mPopupMenuHandle.get()) mPopupMenuHandle.get()->die();
+
+ mAutoOpenItems.removeAllNodes();
+ clearSelection();
+ mItems.clear();
+ mFolders.clear();
+
+ delete mViewModel;
+ mViewModel = NULL;
+}
+
+BOOL LLFolderView::canFocusChildren() const
+{
+ return FALSE;
+}
+
+BOOL LLFolderView::addFolder( LLFolderViewFolder* folder)
+{
+ LLFolderViewFolder::addFolder(folder);
+
+ // TODO RN: enforce sort order of My Inventory followed by Library
+ //mFolders.remove(folder);
+ //if (((LLFolderViewModelItemInventory*)folder->getViewModelItem())->getUUID() == gInventory.getLibraryRootFolderID())
+ //{
+ // mFolders.push_back(folder);
+ //}
+ //else
+ //{
+ // mFolders.insert(mFolders.begin(), folder);
+ //}
+
+ 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);
+ }
+}
+
+// This view grows and shrinks to enclose all of its children items and folders.
+// *width should be 0
+// conform show folder state works
+S32 LLFolderView::arrange( S32* unused_width, S32* unused_height )
+{
+ mMinWidth = 0;
+ S32 target_height;
+
+ LLFolderViewFolder::arrange(&mMinWidth, &target_height);
+
+ LLRect scroll_rect = mScrollContainer->getContentWindowRect();
+ reshape( llmax(scroll_rect.getWidth(), mMinWidth), llround(mCurHeight) );
+
+ LLRect new_scroll_rect = mScrollContainer->getContentWindowRect();
+ if (new_scroll_rect.getWidth() != scroll_rect.getWidth())
+ {
+ reshape( llmax(scroll_rect.getWidth(), mMinWidth), llround(mCurHeight) );
+ }
+
+ // move item renamer text field to item's new position
+ updateRenamerPosition();
+
+ return llround(mTargetHeight);
+}
+
+static LLFastTimer::DeclareTimer FTM_FILTER("Filter Folder View");
+
+void LLFolderView::filter( LLFolderViewFilter& filter )
+{
+ LLFastTimer t2(FTM_FILTER);
+ filter.setFilterCount(llclamp(LLUI::sSettingGroups["config"]->getS32("FilterItemsPerFrame"), 1, 5000));
+
+ getViewModelItem()->filter(filter);
+}
+
+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(llround(mCurHeight), scroll_rect.getHeight());
+
+ // Restrict width within scroll container's width
+ if (mUseEllipses && mScrollContainer)
+ {
+ 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;
+}
+
+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();
+
+ 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->getViewModelItem()->potentiallyVisible(); // initialize from filter state for this item
+ // modify with parent open and filters states
+ LLFolderViewFolder* parent_folder = item->getParentFolder();
+ // Move up through parent folders and see what's visible
+ while(parent_folder)
+ {
+ visible = visible && parent_folder->isOpen() && parent_folder->getViewModelItem()->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
+ 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->getViewModelItem()->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();
+}
+
+std::set<LLFolderViewItem*> LLFolderView::getSelectionList() const
+{
+ std::set<LLFolderViewItem*> selection;
+ std::copy(mSelectedItems.begin(), mSelectedItems.end(), std::inserter(selection, selection.begin()));
+ return selection;
+}
+
+bool LLFolderView::startDrag()
+{
+ std::vector<LLFolderViewModelItem*> selected_items;
+ selected_items_t::iterator item_it;
+
+ if (!mSelectedItems.empty())
+ {
+ for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it)
+ {
+ selected_items.push_back((*item_it)->getViewModelItem());
+ }
+
+ return getFolderViewModel()->startDrag(selected_items);
+ }
+ return false;
+}
+
+void LLFolderView::commitRename( const LLSD& data )
+{
+ finishRenamingItem();
+}
+
+void LLFolderView::draw()
+{
+ //LLFontGL* font = getLabelFontForStyle(mLabelStyle);
+
+ // if cursor has moved off of me during drag and drop
+ // close all auto opened folders
+ if (!mDragAndDropThisFrame)
+ {
+ closeAutoOpenedFolders();
+ }
+
+ if (mSearchTimer.getElapsedTimeF32() > LLUI::sSettingGroups["config"]->getF32("TypeAheadTimeout") || !mSearchString.size())
+ {
+ mSearchString.clear();
+ }
+
+ if (hasVisibleChildren())
+ {
+ mStatusTextBox->setVisible( FALSE );
+ }
+ else if (mShowEmptyMessage)
+ {
+ mStatusTextBox->setValue(getFolderViewModel()->getStatusText());
+ 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.
+ S32 height = 0;
+ S32 width = 0;
+ S32 total_height = arrange( &width, &height );
+ notifyParent(LLSD().with("action", "size_changes").with("height", total_height));
+
+ LLUI::popMatrix();
+ LLUI::pushMatrix();
+ LLUI::translate((F32)getRect().mLeft, (F32)getRect().mBottom);
+ }
+ }
+
+ // skip over LLFolderViewFolder::draw since we don't want the folder icon, label,
+ // and arrow for the root folder
+ LLView::draw();
+
+ mDragAndDropThisFrame = FALSE;
+}
+
+void LLFolderView::finishRenamingItem( void )
+{
+ if(!mRenamer)
+ {
+ return;
+ }
+ if( mRenameItem )
+ {
+ mRenameItem->rename( mRenamer->getText() );
+ }
+
+ closeRenamer();
+
+ // List is re-sorted alphabetically, 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.
+ LLUI::removePopup(mRenamer);
+ }
+}
+
+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()
+{
+ 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 (item_to_delete->remove())
+ {
+ // change selection on successful delete
+ if (new_selection)
+ {
+ getRoot()->setSelection(new_selection, new_selection->isOpen(), mParentPanel->hasFocus());
+ }
+ else
+ {
+ getRoot()->setSelection(NULL, mParentPanel->hasFocus());
+ }
+ }
+ }
+ arrangeAll();
+ }
+ else if (count > 1)
+ {
+ LLDynamicArray<LLFolderViewModelItem*> listeners;
+ LLFolderViewModelItem* 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)
+ {
+ getRoot()->setSelection(new_selection, new_selection->isOpen(), mParentPanel->hasFocus());
+ }
+ else
+ {
+ getRoot()->setSelection(NULL, mParentPanel->hasFocus());
+ }
+
+ for(S32 i = 0; i < count; ++i)
+ {
+ listener = items[i]->getViewModelItem();
+ if(listener && (listeners.find(listener) == LLDynamicArray<LLFolderViewModelItem*>::FAIL))
+ {
+ listeners.put(listener);
+ }
+ }
+ listener = static_cast<LLFolderViewModelItem*>(listeners.get(0));
+ if(listener)
+ {
+ listener->removeBatch(listeners);
+ }
+ }
+ arrangeAll();
+ scrollToShowSelection();
+ }
+}
+
+// TODO RN: abstract
+// open the selected item.
+void LLFolderView::openSelectedItems( void )
+{
+ //TODO RN: get working again
+ //if(getVisible() && getEnabled())
+ //{
+ // if (mSelectedItems.size() == 1)
+ // {
+ // mSelectedItems.front()->openItem();
+ // }
+ // else
+ // {
+ // LLMultiPreview* multi_previewp = new LLMultiPreview();
+ // LLMultiProperties* multi_propertiesp = new LLMultiProperties();
+
+ // 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.
+ // LLFolderViewModelItemInventory* listener = static_cast<LLFolderViewModelItemInventory*>((*item_it)->getViewModelItem());
+ // 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);
+ // listener->openItem();
+ // }
+
+ // LLFloater::setFloaterHost(NULL);
+ // // *NOTE: LLMulti* will safely auto-delete when open'd
+ // // without any children.
+ // multi_previewp->openFloater(LLSD());
+ // multi_propertiesp->openFloater(LLSD());
+ // }
+ //}
+}
+
+void LLFolderView::propertiesSelectedItems( void )
+{
+ //TODO RN: get working again
+ //if(getVisible() && getEnabled())
+ //{
+ // if (mSelectedItems.size() == 1)
+ // {
+ // LLFolderViewItem* folder_item = mSelectedItems.front();
+ // if(!folder_item) return;
+ // folder_item->getViewModelItem()->showProperties();
+ // }
+ // else
+ // {
+ // LLMultiProperties* multi_propertiesp = new LLMultiProperties();
+
+ // LLFloater::setFloaterHost(multi_propertiesp);
+
+ // selected_items_t::iterator item_it;
+ // for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it)
+ // {
+ // (*item_it)->getViewModelItem()->showProperties();
+ // }
+
+ // LLFloater::setFloaterHost(NULL);
+ // multi_propertiesp->openFloater(LLSD());
+ // }
+ //}
+}
+
+
+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->getViewModelItem()->isItemCopyable())
+ {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+// copy selected item
+void LLFolderView::copy()
+{
+ // *NOTE: total hack to clear the inventory clipboard
+ LLClipboard::instance().reset();
+ S32 count = mSelectedItems.size();
+ if(getVisible() && getEnabled() && (count > 0))
+ {
+ LLFolderViewModelItem* listener = NULL;
+ selected_items_t::iterator item_it;
+ for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it)
+ {
+ listener = (*item_it)->getViewModelItem();
+ 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 LLFolderViewModelItem* listener = item->getViewModelItem();
+
+ if (!listener || !listener->isItemRemovable())
+ {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+void LLFolderView::cut()
+{
+ // clear the inventory clipboard
+ LLClipboard::instance().reset();
+ S32 count = mSelectedItems.size();
+ if(getVisible() && getEnabled() && (count > 0))
+ {
+ LLFolderViewModelItem* listener = NULL;
+ selected_items_t::iterator item_it;
+ for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it)
+ {
+ listener = (*item_it)->getViewModelItem();
+ if(listener)
+ {
+ listener->cutToClipboard();
+ listener->removeItem();
+ }
+ }
+ }
+ 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 LLFolderViewModelItem* listener = item->getViewModelItem();
+ if(!listener || !listener->isClipboardPasteable())
+ {
+ const LLFolderViewFolder* folderp = item->getParentFolder();
+ listener = folderp->getViewModelItem();
+ 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<LLFolderViewFolder*> folder_set;
+
+ selected_items_t::iterator selected_it;
+ for (selected_it = mSelectedItems.begin(); selected_it != mSelectedItems.end(); ++selected_it)
+ {
+ LLFolderViewItem* item = *selected_it;
+ LLFolderViewFolder* folder = dynamic_cast<LLFolderViewFolder*>(item);
+ if (folder == NULL)
+ {
+ item = item->getParentFolder();
+ }
+ folder_set.insert(folder);
+ }
+
+ std::set<LLFolderViewFolder*>::iterator set_iter;
+ for(set_iter = folder_set.begin(); set_iter != folder_set.end(); ++set_iter)
+ {
+ LLFolderViewModelItem* listener = (*set_iter)->getViewModelItem();
+ 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->getViewModelItem() &&
+ item->getViewModelItem()->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));
+ LLUI::addPopup(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
+ getRoot()->changeSelection(last_selected, FALSE);
+ }
+ else if (last_selected->getParentFolder() == next->getParentFolder())
+ {
+ // grow selection
+ getRoot()->changeSelection(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
+ getRoot()->changeSelection(last_selected, FALSE);
+ }
+ else if (last_selected->getParentFolder() == prev->getParentFolder())
+ {
+ // grow selection
+ getRoot()->changeSelection(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() > LLUI::sSettingGroups["config"]->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)->getViewModelItem()->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;
+ }
+ }
+
+ const std::string current_item_label(search_item->getViewModelItem()->getSearchableName());
+ 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()) ) // show menu only if selected items are visible
+ && menu )
+ {
+ if (mCallbackRegistrar)
+ mCallbackRegistrar->pushScope();
+
+ updateMenuOptions(menu);
+
+ menu->updateParent(LLMenuGL::sMenuContainer);
+ LLMenuGL::showPopup(this, menu, x, y);
+ if (mCallbackRegistrar)
+ mCallbackRegistrar->popScope();
+ }
+ 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)
+ {
+ handled = LLFolderViewFolder::handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg);
+ }
+
+ return handled;
+}
+
+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 ( mSelectedItems.size() )
+ {
+ mNeedsScroll = TRUE;
+ }
+}
+
+// If the parent is scroll container, 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 = 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;
+ }
+}
+
+static LLFastTimer::DeclareTimer FTM_AUTO_SELECT("Open and Select");
+static LLFastTimer::DeclareTimer FTM_INVENTORY("Inventory");
+
+// Main idle routine
+void LLFolderView::update()
+{
+ // If this is associated with the user's inventory, don't do anything
+ // until that inventory is loaded up.
+ LLFastTimer t2(FTM_INVENTORY);
+
+ if (getFolderViewModel()->getFilter()->isModified() && getFolderViewModel()->getFilter()->isNotDefault())
+ {
+ mNeedsAutoSelect = TRUE;
+ }
+ getFolderViewModel()->getFilter()->clearModified();
+
+ // filter to determine visibility before arranging
+ filter(*(getFolderViewModel()->getFilter()));
+
+ // automatically show matching items, and select first one if we had a selection
+ 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 (!mAutoSelectOverride && (!selected_itemp || !selected_itemp->getViewModelItem()->potentiallyVisible()))
+ {
+ // these are named variables to get around gcc not binding non-const references to rvalues
+ // and functor application is inherently non-const to allow for stateful functors
+ LLSelectFirstFilteredItem functor;
+ applyFunctorRecursively(functor);
+ }
+
+ // Open filtered folders for folder views with mAutoSelectOverride=TRUE.
+ // Used by LLPlacesFolderView.
+ if (getFolderViewModel()->getFilter()->showAllResults())
+ {
+ // these are named variables to get around gcc not binding non-const references to rvalues
+ // and functor application is inherently non-const to allow for stateful functors
+ LLOpenFilteredFolders functor;
+ applyFunctorRecursively(functor);
+ }
+
+ scrollToShowSelection();
+ }
+
+ BOOL filter_finished = getViewModelItem()->passedFilter()
+ && mViewModel->contentsReady();
+ if (filter_finished
+ || gFocusMgr.childHasKeyboardFocus(getParent()) // assume we are inside a scroll container
+ || gFocusMgr.childHasMouseCapture(getParent()))
+ {
+ // finishing the filter process, giving focus to the folder view, or dragging the scrollbar all stop the auto select process
+ mNeedsAutoSelect = FALSE;
+ }
+
+
+ // 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_finished)
+ {
+ // 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() )
+ {
+ S32 height = 0;
+ S32 width = 0;
+ S32 total_height = arrange( &width, &height );
+ notifyParent(LLSD().with("action", "size_changes").with("height", total_height));
+ }
+ }
+
+ if (mSelectedItems.size() && mNeedsScroll)
+ {
+ scrollToShowItem(mSelectedItems.back(), constraint_rect);
+ // continue scrolling until animated layout change is done
+ if (filter_finished
+ && (!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;
+}
+
+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, LLUI::getWindowSize().mV[VX], 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 )
+ {
+ setSelection( mRenameItem, TRUE );
+ mRenameItem = NULL;
+ }
+}
+
+S32 LLFolderView::getItemHeight()
+{
+ if(!hasVisibleChildren())
+ {
+ //We need to display status textbox, let's reserve some place for it
+ return llmax(0, mStatusTextBox->getTextPixelHeight());
+ }
+ return 0;
+}
diff --git a/indra/llui/llfolderview.h b/indra/llui/llfolderview.h
new file mode 100644
index 0000000000..ba37a11bbe
--- /dev/null
+++ b/indra/llui/llfolderview.h
@@ -0,0 +1,388 @@
+/**
+ * @file llfolderview.h
+ * @brief Definition of the folder view collection of classes.
+ *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+/**
+ *
+ * The folder view collection of classes provides an interface for
+ * making a 'folder view' similar to the way the a single pane file
+ * folder interface works.
+ *
+ */
+
+#ifndef LL_LLFOLDERVIEW_H
+#define LL_LLFOLDERVIEW_H
+
+#include "llfolderviewitem.h" // because LLFolderView is-a LLFolderViewFolder
+
+#include "lluictrl.h"
+#include "v4color.h"
+#include "stdenums.h"
+#include "lldepthstack.h"
+#include "lleditmenuhandler.h"
+#include "llfontgl.h"
+#include "llscrollcontainer.h"
+
+class LLFolderViewModelInterface;
+class LLFolderViewFolder;
+class LLFolderViewItem;
+class LLFolderViewFilter;
+class LLPanel;
+class LLLineEditor;
+class LLMenuGL;
+class LLUICtrl;
+class LLTextBox;
+
+/**
+ * Class LLFolderViewScrollContainer
+ *
+ * A scroll container which provides the information about the height
+ * of currently displayed folder view contents.
+ * Used for updating vertical scroll bar visibility in inventory panel.
+ * See LLScrollContainer::calcVisibleSize().
+ */
+class LLFolderViewScrollContainer : public LLScrollContainer
+{
+public:
+ /*virtual*/ ~LLFolderViewScrollContainer() {};
+ /*virtual*/ const LLRect getScrolledViewRect() const;
+
+protected:
+ LLFolderViewScrollContainer(const LLScrollContainer::Params& p);
+ friend class LLUICtrlFactory;
+};
+
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// Class LLFolderView
+//
+// The LLFolderView represents the root level folder view object.
+// It manages the screen region of the folder view.
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+class LLFolderView : public LLFolderViewFolder, public LLEditMenuHandler
+{
+public:
+ struct Params : public LLInitParam::Block<Params, LLFolderViewFolder::Params>
+ {
+ Mandatory<LLPanel*> parent_panel;
+ Optional<std::string> title;
+ Optional<bool> use_label_suffix,
+ allow_multiselect,
+ show_empty_message,
+ use_ellipses,
+ show_item_link_overlays;
+ Mandatory<LLFolderViewModelInterface*> view_model;
+
+ Params();
+ };
+
+ friend class LLFolderViewScrollContainer;
+
+ LLFolderView(const Params&);
+ virtual ~LLFolderView( void );
+
+ virtual BOOL canFocusChildren() const;
+
+ virtual const LLFolderView* getRoot() const { return this; }
+ virtual LLFolderView* getRoot() { return this; }
+
+ LLFolderViewModelInterface* getFolderViewModel() { return mViewModel; }
+ const LLFolderViewModelInterface* getFolderViewModel() const { return mViewModel; }
+
+ typedef boost::signals2::signal<void (const std::deque<LLFolderViewItem*>& items, BOOL user_action)> signal_t;
+ void setSelectCallback(const signal_t::slot_type& cb) { mSelectSignal.connect(cb); }
+ void setReshapeCallback(const signal_t::slot_type& cb) { mReshapeSignal.connect(cb); }
+
+ bool getAllowMultiSelect() { return mAllowMultiSelect; }
+
+ // Close all folders in the view
+ void closeAllFolders();
+ void openTopLevelFolders();
+
+ virtual BOOL addFolder( LLFolderViewFolder* folder);
+
+ // Find width and height of this object and its children. Also
+ // makes sure that this view and its children are the right size.
+ virtual S32 arrange( S32* width, S32* height );
+ virtual S32 getItemHeight();
+
+ void arrangeAll() { mArrangeGeneration++; }
+ S32 getArrangeGeneration() { return mArrangeGeneration; }
+
+ // applies filters to control visibility of items
+ virtual void filter( LLFolderViewFilter& filter);
+
+ // Get the last selected item
+ virtual LLFolderViewItem* getCurSelectedItem( void );
+
+ // Record the selected item and pass it down the hierarchy.
+ virtual BOOL setSelection(LLFolderViewItem* selection, BOOL openitem,
+ BOOL take_keyboard_focus = TRUE);
+
+ // This method is used to toggle the selection of an item. Walks
+ // children, and keeps track of selected objects.
+ virtual BOOL changeSelection(LLFolderViewItem* selection, BOOL selected);
+
+ virtual std::set<LLFolderViewItem*> getSelectionList() const;
+ S32 getNumSelectedItems() { return mSelectedItems.size(); }
+
+ // Make sure if ancestor is selected, descendants are not
+ void sanitizeSelection();
+ virtual void clearSelection();
+ void addToSelectionList(LLFolderViewItem* item);
+ void removeFromSelectionList(LLFolderViewItem* item);
+
+ bool startDrag();
+ void setDragAndDropThisFrame() { mDragAndDropThisFrame = TRUE; }
+ void setDraggingOverItem(LLFolderViewItem* item) { mDraggingOverItem = item; }
+ LLFolderViewItem* getDraggingOverItem() { return mDraggingOverItem; }
+
+ // Deletion functionality
+ void removeSelectedItems();
+
+ // Open the selected item
+ void openSelectedItems( void );
+ void propertiesSelectedItems( void );
+
+ void autoOpenItem(LLFolderViewFolder* item);
+ void closeAutoOpenedFolders();
+ BOOL autoOpenTest(LLFolderViewFolder* item);
+ BOOL isOpen() const { return TRUE; } // root folder always open
+
+ // Copy & paste
+ virtual void copy();
+ virtual BOOL canCopy() const;
+
+ virtual void cut();
+ virtual BOOL canCut() const;
+
+ virtual void paste();
+ virtual BOOL canPaste() const;
+
+ virtual void doDelete();
+ virtual BOOL canDoDelete() const;
+
+ // Public rename functionality - can only start the process
+ void startRenamingSelectedItem( void );
+
+ // LLView functionality
+ ///*virtual*/ BOOL handleKey( KEY key, MASK mask, BOOL called_from_parent );
+ /*virtual*/ BOOL handleKeyHere( KEY key, MASK mask );
+ /*virtual*/ BOOL handleUnicodeCharHere(llwchar uni_char);
+ /*virtual*/ BOOL handleMouseDown( S32 x, S32 y, MASK mask );
+ /*virtual*/ BOOL handleDoubleClick( S32 x, S32 y, MASK mask );
+ /*virtual*/ BOOL handleRightMouseDown( S32 x, S32 y, MASK mask );
+ /*virtual*/ BOOL handleHover( S32 x, S32 y, MASK mask );
+ /*virtual*/ BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop,
+ EDragAndDropType cargo_type,
+ void* cargo_data,
+ EAcceptance* accept,
+ std::string& tooltip_msg);
+ /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE);
+ /*virtual*/ void onMouseLeave(S32 x, S32 y, MASK mask) { setShowSelectionContext(FALSE); }
+ virtual void draw();
+ virtual void deleteAllChildren();
+
+ void scrollToShowSelection();
+ void scrollToShowItem(LLFolderViewItem* item, const LLRect& constraint_rect);
+ void setScrollContainer( LLScrollContainer* parent ) { mScrollContainer = parent; }
+ LLRect getVisibleRect();
+
+ BOOL search(LLFolderViewItem* first_item, const std::string &search_string, BOOL backward);
+ void setShowSelectionContext(BOOL show) { mShowSelectionContext = show; }
+ BOOL getShowSelectionContext();
+ void setShowSingleSelection(BOOL show);
+ BOOL getShowSingleSelection() { return mShowSingleSelection; }
+ F32 getSelectionFadeElapsedTime() { return mMultiSelectionFadeTimer.getElapsedTimeF32(); }
+ bool getUseEllipses() { return mUseEllipses; }
+
+ void update(); // needs to be called periodically (e.g. once per frame)
+
+ BOOL needsAutoSelect() { return mNeedsAutoSelect && !mAutoSelectOverride; }
+ BOOL needsAutoRename() { return mNeedsAutoRename; }
+ void setNeedsAutoRename(BOOL val) { mNeedsAutoRename = val; }
+ void setPinningSelectedItem(BOOL val) { mPinningSelectedItem = val; }
+ void setAutoSelectOverride(BOOL val) { mAutoSelectOverride = val; }
+
+ bool showItemLinkOverlays() { return mShowItemLinkOverlays; }
+
+ void setCallbackRegistrar(LLUICtrl::CommitCallbackRegistry::ScopedRegistrar* registrar) { mCallbackRegistrar = registrar; }
+
+ LLPanel* getParentPanel() { return mParentPanel; }
+ // DEBUG only
+ void dumpSelectionInformation();
+
+ virtual S32 notify(const LLSD& info) ;
+
+ bool useLabelSuffix() { return mUseLabelSuffix; }
+ void updateMenu();
+
+private:
+ void updateMenuOptions(LLMenuGL* menu);
+ void updateRenamerPosition();
+
+protected:
+ LLScrollContainer* mScrollContainer; // NULL if this is not a child of a scroll container.
+
+ void commitRename( const LLSD& data );
+ void onRenamerLost();
+
+ void finishRenamingItem( void );
+ void closeRenamer( void );
+
+ bool selectFirstItem();
+ bool selectLastItem();
+
+ BOOL addNoOptions(LLMenuGL* menu) const;
+
+
+protected:
+ LLHandle<LLView> mPopupMenuHandle;
+
+ typedef std::deque<LLFolderViewItem*> selected_items_t;
+ selected_items_t mSelectedItems;
+ BOOL mKeyboardSelection;
+ BOOL mAllowMultiSelect;
+ BOOL mShowEmptyMessage;
+ BOOL mShowFolderHierarchy;
+
+ // Renaming variables and methods
+ LLFolderViewItem* mRenameItem; // The item currently being renamed
+ LLLineEditor* mRenamer;
+
+ BOOL mNeedsScroll;
+ BOOL mPinningSelectedItem;
+ LLRect mScrollConstraintRect;
+ BOOL mNeedsAutoSelect;
+ BOOL mAutoSelectOverride;
+ BOOL mNeedsAutoRename;
+ bool mUseLabelSuffix;
+ bool mShowItemLinkOverlays;
+
+ LLDepthStack<LLFolderViewFolder> mAutoOpenItems;
+ LLFolderViewFolder* mAutoOpenCandidate;
+ LLFrameTimer mAutoOpenTimer;
+ LLFrameTimer mSearchTimer;
+ std::string mSearchString;
+ BOOL mShowSelectionContext;
+ BOOL mShowSingleSelection;
+ LLFrameTimer mMultiSelectionFadeTimer;
+ S32 mArrangeGeneration;
+
+ signal_t mSelectSignal;
+ signal_t mReshapeSignal;
+ S32 mSignalSelectCallback;
+ S32 mMinWidth;
+ BOOL mDragAndDropThisFrame;
+
+ LLPanel* mParentPanel;
+
+ LLFolderViewModelInterface* mViewModel;
+
+ /**
+ * Is used to determine if we need to cut text In LLFolderViewItem to avoid horizontal scroll.
+ * NOTE: For now it's used only to cut LLFolderViewItem::mLabel text for Landmarks in Places Panel.
+ */
+ bool mUseEllipses; // See EXT-719
+
+ /**
+ * Contains item under mouse pointer while dragging
+ */
+ LLFolderViewItem* mDraggingOverItem; // See EXT-719
+
+ LLUICtrl::CommitCallbackRegistry::ScopedRegistrar* mCallbackRegistrar;
+
+public:
+ static F32 sAutoOpenTime;
+ LLTextBox* mStatusTextBox;
+
+};
+
+
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// Class LLFolderViewFunctor
+//
+// Simple abstract base class for applying a functor to folders and
+// items in a folder view hierarchy. This is suboptimal for algorithms
+// that only work folders or only work on items, but I'll worry about
+// that later when it's determined to be too slow.
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+class LLFolderViewFunctor
+{
+public:
+ virtual ~LLFolderViewFunctor() {}
+ virtual void doFolder(LLFolderViewFolder* folder) = 0;
+ virtual void doItem(LLFolderViewItem* item) = 0;
+};
+
+class LLSelectFirstFilteredItem : public LLFolderViewFunctor
+{
+public:
+ LLSelectFirstFilteredItem() : mItemSelected(FALSE) {}
+ virtual ~LLSelectFirstFilteredItem() {}
+ virtual void doFolder(LLFolderViewFolder* folder);
+ virtual void doItem(LLFolderViewItem* item);
+ BOOL wasItemSelected() { return mItemSelected; }
+protected:
+ BOOL mItemSelected;
+};
+
+class LLOpenFilteredFolders : public LLFolderViewFunctor
+{
+public:
+ LLOpenFilteredFolders() {}
+ virtual ~LLOpenFilteredFolders() {}
+ virtual void doFolder(LLFolderViewFolder* folder);
+ virtual void doItem(LLFolderViewItem* item);
+};
+
+class LLSaveFolderState : public LLFolderViewFunctor
+{
+public:
+ LLSaveFolderState() : mApply(FALSE) {}
+ virtual ~LLSaveFolderState() {}
+ virtual void doFolder(LLFolderViewFolder* folder);
+ virtual void doItem(LLFolderViewItem* item) {}
+ void setApply(BOOL apply);
+ void clearOpenFolders() { mOpenFolders.clear(); }
+protected:
+ std::set<LLUUID> mOpenFolders;
+ BOOL mApply;
+};
+
+class LLOpenFoldersWithSelection : public LLFolderViewFunctor
+{
+public:
+ LLOpenFoldersWithSelection() {}
+ virtual ~LLOpenFoldersWithSelection() {}
+ virtual void doFolder(LLFolderViewFolder* folder);
+ virtual void doItem(LLFolderViewItem* item);
+};
+
+// Flags for buildContextMenu()
+const U32 SUPPRESS_OPEN_ITEM = 0x1;
+const U32 FIRST_SELECTED_ITEM = 0x2;
+
+#endif // LL_LLFOLDERVIEW_H
diff --git a/indra/llui/llfolderviewitem.cpp b/indra/llui/llfolderviewitem.cpp
new file mode 100644
index 0000000000..741fc9c324
--- /dev/null
+++ b/indra/llui/llfolderviewitem.cpp
@@ -0,0 +1,2032 @@
+/**
+* @file llfolderviewitem.cpp
+* @brief Items and folders that can appear in a hierarchical folder view
+*
+* $LicenseInfo:firstyear=2001&license=viewerlgpl$
+* Second Life Viewer Source Code
+* Copyright (C) 2010, Linden Research, Inc.
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Lesser General Public
+* License as published by the Free Software Foundation;
+* version 2.1 of the License only.
+*
+* This library is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this library; if not, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*
+* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+* $/LicenseInfo$
+*/
+#include "linden_common.h"
+#include "llfolderviewitem.h"
+
+#include "llfolderview.h"
+#include "llfolderviewmodel.h"
+#include "llpanel.h"
+#include "llcriticaldamp.h"
+#include "llclipboard.h"
+#include "llfocusmgr.h" // gFocusMgr
+#include "lltrans.h"
+#include "llwindow.h"
+
+///----------------------------------------------------------------------------
+/// Class LLFolderViewItem
+///----------------------------------------------------------------------------
+
+static LLDefaultChildRegistry::Register<LLFolderViewItem> r("folder_view_item");
+
+// statics
+std::map<U8, LLFontGL*> LLFolderViewItem::sFonts; // map of styles to fonts
+
+// only integers can be initialized in header
+const F32 LLFolderViewItem::FOLDER_CLOSE_TIME_CONSTANT = 0.02f;
+const F32 LLFolderViewItem::FOLDER_OPEN_TIME_CONSTANT = 0.03f;
+
+const LLColor4U DEFAULT_WHITE(255, 255, 255);
+
+
+//static
+LLFontGL* LLFolderViewItem::getLabelFontForStyle(U8 style)
+{
+ LLFontGL* rtn = sFonts[style];
+ if (!rtn) // grab label font with this style, lazily
+ {
+ LLFontDescriptor labelfontdesc("SansSerif", "Small", style);
+ rtn = LLFontGL::getFont(labelfontdesc);
+ if (!rtn)
+ {
+ rtn = LLFontGL::getFontDefault();
+ }
+ sFonts[style] = rtn;
+ }
+ return rtn;
+}
+
+//static
+void LLFolderViewItem::initClass()
+{
+}
+
+//static
+void LLFolderViewItem::cleanupClass()
+{
+ sFonts.clear();
+}
+
+
+// NOTE: Optimize this, we call it a *lot* when opening a large inventory
+LLFolderViewItem::Params::Params()
+: root(),
+ listener(),
+ folder_arrow_image("folder_arrow_image"),
+ folder_indentation("folder_indentation"),
+ selection_image("selection_image"),
+ item_height("item_height"),
+ item_top_pad("item_top_pad"),
+ creation_date()
+{}
+
+// Default constructor
+LLFolderViewItem::LLFolderViewItem(const LLFolderViewItem::Params& p)
+: LLView(p),
+ mLabelWidth(0),
+ mLabelWidthDirty(false),
+ mParentFolder( NULL ),
+ mIsSelected( FALSE ),
+ mIsCurSelection( FALSE ),
+ mSelectPending(FALSE),
+ mLabelStyle( LLFontGL::NORMAL ),
+ mHasVisibleChildren(FALSE),
+ mIndentation(0),
+ mItemHeight(p.item_height),
+ //TODO RN: create interface for string highlighting
+ //mStringMatchOffset(std::string::npos),
+ mControlLabelRotation(0.f),
+ mDragAndDropTarget(FALSE),
+ mLabel(p.name),
+ mRoot(p.root),
+ mViewModelItem(p.listener),
+ mIsMouseOverTitle(false)
+{
+ if (mViewModelItem)
+ {
+ mViewModelItem->setFolderViewItem(this);
+ }
+}
+
+BOOL LLFolderViewItem::postBuild()
+{
+ refresh();
+ return TRUE;
+}
+
+// Destroys the object
+LLFolderViewItem::~LLFolderViewItem( void )
+{
+ delete mViewModelItem;
+ mViewModelItem = NULL;
+}
+
+LLFolderView* LLFolderViewItem::getRoot()
+{
+ return mRoot;
+}
+
+const LLFolderView* LLFolderViewItem::getRoot() const
+{
+ 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 );
+
+ // Skip over items that are invisible or are hidden from the UI.
+ 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::passedFilter(S32 filter_generation)
+{
+ return getViewModelItem()->passedFilter(filter_generation);
+}
+
+void LLFolderViewItem::refresh()
+{
+ LLFolderViewModelItem& vmi = *getViewModelItem();
+
+ mLabel = vmi.getDisplayName();
+
+ setToolTip(mLabel);
+ mIcon = vmi.getIcon();
+ mIconOpen = vmi.getIconOpen();
+ mIconOverlay = vmi.getIconOverlay();
+
+ if (mRoot->useLabelSuffix())
+ {
+ mLabelStyle = vmi.getLabelStyle();
+ mLabelSuffix = vmi.getLabelSuffix();
+ }
+
+ //TODO RN: make sure this logic still fires
+ //std::string searchable_label(mLabel);
+ //searchable_label.append(mLabelSuffix);
+ //LLStringUtil::toUpper(searchable_label);
+
+ //if (mSearchableLabel.compare(searchable_label))
+ //{
+ // mSearchableLabel.assign(searchable_label);
+ // vmi.dirtyFilter();
+ // // some part of label has changed, so overall width has potentially changed, and sort order too
+ // if (mParentFolder)
+ // {
+ // mParentFolder->requestSort();
+ // mParentFolder->requestArrange();
+ // }
+ //}
+
+ mLabelWidthDirty = true;
+ vmi.dirtyFilter();
+}
+
+// Utility function for LLFolderView
+void LLFolderViewItem::arrangeAndSet(BOOL set_selection,
+ BOOL take_keyboard_focus)
+{
+ LLFolderView* root = getRoot();
+ if (getParentFolder())
+ {
+ getParentFolder()->requestArrange();
+ }
+ if(set_selection)
+ {
+ getRoot()->setSelection(this, TRUE, take_keyboard_focus);
+ if(root)
+ {
+ root->scrollToShowSelection();
+ }
+ }
+}
+
+
+std::set<LLFolderViewItem*> LLFolderViewItem::getSelectionList() const
+{
+ std::set<LLFolderViewItem*> selection;
+ return selection;
+}
+
+// addToFolder() returns TRUE if it succeeds. FALSE otherwise
+BOOL LLFolderViewItem::addToFolder(LLFolderViewFolder* folder)
+{
+ return folder->addItem(this);
+}
+
+
+// Finds width and height of this object and its children. Also
+// makes sure that this view and its children are the right size.
+S32 LLFolderViewItem::arrange( S32* width, S32* height )
+{
+ const Params& p = LLUICtrlFactory::getDefaultParams<LLFolderViewItem>();
+ S32 indentation = p.folder_indentation();
+ // Only indent deeper items in hierarchy
+ mIndentation = (getParentFolder())
+ ? getParentFolder()->getIndentation() + indentation
+ : 0;
+ if (mLabelWidthDirty)
+ {
+ mLabelWidth = ARROW_SIZE + TEXT_PAD + ICON_WIDTH + ICON_PAD + getLabelFontForStyle(mLabelStyle)->getWidth(mLabel) + getLabelFontForStyle(mLabelStyle)->getWidth(mLabelSuffix) + TEXT_PAD_RIGHT;
+ mLabelWidthDirty = false;
+ }
+
+ *width = llmax(*width, mLabelWidth + mIndentation);
+
+ // determine if we need to use ellipses to avoid horizontal scroll. EXT-719
+ bool use_ellipses = getRoot()->getUseEllipses();
+ if (use_ellipses)
+ {
+ // limit to set rect to avoid horizontal scrollbar
+ *width = llmin(*width, getRoot()->getRect().getWidth());
+ }
+ *height = getItemHeight();
+ return *height;
+}
+
+S32 LLFolderViewItem::getItemHeight()
+{
+ return mItemHeight;
+}
+
+// *TODO: This can be optimized a lot by simply recording that it is
+// selected in the appropriate places, and assuming that set selection
+// means 'deselect' for a leaf item. Do this optimization after
+// multiple selection is implemented to make sure it all plays nice
+// together.
+BOOL LLFolderViewItem::setSelection(LLFolderViewItem* selection, BOOL openitem, BOOL take_keyboard_focus)
+{
+ if (selection == this && !mIsSelected)
+ {
+ selectItem();
+ }
+ else if (mIsSelected) // Deselect everything else.
+ {
+ deselectItem();
+ }
+ return mIsSelected;
+}
+
+BOOL LLFolderViewItem::changeSelection(LLFolderViewItem* selection, BOOL selected)
+{
+ if (selection == this)
+ {
+ if (mIsSelected)
+ {
+ deselectItem();
+ }
+ else
+ {
+ selectItem();
+ }
+ return TRUE;
+ }
+ return FALSE;
+}
+
+void LLFolderViewItem::deselectItem(void)
+{
+ mIsSelected = FALSE;
+}
+
+void LLFolderViewItem::selectItem(void)
+{
+ if (mIsSelected == FALSE)
+ {
+ getViewModelItem()->selectItem();
+ mIsSelected = TRUE;
+ }
+}
+
+BOOL LLFolderViewItem::isMovable()
+{
+ return getViewModelItem()->isItemMovable();
+}
+
+BOOL LLFolderViewItem::isRemovable()
+{
+ return getViewModelItem()->isItemRemovable();
+}
+
+void LLFolderViewItem::destroyView()
+{
+ getRoot()->removeFromSelectionList(this);
+
+ if (mParentFolder)
+ {
+ // removeView deletes me
+ mParentFolder->extractItem(this);
+ }
+ delete 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;
+ }
+ return getViewModelItem()->removeItem();
+}
+
+// Build an appropriate context menu for the item.
+void LLFolderViewItem::buildContextMenu(LLMenuGL& menu, U32 flags)
+{
+ getViewModelItem()->buildContextMenu(menu, flags);
+}
+
+void LLFolderViewItem::openItem( void )
+{
+ getViewModelItem()->openItem();
+}
+
+void LLFolderViewItem::rename(const std::string& new_name)
+{
+ if( !new_name.empty() )
+ {
+ getViewModelItem()->renameItem(new_name);
+
+ if(mParentFolder)
+ {
+ mParentFolder->requestSort();
+ }
+ }
+ }
+
+const std::string& LLFolderViewItem::getName( void ) const
+{
+ return getViewModelItem()->getName();
+}
+
+// LLView functionality
+BOOL LLFolderViewItem::handleRightMouseDown( S32 x, S32 y, MASK mask )
+{
+ if(!mIsSelected)
+ {
+ getRoot()->setSelection(this, FALSE);
+ }
+ make_ui_sound("UISndClick");
+ return TRUE;
+}
+
+BOOL LLFolderViewItem::handleMouseDown( S32 x, S32 y, MASK mask )
+{
+ if (LLView::childrenHandleMouseDown(x, y, mask))
+ {
+ return TRUE;
+ }
+
+ // No handler needed for focus lost since this class has no
+ // state that depends on it.
+ gFocusMgr.setMouseCapture( this );
+
+ if (!mIsSelected)
+ {
+ if(mask & MASK_CONTROL)
+ {
+ getRoot()->changeSelection(this, !mIsSelected);
+ }
+ else if (mask & MASK_SHIFT)
+ {
+ getParentFolder()->extendSelectionTo(this);
+ }
+ else
+ {
+ getRoot()->setSelection(this, FALSE);
+ }
+ make_ui_sound("UISndClick");
+ }
+ else
+ {
+ mSelectPending = TRUE;
+ }
+
+ mDragStartX = x;
+ mDragStartY = y;
+ return TRUE;
+}
+
+BOOL LLFolderViewItem::handleHover( S32 x, S32 y, MASK mask )
+{
+ static LLCachedControl<S32> drag_and_drop_threshold(*LLUI::sSettingGroups["config"],"DragAndDropDistanceThreshold");
+
+ mIsMouseOverTitle = (y > (getRect().getHeight() - mItemHeight));
+
+ if( hasMouseCapture() && isMovable() )
+ {
+ LLFolderView* root = getRoot();
+
+ if( (x - mDragStartX) * (x - mDragStartX) + (y - mDragStartY) * (y - mDragStartY) > drag_and_drop_threshold() * drag_and_drop_threshold()
+ && root->getCurSelectedItem()
+ && root->startDrag())
+ {
+ // RN: when starting drag and drop, clear out last auto-open
+ root->autoOpenTest(NULL);
+ root->setShowSelectionContext(TRUE);
+
+ // Release keyboard focus, so that if stuff is dropped into the
+ // world, pressing the delete key won't blow away the inventory
+ // item.
+ gFocusMgr.setKeyboardFocus(NULL);
+
+ getWindow()->setCursor(UI_CURSOR_ARROW);
+ return TRUE;
+ }
+ else
+ {
+ getWindow()->setCursor(UI_CURSOR_NOLOCKED);
+ return TRUE;
+ }
+ }
+ else
+ {
+ getRoot()->setShowSelectionContext(FALSE);
+ getWindow()->setCursor(UI_CURSOR_ARROW);
+ // let parent handle this then...
+ return FALSE;
+ }
+}
+
+
+BOOL LLFolderViewItem::handleDoubleClick( S32 x, S32 y, MASK mask )
+{
+ getViewModelItem()->openItem();
+ return TRUE;
+}
+
+BOOL LLFolderViewItem::handleMouseUp( S32 x, S32 y, MASK mask )
+{
+ if (LLView::childrenHandleMouseUp(x, y, mask))
+ {
+ return TRUE;
+ }
+
+ // if mouse hasn't moved since mouse down...
+ if ( pointInView(x, y) && mSelectPending )
+ {
+ //...then select
+ if(mask & MASK_CONTROL)
+ {
+ getRoot()->changeSelection(this, !mIsSelected);
+ }
+ else if (mask & MASK_SHIFT)
+ {
+ getParentFolder()->extendSelectionTo(this);
+ }
+ else
+ {
+ getRoot()->setSelection(this, FALSE);
+ }
+ }
+
+ mSelectPending = FALSE;
+
+ if( hasMouseCapture() )
+ {
+ if (getRoot())
+ {
+ getRoot()->setShowSelectionContext(FALSE);
+ }
+ gFocusMgr.setMouseCapture( NULL );
+ }
+ return TRUE;
+}
+
+void LLFolderViewItem::onMouseLeave(S32 x, S32 y, MASK mask)
+{
+ mIsMouseOverTitle = false;
+}
+
+BOOL LLFolderViewItem::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop,
+ EDragAndDropType cargo_type,
+ void* cargo_data,
+ EAcceptance* accept,
+ std::string& tooltip_msg)
+{
+ BOOL handled = FALSE;
+ BOOL accepted = getViewModelItem()->dragOrDrop(mask,drop,cargo_type,cargo_data, tooltip_msg);
+ handled = accepted;
+ if (accepted)
+ {
+ mDragAndDropTarget = TRUE;
+ *accept = ACCEPT_YES_MULTI;
+ }
+ else
+ {
+ *accept = ACCEPT_NO;
+ }
+ if(mParentFolder && !handled)
+ {
+ // store this item to get it in LLFolderBridge::dragItemIntoFolder on drop event.
+ mRoot->setDraggingOverItem(this);
+ handled = mParentFolder->handleDragAndDropFromChild(mask,drop,cargo_type,cargo_data,accept,tooltip_msg);
+ mRoot->setDraggingOverItem(NULL);
+ }
+ if (handled)
+ {
+ lldebugst(LLERR_USER_INPUT) << "dragAndDrop handled by LLFolderViewItem" << llendl;
+ }
+
+ return handled;
+}
+
+void LLFolderViewItem::draw()
+{
+ static LLUIColor sFgColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE);
+ static LLUIColor sHighlightBgColor = LLUIColorTable::instance().getColor("MenuItemHighlightBgColor", DEFAULT_WHITE);
+ static LLUIColor sHighlightFgColor = LLUIColorTable::instance().getColor("MenuItemHighlightFgColor", DEFAULT_WHITE);
+ static LLUIColor sFocusOutlineColor = LLUIColorTable::instance().getColor("InventoryFocusOutlineColor", DEFAULT_WHITE);
+ static LLUIColor sFilterBGColor = LLUIColorTable::instance().getColor("FilterBackgroundColor", DEFAULT_WHITE);
+ static LLUIColor sFilterTextColor = LLUIColorTable::instance().getColor("FilterTextColor", DEFAULT_WHITE);
+ static LLUIColor sSuffixColor = LLUIColorTable::instance().getColor("InventoryItemColor", DEFAULT_WHITE);
+ static LLUIColor sLibraryColor = LLUIColorTable::instance().getColor("InventoryItemLibraryColor", DEFAULT_WHITE);
+ static LLUIColor sLinkColor = LLUIColorTable::instance().getColor("InventoryItemLinkColor", DEFAULT_WHITE);
+ static LLUIColor sSearchStatusColor = LLUIColorTable::instance().getColor("InventorySearchStatusColor", DEFAULT_WHITE);
+ static LLUIColor sMouseOverColor = LLUIColorTable::instance().getColor("InventoryMouseOverColor", DEFAULT_WHITE);
+
+ const Params& default_params = LLUICtrlFactory::getDefaultParams<LLFolderViewItem>();
+ const S32 TOP_PAD = default_params.item_top_pad;
+ const S32 FOCUS_LEFT = 1;
+ const LLFontGL* font = getLabelFontForStyle(mLabelStyle);
+
+ getViewModelItem()->update();
+
+ //--------------------------------------------------------------------------------//
+ // Draw open folder arrow
+ //
+ if (hasVisibleChildren() || getViewModelItem()->hasChildren())
+ {
+ LLUIImage* arrow_image = default_params.folder_arrow_image;
+ gl_draw_scaled_rotated_image(
+ mIndentation, getRect().getHeight() - ARROW_SIZE - TEXT_PAD - TOP_PAD,
+ ARROW_SIZE, ARROW_SIZE, mControlLabelRotation, arrow_image->getImage(), sFgColor);
+ }
+
+
+ //--------------------------------------------------------------------------------//
+ // Draw highlight for selected items
+ //
+ const BOOL show_context = (getRoot() ? getRoot()->getShowSelectionContext() : FALSE);
+ const BOOL filled = show_context || (getRoot() ? getRoot()->getParentPanel()->hasFocus() : FALSE); // If we have keyboard focus, draw selection filled
+ const S32 focus_top = getRect().getHeight();
+ const S32 focus_bottom = getRect().getHeight() - mItemHeight;
+ const bool folder_open = (getRect().getHeight() > mItemHeight + 4);
+ if (mIsSelected) // always render "current" item. Only render other selected items if mShowSingleSelection is FALSE
+ {
+ gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
+ LLColor4 bg_color = sHighlightBgColor;
+ if (!mIsCurSelection)
+ {
+ // do time-based fade of extra objects
+ F32 fade_time = (getRoot() ? getRoot()->getSelectionFadeElapsedTime() : 0.0f);
+ if (getRoot() && 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(FOCUS_LEFT,
+ focus_top,
+ getRect().getWidth() - 2,
+ focus_bottom,
+ bg_color, filled);
+ if (mIsCurSelection)
+ {
+ gl_rect_2d(FOCUS_LEFT,
+ focus_top,
+ getRect().getWidth() - 2,
+ focus_bottom,
+ sFocusOutlineColor, FALSE);
+ }
+ if (folder_open)
+ {
+ gl_rect_2d(FOCUS_LEFT,
+ focus_bottom + 1, // overlap with bottom edge of above rect
+ getRect().getWidth() - 2,
+ 0,
+ sFocusOutlineColor, FALSE);
+ if (show_context)
+ {
+ gl_rect_2d(FOCUS_LEFT,
+ focus_bottom + 1,
+ getRect().getWidth() - 2,
+ 0,
+ sHighlightBgColor, TRUE);
+ }
+ }
+ }
+ else if (mIsMouseOverTitle)
+ {
+ gl_rect_2d(FOCUS_LEFT,
+ focus_top,
+ getRect().getWidth() - 2,
+ focus_bottom,
+ sMouseOverColor, FALSE);
+ }
+
+ //--------------------------------------------------------------------------------//
+ // Draw DragNDrop highlight
+ //
+ if (mDragAndDropTarget)
+ {
+ gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
+ gl_rect_2d(FOCUS_LEFT,
+ focus_top,
+ getRect().getWidth() - 2,
+ focus_bottom,
+ sHighlightBgColor, FALSE);
+ if (folder_open)
+ {
+ gl_rect_2d(FOCUS_LEFT,
+ focus_bottom + 1, // overlap with bottom edge of above rect
+ getRect().getWidth() - 2,
+ 0,
+ sHighlightBgColor, FALSE);
+ }
+ mDragAndDropTarget = FALSE;
+ }
+
+ //--------------------------------------------------------------------------------//
+ // Draw open icon
+ //
+ const S32 icon_x = mIndentation + ARROW_SIZE + TEXT_PAD;
+ if (!mIconOpen.isNull() && (llabs(mControlLabelRotation) > 80)) // For open folders
+ {
+ mIconOpen->draw(icon_x, getRect().getHeight() - mIconOpen->getHeight() - TOP_PAD + 1);
+ }
+ else if (mIcon)
+ {
+ mIcon->draw(icon_x, getRect().getHeight() - mIcon->getHeight() - TOP_PAD + 1);
+ }
+
+ if (mIconOverlay && getRoot()->showItemLinkOverlays())
+ {
+ mIconOverlay->draw(icon_x, getRect().getHeight() - mIcon->getHeight() - TOP_PAD + 1);
+ }
+
+ //--------------------------------------------------------------------------------//
+ // Exit if no label to draw
+ //
+ if (mLabel.empty())
+ {
+ return;
+ }
+
+ LLColor4 color = (mIsSelected && filled) ? sHighlightFgColor : sFgColor;
+ //TODO RN: implement this in terms of getColor()
+ //if (highlight_link) color = sLinkColor;
+ //if (gInventory.isObjectDescendentOf(getViewModelItem()->getUUID(), gInventory.getLibraryRootFolderID())) color = sLibraryColor;
+
+ F32 right_x = 0;
+ F32 y = (F32)getRect().getHeight() - font->getLineHeight() - (F32)TEXT_PAD - (F32)TOP_PAD;
+ F32 text_left = (F32)(ARROW_SIZE + TEXT_PAD + ICON_WIDTH + ICON_PAD + mIndentation);
+
+ //--------------------------------------------------------------------------------//
+ // Draw the actual label text
+ //
+ font->renderUTF8(mLabel, 0, text_left, y, color,
+ LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW,
+ S32_MAX, getRect().getWidth() - (S32) text_left, &right_x, TRUE);
+
+ //--------------------------------------------------------------------------------//
+ // Draw label suffix
+ //
+ if (!mLabelSuffix.empty())
+ {
+ font->renderUTF8( mLabelSuffix, 0, right_x, y, sSuffixColor,
+ LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW,
+ S32_MAX, S32_MAX, &right_x, FALSE );
+ }
+
+ //--------------------------------------------------------------------------------//
+ // Highlight string match
+ //
+ //TODO RN: expose interface for highlighting
+ //if (mStringMatchOffset != std::string::npos)
+ //{
+ // // don't draw backgrounds for zero-length strings
+ // S32 filter_string_length = getRoot()->getFilterSubString().size();
+ // if (filter_string_length > 0)
+ // {
+ // std::string combined_string = mLabel + mLabelSuffix;
+ // S32 left = llround(text_left) + font->getWidth(combined_string, 0, mStringMatchOffset) - 1;
+ // S32 right = left + font->getWidth(combined_string, mStringMatchOffset, filter_string_length) + 2;
+ // S32 bottom = llfloor(getRect().getHeight() - font->getLineHeight() - 3 - TOP_PAD);
+ // S32 top = getRect().getHeight() - TOP_PAD;
+ //
+ // LLUIImage* box_image = default_params.selection_image;
+ // LLRect box_rect(left, top, right, bottom);
+ // box_image->draw(box_rect, sFilterBGColor);
+ // F32 match_string_left = text_left + font->getWidthF32(combined_string, 0, mStringMatchOffset);
+ // F32 yy = (F32)getRect().getHeight() - font->getLineHeight() - (F32)TEXT_PAD - (F32)TOP_PAD;
+ // font->renderUTF8( combined_string, mStringMatchOffset, match_string_left, yy,
+ // sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW,
+ // filter_string_length, S32_MAX, &right_x, FALSE );
+ // }
+ //}
+}
+
+const LLFolderViewModelInterface* LLFolderViewItem::getFolderViewModel( void ) const
+ {
+ return getRoot()->getFolderViewModel();
+}
+
+LLFolderViewModelInterface* LLFolderViewItem::getFolderViewModel( void )
+ {
+ return getRoot()->getFolderViewModel();
+}
+
+
+///----------------------------------------------------------------------------
+/// Class LLFolderViewFolder
+///----------------------------------------------------------------------------
+
+LLFolderViewFolder::LLFolderViewFolder( const LLFolderViewItem::Params& p ):
+ LLFolderViewItem( p ),
+ mIsOpen(FALSE),
+ mExpanderHighlighted(FALSE),
+ mCurHeight(0.f),
+ mTargetHeight(0.f),
+ mAutoOpenCountdown(0.f),
+ mLastArrangeGeneration( -1 ),
+ mLastCalculatedWidth(0)
+{
+}
+
+// Destroys the object
+LLFolderViewFolder::~LLFolderViewFolder( void )
+{
+ // The LLView base class takes care of object destruction. make sure that we
+ // don't have mouse or keyboard focus
+ gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit()
+}
+
+// addToFolder() returns TRUE if it succeeds. FALSE otherwise
+BOOL LLFolderViewFolder::addToFolder(LLFolderViewFolder* folder)
+{
+ return folder->addFolder(this);
+}
+
+static LLFastTimer::DeclareTimer FTM_ARRANGE("Arrange");
+
+// Finds width and height of this object and its children. Also
+// makes sure that this view and its children are the right size.
+S32 LLFolderViewFolder::arrange( S32* width, S32* height )
+{
+ // sort before laying out contents
+ getRoot()->getFolderViewModel()->sort(this);
+
+ LLFastTimer t2(FTM_ARRANGE);
+
+ // evaluate mHasVisibleChildren
+ mHasVisibleChildren = false;
+ if (getViewModelItem()->descendantsPassedFilter())
+ {
+ // We have to verify that there's at least one child that's not filtered out
+ bool found = false;
+ // Try the items first
+ for (items_t::iterator iit = mItems.begin(); iit != mItems.end(); ++iit)
+ {
+ LLFolderViewItem* itemp = (*iit);
+ found = itemp->passedFilter();
+ if (found)
+ break;
+ }
+ if (!found)
+ {
+ // If no item found, try the folders
+ for (folders_t::iterator fit = mFolders.begin(); fit != mFolders.end(); ++fit)
+ {
+ LLFolderViewFolder* folderp = (*fit);
+ found = folderp->passedFilter();
+ if (found)
+ break;
+ }
+ }
+
+ mHasVisibleChildren = found;
+ }
+
+ // calculate height as a single item (without any children), and reshapes rectangle to match
+ LLFolderViewItem::arrange( width, height );
+
+ // 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
+ 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 = getRoot()->getArrangeGeneration();
+ if (isOpen())
+ {
+ // Add sizes of children
+ S32 parent_item_height = getRect().getHeight();
+
+ for(folders_t::iterator fit = mFolders.begin(); fit != mFolders.end(); ++fit)
+ {
+ LLFolderViewFolder* folderp = (*fit);
+ folderp->setVisible(folderp->passedFilter()); // 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 );
+
+ running_height += (F32)child_height;
+ *width = llmax(*width, child_width);
+ folderp->setOrigin( 0, child_top - folderp->getRect().getHeight() );
+ }
+ }
+ for(items_t::iterator iit = mItems.begin();
+ iit != mItems.end(); ++iit)
+ {
+ LLFolderViewItem* itemp = (*iit);
+ itemp->setVisible(itemp->passedFilter());
+
+ 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 );
+ // 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(isOpen() ? FOLDER_OPEN_TIME_CONSTANT : FOLDER_CLOSE_TIME_CONSTANT));
+
+ requestArrange();
+
+ // hide child elements that fall out of current animated height
+ for (folders_t::iterator iter = mFolders.begin();
+ iter != mFolders.end();)
+ {
+ folders_t::iterator fit = iter++;
+ // number of pixels that bottom of folder label is from top of parent folder
+ if (getRect().getHeight() - (*fit)->getRect().mTop + (*fit)->getItemHeight()
+ > llround(mCurHeight) + MAX_FOLDER_ITEM_OVERLAP)
+ {
+ // hide if beyond current folder height
+ (*fit)->setVisible(FALSE);
+ }
+ }
+
+ for (items_t::iterator iter = mItems.begin();
+ iter != mItems.end();)
+ {
+ items_t::iterator iit = iter++;
+ // number of pixels that bottom of item label is from top of parent folder
+ if (getRect().getHeight() - (*iit)->getRect().mBottom
+ > llround(mCurHeight) + MAX_FOLDER_ITEM_OVERLAP)
+ {
+ (*iit)->setVisible(FALSE);
+ }
+ }
+ }
+ else
+ {
+ mCurHeight = mTargetHeight;
+ }
+
+ // don't change width as this item is already as wide as its parent folder
+ reshape(getRect().getWidth(),llround(mCurHeight));
+
+ // pass current height value back to parent
+ *height = llround(mCurHeight);
+
+ return llround(mTargetHeight);
+}
+
+BOOL LLFolderViewFolder::needsArrange()
+{
+ return mLastArrangeGeneration < getRoot()->getArrangeGeneration();
+}
+
+void LLFolderViewFolder::requestSort()
+{
+ getViewModelItem()->requestSort();
+}
+
+//TODO RN: get height resetting working
+//void LLFolderViewFolder::setPassedFilter(BOOL passed, BOOL passed_folder, S32 filter_generation)
+//{
+// // if this folder is now filtered, but wasn't before
+// // (it just passed)
+// if (passed && !passedFilter(filter_generation))
+// {
+// // reset current height, because last time we drew it
+// // it might have had more visible items than now
+// mCurHeight = 0.f;
+// }
+//
+// LLFolderViewItem::setPassedFilter(passed, passed_folder, filter_generation);
+//}
+
+
+// Passes selection information on to children and record selection
+// information if necessary.
+BOOL LLFolderViewFolder::setSelection(LLFolderViewItem* selection, BOOL openitem,
+ BOOL take_keyboard_focus)
+{
+ BOOL rv = FALSE;
+ if (selection == this)
+ {
+ if (!isSelected())
+ {
+ selectItem();
+ }
+ rv = TRUE;
+ }
+ else
+ {
+ if (isSelected())
+ {
+ deselectItem();
+ }
+ rv = FALSE;
+ }
+ BOOL child_selected = FALSE;
+
+ for (folders_t::iterator iter = mFolders.begin();
+ iter != mFolders.end();)
+ {
+ folders_t::iterator fit = iter++;
+ if((*fit)->setSelection(selection, openitem, take_keyboard_focus))
+ {
+ rv = TRUE;
+ child_selected = TRUE;
+ }
+ }
+ for (items_t::iterator iter = mItems.begin();
+ iter != mItems.end();)
+ {
+ items_t::iterator iit = iter++;
+ if((*iit)->setSelection(selection, openitem, take_keyboard_focus))
+ {
+ rv = TRUE;
+ child_selected = TRUE;
+ }
+ }
+ if(openitem && child_selected)
+ {
+ setOpenArrangeRecursively(TRUE);
+ }
+ return rv;
+}
+
+// This method is used to change the selection of an item.
+// Recursively traverse all children; if 'selection' is 'this' then change
+// the select status if necessary.
+// Returns TRUE if the selection state of this folder, or of a child, was changed.
+BOOL LLFolderViewFolder::changeSelection(LLFolderViewItem* selection, BOOL selected)
+{
+ BOOL rv = FALSE;
+ if(selection == this)
+ {
+ if (isSelected() != selected)
+ {
+ rv = TRUE;
+ if (selected)
+ {
+ selectItem();
+ }
+ else
+ {
+ deselectItem();
+ }
+ }
+ }
+
+ for (folders_t::iterator iter = mFolders.begin();
+ iter != mFolders.end();)
+ {
+ folders_t::iterator fit = iter++;
+ if((*fit)->changeSelection(selection, selected))
+ {
+ rv = TRUE;
+ }
+ }
+ for (items_t::iterator iter = mItems.begin();
+ iter != mItems.end();)
+ {
+ items_t::iterator iit = iter++;
+ if((*iit)->changeSelection(selection, selected))
+ {
+ rv = TRUE;
+ }
+ }
+ return rv;
+}
+
+LLFolderViewFolder* LLFolderViewFolder::getCommonAncestor(LLFolderViewItem* item_a, LLFolderViewItem* item_b, bool& reverse)
+{
+ if (!item_a->getParentFolder() || !item_b->getParentFolder()) return NULL;
+
+ std::deque<LLFolderViewFolder*> item_a_ancestors;
+
+ LLFolderViewFolder* parent = item_a->getParentFolder();
+ while(parent)
+ {
+ item_a_ancestors.push_back(parent);
+ parent = parent->getParentFolder();
+ }
+
+ std::deque<LLFolderViewFolder*> item_b_ancestors;
+
+ parent = item_b->getParentFolder();
+ while(parent)
+ {
+ item_b_ancestors.push_back(parent);
+ parent = parent->getParentFolder();
+ }
+
+ LLFolderViewFolder* common_ancestor = item_a->getRoot();
+
+ while(item_a_ancestors.size() > item_b_ancestors.size())
+ {
+ item_a = item_a_ancestors.front();
+ item_a_ancestors.pop_front();
+ }
+
+ while(item_b_ancestors.size() > item_a_ancestors.size())
+ {
+ item_b = item_b_ancestors.front();
+ item_b_ancestors.pop_front();
+ }
+
+ while(item_a_ancestors.size())
+ {
+ common_ancestor = item_a_ancestors.front();
+
+ if (item_a_ancestors.front() == item_b_ancestors.front())
+ {
+ // which came first, sibling a or sibling b?
+ for (folders_t::iterator it = common_ancestor->mFolders.begin(), end_it = common_ancestor->mFolders.end();
+ it != end_it;
+ ++it)
+ {
+ LLFolderViewItem* item = *it;
+
+ if (item == item_a)
+ {
+ reverse = false;
+ return common_ancestor;
+ }
+ if (item == item_b)
+ {
+ reverse = true;
+ return common_ancestor;
+ }
+ }
+
+ for (items_t::iterator it = common_ancestor->mItems.begin(), end_it = common_ancestor->mItems.end();
+ it != end_it;
+ ++it)
+ {
+ LLFolderViewItem* item = *it;
+
+ if (item == item_a)
+ {
+ reverse = false;
+ return common_ancestor;
+ }
+ if (item == item_b)
+ {
+ reverse = true;
+ return common_ancestor;
+ }
+ }
+ break;
+ }
+
+ item_a = item_a_ancestors.front();
+ item_a_ancestors.pop_front();
+ item_b = item_b_ancestors.front();
+ item_b_ancestors.pop_front();
+ }
+
+ return NULL;
+}
+
+void LLFolderViewFolder::gatherChildRangeExclusive(LLFolderViewItem* start, LLFolderViewItem* end, bool reverse, std::vector<LLFolderViewItem*>& items)
+{
+ bool selecting = start == NULL;
+ if (reverse)
+ {
+ for (items_t::reverse_iterator it = mItems.rbegin(), end_it = mItems.rend();
+ it != end_it;
+ ++it)
+ {
+ if (*it == end)
+ {
+ return;
+ }
+ if (selecting)
+ {
+ items.push_back(*it);
+ }
+
+ if (*it == start)
+ {
+ selecting = true;
+ }
+ }
+ for (folders_t::reverse_iterator it = mFolders.rbegin(), end_it = mFolders.rend();
+ it != end_it;
+ ++it)
+ {
+ if (*it == end)
+ {
+ return;
+ }
+
+ if (selecting)
+ {
+ items.push_back(*it);
+ }
+
+ if (*it == start)
+ {
+ selecting = true;
+ }
+ }
+ }
+ else
+ {
+ for (folders_t::iterator it = mFolders.begin(), end_it = mFolders.end();
+ it != end_it;
+ ++it)
+ {
+ if (*it == end)
+ {
+ return;
+ }
+
+ if (selecting)
+ {
+ items.push_back(*it);
+ }
+
+ if (*it == start)
+ {
+ selecting = true;
+ }
+ }
+ for (items_t::iterator it = mItems.begin(), end_it = mItems.end();
+ it != end_it;
+ ++it)
+ {
+ if (*it == end)
+ {
+ return;
+ }
+
+ if (selecting)
+ {
+ items.push_back(*it);
+ }
+
+ if (*it == start)
+ {
+ selecting = true;
+ }
+ }
+ }
+}
+
+void LLFolderViewFolder::extendSelectionTo(LLFolderViewItem* new_selection)
+{
+ if (getRoot()->getAllowMultiSelect() == FALSE) return;
+
+ LLFolderViewItem* cur_selected_item = getRoot()->getCurSelectedItem();
+ if (cur_selected_item == NULL)
+ {
+ cur_selected_item = new_selection;
+ }
+
+
+ bool reverse = false;
+ LLFolderViewFolder* common_ancestor = getCommonAncestor(cur_selected_item, new_selection, reverse);
+ if (!common_ancestor) return;
+
+ LLFolderViewItem* last_selected_item_from_cur = cur_selected_item;
+ LLFolderViewFolder* cur_folder = cur_selected_item->getParentFolder();
+
+ std::vector<LLFolderViewItem*> items_to_select_forward;
+
+ while(cur_folder != common_ancestor)
+ {
+ cur_folder->gatherChildRangeExclusive(last_selected_item_from_cur, NULL, reverse, items_to_select_forward);
+
+ last_selected_item_from_cur = cur_folder;
+ cur_folder = cur_folder->getParentFolder();
+ }
+
+ std::vector<LLFolderViewItem*> items_to_select_reverse;
+
+ LLFolderViewItem* last_selected_item_from_new = new_selection;
+ cur_folder = new_selection->getParentFolder();
+ while(cur_folder != common_ancestor)
+ {
+ cur_folder->gatherChildRangeExclusive(last_selected_item_from_new, NULL, !reverse, items_to_select_reverse);
+
+ last_selected_item_from_new = cur_folder;
+ cur_folder = cur_folder->getParentFolder();
+ }
+
+ common_ancestor->gatherChildRangeExclusive(last_selected_item_from_cur, last_selected_item_from_new, reverse, items_to_select_forward);
+
+ for (std::vector<LLFolderViewItem*>::reverse_iterator it = items_to_select_reverse.rbegin(), end_it = items_to_select_reverse.rend();
+ it != end_it;
+ ++it)
+ {
+ items_to_select_forward.push_back(*it);
+ }
+
+ LLFolderView* root = getRoot();
+
+ for (std::vector<LLFolderViewItem*>::iterator it = items_to_select_forward.begin(), end_it = items_to_select_forward.end();
+ it != end_it;
+ ++it)
+ {
+ LLFolderViewItem* item = *it;
+ if (item->isSelected())
+ {
+ root->removeFromSelectionList(item);
+ }
+ else
+ {
+ item->selectItem();
+ }
+ root->addToSelectionList(item);
+ }
+
+ if (new_selection->isSelected())
+ {
+ root->removeFromSelectionList(new_selection);
+ }
+ else
+ {
+ new_selection->selectItem();
+ }
+ root->addToSelectionList(new_selection);
+}
+
+
+void LLFolderViewFolder::destroyView()
+{
+ std::for_each(mItems.begin(), mItems.end(), DeletePointer());
+ mItems.clear();
+
+ while (!mFolders.empty())
+ {
+ LLFolderViewFolder *folderp = mFolders.back();
+ folderp->destroyView(); // removes entry from mFolders
+ }
+
+ LLFolderViewItem::destroyView();
+}
+
+// extractItem() removes the specified item from the folder, but
+// doesn't delete it.
+void LLFolderViewFolder::extractItem( LLFolderViewItem* item )
+{
+ items_t::iterator it = std::find(mItems.begin(), mItems.end(), item);
+ if(it == mItems.end())
+ {
+ // This is an evil downcast. However, it's only doing
+ // pointer comparison to find if (which it should be ) the
+ // item is in the container, so it's pretty safe.
+ LLFolderViewFolder* f = static_cast<LLFolderViewFolder*>(item);
+ folders_t::iterator ft;
+ ft = std::find(mFolders.begin(), mFolders.end(), f);
+ if (ft != mFolders.end())
+ {
+ mFolders.erase(ft);
+ }
+ }
+ else
+ {
+ mItems.erase(it);
+ }
+ //item has been removed, need to update filter
+ getViewModelItem()->removeChild(item->getViewModelItem());
+ getViewModelItem()->dirtyFilter();
+ //because an item is going away regardless of filter status, force rearrange
+ requestArrange();
+ removeChild(item);
+}
+
+BOOL LLFolderViewFolder::isMovable()
+{
+ if( !(getViewModelItem()->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( !(getViewModelItem()->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)
+{
+ if (item->getParentFolder())
+ {
+ item->getParentFolder()->extractItem(item);
+ }
+ item->setParentFolder(this);
+
+ mItems.push_back(item);
+
+ item->setRect(LLRect(0, 0, getRect().getWidth(), 0));
+ item->setVisible(FALSE);
+
+ addChild(item);
+
+ item->getViewModelItem()->dirtyFilter();
+
+ // Handle sorting
+ requestArrange();
+ requestSort();
+
+ getViewModelItem()->addChild(item->getViewModelItem());
+
+ //TODO RN - make sort bubble up as long as parent Folder doesn't have anything matching sort criteria
+ //// Traverse parent folders and update creation date and resort, if necessary
+ //LLFolderViewFolder* parentp = this;
+ //while (parentp)
+ //{
+ // if (parentp->mSortFunction.isByDate())
+ // {
+ // // parent folder doesn't have a time stamp yet, so get it from us
+ // parentp->requestSort();
+ // }
+
+ // parentp = parentp->getParentFolder();
+ //}
+
+ return TRUE;
+}
+
+// this is an internal method used for adding items to folders.
+BOOL LLFolderViewFolder::addFolder(LLFolderViewFolder* folder)
+{
+ if (folder->mParentFolder)
+ {
+ folder->mParentFolder->extractItem(folder);
+ }
+ folder->mParentFolder = this;
+ mFolders.push_back(folder);
+ folder->setOrigin(0, 0);
+ folder->reshape(getRect().getWidth(), 0);
+ folder->setVisible(FALSE);
+ addChild( folder );
+ folder->getViewModelItem()->dirtyFilter();
+ // rearrange all descendants too, as our indentation level might have changed
+ folder->requestArrange();
+ requestSort();
+
+ getViewModelItem()->addChild(folder->getViewModelItem());
+
+ return TRUE;
+}
+
+void LLFolderViewFolder::requestArrange()
+{
+ mLastArrangeGeneration = -1;
+ // flag all items up to root
+ if (mParentFolder)
+ {
+ mParentFolder->requestArrange();
+ }
+}
+
+void LLFolderViewFolder::toggleOpen()
+{
+ setOpen(!isOpen());
+}
+
+// Force a folder open or closed
+void LLFolderViewFolder::setOpen(BOOL openitem)
+{
+ setOpenArrangeRecursively(openitem);
+}
+
+void LLFolderViewFolder::setOpenArrangeRecursively(BOOL openitem, ERecurseType recurse)
+{
+ BOOL was_open = isOpen();
+ mIsOpen = openitem;
+ if(!was_open && openitem)
+ {
+ getViewModelItem()->openItem();
+ }
+ else if(was_open && !openitem)
+ {
+ getViewModelItem()->closeItem();
+ }
+
+ if (recurse == RECURSE_DOWN || recurse == RECURSE_UP_DOWN)
+ {
+ for (folders_t::iterator iter = mFolders.begin();
+ iter != mFolders.end();)
+ {
+ folders_t::iterator fit = iter++;
+ (*fit)->setOpenArrangeRecursively(openitem, RECURSE_DOWN); /* Flawfinder: ignore */
+ }
+ }
+ if (mParentFolder
+ && (recurse == RECURSE_UP
+ || recurse == RECURSE_UP_DOWN))
+ {
+ mParentFolder->setOpenArrangeRecursively(openitem, RECURSE_UP);
+ }
+
+ if (was_open != isOpen())
+ {
+ requestArrange();
+ }
+}
+
+BOOL LLFolderViewFolder::handleDragAndDropFromChild(MASK mask,
+ BOOL drop,
+ EDragAndDropType c_type,
+ void* cargo_data,
+ EAcceptance* accept,
+ std::string& tooltip_msg)
+{
+ BOOL accepted = mViewModelItem->dragOrDrop(mask,drop,c_type,cargo_data, tooltip_msg);
+ if (accepted)
+ {
+ mDragAndDropTarget = TRUE;
+ *accept = ACCEPT_YES_MULTI;
+ }
+ else
+ {
+ *accept = ACCEPT_NO;
+ }
+
+ // drag and drop to child item, so clear pending auto-opens
+ getRoot()->autoOpenTest(NULL);
+
+ return TRUE;
+}
+
+void LLFolderViewFolder::openItem( void )
+{
+ toggleOpen();
+}
+
+void LLFolderViewFolder::applyFunctorToChildren(LLFolderViewFunctor& functor)
+{
+ for (folders_t::iterator iter = mFolders.begin();
+ iter != mFolders.end();)
+ {
+ folders_t::iterator fit = iter++;
+ functor.doItem((*fit));
+ }
+ for (items_t::iterator iter = mItems.begin();
+ iter != mItems.end();)
+ {
+ items_t::iterator iit = iter++;
+ functor.doItem((*iit));
+ }
+}
+
+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));
+ }
+}
+
+// LLView functionality
+BOOL LLFolderViewFolder::handleDragAndDrop(S32 x, S32 y, MASK mask,
+ BOOL drop,
+ EDragAndDropType cargo_type,
+ void* cargo_data,
+ EAcceptance* accept,
+ std::string& tooltip_msg)
+{
+ BOOL handled = FALSE;
+
+ if (isOpen())
+ {
+ handled = (childrenHandleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg) != NULL);
+ }
+
+ if (!handled)
+ {
+ handleDragAndDropToThisFolder(mask, drop, cargo_type, cargo_data, accept, tooltip_msg);
+
+ lldebugst(LLERR_USER_INPUT) << "dragAndDrop handled by LLFolderViewFolder" << llendl;
+ }
+
+ return TRUE;
+}
+
+BOOL LLFolderViewFolder::handleDragAndDropToThisFolder(MASK mask,
+ BOOL drop,
+ EDragAndDropType cargo_type,
+ void* cargo_data,
+ EAcceptance* accept,
+ std::string& tooltip_msg)
+{
+ BOOL accepted = getViewModelItem()->dragOrDrop(mask,drop,cargo_type,cargo_data, tooltip_msg);
+
+ if (accepted)
+ {
+ mDragAndDropTarget = TRUE;
+ *accept = ACCEPT_YES_MULTI;
+ }
+ else
+ {
+ *accept = ACCEPT_NO;
+ }
+
+ if (!drop && accepted)
+ {
+ getRoot()->autoOpenTest(this);
+ }
+
+ return TRUE;
+}
+
+
+BOOL LLFolderViewFolder::handleRightMouseDown( S32 x, S32 y, MASK mask )
+{
+ BOOL handled = FALSE;
+
+ if( isOpen() )
+ {
+ 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)
+{
+ mIsMouseOverTitle = (y > (getRect().getHeight() - mItemHeight));
+
+ BOOL handled = LLView::handleHover(x, y, mask);
+
+ if (!handled)
+ {
+ // this doesn't do child processing
+ handled = LLFolderViewItem::handleHover(x, y, mask);
+ }
+
+ return handled;
+}
+
+BOOL LLFolderViewFolder::handleMouseDown( S32 x, S32 y, MASK mask )
+{
+ BOOL handled = FALSE;
+ if( isOpen() )
+ {
+ handled = childrenHandleMouseDown(x,y,mask) != NULL;
+ }
+ if( !handled )
+ {
+ if(mIndentation < x && x < mIndentation + ARROW_SIZE + TEXT_PAD)
+ {
+ toggleOpen();
+ handled = TRUE;
+ }
+ else
+ {
+ // do normal selection logic
+ handled = LLFolderViewItem::handleMouseDown(x, y, mask);
+ }
+ }
+
+ return handled;
+}
+
+BOOL LLFolderViewFolder::handleDoubleClick( S32 x, S32 y, MASK mask )
+{
+ BOOL handled = FALSE;
+ if( isOpen() )
+ {
+ handled = childrenHandleDoubleClick( x, y, mask ) != NULL;
+ }
+ if( !handled )
+ {
+ if(mIndentation < x && x < mIndentation + ARROW_SIZE + TEXT_PAD)
+ {
+ // don't select when user double-clicks plus sign
+ // so as not to contradict single-click behavior
+ toggleOpen();
+ }
+ else
+ {
+ getRoot()->setSelection(this, FALSE);
+ toggleOpen();
+ }
+ handled = TRUE;
+ }
+ return handled;
+}
+
+void LLFolderViewFolder::draw()
+{
+ if (mAutoOpenCountdown != 0.f)
+ {
+ mControlLabelRotation = mAutoOpenCountdown * -90.f;
+ }
+ else if (isOpen())
+ {
+ mControlLabelRotation = lerp(mControlLabelRotation, -90.f, LLCriticalDamp::getInterpolant(0.04f));
+ }
+ else
+ {
+ mControlLabelRotation = lerp(mControlLabelRotation, 0.f, LLCriticalDamp::getInterpolant(0.025f));
+ }
+
+ LLFolderViewItem::draw();
+
+ // draw children if root folder, or any other folder that is open or animating to closed state
+ if( getRoot() == this || (isOpen() || mCurHeight != mTargetHeight ))
+ {
+ LLView::draw();
+ }
+
+ mExpanderHighlighted = FALSE;
+}
+
+// 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;
+}
+
diff --git a/indra/llui/llfolderviewitem.h b/indra/llui/llfolderviewitem.h
new file mode 100644
index 0000000000..9cb885066a
--- /dev/null
+++ b/indra/llui/llfolderviewitem.h
@@ -0,0 +1,420 @@
+/**
+* @file llfolderviewitem.h
+* @brief Items and folders that can appear in a hierarchical folder view
+*
+* $LicenseInfo:firstyear=2001&license=viewerlgpl$
+* Second Life Viewer Source Code
+* Copyright (C) 2010, Linden Research, Inc.
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Lesser General Public
+* License as published by the Free Software Foundation;
+* version 2.1 of the License only.
+*
+* This library is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this library; if not, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*
+* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+* $/LicenseInfo$
+*/
+#ifndef LLFOLDERVIEWITEM_H
+#define LLFOLDERVIEWITEM_H
+
+#include "llview.h"
+#include "lluiimage.h"
+
+class LLFolderView;
+class LLFolderViewModelItem;
+class LLFolderViewFolder;
+class LLFolderViewFunctor;
+class LLFolderViewFilter;
+class LLFolderViewModelInterface;
+
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// Class LLFolderViewItem
+//
+// An instance of this class represents a single item in a folder view
+// such as an inventory item or a file.
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+class LLFolderViewItem : public LLView
+{
+public:
+ static void initClass();
+ static void cleanupClass();
+
+ struct Params : public LLInitParam::Block<Params, LLView::Params>
+ {
+ Optional<LLUIImage*> folder_arrow_image,
+ selection_image;
+ Optional<LLFolderView*> root;
+ Mandatory<LLFolderViewModelItem*> listener;
+
+ Optional<S32> folder_indentation, // pixels
+ item_height,
+ item_top_pad;
+
+ Optional<time_t> creation_date;
+
+ Params();
+ };
+
+ // layout constants
+ static const S32 LEFT_PAD = 5;
+ // LEFT_INDENTATION is set via folder_indentation above
+ static const S32 ICON_PAD = 2;
+ static const S32 ICON_WIDTH = 16;
+ static const S32 TEXT_PAD = 1;
+ static const S32 TEXT_PAD_RIGHT = 4;
+ static const S32 ARROW_SIZE = 12;
+ static const S32 MAX_FOLDER_ITEM_OVERLAP = 2;
+ // animation parameters
+ static const F32 FOLDER_CLOSE_TIME_CONSTANT;
+ static const F32 FOLDER_OPEN_TIME_CONSTANT;
+
+private:
+ BOOL mIsSelected;
+
+protected:
+ friend class LLUICtrlFactory;
+ friend class LLFolderViewModelItem;
+
+ LLFolderViewItem(const Params& p);
+
+ std::string mLabel;
+ S32 mLabelWidth;
+ bool mLabelWidthDirty;
+ LLFolderViewFolder* mParentFolder;
+ LLFolderViewModelItem* mViewModelItem;
+ BOOL mIsCurSelection;
+ BOOL mSelectPending;
+ LLFontGL::StyleFlags mLabelStyle;
+ std::string mLabelSuffix;
+ LLUIImagePtr mIcon;
+ LLUIImagePtr mIconOpen;
+ LLUIImagePtr mIconOverlay;
+ BOOL mHasVisibleChildren;
+ S32 mIndentation;
+ S32 mItemHeight;
+ S32 mDragStartX,
+ mDragStartY;
+
+ //TODO RN: create interface for string highlighting
+ //std::string::size_type mStringMatchOffset;
+ F32 mControlLabelRotation;
+ LLFolderView* mRoot;
+ BOOL mDragAndDropTarget;
+ bool mIsMouseOverTitle;
+
+ // this is an internal method used for adding items to folders. A
+ // no-op at this level, but reimplemented in derived classes.
+ virtual BOOL addItem(LLFolderViewItem*) { return FALSE; }
+ virtual BOOL addFolder(LLFolderViewFolder*) { return FALSE; }
+
+ static LLFontGL* getLabelFontForStyle(U8 style);
+
+public:
+ BOOL postBuild();
+
+ virtual void openItem( void );
+
+ void arrangeAndSet(BOOL set_selection, BOOL take_keyboard_focus);
+
+ virtual ~LLFolderViewItem( void );
+
+ // addToFolder() returns TRUE if it succeeds. FALSE otherwise
+ virtual BOOL addToFolder(LLFolderViewFolder* folder);
+
+ // 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.
+ virtual S32 arrange( S32* width, S32* height );
+ virtual S32 getItemHeight();
+
+ // If 'selection' is 'this' then note that otherwise ignore.
+ // Returns TRUE if this item ends up being selected.
+ virtual BOOL setSelection(LLFolderViewItem* selection, BOOL openitem, BOOL take_keyboard_focus);
+
+ // This method is used to set the selection state of an item.
+ // If 'selection' is 'this' then note selection.
+ // Returns TRUE if the selection state of this item was changed.
+ virtual BOOL changeSelection(LLFolderViewItem* selection, BOOL selected);
+
+ // this method is used to deselect this element
+ void deselectItem();
+
+ // this method is used to select this element
+ virtual void selectItem();
+
+ // gets multiple-element selection
+ virtual std::set<LLFolderViewItem*> getSelectionList() const;
+
+ // Returns true is this object and all of its children can be removed (deleted by user)
+ virtual BOOL isRemovable();
+
+ // Returns true is this object and all of its children can be moved
+ virtual BOOL isMovable();
+
+ // destroys this item recursively
+ virtual void destroyView();
+
+ BOOL isSelected() const { return mIsSelected; }
+
+ void setUnselected() { mIsSelected = FALSE; }
+
+ void setIsCurSelection(BOOL select) { mIsCurSelection = select; }
+
+ BOOL getIsCurSelection() { return mIsCurSelection; }
+
+ BOOL hasVisibleChildren() { return mHasVisibleChildren; }
+
+ // Call through to the viewed object and return true if it can be
+ // removed. Returns true if it's removed.
+ //virtual BOOL removeRecursively(BOOL single_item);
+ BOOL remove();
+
+ // Build an appropriate context menu for the item. Flags unused.
+ void buildContextMenu(class LLMenuGL& menu, U32 flags);
+
+ // This method returns the actual name of the thing being
+ // viewed. This method will ask the viewed object itself.
+ const std::string& getName( void ) const;
+
+ // This method returns the label displayed on the view. This
+ // method was primarily added to allow sorting on the folder
+ // contents possible before the entire view has been constructed.
+ const std::string& getLabel() const { return mLabel; }
+
+
+ LLFolderViewFolder* getParentFolder( void ) { return mParentFolder; }
+ const LLFolderViewFolder* getParentFolder( void ) const { return mParentFolder; }
+
+ void setParentFolder(LLFolderViewFolder* parent) { mParentFolder = parent; }
+
+ LLFolderViewItem* getNextOpenNode( BOOL include_children = TRUE );
+ LLFolderViewItem* getPreviousOpenNode( BOOL include_children = TRUE );
+
+ const LLFolderViewModelItem* getViewModelItem( void ) const { return mViewModelItem; }
+ LLFolderViewModelItem* getViewModelItem( void ) { return mViewModelItem; }
+
+ const LLFolderViewModelInterface* getFolderViewModel( void ) const;
+ LLFolderViewModelInterface* getFolderViewModel( void );
+
+ // just rename the object.
+ void rename(const std::string& new_name);
+
+
+ // Show children (unfortunate that this is called "open")
+ virtual void setOpen(BOOL open = TRUE) {};
+ virtual BOOL isOpen() const { return FALSE; }
+
+ virtual LLFolderView* getRoot();
+ virtual const LLFolderView* getRoot() const;
+ BOOL isDescendantOf( const LLFolderViewFolder* potential_ancestor );
+ S32 getIndentation() { return mIndentation; }
+
+ virtual BOOL passedFilter(S32 filter_generation = -1);
+
+ // refresh information from the object being viewed.
+ virtual void refresh();
+
+ // LLView functionality
+ virtual BOOL handleRightMouseDown( S32 x, S32 y, MASK mask );
+ virtual BOOL handleMouseDown( S32 x, S32 y, MASK mask );
+ virtual BOOL handleHover( S32 x, S32 y, MASK mask );
+ virtual BOOL handleMouseUp( S32 x, S32 y, MASK mask );
+ virtual BOOL handleDoubleClick( S32 x, S32 y, MASK mask );
+
+ virtual void onMouseLeave(S32 x, S32 y, MASK mask);
+
+ virtual LLView* findChildView(const std::string& name, BOOL recurse) const { return NULL; }
+
+ // virtual void handleDropped();
+ virtual void draw();
+ virtual BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop,
+ EDragAndDropType cargo_type,
+ void* cargo_data,
+ EAcceptance* accept,
+ std::string& tooltip_msg);
+
+private:
+ static std::map<U8, LLFontGL*> sFonts; // map of styles to fonts
+};
+
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// Class LLFolderViewFolder
+//
+// An instance of an LLFolderViewFolder represents a collection of
+// more folders and items. This is used to build the hierarchy of
+// items found in the folder view.
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+class LLFolderViewFolder : public LLFolderViewItem
+{
+protected:
+ LLFolderViewFolder( const LLFolderViewItem::Params& );
+ friend class LLUICtrlFactory;
+
+public:
+
+ typedef std::list<LLFolderViewItem*> items_t;
+ typedef std::list<LLFolderViewFolder*> folders_t;
+
+protected:
+ items_t mItems;
+ folders_t mFolders;
+
+ BOOL mIsOpen;
+ BOOL mExpanderHighlighted;
+ F32 mCurHeight;
+ F32 mTargetHeight;
+ F32 mAutoOpenCountdown;
+ S32 mLastArrangeGeneration;
+ S32 mLastCalculatedWidth;
+ S32 mMostFilteredDescendantGeneration;
+ bool mNeedsSort;
+
+public:
+ typedef enum e_recurse_type
+ {
+ RECURSE_NO,
+ RECURSE_UP,
+ RECURSE_DOWN,
+ RECURSE_UP_DOWN
+ } ERecurseType;
+
+
+ virtual ~LLFolderViewFolder( void );
+
+ LLFolderViewItem* getNextFromChild( LLFolderViewItem*, BOOL include_children = TRUE );
+ LLFolderViewItem* getPreviousFromChild( LLFolderViewItem*, BOOL include_children = TRUE );
+
+ // addToFolder() returns TRUE if it succeeds. FALSE otherwise
+ virtual BOOL addToFolder(LLFolderViewFolder* folder);
+
+ // 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.
+ virtual S32 arrange( S32* width, S32* height );
+
+ BOOL needsArrange();
+
+ bool descendantsPassedFilter(S32 filter_generation = -1);
+
+ // Passes selection information on to children and record
+ // selection information if necessary.
+ // Returns TRUE if this object (or a child) ends up being selected.
+ // If 'openitem' is TRUE then folders are opened up along the way to the selection.
+ virtual BOOL setSelection(LLFolderViewItem* selection, BOOL openitem, BOOL take_keyboard_focus = TRUE);
+
+ // This method is used to change the selection of an item.
+ // Recursively traverse all children; if 'selection' is 'this' then change
+ // the select status if necessary.
+ // Returns TRUE if the selection state of this folder, or of a child, was changed.
+ virtual BOOL changeSelection(LLFolderViewItem* selection, BOOL selected);
+
+ // this method is used to group select items
+ void extendSelectionTo(LLFolderViewItem* selection);
+
+ // Returns true is this object and all of its children can be removed.
+ virtual BOOL isRemovable();
+
+ // Returns true is this object and all of its children can be moved
+ virtual BOOL isMovable();
+
+ // destroys this folder, and all children
+ virtual void destroyView();
+
+ // If this folder can be removed, remove all children that can be
+ // removed, return TRUE if this is empty after the operation and
+ // it's viewed folder object can be removed.
+ //virtual BOOL removeRecursively(BOOL single_item);
+ //virtual BOOL remove();
+
+ // extractItem() removes the specified item from the folder, but
+ // doesn't delete it.
+ virtual void extractItem( LLFolderViewItem* item );
+
+ // This function is called by a child that needs to be resorted.
+ void resort(LLFolderViewItem* item);
+
+ void setAutoOpenCountdown(F32 countdown) { mAutoOpenCountdown = countdown; }
+
+ // folders can be opened. This will usually be called by internal
+ // methods.
+ virtual void toggleOpen();
+
+ // Force a folder open or closed
+ virtual void setOpen(BOOL openitem = TRUE);
+
+ // Called when a child is refreshed.
+ virtual void requestArrange();
+
+ virtual void requestSort();
+
+ // internal method which doesn't update the entire view. This
+ // method was written because the list iterators destroy the state
+ // of other iterations, thus, we can't arrange while iterating
+ // through the children (such as when setting which is selected.
+ virtual void setOpenArrangeRecursively(BOOL openitem, ERecurseType recurse = RECURSE_NO);
+
+ // Get the current state of the folder.
+ virtual BOOL isOpen() const { return mIsOpen; }
+
+ // special case if an object is dropped on the child.
+ BOOL handleDragAndDropFromChild(MASK mask,
+ BOOL drop,
+ EDragAndDropType cargo_type,
+ void* cargo_data,
+ EAcceptance* accept,
+ std::string& tooltip_msg);
+
+ void applyFunctorRecursively(LLFolderViewFunctor& functor);
+
+ // Just apply this functor to the folder's immediate children.
+ void applyFunctorToChildren(LLFolderViewFunctor& functor);
+
+ virtual void openItem( void );
+ virtual BOOL addItem(LLFolderViewItem* item);
+ virtual BOOL addFolder( LLFolderViewFolder* folder);
+
+ // LLView functionality
+ virtual BOOL handleHover(S32 x, S32 y, MASK mask);
+ virtual BOOL handleRightMouseDown( S32 x, S32 y, MASK mask );
+ virtual BOOL handleMouseDown( S32 x, S32 y, MASK mask );
+ virtual BOOL handleDoubleClick( S32 x, S32 y, MASK mask );
+ virtual BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop,
+ EDragAndDropType cargo_type,
+ void* cargo_data,
+ EAcceptance* accept,
+ std::string& tooltip_msg);
+ BOOL handleDragAndDropToThisFolder(MASK mask, BOOL drop,
+ EDragAndDropType cargo_type,
+ void* cargo_data,
+ EAcceptance* accept,
+ std::string& tooltip_msg);
+ virtual void draw();
+
+ folders_t::iterator getFoldersBegin() { return mFolders.begin(); }
+ folders_t::iterator getFoldersEnd() { return mFolders.end(); }
+ folders_t::size_type getFoldersCount() const { return mFolders.size(); }
+
+ items_t::const_iterator getItemsBegin() const { return mItems.begin(); }
+ items_t::const_iterator getItemsEnd() const { return mItems.end(); }
+ items_t::size_type getItemsCount() const { return mItems.size(); }
+
+ LLFolderViewFolder* getCommonAncestor(LLFolderViewItem* item_a, LLFolderViewItem* item_b, bool& reverse);
+ void gatherChildRangeExclusive(LLFolderViewItem* start, LLFolderViewItem* end, bool reverse, std::vector<LLFolderViewItem*>& items);
+
+public:
+ //WARNING: do not call directly...use the appropriate LLFolderViewModel-derived class instead
+ template<typename SORT_FUNC> void sortFolders(const SORT_FUNC& func) { mFolders.sort(func); }
+ template<typename SORT_FUNC> void sortItems(const SORT_FUNC& func) { mItems.sort(func); }
+};
+
+
+#endif // LLFOLDERVIEWITEM_H
diff --git a/indra/llui/llfolderviewmodel.cpp b/indra/llui/llfolderviewmodel.cpp
new file mode 100644
index 0000000000..dc6e4d754b
--- /dev/null
+++ b/indra/llui/llfolderviewmodel.cpp
@@ -0,0 +1,53 @@
+/**
+ * @file llfolderviewmodel.cpp
+ * @brief Implementation of the view model collection of classes.
+ *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "linden_common.h"
+
+#include "llfolderviewmodel.h"
+#include "lltrans.h"
+
+bool LLFolderViewModelCommon::needsSort(LLFolderViewModelItem* item)
+{
+ return item->getSortVersion() < mTargetSortVersion;
+}
+
+std::string LLFolderViewModelCommon::getStatusText()
+{
+ if (!contentsReady() || mFolderView->getViewModelItem()->getLastFilterGeneration() < getFilter()->getCurrentGeneration())
+ {
+ return LLTrans::getString("Searching");
+ }
+ else
+ {
+ return getFilter()->getEmptyLookupMessage();
+ }
+}
+
+void LLFolderViewModelCommon::filter()
+{
+ getFilter()->setFilterCount(llclamp(LLUI::sSettingGroups["config"]->getS32("FilterItemsPerFrame"), 1, 5000));
+ mFolderView->getViewModelItem()->filter(*getFilter());
+}
diff --git a/indra/llui/llfolderviewmodel.h b/indra/llui/llfolderviewmodel.h
new file mode 100644
index 0000000000..0f5f9a1f50
--- /dev/null
+++ b/indra/llui/llfolderviewmodel.h
@@ -0,0 +1,353 @@
+/**
+ * @file llfolderviewmodel.h
+ *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+#ifndef LLFOLDERVIEWMODEL_H
+#define LLFOLDERVIEWMODEL_H
+
+#include "llfontgl.h" // just for StyleFlags enum
+#include "llfolderview.h"
+
+// These are grouping of inventory types.
+// Order matters when sorting system folders to the top.
+enum EInventorySortGroup
+{
+ SG_SYSTEM_FOLDER,
+ SG_TRASH_FOLDER,
+ SG_NORMAL_FOLDER,
+ SG_ITEM
+};
+
+class LLFontGL;
+class LLInventoryModel;
+class LLMenuGL;
+class LLUIImage;
+class LLUUID;
+class LLFolderViewItem;
+class LLFolderViewFolder;
+
+class LLFolderViewFilter
+{
+public:
+ enum EFilterModified
+ {
+ FILTER_NONE, // nothing to do, already filtered
+ FILTER_RESTART, // restart filtering from scratch
+ FILTER_LESS_RESTRICTIVE, // existing filtered items will certainly pass this filter
+ FILTER_MORE_RESTRICTIVE // if you didn't pass the previous filter, you definitely won't pass this one
+ };
+
+public:
+
+ LLFolderViewFilter() {}
+ virtual ~LLFolderViewFilter() {}
+
+ // +-------------------------------------------------------------------+
+ // + Execution And Results
+ // +-------------------------------------------------------------------+
+ virtual bool check(const LLFolderViewModelItem* item) = 0;
+ virtual bool checkFolder(const LLFolderViewModelItem* folder) const = 0;
+
+ virtual void setEmptyLookupMessage(const std::string& message) = 0;
+ virtual std::string getEmptyLookupMessage() const = 0;
+
+ virtual bool showAllResults() const = 0;
+
+ // +-------------------------------------------------------------------+
+ // + Status
+ // +-------------------------------------------------------------------+
+ virtual bool isActive() const = 0;
+ virtual bool isModified() const = 0;
+ virtual void clearModified() = 0;
+ virtual const std::string& getName() const = 0;
+ virtual const std::string& getFilterText() = 0;
+ //RN: this is public to allow system to externally force a global refilter
+ virtual void setModified(EFilterModified behavior = FILTER_RESTART) = 0;
+
+ // +-------------------------------------------------------------------+
+ // + Count
+ // +-------------------------------------------------------------------+
+ virtual void setFilterCount(S32 count) = 0;
+ virtual S32 getFilterCount() const = 0;
+ virtual void decrementFilterCount() = 0;
+
+ // +-------------------------------------------------------------------+
+ // + Default
+ // +-------------------------------------------------------------------+
+ virtual bool isDefault() const = 0;
+ virtual bool isNotDefault() const = 0;
+ virtual void markDefault() = 0;
+ virtual void resetDefault() = 0;
+
+ // +-------------------------------------------------------------------+
+ // + Generation
+ // +-------------------------------------------------------------------+
+ virtual S32 getCurrentGeneration() const = 0;
+ virtual S32 getFirstSuccessGeneration() const = 0;
+ virtual S32 getFirstRequiredGeneration() const = 0;
+};
+
+class LLFolderViewModelInterface
+{
+public:
+ virtual ~LLFolderViewModelInterface() {}
+ virtual void requestSortAll() = 0;
+
+ virtual void sort(class LLFolderViewFolder*) = 0;
+ virtual void filter() = 0;
+
+ virtual bool contentsReady() = 0;
+ virtual void setFolderView(LLFolderView* folder_view) = 0;
+ virtual LLFolderViewFilter* getFilter() = 0;
+ virtual const LLFolderViewFilter* getFilter() const = 0;
+ virtual std::string getStatusText() = 0;
+
+ virtual bool startDrag(std::vector<LLFolderViewModelItem*>& items) = 0;
+};
+
+class LLFolderViewModelCommon : public LLFolderViewModelInterface
+{
+public:
+ LLFolderViewModelCommon()
+ : mTargetSortVersion(0),
+ mFolderView(NULL)
+ {}
+
+ virtual void requestSortAll()
+ {
+ // sort everything
+ mTargetSortVersion++;
+ }
+ virtual std::string getStatusText();
+ virtual void filter();
+
+ void setFolderView(LLFolderView* folder_view) { mFolderView = folder_view;}
+
+protected:
+ bool needsSort(class LLFolderViewModelItem* item);
+
+ S32 mTargetSortVersion;
+ LLFolderView* mFolderView;
+
+};
+
+template <typename SORT_TYPE, typename ITEM_TYPE, typename FOLDER_TYPE, typename FILTER_TYPE>
+class LLFolderViewModel : public LLFolderViewModelCommon
+{
+public:
+ LLFolderViewModel(){}
+ virtual ~LLFolderViewModel() {}
+
+ typedef SORT_TYPE SortType;
+ typedef ITEM_TYPE ItemType;
+ typedef FOLDER_TYPE FolderType;
+ typedef FILTER_TYPE FilterType;
+
+ virtual SortType& getSorter() { return mSorter; }
+ virtual const SortType& getSorter() const { return mSorter; }
+ virtual void setSorter(const SortType& sorter) { mSorter = sorter; requestSortAll(); }
+
+ virtual FilterType* getFilter() { return &mFilter; }
+ virtual const FilterType* getFilter() const { return &mFilter; }
+ virtual void setFilter(const FilterType& filter) { mFilter = filter; }
+
+ // TODO RN: remove this and put all filtering logic in view model
+ // add getStatusText and isFiltering()
+ virtual bool contentsReady() { return true; }
+
+
+ struct ViewModelCompare
+ {
+ ViewModelCompare(const SortType& sorter)
+ : mSorter(sorter)
+ {}
+
+ bool operator () (const LLFolderViewItem* a, const LLFolderViewItem* b) const
+ {
+ return mSorter(static_cast<const ItemType*>(a->getViewModelItem()), static_cast<const ItemType*>(b->getViewModelItem()));
+ }
+
+ bool operator () (const LLFolderViewFolder* a, const LLFolderViewFolder* b) const
+ {
+ return mSorter(static_cast<const ItemType*>(a->getViewModelItem()), static_cast<const ItemType*>(b->getViewModelItem()));
+ }
+
+ const SortType& mSorter;
+ };
+
+ void sort(LLFolderViewFolder* folder)
+ {
+ if (needsSort(folder->getViewModelItem()))
+ {
+ folder->sortFolders(ViewModelCompare(getSorter()));
+ folder->sortItems(ViewModelCompare(getSorter()));
+ folder->getViewModelItem()->setSortVersion(mTargetSortVersion);
+ folder->requestArrange();
+ }
+ }
+
+protected:
+ SortType mSorter;
+ FilterType mFilter;
+};
+
+// This is am abstract base class that users of the folderview classes
+// would use to bridge the folder view with the underlying data
+class LLFolderViewModelItem
+{
+public:
+ virtual ~LLFolderViewModelItem( void ) {};
+
+ virtual void update() {} //called when drawing
+ virtual const std::string& getName() const = 0;
+ virtual const std::string& getDisplayName() const = 0;
+ virtual const std::string& getSearchableName() const = 0;
+
+ virtual LLPointer<LLUIImage> getIcon() const = 0;
+ virtual LLPointer<LLUIImage> getIconOpen() const { return getIcon(); }
+ virtual LLPointer<LLUIImage> getIconOverlay() const { return NULL; }
+
+ virtual LLFontGL::StyleFlags getLabelStyle() const = 0;
+ virtual std::string getLabelSuffix() const = 0;
+
+ virtual void openItem( void ) = 0;
+ virtual void closeItem( void ) = 0;
+ virtual void selectItem(void) = 0;
+
+ virtual BOOL isItemRenameable() const = 0;
+ virtual BOOL renameItem(const std::string& new_name) = 0;
+
+ virtual BOOL isItemMovable( void ) const = 0; // Can be moved to another folder
+ virtual void move( LLFolderViewModelItem* parent_listener ) = 0;
+
+ virtual BOOL isItemRemovable( void ) const = 0; // Can be destroyed
+ virtual BOOL removeItem() = 0;
+ virtual void removeBatch(std::vector<LLFolderViewModelItem*>& batch) = 0;
+
+ virtual BOOL isItemCopyable() const = 0;
+ virtual BOOL copyToClipboard() const = 0;
+ virtual BOOL cutToClipboard() const = 0;
+
+ virtual BOOL isClipboardPasteable() const = 0;
+ virtual void pasteFromClipboard() = 0;
+ virtual void pasteLinkFromClipboard() = 0;
+
+ virtual void buildContextMenu(LLMenuGL& menu, U32 flags) = 0;
+
+ virtual bool potentiallyVisible() = 0; // is the item definitely visible or we haven't made up our minds yet?
+
+ virtual bool filter( LLFolderViewFilter& filter) = 0;
+ virtual bool passedFilter(S32 filter_generation = -1) = 0;
+ virtual bool descendantsPassedFilter(S32 filter_generation = -1) = 0;
+ virtual void setPassedFilter(bool passed, bool passed_folder, S32 filter_generation) = 0;
+ virtual void dirtyFilter() = 0;
+
+ virtual S32 getLastFilterGeneration() const = 0;
+
+ virtual bool hasChildren() const = 0;
+ virtual void addChild(LLFolderViewModelItem* child) = 0;
+ virtual void removeChild(LLFolderViewModelItem* child) = 0;
+
+ // This method will be called to determine if a drop can be
+ // performed, and will set drop to TRUE if a drop is
+ // requested. Returns TRUE if a drop is possible/happened,
+ // otherwise FALSE.
+ virtual BOOL dragOrDrop(MASK mask, BOOL drop,
+ EDragAndDropType cargo_type,
+ void* cargo_data,
+ std::string& tooltip_msg) = 0;
+
+ virtual void requestSort() = 0;
+ virtual S32 getSortVersion() = 0;
+ virtual void setSortVersion(S32 version) = 0;
+ virtual void setParent(LLFolderViewModelItem* parent) = 0;
+
+protected:
+
+ friend class LLFolderViewItem;
+ virtual void setFolderViewItem(LLFolderViewItem* folder_view_item) = 0;
+
+};
+
+class LLFolderViewModelItemCommon : public LLFolderViewModelItem
+{
+public:
+ LLFolderViewModelItemCommon()
+ : mSortVersion(-1),
+ mPassedFilter(true),
+ mPassedFolderFilter(true),
+ mFolderViewItem(NULL),
+ mLastFilterGeneration(-1),
+ mMostFilteredDescendantGeneration(-1),
+ mParent(NULL)
+ {
+ std::for_each(mChildren.begin(), mChildren.end(), DeletePointer());
+ }
+
+ void requestSort() { mSortVersion = -1; }
+ S32 getSortVersion() { return mSortVersion; }
+ void setSortVersion(S32 version) { mSortVersion = version;}
+
+ S32 getLastFilterGeneration() const { return mLastFilterGeneration; }
+ void dirtyFilter()
+ {
+ mLastFilterGeneration = -1;
+
+ // bubble up dirty flag all the way to root
+ if (mParent)
+ {
+ mParent->dirtyFilter();
+ }
+ }
+ virtual void addChild(LLFolderViewModelItem* child)
+ {
+ mChildren.push_back(child);
+ child->setParent(this);
+ }
+ virtual void removeChild(LLFolderViewModelItem* child)
+ {
+ mChildren.remove(child);
+ child->setParent(NULL);
+ }
+
+protected:
+ virtual void setParent(LLFolderViewModelItem* parent) { mParent = parent; }
+
+ S32 mSortVersion;
+ bool mPassedFilter;
+ bool mPassedFolderFilter;
+
+ S32 mLastFilterGeneration;
+ S32 mMostFilteredDescendantGeneration;
+
+
+ typedef std::list<LLFolderViewModelItem*> child_list_t;
+ child_list_t mChildren;
+ LLFolderViewModelItem* mParent;
+
+ void setFolderViewItem(LLFolderViewItem* folder_view_item) { mFolderViewItem = folder_view_item;}
+ LLFolderViewItem* mFolderViewItem;
+};
+
+
+#endif // LLFOLDERVIEWMODEL_H