/** * @file llfolderviewitem.cpp * @brief Items and folders that can appear in a hierarchical folder view * * $LicenseInfo:firstyear=2001&license=viewergpl$ * * Copyright (c) 2001-2009, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at * http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" #include "llfolderviewitem.h" // viewer includes #include "llfolderview.h" // Items depend extensively on LLFolderViews #include "llfoldervieweventlistener.h" #include "llinventorybridge.h" // for LLItemBridge in LLInventorySort::operator() #include "llinventoryfilter.h" #include "llinventorymodel.h" // *TODO: make it take a pointer to an inventory-model interface #include "llviewercontrol.h" // gSavedSettings #include "llviewerwindow.h" // Argh, only for setCursor() // linden library includes #include "llfocusmgr.h" // gFocusMgr #include "llpanel.h" // panel->hasFocus() #include "lltrans.h" ///---------------------------------------------------------------------------- /// Class LLFolderViewItem ///---------------------------------------------------------------------------- // statics std::map LLFolderViewItem::sFonts; // map of styles to fonts const LLFontGL* LLFolderViewItem::sSmallFont = NULL; LLUIImagePtr LLFolderViewItem::sArrowImage; LLUIImagePtr LLFolderViewItem::sBoxImage; // 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); sFonts[style] = rtn; } return rtn; } //static void LLFolderViewItem::initClass() { sSmallFont = LLFontGL::getFontMonospace(); sArrowImage = LLUI::getUIImage("folder_arrow.tga"); sBoxImage = LLUI::getUIImage("rounded_square.tga"); } //static void LLFolderViewItem::cleanupClass() { sFonts.clear(); sArrowImage = NULL; sBoxImage = NULL; } // NOTE: Optimize this, we call it a *lot* when opening a large inventory LLFolderViewItem::Params::Params() : icon("icon"), folder_arrow_image("folder_arrow_image", LLUI::getUIImage("folder_arrow.tga")), selection_image("selection_image", LLUI::getUIImage("rounded_square.tga")) { mouse_opaque(true); follows.flags(FOLLOWS_LEFT|FOLLOWS_TOP|FOLLOWS_RIGHT); // JAMESDEBUG tab_stop(false); } // Default constructor LLFolderViewItem::LLFolderViewItem(LLFolderViewItem::Params p) : LLView(p), mLabelWidth(0), mLabelWidthDirty(false), mParentFolder( NULL ), mIsSelected( FALSE ), mIsCurSelection( FALSE ), mSelectPending(FALSE), mLabelStyle( LLFontGL::NORMAL ), mHasVisibleChildren(FALSE), mIndentation(0), mNumDescendantsSelected(0), mFiltered(FALSE), mLastFilterGeneration(-1), mStringMatchOffset(std::string::npos), mControlLabelRotation(0.f), mDragAndDropTarget(FALSE), mIsLoading(FALSE), mLabel(p.name), mRoot(p.root), mCreationDate(p.creation_date), mIcon(p.icon), mIconOpen(p.icon_open), mListener(p.listener), mArrowImage(p.folder_arrow_image), mBoxImage(p.selection_image) , mDontShowInHierarhy(false) { refresh(); } // Destroys the object LLFolderViewItem::~LLFolderViewItem( void ) { delete mListener; mListener = NULL; } LLFolderView* LLFolderViewItem::getRoot() { return mRoot; } // Returns true if this object is a child (or grandchild, etc.) of potential_ancestor. BOOL LLFolderViewItem::isDescendantOf( const LLFolderViewFolder* potential_ancestor ) { LLFolderViewItem* root = this; while( root->mParentFolder ) { if( root->mParentFolder == potential_ancestor ) { return TRUE; } root = root->mParentFolder; } return FALSE; } LLFolderViewItem* LLFolderViewItem::getNextOpenNode(BOOL include_children) { if (!mParentFolder) { return NULL; } LLFolderViewItem* itemp = mParentFolder->getNextFromChild( this, include_children ); while(itemp && !itemp->getVisible()) { LLFolderViewItem* next_itemp = itemp->mParentFolder->getNextFromChild( itemp, include_children ); if (itemp == next_itemp) { // hit last item return itemp->getVisible() ? itemp : this; } itemp = next_itemp; } return itemp; } LLFolderViewItem* LLFolderViewItem::getPreviousOpenNode(BOOL include_children) { if (!mParentFolder) { return NULL; } LLFolderViewItem* itemp = mParentFolder->getPreviousFromChild( this, include_children ); while(itemp && !itemp->getVisible()) { LLFolderViewItem* next_itemp = itemp->mParentFolder->getPreviousFromChild( itemp, include_children ); if (itemp == next_itemp) { // hit first item return itemp->getVisible() ? itemp : this; } itemp = next_itemp; } return itemp; } // is this item something we think we should be showing? // for example, if we haven't gotten around to filtering it yet, then the answer is yes // until we find out otherwise BOOL LLFolderViewItem::potentiallyVisible() { // we haven't been checked against min required filter // or we have and we passed return getLastFilterGeneration() < getRoot()->getFilter()->getMinRequiredGeneration() || getFiltered(); } BOOL LLFolderViewItem::getFiltered() { return mFiltered && mLastFilterGeneration >= getRoot()->getFilter()->getMinRequiredGeneration(); } BOOL LLFolderViewItem::getFiltered(S32 filter_generation) { return mFiltered && mLastFilterGeneration >= filter_generation; } void LLFolderViewItem::setFiltered(BOOL filtered, S32 filter_generation) { mFiltered = filtered; mLastFilterGeneration = filter_generation; } void LLFolderViewItem::setIcon(LLUIImagePtr icon) { mIcon = icon; } // refresh information from the listener void LLFolderViewItem::refreshFromListener() { if(mListener) { mLabel = mListener->getDisplayName(); LLAssetType::EType preferred_type = mListener->getPreferredType(); // *TODO: to be removed when database supports multi language. This is a // temporary attempt to display the inventory folder in the user locale. // mantipov: *NOTE: be sure this code is synchronized with LLFriendCardsManager::findChildFolderUUID // it uses the same way to find localized string if (LLAssetType::lookupIsProtectedCategoryType(preferred_type)) { LLTrans::findString(mLabel, "InvFolder " + mLabel); }; setIcon(mListener->getIcon()); time_t creation_date = mListener->getCreationDate(); if (mCreationDate != creation_date) { mCreationDate = mListener->getCreationDate(); dirtyFilter(); } mLabelStyle = mListener->getLabelStyle(); mLabelSuffix = mListener->getLabelSuffix(); } } void LLFolderViewItem::refresh() { refreshFromListener(); std::string searchable_label(mLabel); searchable_label.append(mLabelSuffix); LLStringUtil::toUpper(searchable_label); if (mSearchableLabel.compare(searchable_label)) { mSearchableLabel.assign(searchable_label); dirtyFilter(); // some part of label has changed, so overall width has potentially changed, and sort order too if (mParentFolder) { mParentFolder->requestSort(); mParentFolder->requestArrange(); } } mLabelWidthDirty = true; } void LLFolderViewItem::applyListenerFunctorRecursively(LLFolderViewListenerFunctor& functor) { functor(mListener); } // This function is called when items are added or view filters change. It's // implemented here but called by derived classes when folding the // views. void LLFolderViewItem::filterFromRoot( void ) { LLFolderViewItem* root = getRoot(); root->filter(*((LLFolderView*)root)->getFilter()); } // This function is called when the folder view is dirty. It's // implemented here but called by derived classes when folding the // views. void LLFolderViewItem::arrangeFromRoot() { LLFolderViewItem* root = getRoot(); S32 height = 0; S32 width = 0; S32 total_height = root->arrange( &width, &height, 0 ); LLSD params; params["action"] = "size_changes"; params["height"] = total_height; getParent()->notifyParent(params); } // Utility function for LLFolderView void LLFolderViewItem::arrangeAndSet(BOOL set_selection, BOOL take_keyboard_focus) { LLFolderView* root = getRoot(); getParentFolder()->requestArrange(); if(set_selection) { setSelectionFromRoot(this, TRUE, take_keyboard_focus); if(root) { root->scrollToShowSelection(); } } } // This function clears the currently selected item, and records the // specified selected item appropriately for display and use in the // UI. If open is TRUE, then folders are opened up along the way to // the selection. void LLFolderViewItem::setSelectionFromRoot(LLFolderViewItem* selection, BOOL openitem, BOOL take_keyboard_focus) { getRoot()->setSelection(selection, openitem, take_keyboard_focus); } // helper function to change the selection from the root. void LLFolderViewItem::changeSelectionFromRoot(LLFolderViewItem* selection, BOOL selected) { getRoot()->changeSelection(selection, selected); } void LLFolderViewItem::extendSelectionFromRoot(LLFolderViewItem* selection) { LLDynamicArray selected_items; getRoot()->extendSelection(selection, NULL, selected_items); } EInventorySortGroup LLFolderViewItem::getSortGroup() const { return SG_ITEM; } // addToFolder() returns TRUE if it succeeds. FALSE otherwise BOOL LLFolderViewItem::addToFolder(LLFolderViewFolder* folder, LLFolderView* root) { if (!folder) { return FALSE; } mParentFolder = folder; root->addItemID(getListener()->getUUID(), this); return folder->addItem(this); } // Finds width and height of this object and it's children. Also // makes sure that this view and it's children are the right size. S32 LLFolderViewItem::arrange( S32* width, S32* height, S32 filter_generation) { mIndentation = mParentFolder ? mParentFolder->getIndentation() + LEFT_INDENTATION : 0; if (mLabelWidthDirty) { mLabelWidth = ARROW_SIZE + TEXT_PAD + ICON_WIDTH + ICON_PAD + getLabelFontForStyle(mLabelStyle)->getWidth(mSearchableLabel); 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() { if (mDontShowInHierarhy) return 0; S32 icon_height = mIcon->getHeight(); S32 label_height = llround(getLabelFontForStyle(mLabelStyle)->getLineHeight()); return llmax( icon_height, label_height ) + ICON_PAD; } void LLFolderViewItem::filter( LLInventoryFilter& filter) { BOOL filtered = mListener && filter.check(this); // if our visibility will change as a result of this filter, then // we need to be rearranged in our parent folder if (getVisible() != filtered) { if (mParentFolder) { mParentFolder->requestArrange(); } } setFiltered(filtered, filter.getCurrentGeneration()); mStringMatchOffset = filter.getStringMatchOffset(); filter.decrementFilterCount(); if (getRoot()->getDebugFilters()) { mStatusText = llformat("%d", mLastFilterGeneration); } } void LLFolderViewItem::dirtyFilter() { mLastFilterGeneration = -1; // bubble up dirty flag all the way to root if (getParentFolder()) { getParentFolder()->setCompletedFilterGeneration(-1, TRUE); } } // *TODO: This can be optimized a lot by simply recording that it is // selected in the appropriate places, and assuming that set selection // means 'deselect' for a leaf item. Do this optimization after // multiple selection is implemented to make sure it all plays nice // together. BOOL LLFolderViewItem::setSelection(LLFolderViewItem* selection, BOOL openitem, BOOL take_keyboard_focus) { if( selection == this ) { mIsSelected = TRUE; if(mListener) { mListener->selectItem(); } } else { mIsSelected = FALSE; } return mIsSelected; } BOOL LLFolderViewItem::changeSelection(LLFolderViewItem* selection, BOOL selected) { if(selection == this && mIsSelected != selected) { mIsSelected = selected; if(mListener) { mListener->selectItem(); } return TRUE; } return FALSE; } void LLFolderViewItem::recursiveDeselect(BOOL deselect_self) { if (mIsSelected && deselect_self) { mIsSelected = FALSE; // update ancestors' count of selected descendents LLFolderViewFolder* parent_folder = getParentFolder(); while(parent_folder) { parent_folder->mNumDescendantsSelected--; parent_folder = parent_folder->getParentFolder(); } } } BOOL LLFolderViewItem::isMovable() { if( mListener ) { return mListener->isItemMovable(); } else { return TRUE; } } BOOL LLFolderViewItem::isRemovable() { if( mListener ) { return mListener->isItemRemovable(); } else { return TRUE; } } void LLFolderViewItem::destroyView() { if (mParentFolder) { // removeView deletes me mParentFolder->removeView(this); } } // Call through to the viewed object and return true if it can be // removed. //BOOL LLFolderViewItem::removeRecursively(BOOL single_item) BOOL LLFolderViewItem::remove() { if(!isRemovable()) { return FALSE; } if(mListener) { return mListener->removeItem(); } return TRUE; } // Build an appropriate context menu for the item. void LLFolderViewItem::buildContextMenu(LLMenuGL& menu, U32 flags) { if(mListener) { mListener->buildContextMenu(menu, flags); } } void LLFolderViewItem::openItem( void ) { if( mListener ) { mListener->openItem(); } } void LLFolderViewItem::preview( void ) { if (mListener) { mListener->previewItem(); } } void LLFolderViewItem::rename(const std::string& new_name) { if( !new_name.empty() ) { if( mListener ) { mListener->renameItem(new_name); if(mParentFolder) { mParentFolder->requestSort(); } } } } const std::string& LLFolderViewItem::getSearchableLabel() const { return mSearchableLabel; } std::string LLFolderViewItem::getName( void ) const { if(mListener) { return mListener->getName(); } return mLabel; } // LLView functionality BOOL LLFolderViewItem::handleRightMouseDown( S32 x, S32 y, MASK mask ) { if(!mIsSelected) { setSelectionFromRoot(this, FALSE); } make_ui_sound("UISndClick"); return TRUE; } BOOL LLFolderViewItem::handleMouseDown( S32 x, S32 y, MASK mask ) { 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) { changeSelectionFromRoot(this, !mIsSelected); } else if (mask & MASK_SHIFT) { extendSelectionFromRoot(this); } else { setSelectionFromRoot(this, FALSE); } make_ui_sound("UISndClick"); } else { mSelectPending = TRUE; } if( isMovable() ) { S32 screen_x; S32 screen_y; localPointToScreen(x, y, &screen_x, &screen_y ); LLToolDragAndDrop::getInstance()->setDragStart( screen_x, screen_y ); } return TRUE; } BOOL LLFolderViewItem::handleHover( S32 x, S32 y, MASK mask ) { if( hasMouseCapture() && isMovable() ) { S32 screen_x; S32 screen_y; localPointToScreen(x, y, &screen_x, &screen_y ); BOOL can_drag = TRUE; if( LLToolDragAndDrop::getInstance()->isOverThreshold( screen_x, screen_y ) ) { LLFolderView* root = getRoot(); if(root->getCurSelectedItem()) { LLToolDragAndDrop::ESource src = LLToolDragAndDrop::SOURCE_WORLD; // *TODO: push this into listener and remove // dependency on llagent if (mListener && gInventory.isObjectDescendentOf(mListener->getUUID(), gInventory.getRootFolderID())) { src = LLToolDragAndDrop::SOURCE_AGENT; } else if (mListener && gInventory.isObjectDescendentOf(mListener->getUUID(), gInventory.getLibraryRootFolderID())) { src = LLToolDragAndDrop::SOURCE_LIBRARY; } can_drag = root->startDrag(src); if (can_drag) { // if (mListener) mListener->startDrag(); // RN: when starting drag and drop, clear out last auto-open root->autoOpenTest(NULL); root->setShowSelectionContext(TRUE); // Release keyboard focus, so that if stuff is dropped into the // world, pressing the delete key won't blow away the inventory // item. gFocusMgr.setKeyboardFocus(NULL); return LLToolDragAndDrop::getInstance()->handleHover( x, y, mask ); } } } if (can_drag) { gViewerWindow->setCursor(UI_CURSOR_ARROW); } else { gViewerWindow->setCursor(UI_CURSOR_NOLOCKED); } return TRUE; } else { getRoot()->setShowSelectionContext(FALSE); gViewerWindow->setCursor(UI_CURSOR_ARROW); // let parent handle this then... return FALSE; } } BOOL LLFolderViewItem::handleDoubleClick( S32 x, S32 y, MASK mask ) { preview(); return TRUE; } BOOL LLFolderViewItem::handleScrollWheel(S32 x, S32 y, S32 clicks) { if (getParent()) { return getParent()->handleScrollWheel(x, y, clicks); } return FALSE; } BOOL LLFolderViewItem::handleMouseUp( S32 x, S32 y, MASK mask ) { if (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) { changeSelectionFromRoot(this, !mIsSelected); } else if (mask & MASK_SHIFT) { extendSelectionFromRoot(this); } else { setSelectionFromRoot(this, FALSE); } } mSelectPending = FALSE; if( hasMouseCapture() ) { getRoot()->setShowSelectionContext(FALSE); gFocusMgr.setMouseCapture( NULL ); } return TRUE; } BOOL LLFolderViewItem::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg) { BOOL accepted = FALSE; BOOL handled = FALSE; if(mListener) { accepted = mListener->dragOrDrop(mask,drop,cargo_type,cargo_data); handled = accepted; if (accepted) { mDragAndDropTarget = TRUE; *accept = ACCEPT_YES_MULTI; } else { *accept = ACCEPT_NO; } } if(mParentFolder && !handled) { // 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() { if (mDontShowInHierarhy) return; 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 sFilterBGColor = LLUIColorTable::instance().getColor("FilterBackgroundColor", DEFAULT_WHITE); static LLUIColor sFilterTextColor = LLUIColorTable::instance().getColor("FilterTextColor", DEFAULT_WHITE); static LLUIColor sSuffixColor = LLUIColorTable::instance().getColor("InventoryItemSuffixColor", DEFAULT_WHITE); static LLUIColor sSearchStatusColor = LLUIColorTable::instance().getColor("InventorySearchStatusColor", DEFAULT_WHITE); bool possibly_has_children = false; bool up_to_date = mListener && mListener->isUpToDate(); if((up_to_date && hasVisibleChildren() ) || // we fetched our children and some of them have passed the filter... (!up_to_date && mListener && mListener->hasChildren())) // ...or we know we have children but haven't fetched them (doesn't obey filter) { possibly_has_children = true; } if(/*mControlLabel[0] != '\0' && */possibly_has_children) { if (sArrowImage) { gl_draw_scaled_rotated_image(mIndentation, getRect().getHeight() - ARROW_SIZE - TEXT_PAD, ARROW_SIZE, ARROW_SIZE, mControlLabelRotation, sArrowImage->getImage(), sFgColor); } } F32 text_left = (F32)(ARROW_SIZE + TEXT_PAD + ICON_WIDTH + ICON_PAD + mIndentation); LLFontGL* font = getLabelFontForStyle(mLabelStyle); // If we have keyboard focus, draw selection filled BOOL show_context = getRoot()->getShowSelectionContext(); BOOL filled = show_context || (getRoot()->getParentPanel()->hasFocus()); // always render "current" item, only render other selected items if // mShowSingleSelection is FALSE if( mIsSelected ) { gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); LLColor4 bg_color = sHighlightBgColor; //const S32 TRAILING_PAD = 5; // It just looks better with this. if (!mIsCurSelection) { // do time-based fade of extra objects F32 fade_time = getRoot()->getSelectionFadeElapsedTime(); if (getRoot()->getShowSingleSelection()) { // fading out bg_color.mV[VALPHA] = clamp_rescale(fade_time, 0.f, 0.4f, bg_color.mV[VALPHA], 0.f); } else { // fading in bg_color.mV[VALPHA] = clamp_rescale(fade_time, 0.f, 0.4f, 0.f, bg_color.mV[VALPHA]); } } gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth() - 2, llfloor(getRect().getHeight() - font->getLineHeight() - ICON_PAD), bg_color, filled); if (mIsCurSelection) { gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth() - 2, llfloor(getRect().getHeight() - font->getLineHeight() - ICON_PAD), sHighlightFgColor, FALSE); } if (getRect().getHeight() > llround(font->getLineHeight()) + ICON_PAD + 2) { gl_rect_2d( 0, llfloor(getRect().getHeight() - font->getLineHeight() - ICON_PAD) - 2, getRect().getWidth() - 2, 2, sHighlightFgColor, FALSE); if (show_context) { gl_rect_2d( 0, llfloor(getRect().getHeight() - font->getLineHeight() - ICON_PAD) - 2, getRect().getWidth() - 2, 2, sHighlightBgColor, TRUE); } } } if (mDragAndDropTarget) { gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth() - 2, llfloor(getRect().getHeight() - font->getLineHeight() - ICON_PAD), sHighlightBgColor, FALSE); if (getRect().getHeight() > llround(font->getLineHeight()) + ICON_PAD + 2) { gl_rect_2d( 0, llfloor(getRect().getHeight() - font->getLineHeight() - ICON_PAD) - 2, getRect().getWidth() - 2, 2, sHighlightBgColor, FALSE); } mDragAndDropTarget = FALSE; } // First case is used for open folders if (!mIconOpen.isNull() && (llabs(mControlLabelRotation) > 80)) { mIconOpen->draw(mIndentation + ARROW_SIZE + TEXT_PAD, getRect().getHeight() - mIcon->getHeight()); } else if(mIcon) { mIcon->draw(mIndentation + ARROW_SIZE + TEXT_PAD, getRect().getHeight() - mIcon->getHeight()); } if (!mLabel.empty()) { // highlight filtered text BOOL debug_filters = getRoot()->getDebugFilters(); LLColor4 color = ( (mIsSelected && filled) ? sHighlightFgColor : sFgColor ); F32 right_x; F32 y = (F32)getRect().getHeight() - font->getLineHeight() - (F32)TEXT_PAD; if (debug_filters) { if (!getFiltered() && !possibly_has_children) { color.mV[VALPHA] *= 0.5f; } LLColor4 filter_color = mLastFilterGeneration >= getRoot()->getFilter()->getCurrentGeneration() ? LLColor4(0.5f, 0.8f, 0.5f, 1.f) : LLColor4(0.8f, 0.5f, 0.5f, 1.f); sSmallFont->renderUTF8(mStatusText, 0, text_left, y, filter_color, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, &right_x, FALSE ); text_left = right_x; } if ( mIsLoading && mTimeSinceRequestStart.getElapsedTimeF32() >= gSavedSettings.getF32("FolderLoadingMessageWaitTime") ) { font->renderUTF8(LLTrans::getString("LoadingData"), 0, text_left, y, sSearchStatusColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, &right_x, FALSE); text_left = right_x; } 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); 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 ); } if (sBoxImage.notNull() && 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); S32 top = getRect().getHeight(); LLRect box_rect(left, top, right, bottom); sBoxImage->draw(box_rect, sFilterBGColor); F32 match_string_left = text_left + font->getWidthF32(combined_string, 0, mStringMatchOffset); F32 y = (F32)getRect().getHeight() - font->getLineHeight() - (F32)TEXT_PAD; font->renderUTF8( combined_string, mStringMatchOffset, match_string_left, y, sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, filter_string_length, S32_MAX, &right_x, FALSE ); } } } } ///---------------------------------------------------------------------------- /// Class LLFolderViewFolder ///---------------------------------------------------------------------------- LLFolderViewFolder::LLFolderViewFolder( const LLFolderViewItem::Params& p ): LLFolderViewItem( p ), // 0 = no create time mIsOpen(FALSE), mExpanderHighlighted(FALSE), mCurHeight(0.f), mTargetHeight(0.f), mAutoOpenCountdown(0.f), mSubtreeCreationDate(0), mAmTrash(LLFolderViewFolder::UNKNOWN), mLastArrangeGeneration( -1 ), mLastCalculatedWidth(0), mCompletedFilterGeneration(-1), mMostFilteredDescendantGeneration(-1), mNeedsSort(false) {} // Destroys the object LLFolderViewFolder::~LLFolderViewFolder( void ) { // The LLView base class takes care of object destruction. make sure that we // don't have mouse or keyboard focus gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit() } // addToFolder() returns TRUE if it succeeds. FALSE otherwise BOOL LLFolderViewFolder::addToFolder(LLFolderViewFolder* folder, LLFolderView* root) { if (!folder) { return FALSE; } mParentFolder = folder; root->addItemID(getListener()->getUUID(), this); return folder->addFolder(this); } // Finds width and height of this object and it's children. Also // makes sure that this view and it's children are the right size. S32 LLFolderViewFolder::arrange( S32* width, S32* height, S32 filter_generation) { // sort before laying out contents if (mNeedsSort) { mFolders.sort(mSortFunction); mItems.sort(mSortFunction); mNeedsSort = false; } mHasVisibleChildren = hasFilteredDescendants(filter_generation); LLInventoryFilter::EFolderShow show_folder_state = getRoot()->getFilter()->getShowFolderState(); // calculate height as a single item (without any children), and reshapes rectangle to match LLFolderViewItem::arrange( width, height, filter_generation ); // clamp existing animated height so as to never get smaller than a single item mCurHeight = llmax((F32)*height, mCurHeight); // initialize running height value as height of single item in case we have no children *height = getItemHeight(); F32 running_height = (F32)*height; F32 target_height = (F32)*height; // are my children visible? if (needsArrange()) { // set last arrange generation first, in case children are animating // and need to be arranged again mLastArrangeGeneration = getRoot()->getArrangeGeneration(); if (mIsOpen) { // Add sizes of children S32 parent_item_height = getRect().getHeight(); for(folders_t::iterator fit = mFolders.begin(); fit != mFolders.end(); ++fit) { LLFolderViewFolder* folderp = (*fit); if (getRoot()->getDebugFilters()) { folderp->setVisible(TRUE); } else { folderp->setVisible(show_folder_state == LLInventoryFilter::SHOW_ALL_FOLDERS || // always show folders? (folderp->getFiltered(filter_generation) || folderp->hasFilteredDescendants(filter_generation))); // passed filter or has descendants that passed filter } if (folderp->getVisible()) { S32 child_width = *width; S32 child_height = 0; S32 child_top = parent_item_height - llround(running_height); target_height += folderp->arrange( &child_width, &child_height, filter_generation ); running_height += (F32)child_height; *width = llmax(*width, child_width); folderp->setOrigin( 0, child_top - folderp->getRect().getHeight() ); } } for(items_t::iterator iit = mItems.begin(); iit != mItems.end(); ++iit) { LLFolderViewItem* itemp = (*iit); if (getRoot()->getDebugFilters()) { itemp->setVisible(TRUE); } else { itemp->setVisible(itemp->getFiltered(filter_generation)); } if (itemp->getVisible()) { S32 child_width = *width; S32 child_height = 0; S32 child_top = parent_item_height - llround(running_height); target_height += itemp->arrange( &child_width, &child_height, filter_generation ); // don't change width, as this item is as wide as its parent folder by construction itemp->reshape( itemp->getRect().getWidth(), child_height); running_height += (F32)child_height; *width = llmax(*width, child_width); itemp->setOrigin( 0, child_top - itemp->getRect().getHeight() ); } } } mTargetHeight = target_height; // cache this width so next time we can just return it mLastCalculatedWidth = *width; } else { // just use existing width *width = mLastCalculatedWidth; } // animate current height towards target height if (llabs(mCurHeight - mTargetHeight) > 1.f) { mCurHeight = lerp(mCurHeight, mTargetHeight, LLCriticalDamp::getInterpolant(mIsOpen ? FOLDER_OPEN_TIME_CONSTANT : FOLDER_CLOSE_TIME_CONSTANT)); requestArrange(); // hide child elements that fall out of current animated height for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; // number of pixels that bottom of folder label is from top of parent folder if (getRect().getHeight() - (*fit)->getRect().mTop + (*fit)->getItemHeight() > llround(mCurHeight) + MAX_FOLDER_ITEM_OVERLAP) { // hide if beyond current folder height (*fit)->setVisible(FALSE); } } for (items_t::iterator iter = mItems.begin(); iter != mItems.end();) { items_t::iterator iit = iter++; // number of pixels that bottom of item label is from top of parent folder if (getRect().getHeight() - (*iit)->getRect().mBottom > llround(mCurHeight) + MAX_FOLDER_ITEM_OVERLAP) { (*iit)->setVisible(FALSE); } } } else { mCurHeight = mTargetHeight; } // don't change width as this item is already as wide as its parent folder reshape(getRect().getWidth(),llround(mCurHeight)); // pass current height value back to parent *height = llround(mCurHeight); return llround(mTargetHeight); } BOOL LLFolderViewFolder::needsArrange() { return mLastArrangeGeneration < getRoot()->getArrangeGeneration(); } void LLFolderViewFolder::requestSort() { mNeedsSort = true; // whenever item order changes, we need to lay things out again requestArrange(); } void LLFolderViewFolder::setCompletedFilterGeneration(S32 generation, BOOL recurse_up) { mMostFilteredDescendantGeneration = llmin(mMostFilteredDescendantGeneration, generation); mCompletedFilterGeneration = generation; // only aggregate up if we are a lower (older) value if (recurse_up && mParentFolder && generation < mParentFolder->getCompletedFilterGeneration()) { mParentFolder->setCompletedFilterGeneration(generation, TRUE); } } void LLFolderViewFolder::filter( LLInventoryFilter& filter) { S32 filter_generation = filter.getCurrentGeneration(); // if failed to pass filter newer than must_pass_generation // you will automatically fail this time, so we only // check against items that have passed the filter S32 must_pass_generation = filter.getMustPassGeneration(); bool autoopen_folders = (filter.hasFilterString()); // if we have already been filtered against this generation, skip out if (getCompletedFilterGeneration() >= filter_generation) { return; } // filter folder itself if (getLastFilterGeneration() < filter_generation) { if (getLastFilterGeneration() >= must_pass_generation && // folder has been compared to a valid precursor filter !mFiltered) // and did not pass the filter { // go ahead and flag this folder as done mLastFilterGeneration = filter_generation; } else { // filter self only on first pass through LLFolderViewItem::filter( filter ); } } if (getRoot()->getDebugFilters()) { mStatusText = llformat("%d", mLastFilterGeneration); mStatusText += llformat("(%d)", mCompletedFilterGeneration); mStatusText += llformat("+%d", mMostFilteredDescendantGeneration); } // all descendants have been filtered later than must pass generation // but none passed if(getCompletedFilterGeneration() >= must_pass_generation && !hasFilteredDescendants(must_pass_generation)) { // don't traverse children if we've already filtered them since must_pass_generation // and came back with nothing return; } // we entered here with at least one filter iteration left // check to see if we have any more before continuing on to children if (filter.getFilterCount() < 0) { return; } // when applying a filter, matching folders get their contents downloaded first if (filter.isNotDefault() && getFiltered(filter.getMinRequiredGeneration()) && (mListener && !gInventory.isCategoryComplete(mListener->getUUID()))) { gInventory.startBackgroundFetch(mListener->getUUID()); } // now query children for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; // have we run out of iterations this frame? if (filter.getFilterCount() < 0) { break; } // mMostFilteredDescendantGeneration might have been reset // in which case we need to update it even for folders that // don't need to be filtered anymore if ((*fit)->getCompletedFilterGeneration() >= filter_generation) { // track latest generation to pass any child items if ((*fit)->getFiltered() || (*fit)->hasFilteredDescendants(filter.getMinRequiredGeneration())) { mMostFilteredDescendantGeneration = filter_generation; if (getRoot()->needsAutoSelect() && autoopen_folders) { (*fit)->setOpenArrangeRecursively(TRUE); } } // just skip it, it has already been filtered continue; } // update this folders filter status (and children) (*fit)->filter( filter ); // track latest generation to pass any child items if ((*fit)->getFiltered() || (*fit)->hasFilteredDescendants(filter_generation)) { mMostFilteredDescendantGeneration = filter_generation; if (getRoot()->needsAutoSelect() && autoopen_folders) { (*fit)->setOpenArrangeRecursively(TRUE); } } } for (items_t::iterator iter = mItems.begin(); iter != mItems.end();) { items_t::iterator iit = iter++; if (filter.getFilterCount() < 0) { break; } if ((*iit)->getLastFilterGeneration() >= filter_generation) { if ((*iit)->getFiltered()) { mMostFilteredDescendantGeneration = filter_generation; } continue; } if ((*iit)->getLastFilterGeneration() >= must_pass_generation && !(*iit)->getFiltered(must_pass_generation)) { // failed to pass an earlier filter that was a subset of the current one // go ahead and flag this item as done (*iit)->setFiltered(FALSE, filter_generation); continue; } (*iit)->filter( filter ); if ((*iit)->getFiltered(filter.getMinRequiredGeneration())) { mMostFilteredDescendantGeneration = filter_generation; } } // if we didn't use all filter iterations // that means we filtered all of our descendants // instead of exhausting the filter count for this frame if (filter.getFilterCount() > 0) { // flag this folder as having completed filter pass for all descendants setCompletedFilterGeneration(filter_generation, FALSE/*dont recurse up to root*/); } } void LLFolderViewFolder::setFiltered(BOOL filtered, S32 filter_generation) { // if this folder is now filtered, but wasn't before // (it just passed) if (filtered && !mFiltered) { // reset current height, because last time we drew it // it might have had more visible items than now mCurHeight = 0.f; } LLFolderViewItem::setFiltered(filtered, filter_generation); } void LLFolderViewFolder::dirtyFilter() { // we're a folder, so invalidate our completed generation setCompletedFilterGeneration(-1, FALSE); LLFolderViewItem::dirtyFilter(); } BOOL LLFolderViewFolder::hasFilteredDescendants() { return mMostFilteredDescendantGeneration >= getRoot()->getFilter()->getCurrentGeneration(); } // Passes selection information on to children and record selection // information if necessary. BOOL LLFolderViewFolder::setSelection(LLFolderViewItem* selection, BOOL openitem, BOOL take_keyboard_focus) { BOOL rv = FALSE; if( selection == this ) { mIsSelected = TRUE; if(mListener) { mListener->selectItem(); } rv = TRUE; } else { mIsSelected = FALSE; rv = FALSE; } BOOL child_selected = FALSE; for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; if((*fit)->setSelection(selection, openitem, take_keyboard_focus)) { rv = TRUE; child_selected = TRUE; mNumDescendantsSelected++; } } for (items_t::iterator iter = mItems.begin(); iter != mItems.end();) { items_t::iterator iit = iter++; if((*iit)->setSelection(selection, openitem, take_keyboard_focus)) { rv = TRUE; child_selected = TRUE; mNumDescendantsSelected++; } } if(openitem && child_selected) { setOpenArrangeRecursively(TRUE); } return rv; } // This method is used to change the selection of an item. If // selection is 'this', then note selection as true. Returns TRUE // if this or a child is now selected. BOOL LLFolderViewFolder::changeSelection(LLFolderViewItem* selection, BOOL selected) { BOOL rv = FALSE; if(selection == this) { mIsSelected = selected; if(mListener && selected) { mListener->selectItem(); } rv = TRUE; } for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; if((*fit)->changeSelection(selection, selected)) { if (selected) { mNumDescendantsSelected++; } else { mNumDescendantsSelected--; } rv = TRUE; } } for (items_t::iterator iter = mItems.begin(); iter != mItems.end();) { items_t::iterator iit = iter++; if((*iit)->changeSelection(selection, selected)) { if (selected) { mNumDescendantsSelected++; } else { mNumDescendantsSelected--; } rv = TRUE; } } return rv; } S32 LLFolderViewFolder::extendSelection(LLFolderViewItem* selection, LLFolderViewItem* last_selected, LLDynamicArray& selected_items) { S32 num_selected = 0; // pass on to child folders first for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; num_selected += (*fit)->extendSelection(selection, last_selected, selected_items); mNumDescendantsSelected += num_selected; } // handle selection of our immediate children... BOOL reverse_select = FALSE; BOOL found_last_selected = FALSE; BOOL found_selection = FALSE; LLDynamicArray items_to_select; LLFolderViewItem* item; //...folders first... for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; item = (*fit); if(item == selection) { found_selection = TRUE; } else if (item == last_selected) { found_last_selected = TRUE; if (found_selection) { reverse_select = TRUE; } } if (found_selection || found_last_selected) { // deselect currently selected items so they can be pushed back on queue if (item->isSelected()) { item->changeSelection(item, FALSE); } items_to_select.put(item); } if (found_selection && found_last_selected) { break; } } if (!(found_selection && found_last_selected)) { //,,,then items for (items_t::iterator iter = mItems.begin(); iter != mItems.end();) { items_t::iterator iit = iter++; item = (*iit); if(item == selection) { found_selection = TRUE; } else if (item == last_selected) { found_last_selected = TRUE; if (found_selection) { reverse_select = TRUE; } } if (found_selection || found_last_selected) { // deselect currently selected items so they can be pushed back on queue if (item->isSelected()) { item->changeSelection(item, FALSE); } items_to_select.put(item); } if (found_selection && found_last_selected) { break; } } } if (found_last_selected && found_selection) { // we have a complete selection inside this folder for (S32 index = reverse_select ? items_to_select.getLength() - 1 : 0; reverse_select ? index >= 0 : index < items_to_select.getLength(); reverse_select ? index-- : index++) { LLFolderViewItem* item = items_to_select[index]; if (item->changeSelection(item, TRUE)) { selected_items.put(item); mNumDescendantsSelected++; num_selected++; } } } else if (found_selection) { // last selection was not in this folder....go ahead and select just the new item if (selection->changeSelection(selection, TRUE)) { selected_items.put(selection); mNumDescendantsSelected++; num_selected++; } } return num_selected; } void LLFolderViewFolder::recursiveDeselect(BOOL deselect_self) { // make sure we don't have negative values llassert(mNumDescendantsSelected >= 0); if (mIsSelected && deselect_self) { mIsSelected = FALSE; // update ancestors' count of selected descendents LLFolderViewFolder* parent_folder = getParentFolder(); while(parent_folder) { parent_folder->mNumDescendantsSelected--; parent_folder = parent_folder->getParentFolder(); } } if (0 == mNumDescendantsSelected) { return; } for (items_t::iterator iter = mItems.begin(); iter != mItems.end();) { items_t::iterator iit = iter++; LLFolderViewItem* item = (*iit); item->recursiveDeselect(TRUE); } for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; LLFolderViewFolder* folder = (*fit); folder->recursiveDeselect(TRUE); } } void LLFolderViewFolder::destroyView() { for (items_t::iterator iter = mItems.begin(); iter != mItems.end();) { items_t::iterator iit = iter++; LLFolderViewItem* item = (*iit); getRoot()->removeItemID(item->getListener()->getUUID()); } std::for_each(mItems.begin(), mItems.end(), DeletePointer()); mItems.clear(); while (!mFolders.empty()) { LLFolderViewFolder *folderp = mFolders.back(); folderp->destroyView(); // removes entry from mFolders } deleteAllChildren(); if (mParentFolder) { mParentFolder->removeView(this); } } // remove the specified item (and any children) if possible. Return // TRUE if the item was deleted. BOOL LLFolderViewFolder::removeItem(LLFolderViewItem* item) { if(item->remove()) { //RN: this seem unneccessary as remove() moves to trash //removeView(item); return TRUE; } return FALSE; } // simply remove the view (and any children) Don't bother telling the // listeners. void LLFolderViewFolder::removeView(LLFolderViewItem* item) { if (!item || item->getParentFolder() != this) { return; } // deselect without traversing hierarchy item->recursiveDeselect(TRUE); getRoot()->removeFromSelectionList(item); extractItem(item); delete item; } // extractItem() removes the specified item from the folder, but // doesn't delete it. void LLFolderViewFolder::extractItem( LLFolderViewItem* item ) { items_t::iterator it = std::find(mItems.begin(), mItems.end(), item); if(it == mItems.end()) { // This is an evil downcast. However, it's only doing // pointer comparison to find if (which it should be ) the // item is in the container, so it's pretty safe. LLFolderViewFolder* f = reinterpret_cast(item); folders_t::iterator ft; ft = std::find(mFolders.begin(), mFolders.end(), f); if(ft != mFolders.end()) { mFolders.erase(ft); } } else { mItems.erase(it); } //item has been removed, need to update filter dirtyFilter(); //because an item is going away regardless of filter status, force rearrange requestArrange(); getRoot()->removeItemID(item->getListener()->getUUID()); removeChild(item); } bool LLFolderViewFolder::isTrash() const { if (mAmTrash == LLFolderViewFolder::UNKNOWN) { mAmTrash = mListener->getUUID() == gInventory.findCategoryUUIDForType(LLAssetType::AT_TRASH, false) ? LLFolderViewFolder::TRASH : LLFolderViewFolder::NOT_TRASH; } return mAmTrash == LLFolderViewFolder::TRASH; } void LLFolderViewFolder::sortBy(U32 order) { if (!mSortFunction.updateSort(order)) { // No changes. return; } // Propegate this change to sub folders for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; (*fit)->sortBy(order); } mFolders.sort(mSortFunction); mItems.sort(mSortFunction); if (order & LLInventoryFilter::SO_DATE) { time_t latest = 0; if (!mItems.empty()) { LLFolderViewItem* item = *(mItems.begin()); latest = item->getCreationDate(); } if (!mFolders.empty()) { LLFolderViewFolder* folder = *(mFolders.begin()); if (folder->getCreationDate() > latest) { latest = folder->getCreationDate(); } } mSubtreeCreationDate = latest; } } void LLFolderViewFolder::setItemSortOrder(U32 ordering) { if (mSortFunction.updateSort(ordering)) { for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; (*fit)->setItemSortOrder(ordering); } mFolders.sort(mSortFunction); mItems.sort(mSortFunction); } } EInventorySortGroup LLFolderViewFolder::getSortGroup() const { if (isTrash()) { return SG_TRASH_FOLDER; } // Folders that can't be moved are 'system' folders. if( mListener ) { if( !(mListener->isItemMovable()) ) { return SG_SYSTEM_FOLDER; } } return SG_NORMAL_FOLDER; } BOOL LLFolderViewFolder::isMovable() { if( mListener ) { if( !(mListener->isItemMovable()) ) { return FALSE; } for (items_t::iterator iter = mItems.begin(); iter != mItems.end();) { items_t::iterator iit = iter++; if(!(*iit)->isMovable()) { return FALSE; } } for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; if(!(*fit)->isMovable()) { return FALSE; } } } return TRUE; } BOOL LLFolderViewFolder::isRemovable() { if( mListener ) { if( !(mListener->isItemRemovable()) ) { return FALSE; } for (items_t::iterator iter = mItems.begin(); iter != mItems.end();) { items_t::iterator iit = iter++; if(!(*iit)->isRemovable()) { return FALSE; } } for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; if(!(*fit)->isRemovable()) { return FALSE; } } } return TRUE; } // this is an internal method used for adding items to folders. BOOL LLFolderViewFolder::addItem(LLFolderViewItem* item) { mItems.push_back(item); item->setRect(LLRect(0, 0, getRect().getWidth(), 0)); item->setVisible(FALSE); addChild( item ); item->dirtyFilter(); requestArrange(); requestSort(); return TRUE; } // this is an internal method used for adding items to folders. BOOL LLFolderViewFolder::addFolder(LLFolderViewFolder* folder) { mFolders.push_back(folder); folder->setOrigin(0, 0); folder->reshape(getRect().getWidth(), 0); folder->setVisible(FALSE); addChild( folder ); folder->dirtyFilter(); // rearrange all descendants too, as our indentation level might have changed folder->requestArrange(TRUE); requestSort(); return TRUE; } void LLFolderViewFolder::requestArrange(BOOL include_descendants) { mLastArrangeGeneration = -1; // flag all items up to root if (mParentFolder) { mParentFolder->requestArrange(); } if (include_descendants) { for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end(); ++iter) { (*iter)->requestArrange(TRUE); } } } void LLFolderViewFolder::toggleOpen() { setOpen(!mIsOpen); } // Force a folder open or closed void LLFolderViewFolder::setOpen(BOOL openitem) { setOpenArrangeRecursively(openitem); } void LLFolderViewFolder::setOpenArrangeRecursively(BOOL openitem, ERecurseType recurse) { BOOL was_open = mIsOpen; mIsOpen = openitem; if (mListener) { if(!was_open && openitem) { mListener->openItem(); } else if(was_open && !openitem) { mListener->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 != mIsOpen) { requestArrange(); } } BOOL LLFolderViewFolder::handleDragAndDropFromChild(MASK mask, BOOL drop, EDragAndDropType c_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg) { BOOL accepted = mListener && mListener->dragOrDrop(mask,drop,c_type,cargo_data); if (accepted) { mDragAndDropTarget = TRUE; *accept = ACCEPT_YES_MULTI; } else { *accept = ACCEPT_NO; } // drag and drop to child item, so clear pending auto-opens getRoot()->autoOpenTest(NULL); return TRUE; } void LLFolderViewFolder::openItem( void ) { toggleOpen(); } void LLFolderViewFolder::applyFunctorRecursively(LLFolderViewFunctor& functor) { functor.doFolder(this); for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; (*fit)->applyFunctorRecursively(functor); } for (items_t::iterator iter = mItems.begin(); iter != mItems.end();) { items_t::iterator iit = iter++; functor.doItem((*iit)); } } void LLFolderViewFolder::applyListenerFunctorRecursively(LLFolderViewListenerFunctor& functor) { functor(mListener); for (folders_t::iterator iter = mFolders.begin(); iter != mFolders.end();) { folders_t::iterator fit = iter++; (*fit)->applyListenerFunctorRecursively(functor); } for (items_t::iterator iter = mItems.begin(); iter != mItems.end();) { items_t::iterator iit = iter++; (*iit)->applyListenerFunctorRecursively(functor); } } // LLView functionality BOOL LLFolderViewFolder::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg) { LLFolderView* root_view = getRoot(); BOOL handled = FALSE; if(mIsOpen) { handled = childrenHandleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg) != NULL; } if (!handled) { BOOL accepted = mListener && mListener->dragOrDrop(mask, drop,cargo_type,cargo_data); if (accepted) { mDragAndDropTarget = TRUE; *accept = ACCEPT_YES_MULTI; } else { *accept = ACCEPT_NO; } if (!drop && accepted) { root_view->autoOpenTest(this); } lldebugst(LLERR_USER_INPUT) << "dragAndDrop handled by LLFolderViewFolder" << llendl; } return TRUE; } BOOL LLFolderViewFolder::handleRightMouseDown( S32 x, S32 y, MASK mask ) { BOOL handled = FALSE; // fetch contents of this folder, as context menu can depend on contents // still, user would have to open context menu again to see the changes gInventory.fetchDescendentsOf(mListener->getUUID()); if( mIsOpen ) { handled = childrenHandleRightMouseDown( x, y, mask ) != NULL; } if (!handled) { handled = LLFolderViewItem::handleRightMouseDown( x, y, mask ); } return handled; } BOOL LLFolderViewFolder::handleHover(S32 x, S32 y, MASK mask) { BOOL handled = LLView::handleHover(x, y, mask); if (!handled) { // this doesn't do child processing handled = LLFolderViewItem::handleHover(x, y, mask); } //if(x < LEFT_INDENTATION + mIndentation && x > mIndentation - LEFT_PAD && y > getRect().getHeight() - ) //{ // gViewerWindow->setCursor(UI_CURSOR_ARROW); // mExpanderHighlighted = TRUE; // handled = TRUE; //} return handled; } BOOL LLFolderViewFolder::handleMouseDown( S32 x, S32 y, MASK mask ) { BOOL handled = FALSE; if( mIsOpen ) { handled = childrenHandleMouseDown(x,y,mask) != NULL; } if( !handled ) { if(x < LEFT_INDENTATION + mIndentation && x > mIndentation - LEFT_PAD) { toggleOpen(); handled = TRUE; } else { // do normal selection logic handled = LLFolderViewItem::handleMouseDown(x, y, mask); } } return handled; } BOOL LLFolderViewFolder::handleDoubleClick( S32 x, S32 y, MASK mask ) { const LLUUID &cat_uuid = getListener()->getUUID(); const LLViewerInventoryCategory *cat = gInventory.getCategory(cat_uuid); if (cat && cat->getPreferredType() == LLAssetType::AT_OUTFIT) { getListener()->performAction(NULL, NULL,"replaceoutfit"); return TRUE; } BOOL handled = FALSE; if( mIsOpen ) { handled = childrenHandleDoubleClick( x, y, mask ) != NULL; } if( !handled ) { if(x < LEFT_INDENTATION + mIndentation && x > mIndentation - LEFT_PAD) { // don't select when user double-clicks plus sign // so as not to contradict single-click behavior toggleOpen(); } else { setSelectionFromRoot(this, FALSE); toggleOpen(); } handled = TRUE; } return handled; } void LLFolderViewFolder::draw() { if (mAutoOpenCountdown != 0.f) { mControlLabelRotation = mAutoOpenCountdown * -90.f; } else if (mIsOpen) { mControlLabelRotation = lerp(mControlLabelRotation, -90.f, LLCriticalDamp::getInterpolant(0.04f)); } else { mControlLabelRotation = lerp(mControlLabelRotation, 0.f, LLCriticalDamp::getInterpolant(0.025f)); } bool possibly_has_children = false; bool up_to_date = mListener && mListener->isUpToDate(); if(!up_to_date && mListener && mListener->hasChildren()) // we know we have children but haven't fetched them (doesn't obey filter) { possibly_has_children = true; } BOOL loading = ( mIsOpen && possibly_has_children && !up_to_date ); if ( loading && !mIsLoading ) { // Measure how long we've been in the loading state mTimeSinceRequestStart.reset(); } mIsLoading = loading; LLFolderViewItem::draw(); // draw children if root folder, or any other folder that is open or animating to closed state if( getRoot() == this || (mIsOpen || mCurHeight != mTargetHeight )) { LLView::draw(); } mExpanderHighlighted = FALSE; } time_t LLFolderViewFolder::getCreationDate() const { return llmax(mCreationDate, mSubtreeCreationDate); } BOOL LLFolderViewFolder::potentiallyVisible() { // folder should be visible by it's own filter status return LLFolderViewItem::potentiallyVisible() // or one or more of its descendants have passed the minimum filter requirement || hasFilteredDescendants(getRoot()->getFilter()->getMinRequiredGeneration()) // or not all of its descendants have been checked against minimum filter requirement || getCompletedFilterGeneration() < getRoot()->getFilter()->getMinRequiredGeneration(); } // this does prefix traversal, as folders are listed above their contents LLFolderViewItem* LLFolderViewFolder::getNextFromChild( LLFolderViewItem* item, BOOL include_children ) { BOOL found_item = FALSE; LLFolderViewItem* result = NULL; // when not starting from a given item, start at beginning if(item == NULL) { found_item = TRUE; } // find current item among children folders_t::iterator fit = mFolders.begin(); folders_t::iterator fend = mFolders.end(); items_t::iterator iit = mItems.begin(); items_t::iterator iend = mItems.end(); // if not trivially starting at the beginning, we have to find the current item if (!found_item) { // first, look among folders, since they are always above items for(; fit != fend; ++fit) { if(item == (*fit)) { found_item = TRUE; // if we are on downwards traversal if (include_children && (*fit)->isOpen()) { // look for first descendant return (*fit)->getNextFromChild(NULL, TRUE); } // otherwise advance to next folder ++fit; include_children = TRUE; break; } } // didn't find in folders? Check items... if (!found_item) { for(; iit != iend; ++iit) { if(item == (*iit)) { found_item = TRUE; // point to next item ++iit; break; } } } } if (!found_item) { // you should never call this method with an item that isn't a child // so we should always find something llassert(FALSE); return NULL; } // at this point, either iit or fit point to a candidate "next" item // if both are out of range, we need to punt up to our parent // now, starting from found folder, continue through folders // searching for next visible folder while(fit != fend && !(*fit)->getVisible()) { // turn on downwards traversal for next folder ++fit; } if (fit != fend) { result = (*fit); } else { // otherwise, scan for next visible item while(iit != iend && !(*iit)->getVisible()) { ++iit; } // check to see if we have a valid item if (iit != iend) { result = (*iit); } } if( !result && mParentFolder ) { // If there are no siblings or children to go to, recurse up one level in the tree // and skip children for this folder, as we've already discounted them result = mParentFolder->getNextFromChild(this, FALSE); } return result; } // this does postfix traversal, as folders are listed above their contents LLFolderViewItem* LLFolderViewFolder::getPreviousFromChild( LLFolderViewItem* item, BOOL include_children ) { BOOL found_item = FALSE; LLFolderViewItem* result = NULL; // when not starting from a given item, start at end if(item == NULL) { found_item = TRUE; } // find current item among children folders_t::reverse_iterator fit = mFolders.rbegin(); folders_t::reverse_iterator fend = mFolders.rend(); items_t::reverse_iterator iit = mItems.rbegin(); items_t::reverse_iterator iend = mItems.rend(); // if not trivially starting at the end, we have to find the current item if (!found_item) { // first, look among items, since they are always below the folders for(; iit != iend; ++iit) { if(item == (*iit)) { found_item = TRUE; // point to next item ++iit; break; } } // didn't find in items? Check folders... if (!found_item) { for(; fit != fend; ++fit) { if(item == (*fit)) { found_item = TRUE; // point to next folder ++fit; break; } } } } if (!found_item) { // you should never call this method with an item that isn't a child // so we should always find something llassert(FALSE); return NULL; } // at this point, either iit or fit point to a candidate "next" item // if both are out of range, we need to punt up to our parent // now, starting from found item, continue through items // searching for next visible item while(iit != iend && !(*iit)->getVisible()) { ++iit; } if (iit != iend) { // we found an appropriate item result = (*iit); } else { // otherwise, scan for next visible folder while(fit != fend && !(*fit)->getVisible()) { ++fit; } // check to see if we have a valid folder if (fit != fend) { // try selecting child element of this folder if ((*fit)->isOpen()) { result = (*fit)->getPreviousFromChild(NULL); } else { result = (*fit); } } } if( !result ) { // If there are no siblings or children to go to, recurse up one level in the tree // which gets back to this folder, which will only be visited if it is a valid, visible item result = this; } return result; } bool LLInventorySort::updateSort(U32 order) { if (order != mSortOrder) { mSortOrder = order; mByDate = (order & LLInventoryFilter::SO_DATE); mSystemToTop = (order & LLInventoryFilter::SO_SYSTEM_FOLDERS_TO_TOP); mFoldersByName = (order & LLInventoryFilter::SO_FOLDERS_BY_NAME); return true; } return false; } bool LLInventorySort::operator()(const LLFolderViewItem* const& a, const LLFolderViewItem* const& b) { // ignore sort order for landmarks in the Favorites folder. // they should be always sorted as in Favorites bar. See EXT-719 if (a->getSortGroup() == SG_ITEM && b->getSortGroup() == SG_ITEM && a->getListener()->getInventoryType() == LLInventoryType::IT_LANDMARK && b->getListener()->getInventoryType() == LLInventoryType::IT_LANDMARK) { static LLUUID favorites_folder_id = gInventory.findCategoryUUIDForType(LLAssetType::AT_FAVORITE); LLUUID a_uuid = a->getParentFolder()->getListener()->getUUID(); LLUUID b_uuid = b->getParentFolder()->getListener()->getUUID(); if (a_uuid == favorites_folder_id && b_uuid == favorites_folder_id) { // *TODO: mantipov: probably it is better to add an appropriate method to LLFolderViewItem // or to LLInvFVBridge S32 a_sort = (static_cast(a->getListener()))->getItem()->getSortField(); S32 b_sort = (static_cast(b->getListener()))->getItem()->getSortField(); return a_sort < b_sort; } } // We sort by name if we aren't sorting by date // OR if these are folders and we are sorting folders by name. bool by_name = (!mByDate || (mFoldersByName && (a->getSortGroup() != SG_ITEM))); if (a->getSortGroup() != b->getSortGroup()) { if (mSystemToTop) { // Group order is System Folders, Trash, Normal Folders, Items return (a->getSortGroup() < b->getSortGroup()); } else if (mByDate) { // Trash needs to go to the bottom if we are sorting by date if ( (a->getSortGroup() == SG_TRASH_FOLDER) || (b->getSortGroup() == SG_TRASH_FOLDER)) { return (b->getSortGroup() == SG_TRASH_FOLDER); } } } if (by_name) { S32 compare = LLStringUtil::compareDict(a->getLabel(), b->getLabel()); if (0 == compare) { return (a->getCreationDate() > b->getCreationDate()); } else { return (compare < 0); } } else { // BUG: This is very very slow. The getCreationDate() is log n in number // of inventory items. time_t first_create = a->getCreationDate(); time_t second_create = b->getCreationDate(); if (first_create == second_create) { return (LLStringUtil::compareDict(a->getLabel(), b->getLabel()) < 0); } else { return (first_create > second_create); } } }