aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/linden/indra/newview/llfolderview.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--linden/indra/newview/llfolderview.cpp4924
1 files changed, 4924 insertions, 0 deletions
diff --git a/linden/indra/newview/llfolderview.cpp b/linden/indra/newview/llfolderview.cpp
new file mode 100644
index 0000000..431f212
--- /dev/null
+++ b/linden/indra/newview/llfolderview.cpp
@@ -0,0 +1,4924 @@
1/**
2 * @file llfolderview.cpp
3 * @brief Implementation of the folder view collection of classes.
4 *
5 * Copyright (c) 2001-2007, Linden Research, Inc.
6 *
7 * The source code in this file ("Source Code") is provided by Linden Lab
8 * to you under the terms of the GNU General Public License, version 2.0
9 * ("GPL"), unless you have obtained a separate licensing agreement
10 * ("Other License"), formally executed by you and Linden Lab. Terms of
11 * the GPL can be found in doc/GPL-license.txt in this distribution, or
12 * online at http://secondlife.com/developers/opensource/gplv2
13 *
14 * There are special exceptions to the terms and conditions of the GPL as
15 * it is applied to this Source Code. View the full text of the exception
16 * in the file doc/FLOSS-exception.txt in this software distribution, or
17 * online at http://secondlife.com/developers/opensource/flossexception
18 *
19 * By copying, modifying or distributing this software, you acknowledge
20 * that you have read and understood your obligations described above,
21 * and agree to abide by those obligations.
22 *
23 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
24 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
25 * COMPLETENESS OR PERFORMANCE.
26 */
27
28#include "llviewerprecompiledheaders.h"
29
30#include "llfolderview.h"
31
32#include <algorithm>
33
34#include "llviewercontrol.h"
35#include "lldbstrings.h"
36#include "llfocusmgr.h"
37#include "llfontgl.h"
38#include "llgl.h"
39#include "llinventory.h"
40
41#include "llcallbacklist.h"
42#include "llinventoryclipboard.h" // *TODO: remove this once hack below gone.
43#include "llinventoryview.h"// hacked in for the bonus context menu items.
44#include "llkeyboard.h"
45#include "lllineeditor.h"
46#include "llmenugl.h"
47#include "llresmgr.h"
48#include "llpreview.h"
49#include "llscrollcontainer.h" // hack to allow scrolling
50#include "lltooldraganddrop.h"
51#include "llui.h"
52#include "llviewerimage.h"
53#include "llviewerimagelist.h"
54#include "llviewerjointattachment.h"
55#include "llviewermenu.h"
56#include "llvieweruictrlfactory.h"
57#include "llviewerwindow.h"
58#include "llvoavatar.h"
59#include "llfloaterproperties.h"
60
61// RN: HACK
62// We need these because some of the code below relies on things like
63// gAgent root folder. Remove them once the abstraction leak is fixed.
64#include "llagent.h"
65#include "viewer.h"
66
67///----------------------------------------------------------------------------
68/// Local function declarations, constants, enums, and typedefs
69///----------------------------------------------------------------------------
70
71const S32 LEFT_PAD = 5;
72const S32 LEFT_INDENTATION = 13;
73const S32 ICON_PAD = 2;
74const S32 ICON_WIDTH = 16;
75const S32 TEXT_PAD = 1;
76const S32 ARROW_SIZE = 12;
77const S32 RENAME_WIDTH_PAD = 4;
78const S32 RENAME_HEIGHT_PAD = 6;
79const S32 AUTO_OPEN_STACK_DEPTH = 16;
80const S32 MIN_ITEM_WIDTH_VISIBLE = ICON_WIDTH + ICON_PAD + ARROW_SIZE + TEXT_PAD + /*first few characters*/ 40;
81const S32 MINIMUM_RENAMER_WIDTH = 80;
82const F32 FOLDER_CLOSE_TIME_CONSTANT = 0.02f;
83const F32 FOLDER_OPEN_TIME_CONSTANT = 0.03f;
84const S32 MAX_FOLDER_ITEM_OVERLAP = 2;
85
86F32 LLFolderView::sAutoOpenTime = 1.f;
87
88void delete_selected_item(void* user_data);
89void copy_selected_item(void* user_data);
90void open_selected_items(void* user_data);
91void properties_selected_items(void* user_data);
92void paste_items(void* user_data);
93void top_view_lost( LLView* handler );
94
95///----------------------------------------------------------------------------
96/// Class LLFolderViewItem
97///----------------------------------------------------------------------------
98
99// statics
100const LLFontGL* LLFolderViewItem::sFont = NULL;
101const LLFontGL* LLFolderViewItem::sSmallFont = NULL;
102LLColor4 LLFolderViewItem::sFgColor;
103LLColor4 LLFolderViewItem::sHighlightBgColor;
104LLColor4 LLFolderViewItem::sHighlightFgColor;
105LLColor4 LLFolderViewItem::sFilterBGColor;
106LLColor4 LLFolderViewItem::sFilterTextColor;
107
108// Default constructor
109LLFolderViewItem::LLFolderViewItem( const LLString& name, LLViewerImage* icon,
110 S32 creation_date,
111 LLFolderView* root,
112 LLFolderViewEventListener* listener ) :
113 LLUICtrl( name, LLRect(0, 0, 0, 0), TRUE, NULL, NULL, FOLLOWS_LEFT|FOLLOWS_TOP|FOLLOWS_RIGHT),
114 mLabel( name ),
115 mLabelWidth(0),
116 mCreationDate(creation_date),
117 mParentFolder( NULL ),
118 mListener( listener ),
119 mIsSelected( FALSE ),
120 mIsCurSelection( FALSE ),
121 mSelectPending(FALSE),
122 mLabelStyle( LLFontGL::NORMAL ),
123 mHasVisibleChildren(FALSE),
124 mIndentation(0),
125 mNumDescendantsSelected(0),
126 mFiltered(FALSE),
127 mLastFilterGeneration(-1),
128 mStringMatchOffset(LLString::npos),
129 mControlLabelRotation(0.f),
130 mRoot( root ),
131 mDragAndDropTarget(FALSE)
132{
133 setIcon(icon);
134 if( !LLFolderViewItem::sFont )
135 {
136 LLFolderViewItem::sFont = gResMgr->getRes( LLFONT_SANSSERIF_SMALL );
137 }
138
139 if (!LLFolderViewItem::sSmallFont)
140 {
141 LLFolderViewItem::sSmallFont = gResMgr->getRes( LLFONT_SMALL );
142 }
143
144 // HACK: Can't be set above because gSavedSettings might not be constructed.
145 LLFolderViewItem::sFgColor = gColors.getColor( "MenuItemEnabledColor" );
146 LLFolderViewItem::sHighlightBgColor = gColors.getColor( "MenuItemHighlightBgColor" );
147 LLFolderViewItem::sHighlightFgColor = gColors.getColor( "MenuItemHighlightFgColor" );
148 LLFolderViewItem::sFilterBGColor = gColors.getColor( "FilterBackgroundColor" );
149 LLFolderViewItem::sFilterTextColor = gColors.getColor( "FilterTextColor" );
150
151 mArrowImage = gImageList.getImage(LLUUID(gViewerArt.getString("folder_arrow.tga")), MIPMAP_FALSE, TRUE);
152 mBoxImage = gImageList.getImage(LLUUID(gViewerArt.getString("rounded_square.tga")), MIPMAP_FALSE, TRUE);
153
154 refresh();
155 setTabStop(FALSE);
156}
157
158// Destroys the object
159LLFolderViewItem::~LLFolderViewItem( void )
160{
161 delete mListener;
162 mListener = NULL;
163 mArrowImage = NULL;
164 mBoxImage = NULL;
165}
166
167LLFolderView* LLFolderViewItem::getRoot()
168{
169 return mRoot;
170}
171
172// Returns true if this object is a child (or grandchild, etc.) of potential_ancestor.
173BOOL LLFolderViewItem::isDescendantOf( const LLFolderViewFolder* potential_ancestor )
174{
175 LLFolderViewItem* root = this;
176 while( root->mParentFolder )
177 {
178 if( root->mParentFolder == potential_ancestor )
179 {
180 return TRUE;
181 }
182 root = root->mParentFolder;
183 }
184 return FALSE;
185}
186
187LLFolderViewItem* LLFolderViewItem::getNextOpenNode(BOOL include_children)
188{
189 if (!mParentFolder)
190 {
191 return NULL;
192 }
193
194 LLFolderViewItem* itemp = mParentFolder->getNextFromChild( this, include_children );
195 while(itemp && !itemp->getVisible())
196 {
197 LLFolderViewItem* next_itemp = itemp->mParentFolder->getNextFromChild( itemp, include_children );
198 if (itemp == next_itemp)
199 {
200 // hit last item
201 return itemp->getVisible() ? itemp : this;
202 }
203 itemp = next_itemp;
204 }
205
206 return itemp;
207}
208
209LLFolderViewItem* LLFolderViewItem::getPreviousOpenNode(BOOL include_children)
210{
211 if (!mParentFolder)
212 {
213 return NULL;
214 }
215
216 LLFolderViewItem* itemp = mParentFolder->getPreviousFromChild( this, include_children );
217 while(itemp && !itemp->getVisible())
218 {
219 LLFolderViewItem* next_itemp = itemp->mParentFolder->getPreviousFromChild( itemp, include_children );
220 if (itemp == next_itemp)
221 {
222 // hit first item
223 return itemp->getVisible() ? itemp : this;
224 }
225 itemp = next_itemp;
226 }
227
228 return itemp;
229}
230
231BOOL LLFolderViewItem::getFiltered()
232{
233 return mFiltered && mLastFilterGeneration >= mRoot->getFilter()->getMinRequiredGeneration();
234}
235
236BOOL LLFolderViewItem::getFiltered(S32 filter_generation)
237{
238 return mFiltered && mLastFilterGeneration >= filter_generation;
239}
240
241void LLFolderViewItem::setFiltered(BOOL filtered, S32 filter_generation)
242{
243 mFiltered = filtered;
244 mLastFilterGeneration = filter_generation;
245}
246
247void LLFolderViewItem::setIcon(LLViewerImage* icon)
248{
249 mIcon = icon;
250 if (mIcon)
251 {
252 mIcon->setBoostLevel(LLViewerImage::BOOST_UI);
253 }
254}
255
256// refresh information from the listener
257void LLFolderViewItem::refresh()
258{
259 if(mListener)
260 {
261 const char* label = mListener->getDisplayName().c_str();
262 mLabel = label ? label : "";
263 setIcon(mListener->getIcon());
264 U32 creation_date = mListener->getCreationDate();
265 if (mCreationDate != creation_date)
266 {
267 mCreationDate = mListener->getCreationDate();
268 dirtyFilter();
269 }
270 mLabelStyle = mListener->getLabelStyle();
271 mLabelSuffix = mListener->getLabelSuffix();
272
273 LLString searchable_label(mLabel);
274 searchable_label.append(mLabelSuffix);
275 LLString::toUpper(searchable_label);
276
277 if (mSearchableLabel.compare(searchable_label))
278 {
279 mSearchableLabel.assign(searchable_label);
280 dirtyFilter();
281 // some part of label has changed, so overall width has potentially changed
282 if (mParentFolder)
283 {
284 mParentFolder->requestArrange();
285 }
286 }
287
288 S32 label_width = sFont->getWidth(mLabel);
289 if( mLabelSuffix.size() )
290 {
291 label_width += sFont->getWidth( mLabelSuffix );
292 }
293
294 mLabelWidth = ARROW_SIZE + TEXT_PAD + ICON_WIDTH + ICON_PAD + label_width;
295 }
296}
297
298void LLFolderViewItem::applyListenerFunctorRecursively(LLFolderViewListenerFunctor& functor)
299{
300 functor(mListener);
301}
302
303// This function is called when items are added or view filters change. It's
304// implemented here but called by derived classes when folding the
305// views.
306void LLFolderViewItem::filterFromRoot( void )
307{
308 LLFolderViewItem* root = getRoot();
309
310 root->filter(*((LLFolderView*)root)->getFilter());
311}
312
313// This function is called when the folder view is dirty. It's
314// implemented here but called by derived classes when folding the
315// views.
316void LLFolderViewItem::arrangeFromRoot()
317{
318 LLFolderViewItem* root = getRoot();
319
320 S32 height = 0;
321 S32 width = 0;
322 root->arrange( &width, &height, 0 );
323}
324
325// This function clears the currently selected item, and records the
326// specified selected item appropriately for display and use in the
327// UI. If open is TRUE, then folders are opened up along the way to
328// the selection.
329void LLFolderViewItem::setSelectionFromRoot(LLFolderViewItem* selection,
330 BOOL open,
331 BOOL take_keyboard_focus)
332{
333 getRoot()->setSelection(selection, open, take_keyboard_focus);
334}
335
336// helper function to change the selection from the root.
337void LLFolderViewItem::changeSelectionFromRoot(LLFolderViewItem* selection,
338 BOOL selected)
339{
340 getRoot()->changeSelection(selection, selected);
341}
342
343void LLFolderViewItem::extendSelectionFromRoot(LLFolderViewItem* selection)
344{
345 LLDynamicArray<LLFolderViewItem*> selected_items;
346
347 getRoot()->extendSelection(selection, NULL, selected_items);
348}
349
350EWidgetType LLFolderViewItem::getWidgetType() const
351{
352 return WIDGET_TYPE_FOLDER_ITEM;
353}
354
355LLString LLFolderViewItem::getWidgetTag() const
356{
357 return LL_FOLDER_VIEW_ITEM_TAG;
358}
359
360// addToFolder() returns TRUE if it succeeds. FALSE otherwise
361BOOL LLFolderViewItem::addToFolder(LLFolderViewFolder* folder, LLFolderView* root)
362{
363 if (!folder)
364 {
365 return FALSE;
366 }
367 mParentFolder = folder;
368 root->addItemID(getListener()->getUUID(), this);
369 return folder->addItem(this);
370}
371
372
373// Finds width and height of this object and it's children. Also
374// makes sure that this view and it's children are the right size.
375S32 LLFolderViewItem::arrange( S32* width, S32* height, S32 filter_generation)
376{
377 mIndentation = mParentFolder ? mParentFolder->getIndentation() + LEFT_INDENTATION : 0;
378 *width = llmax(*width, mLabelWidth + mIndentation);
379 *height = getItemHeight();
380 return *height;
381}
382
383S32 LLFolderViewItem::getItemHeight()
384{
385 S32 icon_height = mIcon->getHeight();
386 S32 label_height = llround(sFont->getLineHeight());
387 return llmax( icon_height, label_height ) + ICON_PAD;
388}
389
390void LLFolderViewItem::filter( LLInventoryFilter& filter)
391{
392 BOOL filtered = mListener && filter.check(this);
393
394 // if our visibility will change as a result of this filter, then
395 // we need to be rearranged in our parent folder
396 if (getVisible() != filtered)
397 {
398 if (mParentFolder)
399 {
400 mParentFolder->requestArrange();
401 }
402 }
403
404 setFiltered(filtered, filter.getCurrentGeneration());
405 mStringMatchOffset = filter.getStringMatchOffset();
406 filter.decrementFilterCount();
407
408 if (getRoot()->getDebugFilters())
409 {
410 mStatusText = llformat("%d", mLastFilterGeneration);
411 }
412}
413
414void LLFolderViewItem::dirtyFilter()
415{
416 mLastFilterGeneration = -1;
417 // bubble up dirty flag all the way to root
418 if (getParentFolder())
419 {
420 getParentFolder()->setCompletedFilterGeneration(-1, TRUE);
421 }
422}
423
424// *TODO: This can be optimized a lot by simply recording that it is
425// selected in the appropriate places, and assuming that set selection
426// means 'deselect' for a leaf item. Do this optimization after
427// multiple selection is implemented to make sure it all plays nice
428// together.
429BOOL LLFolderViewItem::setSelection(LLFolderViewItem* selection, BOOL open,
430 BOOL take_keyboard_focus)
431{
432 if( selection == this )
433 {
434 mIsSelected = TRUE;
435 if(mListener)
436 {
437 mListener->selectItem();
438 }
439 }
440 else
441 {
442 mIsSelected = FALSE;
443 }
444 return mIsSelected;
445}
446
447BOOL LLFolderViewItem::changeSelection(LLFolderViewItem* selection,
448 BOOL selected)
449{
450 if(selection == this && mIsSelected != selected)
451 {
452 mIsSelected = selected;
453 if(mListener)
454 {
455 mListener->selectItem();
456 }
457 return TRUE;
458 }
459 return FALSE;
460}
461
462void LLFolderViewItem::recursiveDeselect(BOOL deselect_self)
463{
464 if (mIsSelected && deselect_self)
465 {
466 mIsSelected = FALSE;
467
468 // update ancestors' count of selected descendents
469 LLFolderViewFolder* parent_folder = getParentFolder();
470 while(parent_folder)
471 {
472 parent_folder->mNumDescendantsSelected--;
473 parent_folder = parent_folder->getParentFolder();
474 }
475 }
476}
477
478
479BOOL LLFolderViewItem::isMovable()
480{
481 if( mListener )
482 {
483 return mListener->isItemMovable();
484 }
485 else
486 {
487 return TRUE;
488 }
489}
490
491BOOL LLFolderViewItem::isRemovable()
492{
493 if( mListener )
494 {
495 return mListener->isItemRemovable();
496 }
497 else
498 {
499 return TRUE;
500 }
501}
502
503void LLFolderViewItem::destroyView()
504{
505 if (mParentFolder)
506 {
507 // removeView deletes me
508 mParentFolder->removeView(this);
509 }
510}
511
512// Call through to the viewed object and return true if it can be
513// removed.
514//BOOL LLFolderViewItem::removeRecursively(BOOL single_item)
515BOOL LLFolderViewItem::remove()
516{
517 if(!isRemovable())
518 {
519 return FALSE;
520 }
521 if(mListener)
522 {
523 return mListener->removeItem();
524 }
525 return TRUE;
526}
527
528// Build an appropriate context menu for the item.
529void LLFolderViewItem::buildContextMenu(LLMenuGL& menu, U32 flags)
530{
531 if(mListener)
532 {
533 mListener->buildContextMenu(menu, flags);
534 }
535}
536
537void LLFolderViewItem::open( void )
538{
539 if( mListener )
540 {
541 mListener->openItem();
542 }
543}
544
545void LLFolderViewItem::preview( void )
546{
547 if (mListener)
548 {
549 mListener->previewItem();
550 }
551}
552
553void LLFolderViewItem::rename(const LLString& new_name)
554{
555 if( !new_name.empty() )
556 {
557 mLabel = new_name.c_str();
558 BOOL is_renamed = TRUE;
559 if( mListener )
560 {
561 is_renamed = mListener->renameItem(new_name);
562 }
563 if(mParentFolder && is_renamed)
564 {
565 mParentFolder->resort(this);
566 }
567 //refresh();
568 }
569}
570
571const LLString& LLFolderViewItem::getSearchableLabel() const
572{
573 return mSearchableLabel;
574}
575
576const LLString& LLFolderViewItem::getName( void ) const
577{
578 if(mListener)
579 {
580 return mListener->getName();
581 }
582 return mLabel;
583}
584
585LLFolderViewFolder* LLFolderViewItem::getParentFolder( void )
586{
587 return mParentFolder;
588}
589
590LLFolderViewEventListener* LLFolderViewItem::getListener( void )
591{
592 return mListener;
593}
594
595// LLView functionality
596BOOL LLFolderViewItem::handleRightMouseDown( S32 x, S32 y, MASK mask )
597{
598 if(!mIsSelected)
599 {
600 setSelectionFromRoot(this, FALSE);
601 }
602 make_ui_sound("UISndClick");
603 return TRUE;
604}
605
606BOOL LLFolderViewItem::handleMouseDown( S32 x, S32 y, MASK mask )
607{
608 // No handler needed for focus lost since this class has no
609 // state that depends on it.
610 gViewerWindow->setMouseCapture( this, NULL );
611
612 if (!mIsSelected)
613 {
614 if(mask & MASK_CONTROL)
615 {
616 changeSelectionFromRoot(this, !mIsSelected);
617 }
618 else if (mask & MASK_SHIFT)
619 {
620 extendSelectionFromRoot(this);
621 }
622 else
623 {
624 setSelectionFromRoot(this, FALSE);
625 }
626 make_ui_sound("UISndClick");
627 }
628 else
629 {
630 mSelectPending = TRUE;
631 }
632
633 if( isMovable() )
634 {
635 S32 screen_x;
636 S32 screen_y;
637 localPointToScreen(x, y, &screen_x, &screen_y );
638 gToolDragAndDrop->setDragStart( screen_x, screen_y );
639 }
640 return TRUE;
641}
642
643BOOL LLFolderViewItem::handleHover( S32 x, S32 y, MASK mask )
644{
645 if( gViewerWindow->hasMouseCapture( this ) && isMovable() )
646 {
647 S32 screen_x;
648 S32 screen_y;
649 localPointToScreen(x, y, &screen_x, &screen_y );
650 BOOL can_drag = TRUE;
651 if( gToolDragAndDrop->isOverThreshold( screen_x, screen_y ) )
652 {
653 LLFolderView* root = getRoot();
654
655 if(root->getCurSelectedItem())
656 {
657 LLToolDragAndDrop::ESource src = LLToolDragAndDrop::SOURCE_WORLD;
658
659 // *TODO: push this into listener and remove
660 // dependency on llagent
661 if(mListener && gInventory.isObjectDescendentOf(mListener->getUUID(), gAgent.getInventoryRootID()))
662 {
663 src = LLToolDragAndDrop::SOURCE_AGENT;
664 }
665 else if (mListener && gInventory.isObjectDescendentOf(mListener->getUUID(), gInventoryLibraryRoot))
666 {
667 src = LLToolDragAndDrop::SOURCE_LIBRARY;
668 }
669
670 can_drag = root->startDrag(src);
671 if (can_drag)
672 {
673 // if (mListener) mListener->startDrag();
674 // RN: when starting drag and drop, clear out last auto-open
675 root->autoOpenTest(NULL);
676 root->setShowSelectionContext(TRUE);
677
678 // Release keyboard focus, so that if stuff is dropped into the
679 // world, pressing the delete key won't blow away the inventory
680 // item.
681 gViewerWindow->setKeyboardFocus(NULL, NULL);
682
683 return gToolDragAndDrop->handleHover( x, y, mask );
684 }
685 }
686 }
687
688 if (can_drag)
689 {
690 gViewerWindow->setCursor(UI_CURSOR_ARROW);
691 }
692 else
693 {
694 gViewerWindow->setCursor(UI_CURSOR_NOLOCKED);
695 }
696 return TRUE;
697 }
698 else
699 {
700 getRoot()->setShowSelectionContext(FALSE);
701 gViewerWindow->setCursor(UI_CURSOR_ARROW);
702 // let parent handle this then...
703 return FALSE;
704 }
705}
706
707
708BOOL LLFolderViewItem::handleDoubleClick( S32 x, S32 y, MASK mask )
709{
710 preview();
711 return TRUE;
712}
713
714BOOL LLFolderViewItem::handleScrollWheel(S32 x, S32 y, S32 clicks)
715{
716 if (getParent())
717 {
718 return getParent()->handleScrollWheel(x, y, clicks);
719 }
720 return FALSE;
721}
722
723BOOL LLFolderViewItem::handleMouseUp( S32 x, S32 y, MASK mask )
724{
725 // if mouse hasn't moved since mouse down...
726 if ( pointInView(x, y) && mSelectPending )
727 {
728 //...then select
729 if(mask & MASK_CONTROL)
730 {
731 changeSelectionFromRoot(this, !mIsSelected);
732 }
733 else if (mask & MASK_SHIFT)
734 {
735 extendSelectionFromRoot(this);
736 }
737 else
738 {
739 setSelectionFromRoot(this, FALSE);
740 }
741 }
742
743 mSelectPending = FALSE;
744
745 if( gViewerWindow->hasMouseCapture( this ) )
746 {
747 getRoot()->setShowSelectionContext(FALSE);
748 gViewerWindow->setMouseCapture( NULL, NULL );
749 }
750 return TRUE;
751}
752
753BOOL LLFolderViewItem::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop,
754 EDragAndDropType cargo_type,
755 void* cargo_data,
756 EAcceptance* accept,
757 LLString& tooltip_msg)
758{
759 BOOL accepted = FALSE;
760 BOOL handled = FALSE;
761 if(mListener)
762 {
763 accepted = mListener->dragOrDrop(mask,drop,cargo_type,cargo_data);
764 handled = accepted;
765 if (accepted)
766 {
767 mDragAndDropTarget = TRUE;
768 *accept = ACCEPT_YES_MULTI;
769 }
770 else
771 {
772 *accept = ACCEPT_NO;
773 }
774 }
775 if(mParentFolder && !handled)
776 {
777 handled = mParentFolder->handleDragAndDropFromChild(mask,drop,cargo_type,cargo_data,accept,tooltip_msg);
778 }
779 if (handled)
780 {
781 lldebugst(LLERR_USER_INPUT) << "dragAndDrop handled by LLFolderViewItem" << llendl;
782 }
783
784 return handled;
785}
786
787
788void LLFolderViewItem::draw()
789{
790 if( getVisible() )
791 {
792 bool possibly_has_children = false;
793 bool up_to_date = mListener && mListener->isUpToDate();
794 if((up_to_date && hasVisibleChildren() ) || // we fetched our children and some of them have passed the filter...
795 (!up_to_date && mListener && mListener->hasChildren())) // ...or we know we have children but haven't fetched them (doesn't obey filter)
796 {
797 possibly_has_children = true;
798 }
799 if(/*mControlLabel[0] != '\0' && */possibly_has_children)
800 {
801 LLGLSTexture gls_texture;
802 if (mArrowImage)
803 {
804 gl_draw_scaled_rotated_image(mIndentation, mRect.getHeight() - ARROW_SIZE - TEXT_PAD,
805 ARROW_SIZE, ARROW_SIZE, mControlLabelRotation, mArrowImage, sFgColor);
806 }
807 }
808
809 F32 text_left = (F32)(ARROW_SIZE + TEXT_PAD + ICON_WIDTH + ICON_PAD + mIndentation);
810
811 // If we have keyboard focus, draw selection filled
812 BOOL show_context = getRoot()->getShowSelectionContext();
813 BOOL filled = show_context || (gFocusMgr.getKeyboardFocus() == getRoot());
814
815 // always render "current" item, only render other selected items if
816 // mShowSingleSelection is FALSE
817 if( mIsSelected )
818 {
819 LLGLSNoTexture gls_no_texture;
820 LLColor4 bg_color = sHighlightBgColor;
821 //const S32 TRAILING_PAD = 5; // It just looks better with this.
822 if (!mIsCurSelection)
823 {
824 // do time-based fade of extra objects
825 F32 fade_time = getRoot()->getSelectionFadeElapsedTime();
826 if (getRoot()->getShowSingleSelection())
827 {
828 // fading out
829 bg_color.mV[VALPHA] = clamp_rescale(fade_time, 0.f, 0.4f, bg_color.mV[VALPHA], 0.f);
830 }
831 else
832 {
833 // fading in
834 bg_color.mV[VALPHA] = clamp_rescale(fade_time, 0.f, 0.4f, 0.f, bg_color.mV[VALPHA]);
835 }
836 }
837
838 gl_rect_2d(
839 0,
840 mRect.getHeight(),
841 mRect.getWidth() - 2,
842 llfloor(mRect.getHeight() - sFont->getLineHeight() - ICON_PAD),
843 bg_color, filled);
844 if (mIsCurSelection)
845 {
846 gl_rect_2d(
847 0,
848 mRect.getHeight(),
849 mRect.getWidth() - 2,
850 llfloor(mRect.getHeight() - sFont->getLineHeight() - ICON_PAD),
851 sHighlightFgColor, FALSE);
852 }
853 if (mRect.getHeight() > llround(sFont->getLineHeight()) + ICON_PAD + 2)
854 {
855 gl_rect_2d(
856 0,
857 llfloor(mRect.getHeight() - sFont->getLineHeight() - ICON_PAD) - 2,
858 mRect.getWidth() - 2,
859 2,
860 sHighlightFgColor, FALSE);
861 if (show_context)
862 {
863 gl_rect_2d(
864 0,
865 llfloor(mRect.getHeight() - sFont->getLineHeight() - ICON_PAD) - 2,
866 mRect.getWidth() - 2,
867 2,
868 sHighlightBgColor, TRUE);
869 }
870 }
871 }
872 if (mDragAndDropTarget)
873 {
874 LLGLSNoTexture gls_no_texture;
875 gl_rect_2d(
876 0,
877 mRect.getHeight(),
878 mRect.getWidth() - 2,
879 llfloor(mRect.getHeight() - sFont->getLineHeight() - ICON_PAD),
880 sHighlightBgColor, FALSE);
881
882 if (mRect.getHeight() > llround(sFont->getLineHeight()) + ICON_PAD + 2)
883 {
884 gl_rect_2d(
885 0,
886 llfloor(mRect.getHeight() - sFont->getLineHeight() - ICON_PAD) - 2,
887 mRect.getWidth() - 2,
888 2,
889 sHighlightBgColor, FALSE);
890 }
891 mDragAndDropTarget = FALSE;
892 }
893
894
895 if(mIcon)
896 {
897 gl_draw_image(mIndentation + ARROW_SIZE + TEXT_PAD, mRect.getHeight() - mIcon->getHeight(), mIcon);
898 mIcon->addTextureStats( (F32)(mIcon->getWidth() * mIcon->getHeight()));
899 }
900
901 if (!mLabel.empty())
902 {
903 // highlight filtered text
904 BOOL debug_filters = getRoot()->getDebugFilters();
905 LLColor4 color = ( (mIsSelected && filled) ? sHighlightFgColor : sFgColor );
906 F32 right_x;
907 F32 y = (F32)mRect.getHeight() - sFont->getLineHeight() - (F32)TEXT_PAD;
908
909 if (debug_filters)
910 {
911 if (!getFiltered() && !possibly_has_children)
912 {
913 color.mV[VALPHA] *= 0.5f;
914 }
915
916 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);
917 sSmallFont->renderUTF8(mStatusText, 0, text_left, y, filter_color,
918 LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL,
919 S32_MAX, S32_MAX, &right_x, FALSE );
920 text_left = right_x;
921 }
922
923 sFont->renderUTF8( mLabel, 0, text_left, y, color,
924 LLFontGL::LEFT, LLFontGL::BOTTOM, mLabelStyle,
925 S32_MAX, S32_MAX, &right_x, FALSE );
926 if (!mLabelSuffix.empty())
927 {
928 sFont->renderUTF8( mLabelSuffix, 0, right_x, y, LLColor4(0.75f, 0.85f, 0.85f, 1.f),
929 LLFontGL::LEFT, LLFontGL::BOTTOM, mLabelStyle,
930 S32_MAX, S32_MAX, &right_x, FALSE );
931 }
932
933 if (mBoxImage.notNull() && mStringMatchOffset != LLString::npos)
934 {
935 // don't draw backgrounds for zero-length strings
936 S32 filter_string_length = mRoot->getFilterSubString().size();
937 if (filter_string_length > 0)
938 {
939 LLString combined_string = mLabel + mLabelSuffix;
940 S32 left = llround(text_left) + sFont->getWidth(combined_string, 0, mStringMatchOffset) - 1;
941 S32 right = left + sFont->getWidth(combined_string, mStringMatchOffset, filter_string_length) + 2;
942 S32 bottom = llfloor(mRect.getHeight() - sFont->getLineHeight() - 3);
943 S32 top = mRect.getHeight();
944
945 LLViewerImage::bindTexture(mBoxImage);
946 glColor4fv(sFilterBGColor.mV);
947 gl_segmented_rect_2d_tex(left, top, right, bottom, mBoxImage->getWidth(), mBoxImage->getHeight(), 16);
948 F32 match_string_left = text_left + sFont->getWidthF32(combined_string, 0, mStringMatchOffset);
949 F32 y = (F32)mRect.getHeight() - sFont->getLineHeight() - (F32)TEXT_PAD;
950 sFont->renderUTF8( combined_string, mStringMatchOffset, match_string_left, y,
951 sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, mLabelStyle,
952 filter_string_length, S32_MAX, &right_x, FALSE );
953 }
954 }
955 }
956
957 if( sDebugRects )
958 {
959 drawDebugRect();
960 }
961 }
962 else if (mStatusText.size())
963 {
964 // just draw status text
965 sFont->renderUTF8( mStatusText, 0, 0, 1, sFgColor, LLFontGL::LEFT, LLFontGL::BOTTOM, mLabelStyle, S32_MAX, S32_MAX, NULL, FALSE );
966 }
967}
968
969
970///----------------------------------------------------------------------------
971/// Class LLFolderViewFolder
972///----------------------------------------------------------------------------
973
974// Default constructor
975LLFolderViewFolder::LLFolderViewFolder( const LLString& name, LLViewerImage* icon,
976 LLFolderView* root,
977 LLFolderViewEventListener* listener ):
978 LLFolderViewItem( name, icon, 0, root, listener ), // 0 = no create time
979 mSortFunction(sort_item_name),
980 mIsOpen(FALSE),
981 mExpanderHighlighted(FALSE),
982 mCurHeight(0.f),
983 mTargetHeight(0.f),
984 mAutoOpenCountdown(0.f),
985 mSubtreeCreationDate(0),
986 mAmTrash(LLFolderViewFolder::UNKNOWN),
987 mLastArrangeGeneration( -1 ),
988 mLastCalculatedWidth(0),
989 mCompletedFilterGeneration(-1),
990 mMostFilteredDescendantGeneration(-1)
991{
992 mType = "(folder)";
993
994 //mItems.setInsertBefore( &sort_item_name );
995 //mFolders.setInsertBefore( &folder_insert_before );
996}
997
998// Destroys the object
999LLFolderViewFolder::~LLFolderViewFolder( void )
1000{
1001 // The LLView base class takes care of object destruction. make sure that we
1002 // don't have mouse or keyboard focus
1003 gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit()
1004
1005 //mItems.reset();
1006 //mItems.removeAllNodes();
1007 //mFolders.removeAllNodes();
1008}
1009
1010EWidgetType LLFolderViewFolder::getWidgetType() const
1011{
1012 return WIDGET_TYPE_FOLDER;
1013}
1014
1015LLString LLFolderViewFolder::getWidgetTag() const
1016{
1017 return LL_FOLDER_VIEW_FOLDER_TAG;
1018}
1019
1020// addToFolder() returns TRUE if it succeeds. FALSE otherwise
1021BOOL LLFolderViewFolder::addToFolder(LLFolderViewFolder* folder, LLFolderView* root)
1022{
1023 if (!folder)
1024 {
1025 return FALSE;
1026 }
1027 mParentFolder = folder;
1028 root->addItemID(getListener()->getUUID(), this);
1029 return folder->addFolder(this);
1030}
1031
1032// Finds width and height of this object and it's children. Also
1033// makes sure that this view and it's children are the right size.
1034S32 LLFolderViewFolder::arrange( S32* width, S32* height, S32 filter_generation)
1035{
1036 mHasVisibleChildren = hasFilteredDescendants(filter_generation);
1037
1038 LLInventoryFilter::EFolderShow show_folder_state = getRoot()->getShowFolderState();
1039
1040 // calculate height as a single item (without any children), and reshapes rectangle to match
1041 LLFolderViewItem::arrange( width, height, filter_generation );
1042
1043 // clamp existing animated height so as to never get smaller than a single item
1044 mCurHeight = llmax((F32)*height, mCurHeight);
1045
1046 // initialize running height value as height of single item in case we have no children
1047 *height = getItemHeight();
1048 F32 running_height = (F32)*height;
1049 F32 target_height = (F32)*height;
1050
1051 // are my children visible?
1052 if (needsArrange())
1053 {
1054 // set last arrange generation first, in case children are animating
1055 // and need to be arranged again
1056 mLastArrangeGeneration = mRoot->getArrangeGeneration();
1057 if (mIsOpen)
1058 {
1059 // Add sizes of children
1060 S32 parent_item_height = mRect.getHeight();
1061
1062 folders_t::iterator fit = mFolders.begin();
1063 folders_t::iterator fend = mFolders.end();
1064 for(; fit < fend; ++fit)
1065 {
1066 LLFolderViewFolder* folderp = (*fit);
1067 if (getRoot()->getDebugFilters())
1068 {
1069 folderp->setVisible(TRUE);
1070 }
1071 else
1072 {
1073 folderp->setVisible(show_folder_state == LLInventoryFilter::SHOW_ALL_FOLDERS || // always show folders?
1074 (folderp->getFiltered(filter_generation) || folderp->hasFilteredDescendants(filter_generation))); // passed filter or has descendants that passed filter
1075 }
1076
1077 if (folderp->getVisible())
1078 {
1079 S32 child_width = *width;
1080 S32 child_height = 0;
1081 S32 child_top = parent_item_height - llround(running_height);
1082
1083 target_height += folderp->arrange( &child_width, &child_height, filter_generation );
1084
1085 running_height += (F32)child_height;
1086 *width = llmax(*width, child_width);
1087 folderp->setOrigin( 0, child_top - folderp->getRect().getHeight() );
1088 }
1089 }
1090 items_t::iterator iit = mItems.begin();
1091 items_t::iterator iend = mItems.end();
1092 for(;iit < iend; ++iit)
1093 {
1094 LLFolderViewItem* itemp = (*iit);
1095 if (getRoot()->getDebugFilters())
1096 {
1097 itemp->setVisible(TRUE);
1098 }
1099 else
1100 {
1101 itemp->setVisible(itemp->getFiltered(filter_generation));
1102 }
1103
1104 if (itemp->getVisible())
1105 {
1106 S32 child_width = *width;
1107 S32 child_height = 0;
1108 S32 child_top = parent_item_height - llround(running_height);
1109
1110 target_height += itemp->arrange( &child_width, &child_height, filter_generation );
1111 // don't change width, as this item is as wide as its parent folder by construction
1112 itemp->reshape( itemp->getRect().getWidth(), child_height);
1113
1114 running_height += (F32)child_height;
1115 *width = llmax(*width, child_width);
1116 itemp->setOrigin( 0, child_top - itemp->getRect().getHeight() );
1117 }
1118 }
1119 }
1120
1121 mTargetHeight = target_height;
1122 // cache this width so next time we can just return it
1123 mLastCalculatedWidth = *width;
1124 }
1125 else
1126 {
1127 // just use existing width
1128 *width = mLastCalculatedWidth;
1129 }
1130
1131 // animate current height towards target height
1132 if (llabs(mCurHeight - mTargetHeight) > 1.f)
1133 {
1134 mCurHeight = lerp(mCurHeight, mTargetHeight, LLCriticalDamp::getInterpolant(mIsOpen ? FOLDER_OPEN_TIME_CONSTANT : FOLDER_CLOSE_TIME_CONSTANT));
1135
1136 requestArrange();
1137
1138 // hide child elements that fall out of current animated height
1139 for (folders_t::iterator iter = mFolders.begin();
1140 iter != mFolders.end();)
1141 {
1142 folders_t::iterator fit = iter++;
1143 // number of pixels that bottom of folder label is from top of parent folder
1144 if (mRect.getHeight() - (*fit)->getRect().mTop + (*fit)->getItemHeight()
1145 > llround(mCurHeight) + MAX_FOLDER_ITEM_OVERLAP)
1146 {
1147 // hide if beyond current folder height
1148 (*fit)->setVisible(FALSE);
1149 }
1150 }
1151
1152 for (items_t::iterator iter = mItems.begin();
1153 iter != mItems.end();)
1154 {
1155 items_t::iterator iit = iter++;
1156 // number of pixels that bottom of item label is from top of parent folder
1157 if (mRect.getHeight() - (*iit)->getRect().mBottom
1158 > llround(mCurHeight) + MAX_FOLDER_ITEM_OVERLAP)
1159 {
1160 (*iit)->setVisible(FALSE);
1161 }
1162 }
1163 }
1164 else
1165 {
1166 mCurHeight = mTargetHeight;
1167 }
1168
1169 // don't change width as this item is already as wide as its parent folder
1170 reshape(mRect.getWidth(),llround(mCurHeight));
1171
1172 // pass current height value back to parent
1173 *height = llround(mCurHeight);
1174
1175 return llround(mTargetHeight);
1176}
1177
1178BOOL LLFolderViewFolder::needsArrange()
1179{
1180 return mLastArrangeGeneration < mRoot->getArrangeGeneration();
1181}
1182
1183void LLFolderViewFolder::setCompletedFilterGeneration(S32 generation, BOOL recurse_up)
1184{
1185 mMostFilteredDescendantGeneration = llmin(mMostFilteredDescendantGeneration, generation);
1186 mCompletedFilterGeneration = generation;
1187 // only aggregate up if we are a lower (older) value
1188 if (recurse_up && mParentFolder && generation < mParentFolder->getCompletedFilterGeneration())
1189 {
1190 mParentFolder->setCompletedFilterGeneration(generation, TRUE);
1191 }
1192}
1193
1194void LLFolderViewFolder::filter( LLInventoryFilter& filter)
1195{
1196 S32 filter_generation = filter.getCurrentGeneration();
1197 // if failed to pass filter newer than must_pass_generation
1198 // you will automatically fail this time, so we only
1199 // check against items that have passed the filter
1200 S32 must_pass_generation = filter.getMustPassGeneration();
1201
1202 // if we have already been filtered against this generation, skip out
1203 if (getCompletedFilterGeneration() >= filter_generation)
1204 {
1205 return;
1206 }
1207
1208 // filter folder itself
1209 if (getLastFilterGeneration() < filter_generation)
1210 {
1211 if (getLastFilterGeneration() >= must_pass_generation && // folder has been compared to a valid precursor filter
1212 !mFiltered) // and did not pass the filter
1213 {
1214 // go ahead and flag this folder as done
1215 mLastFilterGeneration = filter_generation;
1216 }
1217 else
1218 {
1219 // filter self only on first pass through
1220 LLFolderViewItem::filter( filter );
1221 }
1222 }
1223
1224 if (getRoot()->getDebugFilters())
1225 {
1226 mStatusText = llformat("%d", mLastFilterGeneration);
1227 mStatusText += llformat("(%d)", mCompletedFilterGeneration);
1228 mStatusText += llformat("+%d", mMostFilteredDescendantGeneration);
1229 }
1230
1231 // all descendants have been filtered later than must pass generation
1232 // but none passed
1233 if(getCompletedFilterGeneration() >= must_pass_generation && !hasFilteredDescendants(must_pass_generation))
1234 {
1235 // don't traverse children if we've already filtered them since must_pass_generation
1236 // and came back with nothing
1237 return;
1238 }
1239
1240 // we entered here with at least one filter iteration left
1241 // check to see if we have any more before continuing on to children
1242 if (filter.getFilterCount() < 0)
1243 {
1244 return;
1245 }
1246
1247 // when applying a filter, matching folders get their contents downloaded first
1248 if (getRoot()->isFilterActive() && getFiltered(filter.getMinRequiredGeneration()) && !gInventory.isCategoryComplete(mListener->getUUID()))
1249 {
1250 gInventory.startBackgroundFetch(mListener->getUUID());
1251 }
1252
1253 // now query children
1254 for (folders_t::iterator iter = mFolders.begin();
1255 iter != mFolders.end();)
1256 {
1257 folders_t::iterator fit = iter++;
1258 // have we run out of iterations this frame?
1259 if (filter.getFilterCount() < 0)
1260 {
1261 break;
1262 }
1263
1264 // mMostFilteredDescendantGeneration might have been reset
1265 // in which case we need to update it even for folders that
1266 // don't need to be filtered anymore
1267 if ((*fit)->getCompletedFilterGeneration() >= filter_generation)
1268 {
1269 // track latest generation to pass any child items
1270 if ((*fit)->getFiltered() || (*fit)->hasFilteredDescendants(filter.getMinRequiredGeneration()))
1271 {
1272 mMostFilteredDescendantGeneration = filter_generation;
1273 if (mRoot->needsAutoSelect())
1274 {
1275 (*fit)->setOpenArrangeRecursively(TRUE);
1276 }
1277 }
1278 // just skip it, it has already been filtered
1279 continue;
1280 }
1281
1282 // update this folders filter status (and children)
1283 (*fit)->filter( filter );
1284
1285 // track latest generation to pass any child items
1286 if ((*fit)->getFiltered() || (*fit)->hasFilteredDescendants(filter_generation))
1287 {
1288 mMostFilteredDescendantGeneration = filter_generation;
1289 if (mRoot->needsAutoSelect())
1290 {
1291 (*fit)->setOpenArrangeRecursively(TRUE);
1292 }
1293 }
1294 }
1295
1296 for (items_t::iterator iter = mItems.begin();
1297 iter != mItems.end();)
1298 {
1299 items_t::iterator iit = iter++;
1300 if (filter.getFilterCount() < 0)
1301 {
1302 break;
1303 }
1304 if ((*iit)->getLastFilterGeneration() >= filter_generation)
1305 {
1306 if ((*iit)->getFiltered())
1307 {
1308 mMostFilteredDescendantGeneration = filter_generation;
1309 }
1310 continue;
1311 }
1312
1313 if ((*iit)->getLastFilterGeneration() >= must_pass_generation &&
1314 !(*iit)->getFiltered(must_pass_generation))
1315 {
1316 // failed to pass an earlier filter that was a subset of the current one
1317 // go ahead and flag this item as done
1318 (*iit)->setFiltered(FALSE, filter_generation);
1319 continue;
1320 }
1321
1322 (*iit)->filter( filter );
1323
1324 if ((*iit)->getFiltered(filter.getMinRequiredGeneration()))
1325 {
1326 mMostFilteredDescendantGeneration = filter_generation;
1327 }
1328 }
1329
1330 // if we didn't use all filter iterations
1331 // that means we filtered all of our descendants
1332 // instead of exhausting the filter count for this frame
1333 if (filter.getFilterCount() > 0)
1334 {
1335 // flag this folder as having completed filter pass for all descendants
1336 setCompletedFilterGeneration(filter_generation, FALSE/*dont recurse up to root*/);
1337 }
1338}
1339
1340void LLFolderViewFolder::setFiltered(BOOL filtered, S32 filter_generation)
1341{
1342 // if this folder is now filtered, but wasn't before
1343 // (it just passed)
1344 if (filtered && !mFiltered)
1345 {
1346 // reset current height, because last time we drew it
1347 // it might have had more visible items than now
1348 mCurHeight = 0.f;
1349 }
1350
1351 LLFolderViewItem::setFiltered(filtered, filter_generation);
1352}
1353
1354void LLFolderViewFolder::dirtyFilter()
1355{
1356 // we're a folder, so invalidate our completed generation
1357 setCompletedFilterGeneration(-1, FALSE);
1358 LLFolderViewItem::dirtyFilter();
1359}
1360
1361BOOL LLFolderViewFolder::hasFilteredDescendants()
1362{
1363 return mMostFilteredDescendantGeneration >= mRoot->getFilter()->getCurrentGeneration();
1364}
1365
1366// Passes selection information on to children and record selection
1367// information if necessary.
1368BOOL LLFolderViewFolder::setSelection(LLFolderViewItem* selection, BOOL open,
1369 BOOL take_keyboard_focus)
1370{
1371 BOOL rv = FALSE;
1372 if( selection == this )
1373 {
1374 mIsSelected = TRUE;
1375 if(mListener)
1376 {
1377 mListener->selectItem();
1378 }
1379 rv = TRUE;
1380 }
1381 else
1382 {
1383 mIsSelected = FALSE;
1384 rv = FALSE;
1385 }
1386 BOOL child_selected = FALSE;
1387
1388 for (folders_t::iterator iter = mFolders.begin();
1389 iter != mFolders.end();)
1390 {
1391 folders_t::iterator fit = iter++;
1392 if((*fit)->setSelection(selection, open, take_keyboard_focus))
1393 {
1394 rv = TRUE;
1395 child_selected = TRUE;
1396 mNumDescendantsSelected++;
1397 }
1398 }
1399 for (items_t::iterator iter = mItems.begin();
1400 iter != mItems.end();)
1401 {
1402 items_t::iterator iit = iter++;
1403 if((*iit)->setSelection(selection, open, take_keyboard_focus))
1404 {
1405 rv = TRUE;
1406 child_selected = TRUE;
1407 mNumDescendantsSelected++;
1408 }
1409 }
1410 if(open && child_selected)
1411 {
1412 setOpenArrangeRecursively(TRUE);
1413 }
1414 return rv;
1415}
1416
1417// This method is used to change the selection of an item. If
1418// selection is 'this', then note selection as true. Returns TRUE
1419// if this or a child is now selected.
1420BOOL LLFolderViewFolder::changeSelection(LLFolderViewItem* selection,
1421 BOOL selected)
1422{
1423 BOOL rv = FALSE;
1424 if(selection == this)
1425 {
1426 mIsSelected = selected;
1427 if(mListener && selected)
1428 {
1429 mListener->selectItem();
1430 }
1431 rv = TRUE;
1432 }
1433
1434 for (folders_t::iterator iter = mFolders.begin();
1435 iter != mFolders.end();)
1436 {
1437 folders_t::iterator fit = iter++;
1438 if((*fit)->changeSelection(selection, selected))
1439 {
1440 if (selected)
1441 {
1442 mNumDescendantsSelected++;
1443 }
1444 else
1445 {
1446 mNumDescendantsSelected--;
1447 }
1448 rv = TRUE;
1449 }
1450 }
1451 for (items_t::iterator iter = mItems.begin();
1452 iter != mItems.end();)
1453 {
1454 items_t::iterator iit = iter++;
1455 if((*iit)->changeSelection(selection, selected))
1456 {
1457 if (selected)
1458 {
1459 mNumDescendantsSelected++;
1460 }
1461 else
1462 {
1463 mNumDescendantsSelected--;
1464 }
1465 rv = TRUE;
1466 }
1467 }
1468 return rv;
1469}
1470
1471S32 LLFolderViewFolder::extendSelection(LLFolderViewItem* selection, LLFolderViewItem* last_selected, LLDynamicArray<LLFolderViewItem*>& selected_items)
1472{
1473 S32 num_selected = 0;
1474
1475 // pass on to child folders first
1476 for (folders_t::iterator iter = mFolders.begin();
1477 iter != mFolders.end();)
1478 {
1479 folders_t::iterator fit = iter++;
1480 num_selected += (*fit)->extendSelection(selection, last_selected, selected_items);
1481 mNumDescendantsSelected += num_selected;
1482 }
1483
1484 // handle selection of our immediate children...
1485 BOOL reverse_select = FALSE;
1486 BOOL found_last_selected = FALSE;
1487 BOOL found_selection = FALSE;
1488 LLDynamicArray<LLFolderViewItem*> items_to_select;
1489 LLFolderViewItem* item;
1490
1491 //...folders first...
1492 for (folders_t::iterator iter = mFolders.begin();
1493 iter != mFolders.end();)
1494 {
1495 folders_t::iterator fit = iter++;
1496 item = (*fit);
1497 if(item == selection)
1498 {
1499 found_selection = TRUE;
1500 }
1501 else if (item == last_selected)
1502 {
1503 found_last_selected = TRUE;
1504 if (found_selection)
1505 {
1506 reverse_select = TRUE;
1507 }
1508 }
1509
1510 if (found_selection || found_last_selected)
1511 {
1512 // deselect currently selected items so they can be pushed back on queue
1513 if (item->isSelected())
1514 {
1515 item->changeSelection(item, FALSE);
1516 }
1517 items_to_select.put(item);
1518 }
1519
1520 if (found_selection && found_last_selected)
1521 {
1522 break;
1523 }
1524 }
1525
1526 if (!(found_selection && found_last_selected))
1527 {
1528 //,,,then items
1529 for (items_t::iterator iter = mItems.begin();
1530 iter != mItems.end();)
1531 {
1532 items_t::iterator iit = iter++;
1533 item = (*iit);
1534 if(item == selection)
1535 {
1536 found_selection = TRUE;
1537 }
1538 else if (item == last_selected)
1539 {
1540 found_last_selected = TRUE;
1541 if (found_selection)
1542 {
1543 reverse_select = TRUE;
1544 }
1545 }
1546
1547 if (found_selection || found_last_selected)
1548 {
1549 // deselect currently selected items so they can be pushed back on queue
1550 if (item->isSelected())
1551 {
1552 item->changeSelection(item, FALSE);
1553 }
1554 items_to_select.put(item);
1555 }
1556
1557 if (found_selection && found_last_selected)
1558 {
1559 break;
1560 }
1561 }
1562 }
1563
1564 if (found_last_selected && found_selection)
1565 {
1566 // we have a complete selection inside this folder
1567 for (S32 index = reverse_select ? items_to_select.getLength() - 1 : 0;
1568 reverse_select ? index >= 0 : index < items_to_select.getLength(); reverse_select ? index-- : index++)
1569 {
1570 LLFolderViewItem* item = items_to_select[index];
1571 if (item->changeSelection(item, TRUE))
1572 {
1573 selected_items.put(item);
1574 mNumDescendantsSelected++;
1575 num_selected++;
1576 }
1577 }
1578 }
1579 else if (found_selection)
1580 {
1581 // last selection was not in this folder....go ahead and select just the new item
1582 if (selection->changeSelection(selection, TRUE))
1583 {
1584 selected_items.put(selection);
1585 mNumDescendantsSelected++;
1586 num_selected++;
1587 }
1588 }
1589
1590 return num_selected;
1591}
1592
1593void LLFolderViewFolder::recursiveDeselect(BOOL deselect_self)
1594{
1595 // make sure we don't have negative values
1596 llassert(mNumDescendantsSelected >= 0);
1597
1598 if (mIsSelected && deselect_self)
1599 {
1600 mIsSelected = FALSE;
1601
1602 // update ancestors' count of selected descendents
1603 LLFolderViewFolder* parent_folder = getParentFolder();
1604 while(parent_folder)
1605 {
1606 parent_folder->mNumDescendantsSelected--;
1607 parent_folder = parent_folder->getParentFolder();
1608 }
1609 }
1610
1611 if (0 == mNumDescendantsSelected)
1612 {
1613 return;
1614 }
1615
1616 for (items_t::iterator iter = mItems.begin();
1617 iter != mItems.end();)
1618 {
1619 items_t::iterator iit = iter++;
1620 LLFolderViewItem* item = (*iit);
1621 item->recursiveDeselect(TRUE);
1622 }
1623
1624 for (folders_t::iterator iter = mFolders.begin();
1625 iter != mFolders.end();)
1626 {
1627 folders_t::iterator fit = iter++;
1628 LLFolderViewFolder* folder = (*fit);
1629 folder->recursiveDeselect(TRUE);
1630 }
1631
1632}
1633
1634void LLFolderViewFolder::destroyView()
1635{
1636 for (items_t::iterator iter = mItems.begin();
1637 iter != mItems.end();)
1638 {
1639 items_t::iterator iit = iter++;
1640 LLFolderViewItem* item = (*iit);
1641 getRoot()->removeItemID(item->getListener()->getUUID());
1642 }
1643
1644 std::for_each(mItems.begin(), mItems.end(), DeletePointer());
1645 mItems.clear();
1646
1647 while (!mFolders.empty())
1648 {
1649 LLFolderViewFolder *folderp = mFolders.back();
1650 folderp->destroyView();
1651 }
1652
1653 mFolders.clear();
1654
1655 deleteAllChildren();
1656
1657 if (mParentFolder)
1658 {
1659 mParentFolder->removeView(this);
1660 }
1661}
1662
1663// remove the specified item (and any children) if possible. Return
1664// TRUE if the item was deleted.
1665BOOL LLFolderViewFolder::removeItem(LLFolderViewItem* item)
1666{
1667 if(item->remove())
1668 {
1669 removeView(item);
1670 return TRUE;
1671 }
1672 return FALSE;
1673}
1674
1675// simply remove the view (and any children) Don't bother telling the
1676// listeners.
1677void LLFolderViewFolder::removeView(LLFolderViewItem* item)
1678{
1679 if (!item)
1680 {
1681 return;
1682 }
1683 // deselect without traversing hierarchy
1684 item->recursiveDeselect(TRUE);
1685 getRoot()->removeFromSelectionList(item);
1686 extractItem(item);
1687 delete item;
1688}
1689
1690// extractItem() removes the specified item from the folder, but
1691// doesn't delete it.
1692void LLFolderViewFolder::extractItem( LLFolderViewItem* item )
1693{
1694 items_t::iterator it = std::find(mItems.begin(), mItems.end(), item);
1695 if(it == mItems.end())
1696 {
1697 // This is an evil downcast. However, it's only doing
1698 // pointer comparison to find if (which it should be ) the
1699 // item is in the container, so it's pretty safe.
1700 LLFolderViewFolder* f = reinterpret_cast<LLFolderViewFolder*>(item);
1701 folders_t::iterator ft;
1702 ft = std::find(mFolders.begin(), mFolders.end(), f);
1703 if(ft != mFolders.end())
1704 {
1705 mFolders.erase(ft);
1706 }
1707 }
1708 else
1709 {
1710 mItems.erase(it);
1711 }
1712 //item has been removed, need to update filter
1713 dirtyFilter();
1714 //because an item is going away regardless of filter status, force rearrange
1715 requestArrange();
1716 getRoot()->removeItemID(item->getListener()->getUUID());
1717 removeChild(item);
1718}
1719
1720// This function is called by a child that needs to be resorted.
1721// This is only called for renaming an object because it won't work for date
1722void LLFolderViewFolder::resort(LLFolderViewItem* item)
1723{
1724 std::sort(mItems.begin(), mItems.end(), *mSortFunction);
1725 std::sort(mFolders.begin(), mFolders.end(), *mSortFunction);
1726 //if(mItems.removeData(item))
1727 //{
1728 // mItems.addDataSorted(item);
1729 //}
1730 //else
1731 //{
1732 // // This is an evil downcast. However, it's only doing
1733 // // pointer comparison to find if (which it should be ) the
1734 // // item is in the container, so it's pretty safe.
1735 // LLFolderViewFolder* f = reinterpret_cast<LLFolderViewFolder*>(item);
1736 // if(mFolders.removeData(f))
1737 // {
1738 // mFolders.addDataSorted(f);
1739 // }
1740 //}
1741}
1742
1743bool LLFolderViewFolder::isTrash()
1744{
1745 if (mAmTrash == LLFolderViewFolder::UNKNOWN)
1746 {
1747 mAmTrash = mListener->getUUID() == gInventory.findCategoryUUIDForType(LLAssetType::AT_TRASH) ? LLFolderViewFolder::TRASH : LLFolderViewFolder::NOT_TRASH;
1748 }
1749 return mAmTrash == LLFolderViewFolder::TRASH;
1750}
1751
1752void LLFolderViewFolder::sortBy(U32 order)
1753{
1754 BOOL sort_order_changed = FALSE;
1755 if (!(order & LLInventoryFilter::SO_DATE))
1756 {
1757 if (mSortFunction != sort_item_name)
1758 {
1759 mSortFunction = sort_item_name;
1760 sort_order_changed = TRUE;
1761 }
1762 }
1763 else
1764 {
1765 if (mSortFunction != sort_item_date)
1766 {
1767 mSortFunction = sort_item_date;
1768 sort_order_changed = TRUE;
1769 }
1770 }
1771
1772 for (folders_t::iterator iter = mFolders.begin();
1773 iter != mFolders.end();)
1774 {
1775 folders_t::iterator fit = iter++;
1776 (*fit)->sortBy(order);
1777 }
1778 if (order & LLInventoryFilter::SO_FOLDERS_BY_NAME)
1779 {
1780 // sort folders by name if always by name
1781 std::sort(mFolders.begin(), mFolders.end(), sort_item_name);
1782 }
1783 else
1784 {
1785 // sort folders by the default sort ordering
1786 std::sort(mFolders.begin(), mFolders.end(), *mSortFunction);
1787
1788 // however, if we are at the root of the inventory and we are sorting by date
1789 if (mListener->getUUID() == gAgent.getInventoryRootID() && order & LLInventoryFilter::SO_DATE)
1790 {
1791 // pull the trash folder and stick it on the end of the list
1792 LLFolderViewFolder *t = NULL;
1793 for (folders_t::iterator fit = mFolders.begin();
1794 fit != mFolders.end(); ++fit)
1795 {
1796 if ((*fit)->isTrash())
1797 {
1798 t = *fit;
1799 mFolders.erase(fit);
1800 break;
1801 }
1802 }
1803 if (t)
1804 {
1805 mFolders.push_back(t);
1806 }
1807 }
1808 }
1809 if (sort_order_changed)
1810 {
1811 std::sort(mItems.begin(), mItems.end(), *mSortFunction);
1812 }
1813
1814 if (order & LLInventoryFilter::SO_DATE)
1815 {
1816 U32 latest = 0;
1817
1818 if (!mItems.empty())
1819 {
1820 LLFolderViewItem* item = *(mItems.begin());
1821 latest = item->getCreationDate();
1822 }
1823
1824 if (!mFolders.empty())
1825 {
1826 LLFolderViewFolder* folder = *(mFolders.begin());
1827 if (folder->getCreationDate() > latest)
1828 {
1829 latest = folder->getCreationDate();
1830 }
1831 }
1832 mSubtreeCreationDate = latest;
1833 }
1834}
1835
1836void LLFolderViewFolder::setItemSortFunction(sort_order_f ordering)
1837{
1838 mSortFunction = ordering;
1839
1840 for (folders_t::iterator iter = mFolders.begin();
1841 iter != mFolders.end();)
1842 {
1843 folders_t::iterator fit = iter++;
1844 (*fit)->setItemSortFunction(ordering);
1845 }
1846
1847 std::sort(mFolders.begin(), mFolders.end(), *mSortFunction);
1848 std::sort(mItems.begin(), mItems.end(), *mSortFunction);
1849}
1850
1851BOOL LLFolderViewFolder::isMovable()
1852{
1853 if( mListener )
1854 {
1855 if( !(mListener->isItemMovable()) )
1856 {
1857 return FALSE;
1858 }
1859
1860 for (items_t::iterator iter = mItems.begin();
1861 iter != mItems.end();)
1862 {
1863 items_t::iterator iit = iter++;
1864 if(!(*iit)->isMovable())
1865 {
1866 return FALSE;
1867 }
1868 }
1869
1870 for (folders_t::iterator iter = mFolders.begin();
1871 iter != mFolders.end();)
1872 {
1873 folders_t::iterator fit = iter++;
1874 if(!(*fit)->isMovable())
1875 {
1876 return FALSE;
1877 }
1878 }
1879 }
1880 return TRUE;
1881}
1882
1883
1884BOOL LLFolderViewFolder::isRemovable()
1885{
1886 if( mListener )
1887 {
1888 if( !(mListener->isItemRemovable()) )
1889 {
1890 return FALSE;
1891 }
1892
1893 for (items_t::iterator iter = mItems.begin();
1894 iter != mItems.end();)
1895 {
1896 items_t::iterator iit = iter++;
1897 if(!(*iit)->isRemovable())
1898 {
1899 return FALSE;
1900 }
1901 }
1902
1903 for (folders_t::iterator iter = mFolders.begin();
1904 iter != mFolders.end();)
1905 {
1906 folders_t::iterator fit = iter++;
1907 if(!(*fit)->isRemovable())
1908 {
1909 return FALSE;
1910 }
1911 }
1912 }
1913 return TRUE;
1914}
1915
1916// this is an internal method used for adding items to folders.
1917BOOL LLFolderViewFolder::addItem(LLFolderViewItem* item)
1918{
1919 items_t::iterator it = std::lower_bound(
1920 mItems.begin(),
1921 mItems.end(),
1922 item,
1923 mSortFunction);
1924 mItems.insert(it,item);
1925 item->setRect(LLRect(0, 0, mRect.getWidth(), 0));
1926 item->setVisible(FALSE);
1927 addChild( item );
1928 item->dirtyFilter();
1929 requestArrange();
1930 return TRUE;
1931}
1932
1933// this is an internal method used for adding items to folders.
1934BOOL LLFolderViewFolder::addFolder(LLFolderViewFolder* folder)
1935{
1936 folders_t::iterator it = std::lower_bound(
1937 mFolders.begin(),
1938 mFolders.end(),
1939 folder,
1940 mSortFunction);
1941 mFolders.insert(it,folder);
1942 folder->setOrigin(0, 0);
1943 folder->reshape(mRect.getWidth(), 0);
1944 folder->setVisible(FALSE);
1945 addChild( folder );
1946 folder->dirtyFilter();
1947 requestArrange();
1948 return TRUE;
1949}
1950
1951void LLFolderViewFolder::requestArrange()
1952{
1953 mLastArrangeGeneration = -1;
1954 // flag all items up to root
1955 if (mParentFolder)
1956 {
1957 mParentFolder->requestArrange();
1958 }
1959}
1960
1961void LLFolderViewFolder::toggleOpen()
1962{
1963 setOpen(!mIsOpen);
1964}
1965
1966// Force a folder open or closed
1967void LLFolderViewFolder::setOpen(BOOL open)
1968{
1969 setOpenArrangeRecursively(open);
1970}
1971
1972void LLFolderViewFolder::setOpenArrangeRecursively(BOOL open, ERecurseType recurse)
1973{
1974 BOOL was_open = mIsOpen;
1975 mIsOpen = open;
1976 if(!was_open && open)
1977 {
1978 if(mListener)
1979 {
1980 mListener->openItem();
1981 }
1982 }
1983 if (recurse == RECURSE_DOWN || recurse == RECURSE_UP_DOWN)
1984 {
1985 for (folders_t::iterator iter = mFolders.begin();
1986 iter != mFolders.end();)
1987 {
1988 folders_t::iterator fit = iter++;
1989 (*fit)->setOpenArrangeRecursively(open, RECURSE_DOWN);
1990 }
1991 }
1992 if (mParentFolder && (recurse == RECURSE_UP || recurse == RECURSE_UP_DOWN))
1993 {
1994 mParentFolder->setOpenArrangeRecursively(open, RECURSE_UP);
1995 }
1996
1997 if (was_open != mIsOpen)
1998 {
1999 requestArrange();
2000 }
2001}
2002
2003BOOL LLFolderViewFolder::handleDragAndDropFromChild(MASK mask,
2004 BOOL drop,
2005 EDragAndDropType c_type,
2006 void* cargo_data,
2007 EAcceptance* accept,
2008 LLString& tooltip_msg)
2009{
2010 BOOL accepted = mListener && mListener->dragOrDrop(mask,drop,c_type,cargo_data);
2011 if (accepted)
2012 {
2013 mDragAndDropTarget = TRUE;
2014 *accept = ACCEPT_YES_MULTI;
2015 }
2016 else
2017 {
2018 *accept = ACCEPT_NO;
2019 }
2020
2021 // drag and drop to child item, so clear pending auto-opens
2022 getRoot()->autoOpenTest(NULL);
2023
2024 return TRUE;
2025}
2026
2027void LLFolderViewFolder::open( void )
2028{
2029 toggleOpen();
2030}
2031
2032void LLFolderViewFolder::applyFunctorRecursively(LLFolderViewFunctor& functor)
2033{
2034 functor.doFolder(this);
2035
2036 for (folders_t::iterator iter = mFolders.begin();
2037 iter != mFolders.end();)
2038 {
2039 folders_t::iterator fit = iter++;
2040 (*fit)->applyFunctorRecursively(functor);
2041 }
2042 for (items_t::iterator iter = mItems.begin();
2043 iter != mItems.end();)
2044 {
2045 items_t::iterator iit = iter++;
2046 functor.doItem((*iit));
2047 }
2048}
2049
2050void LLFolderViewFolder::applyListenerFunctorRecursively(LLFolderViewListenerFunctor& functor)
2051{
2052 functor(mListener);
2053 for (folders_t::iterator iter = mFolders.begin();
2054 iter != mFolders.end();)
2055 {
2056 folders_t::iterator fit = iter++;
2057 (*fit)->applyListenerFunctorRecursively(functor);
2058 }
2059 for (items_t::iterator iter = mItems.begin();
2060 iter != mItems.end();)
2061 {
2062 items_t::iterator iit = iter++;
2063 (*iit)->applyListenerFunctorRecursively(functor);
2064 }
2065}
2066
2067// LLView functionality
2068BOOL LLFolderViewFolder::handleDragAndDrop(S32 x, S32 y, MASK mask,
2069 BOOL drop,
2070 EDragAndDropType cargo_type,
2071 void* cargo_data,
2072 EAcceptance* accept,
2073 LLString& tooltip_msg)
2074{
2075 LLFolderView* root_view = getRoot();
2076
2077 BOOL handled = FALSE;
2078 if(mIsOpen)
2079 {
2080 handled = childrenHandleDragAndDrop(x, y, mask, drop, cargo_type,
2081 cargo_data, accept, tooltip_msg) != NULL;
2082 }
2083
2084 if (!handled)
2085 {
2086 BOOL accepted = mListener && mListener->dragOrDrop(mask, drop,cargo_type,cargo_data);
2087
2088 if (accepted)
2089 {
2090 mDragAndDropTarget = TRUE;
2091 *accept = ACCEPT_YES_MULTI;
2092 }
2093 else
2094 {
2095 *accept = ACCEPT_NO;
2096 }
2097
2098 if (!drop && accepted)
2099 {
2100 root_view->autoOpenTest(this);
2101 }
2102
2103 lldebugst(LLERR_USER_INPUT) << "dragAndDrop handled by LLFolderViewFolder" << llendl;
2104 }
2105
2106 return TRUE;
2107}
2108
2109
2110BOOL LLFolderViewFolder::handleRightMouseDown( S32 x, S32 y, MASK mask )
2111{
2112 BOOL handled = FALSE;
2113 if( getVisible() )
2114 {
2115 // fetch contents of this folder, as context menu can depend on contents
2116 // still, user would have to open context menu again to see the changes
2117 gInventory.fetchDescendentsOf(mListener->getUUID());
2118
2119 if( mIsOpen )
2120 {
2121 handled = childrenHandleRightMouseDown( x, y, mask ) != NULL;
2122 }
2123 if (!handled)
2124 {
2125 handled = LLFolderViewItem::handleRightMouseDown( x, y, mask );
2126 }
2127 }
2128 return handled;
2129}
2130
2131
2132BOOL LLFolderViewFolder::handleHover(S32 x, S32 y, MASK mask)
2133{
2134 BOOL handled = LLView::handleHover(x, y, mask);
2135
2136 if (!handled)
2137 {
2138 // this doesn't do child processing
2139 handled = LLFolderViewItem::handleHover(x, y, mask);
2140 }
2141
2142 //if(x < LEFT_INDENTATION + mIndentation && x > mIndentation - LEFT_PAD && y > mRect.getHeight() - )
2143 //{
2144 // gViewerWindow->setCursor(UI_CURSOR_ARROW);
2145 // mExpanderHighlighted = TRUE;
2146 // handled = TRUE;
2147 //}
2148 return handled;
2149}
2150
2151BOOL LLFolderViewFolder::handleMouseDown( S32 x, S32 y, MASK mask )
2152{
2153 BOOL handled = FALSE;
2154 if( mIsOpen )
2155 {
2156 handled = childrenHandleMouseDown(x,y,mask) != NULL;
2157 }
2158 if( !handled )
2159 {
2160 if(x < LEFT_INDENTATION + mIndentation && x > mIndentation - LEFT_PAD)
2161 {
2162 toggleOpen();
2163 handled = TRUE;
2164 }
2165 else
2166 {
2167 // do normal selection logic
2168 handled = LLFolderViewItem::handleMouseDown(x, y, mask);
2169 }
2170 }
2171
2172 return handled;
2173}
2174
2175BOOL LLFolderViewFolder::handleDoubleClick( S32 x, S32 y, MASK mask )
2176{
2177 if (!getVisible())
2178 {
2179 return FALSE;
2180 }
2181 BOOL rv = false;
2182 if( mIsOpen )
2183 {
2184 rv = childrenHandleDoubleClick( x, y, mask ) != NULL;
2185 }
2186 if( !rv )
2187 {
2188 if(x < LEFT_INDENTATION + mIndentation && x > mIndentation - LEFT_PAD)
2189 {
2190 // don't select when user double-clicks plus sign
2191 // so as not to contradict single-click behavior
2192 toggleOpen();
2193 }
2194 else
2195 {
2196 setSelectionFromRoot(this, FALSE);
2197 toggleOpen();
2198 }
2199 return TRUE;
2200 }
2201 return rv;
2202}
2203
2204void LLFolderViewFolder::draw()
2205{
2206 if (mAutoOpenCountdown != 0.f)
2207 {
2208 mControlLabelRotation = mAutoOpenCountdown * -90.f;
2209 }
2210 else if (mIsOpen)
2211 {
2212 mControlLabelRotation = lerp(mControlLabelRotation, -90.f, LLCriticalDamp::getInterpolant(0.04f));
2213 }
2214 else
2215 {
2216 mControlLabelRotation = lerp(mControlLabelRotation, 0.f, LLCriticalDamp::getInterpolant(0.025f));
2217 }
2218
2219 LLFolderViewItem::draw();
2220 if( mIsOpen )
2221 {
2222 LLView::draw();
2223 }
2224
2225// if (mExpanderHighlighted)
2226// {
2227// gl_rect_2d(mIndentation - TEXT_PAD, llfloor(mRect.getHeight() - TEXT_PAD), mIndentation + sFont->getWidth(mControlLabel) + TEXT_PAD, llfloor(mRect.getHeight() - sFont->getLineHeight() - TEXT_PAD), sFgColor, FALSE);
2228// //sFont->renderUTF8( mControlLabel, 0, mIndentation, llfloor(mRect.getHeight() - sFont->getLineHeight() - TEXT_PAD), sFgColor, LLFontGL::LEFT, LLFontGL::BOTTOM, mLabelStyle, S32_MAX, S32_MAX, NULL, FALSE );
2229// }
2230 mExpanderHighlighted = FALSE;
2231}
2232
2233U32 LLFolderViewFolder::getCreationDate() const
2234{
2235 return llmax<U32>(mCreationDate, mSubtreeCreationDate);
2236}
2237
2238
2239// this does prefix traversal, as folders are listed above their contents
2240LLFolderViewItem* LLFolderViewFolder::getNextFromChild( LLFolderViewItem* item, BOOL include_children )
2241{
2242 BOOL found_item = FALSE;
2243
2244 LLFolderViewItem* result = NULL;
2245 // when not starting from a given item, start at beginning
2246 if(item == NULL)
2247 {
2248 found_item = TRUE;
2249 }
2250
2251 // find current item among children
2252 folders_t::iterator fit = mFolders.begin();
2253 folders_t::iterator fend = mFolders.end();
2254
2255 items_t::iterator iit = mItems.begin();
2256 items_t::iterator iend = mItems.end();
2257
2258 // if not trivially starting at the beginning, we have to find the current item
2259 if (!found_item)
2260 {
2261 // first, look among folders, since they are always above items
2262 for(; fit != fend; ++fit)
2263 {
2264 if(item == (*fit))
2265 {
2266 found_item = TRUE;
2267 // if we are on downwards traversal
2268 if (include_children && (*fit)->isOpen())
2269 {
2270 // look for first descendant
2271 return (*fit)->getNextFromChild(NULL, TRUE);
2272 }
2273 // otherwise advance to next folder
2274 ++fit;
2275 include_children = TRUE;
2276 break;
2277 }
2278 }
2279
2280 // didn't find in folders? Check items...
2281 if (!found_item)
2282 {
2283 for(; iit != iend; ++iit)
2284 {
2285 if(item == (*iit))
2286 {
2287 found_item = TRUE;
2288 // point to next item
2289 ++iit;
2290 break;
2291 }
2292 }
2293 }
2294 }
2295
2296 if (!found_item)
2297 {
2298 // you should never call this method with an item that isn't a child
2299 // so we should always find something
2300 llassert(FALSE);
2301 return NULL;
2302 }
2303
2304 // at this point, either iit or fit point to a candidate "next" item
2305 // if both are out of range, we need to punt up to our parent
2306
2307 // now, starting from found folder, continue through folders
2308 // searching for next visible folder
2309 while(fit != fend && !(*fit)->getVisible())
2310 {
2311 // turn on downwards traversal for next folder
2312 ++fit;
2313 }
2314
2315 if (fit != fend)
2316 {
2317 result = (*fit);
2318 }
2319 else
2320 {
2321 // otherwise, scan for next visible item
2322 while(iit != iend && !(*iit)->getVisible())
2323 {
2324 ++iit;
2325 }
2326
2327 // check to see if we have a valid item
2328 if (iit != iend)
2329 {
2330 result = (*iit);
2331 }
2332 }
2333
2334 if( !result && mParentFolder )
2335 {
2336 // If there are no siblings or children to go to, recurse up one level in the tree
2337 // and skip children for this folder, as we've already discounted them
2338 result = mParentFolder->getNextFromChild(this, FALSE);
2339 }
2340
2341 return result;
2342}
2343
2344// this does postfix traversal, as folders are listed above their contents
2345LLFolderViewItem* LLFolderViewFolder::getPreviousFromChild( LLFolderViewItem* item, BOOL include_children )
2346{
2347 BOOL found_item = FALSE;
2348
2349 LLFolderViewItem* result = NULL;
2350 // when not starting from a given item, start at end
2351 if(item == NULL)
2352 {
2353 found_item = TRUE;
2354 }
2355
2356 // find current item among children
2357 folders_t::reverse_iterator fit = mFolders.rbegin();
2358 folders_t::reverse_iterator fend = mFolders.rend();
2359
2360 items_t::reverse_iterator iit = mItems.rbegin();
2361 items_t::reverse_iterator iend = mItems.rend();
2362
2363 // if not trivially starting at the end, we have to find the current item
2364 if (!found_item)
2365 {
2366 // first, look among items, since they are always below the folders
2367 for(; iit != iend; ++iit)
2368 {
2369 if(item == (*iit))
2370 {
2371 found_item = TRUE;
2372 // point to next item
2373 ++iit;
2374 break;
2375 }
2376 }
2377
2378 // didn't find in items? Check folders...
2379 if (!found_item)
2380 {
2381 for(; fit != fend; ++fit)
2382 {
2383 if(item == (*fit))
2384 {
2385 found_item = TRUE;
2386 // point to next folder
2387 ++fit;
2388 break;
2389 }
2390 }
2391 }
2392 }
2393
2394 if (!found_item)
2395 {
2396 // you should never call this method with an item that isn't a child
2397 // so we should always find something
2398 llassert(FALSE);
2399 return NULL;
2400 }
2401
2402 // at this point, either iit or fit point to a candidate "next" item
2403 // if both are out of range, we need to punt up to our parent
2404
2405 // now, starting from found item, continue through items
2406 // searching for next visible item
2407 while(iit != iend && !(*iit)->getVisible())
2408 {
2409 ++iit;
2410 }
2411
2412 if (iit != iend)
2413 {
2414 // we found an appropriate item
2415 result = (*iit);
2416 }
2417 else
2418 {
2419 // otherwise, scan for next visible folder
2420 while(fit != fend && !(*fit)->getVisible())
2421 {
2422 ++fit;
2423 }
2424
2425 // check to see if we have a valid folder
2426 if (fit != fend)
2427 {
2428 // try selecting child element of this folder
2429 if ((*fit)->isOpen())
2430 {
2431 result = (*fit)->getPreviousFromChild(NULL);
2432 }
2433 else
2434 {
2435 result = (*fit);
2436 }
2437 }
2438 }
2439
2440 if( !result )
2441 {
2442 // If there are no siblings or children to go to, recurse up one level in the tree
2443 // which gets back to this folder, which will only be visited if it is a valid, visible item
2444 result = this;
2445 }
2446
2447 return result;
2448}
2449
2450
2451//---------------------------------------------------------------------------
2452
2453// Tells all folders in a folderview to sort their items
2454// (and only their items, not folders) by a certain function.
2455class LLSetItemSortFunction : public LLFolderViewFunctor
2456{
2457public:
2458 LLSetItemSortFunction(sort_order_f ordering)
2459 : mSortFunction(ordering) {}
2460 virtual ~LLSetItemSortFunction() {}
2461 virtual void doFolder(LLFolderViewFolder* folder);
2462 virtual void doItem(LLFolderViewItem* item);
2463
2464 sort_order_f mSortFunction;
2465};
2466
2467
2468// Set the sort order.
2469void LLSetItemSortFunction::doFolder(LLFolderViewFolder* folder)
2470{
2471 folder->setItemSortFunction(mSortFunction);
2472}
2473
2474// Do nothing.
2475void LLSetItemSortFunction::doItem(LLFolderViewItem* item)
2476{
2477 return;
2478}
2479
2480//---------------------------------------------------------------------------
2481
2482// Tells all folders in a folderview to close themselves
2483// For efficiency, calls setOpenArrangeRecursively().
2484// The calling function must then call:
2485// LLFolderView* root = getRoot();
2486// if( root )
2487// {
2488// root->arrange( NULL, NULL );
2489// root->scrollToShowSelection();
2490// }
2491// to patch things up.
2492class LLCloseAllFoldersFunctor : public LLFolderViewFunctor
2493{
2494public:
2495 LLCloseAllFoldersFunctor(BOOL close) { mOpen = !close; }
2496 virtual ~LLCloseAllFoldersFunctor() {}
2497 virtual void doFolder(LLFolderViewFolder* folder);
2498 virtual void doItem(LLFolderViewItem* item);
2499
2500 BOOL mOpen;
2501};
2502
2503
2504// Set the sort order.
2505void LLCloseAllFoldersFunctor::doFolder(LLFolderViewFolder* folder)
2506{
2507 folder->setOpenArrangeRecursively(mOpen);
2508}
2509
2510// Do nothing.
2511void LLCloseAllFoldersFunctor::doItem(LLFolderViewItem* item)
2512{ }
2513
2514///----------------------------------------------------------------------------
2515/// Class LLFolderView
2516///----------------------------------------------------------------------------
2517
2518// Default constructor
2519LLFolderView::LLFolderView( const LLString& name, LLViewerImage* root_folder_icon,
2520 const LLRect& rect, const LLUUID& source_id, LLView *parent_view ) :
2521#if LL_WINDOWS
2522#pragma warning( push )
2523#pragma warning( disable : 4355 ) // warning C4355: 'this' : used in base member initializer list
2524#endif
2525 LLFolderViewFolder( name, root_folder_icon, this, NULL ),
2526#if LL_WINDOWS
2527#pragma warning( pop )
2528#endif
2529 mScrollContainer( NULL ),
2530 mPopupMenuHandle( LLViewHandle::sDeadHandle ),
2531 mAllowMultiSelect(TRUE),
2532 mShowFolderHierarchy(FALSE),
2533 mSourceID(source_id),
2534 mRenameItem( NULL ),
2535 mNeedsScroll( FALSE ),
2536 mLastScrollItem( NULL ),
2537 mNeedsAutoSelect( FALSE ),
2538 mAutoSelectOverride(FALSE),
2539 mDebugFilters(FALSE),
2540 mSortOrder(LLInventoryFilter::SO_FOLDERS_BY_NAME), // This gets overridden by a pref immediately
2541 mFilter(name),
2542 mShowSelectionContext(FALSE),
2543 mShowSingleSelection(FALSE),
2544 mArrangeGeneration(0),
2545 mSelectCallback(NULL),
2546 mMinWidth(0),
2547 mDragAndDropThisFrame(FALSE)
2548{
2549 LLRect new_rect(rect.mLeft, rect.mBottom + mRect.getHeight(), rect.mLeft + mRect.getWidth(), rect.mBottom);
2550 setRect( rect );
2551 reshape(rect.getWidth(), rect.getHeight());
2552 mIsOpen = TRUE; // this view is always open.
2553 mAutoOpenItems.setDepth(AUTO_OPEN_STACK_DEPTH);
2554 mAutoOpenCandidate = NULL;
2555 mAutoOpenTimer.stop();
2556 mKeyboardSelection = FALSE;
2557 mIndentation = -LEFT_INDENTATION; // children start at indentation 0
2558 gIdleCallbacks.addFunction(idle, this);
2559
2560 //clear label
2561 // go ahead and render root folder as usual
2562 // just make sure the label ("Inventory Folder") never shows up
2563 mLabel = LLString::null;
2564
2565 mRenamer = new LLLineEditor("ren", mRect, "", sFont,
2566 DB_INV_ITEM_NAME_STR_LEN,
2567 &LLFolderView::commitRename,
2568 NULL,
2569 NULL,
2570 this,
2571 &LLLineEditor::prevalidatePrintableNotPipe,
2572 LLViewBorder::BEVEL_NONE,
2573 LLViewBorder::STYLE_LINE,
2574 2);
2575 mRenamer->setWriteableBgColor(LLColor4::white);
2576 // Escape is handled by reverting the rename, not commiting it (default behavior)
2577 mRenamer->setCommitOnFocusLost(TRUE);
2578 mRenamer->setVisible(FALSE);
2579 addChild(mRenamer);
2580
2581 // make the popup menu available
2582 LLMenuGL* menu = gUICtrlFactory->buildMenu("menu_inventory.xml", parent_view);
2583 if (!menu)
2584 {
2585 menu = new LLMenuGL("");
2586 }
2587 menu->setBackgroundColor(gColors.getColor("MenuPopupBgColor"));
2588 menu->setVisible(FALSE);
2589 mPopupMenuHandle = menu->mViewHandle;
2590
2591 setTabStop(TRUE);
2592}
2593
2594// Destroys the object
2595LLFolderView::~LLFolderView( void )
2596{
2597 // The release focus call can potentially call the
2598 // scrollcontainer, which can potentially be called with a partly
2599 // destroyed scollcontainer. Just null it out here, and no worries
2600 // about calling into the invalid scroll container.
2601 // Same with the renamer.
2602 mScrollContainer = NULL;
2603 mRenameItem = NULL;
2604 mRenamer = NULL;
2605 gFocusMgr.releaseFocusIfNeeded( this );
2606
2607 if( gEditMenuHandler == this )
2608 {
2609 gEditMenuHandler = NULL;
2610 }
2611
2612 mAutoOpenItems.removeAllNodes();
2613 gIdleCallbacks.deleteFunction(idle, this);
2614
2615 LLView::deleteViewByHandle(mPopupMenuHandle);
2616
2617 if(gViewerWindow->hasTopView(mRenamer))
2618 {
2619 gViewerWindow->setTopView(NULL, NULL);
2620 }
2621
2622 mAutoOpenItems.removeAllNodes();
2623 clearSelection();
2624 mItems.clear();
2625 mFolders.clear();
2626
2627 mItemMap.clear();
2628}
2629
2630EWidgetType LLFolderView::getWidgetType() const
2631{
2632 return WIDGET_TYPE_FOLDER_VIEW;
2633}
2634
2635LLString LLFolderView::getWidgetTag() const
2636{
2637 return LL_FOLDER_VIEW_TAG;
2638}
2639
2640BOOL LLFolderView::canFocusChildren() const
2641{
2642 return FALSE;
2643}
2644
2645void LLFolderView::checkTreeResortForModelChanged()
2646{
2647 if (mSortOrder & LLInventoryFilter::SO_DATE && !(mSortOrder & LLInventoryFilter::SO_FOLDERS_BY_NAME))
2648 {
2649 // This is the case where something got added or removed. If we are date sorting
2650 // everything including folders, then we need to rebuild the whole tree.
2651 // Just set to something not SO_DATE to force the folder most resent date resort.
2652 mSortOrder = mSortOrder & ~LLInventoryFilter::SO_DATE;
2653 setSortOrder(mSortOrder | LLInventoryFilter::SO_DATE);
2654 }
2655}
2656
2657void LLFolderView::setSortOrder(U32 order)
2658{
2659 if (order != mSortOrder)
2660 {
2661 LLFastTimer t(LLFastTimer::FTM_SORT);
2662 mSortOrder = order;
2663
2664 for (folders_t::iterator iter = mFolders.begin();
2665 iter != mFolders.end();)
2666 {
2667 folders_t::iterator fit = iter++;
2668 (*fit)->sortBy(order);
2669 }
2670
2671 arrangeAll();
2672 }
2673}
2674
2675
2676U32 LLFolderView::getSortOrder() const
2677{
2678 return mSortOrder;
2679}
2680
2681BOOL LLFolderView::addFolder( LLFolderViewFolder* folder)
2682{
2683 // enforce sort order of My Inventory followed by Library
2684 if (folder->getListener()->getUUID() == gInventoryLibraryRoot)
2685 {
2686 mFolders.push_back(folder);
2687 }
2688 else
2689 {
2690 mFolders.insert(mFolders.begin(), folder);
2691 }
2692 folder->setOrigin(0, 0);
2693 folder->reshape(mRect.getWidth(), 0);
2694 folder->setVisible(FALSE);
2695 addChild( folder );
2696 folder->dirtyFilter();
2697 return TRUE;
2698}
2699
2700void LLFolderView::closeAllFolders()
2701{
2702 // Close all the folders
2703 setOpenArrangeRecursively(FALSE, LLFolderViewFolder::RECURSE_DOWN);
2704}
2705
2706void LLFolderView::openFolder(const LLString& foldername)
2707{
2708 LLFolderViewFolder* inv = (LLFolderViewFolder*)getChildByName(foldername);
2709 if (inv)
2710 {
2711 setSelection(inv, FALSE, FALSE);
2712 inv->setOpen(TRUE);
2713 }
2714}
2715
2716void LLFolderView::setOpenArrangeRecursively(BOOL open, ERecurseType recurse)
2717{
2718 // call base class to do proper recursion
2719 LLFolderViewFolder::setOpenArrangeRecursively(open, recurse);
2720 // make sure root folder is always open
2721 mIsOpen = TRUE;
2722}
2723
2724// This view grows and shinks to enclose all of its children items and folders.
2725S32 LLFolderView::arrange( S32* unused_width, S32* unused_height, S32 filter_generation )
2726{
2727 LLFastTimer t2(LLFastTimer::FTM_ARRANGE);
2728
2729 filter_generation = mFilter.getMinRequiredGeneration();
2730 mMinWidth = 0;
2731
2732 mHasVisibleChildren = hasFilteredDescendants(filter_generation);
2733 // arrange always finishes, so optimistically set the arrange generation to the most current
2734 mLastArrangeGeneration = mRoot->getArrangeGeneration();
2735
2736 LLInventoryFilter::EFolderShow show_folder_state = getRoot()->getShowFolderState();
2737
2738 S32 total_width = LEFT_PAD;
2739 S32 running_height = mDebugFilters ? llceil(sSmallFont->getLineHeight()) : 0;
2740 S32 target_height = running_height;
2741 S32 parent_item_height = mRect.getHeight();
2742
2743 for (folders_t::iterator iter = mFolders.begin();
2744 iter != mFolders.end();)
2745 {
2746 folders_t::iterator fit = iter++;
2747 LLFolderViewFolder* folderp = (*fit);
2748 if (getDebugFilters())
2749 {
2750 folderp->setVisible(TRUE);
2751 }
2752 else
2753 {
2754 folderp->setVisible(show_folder_state == LLInventoryFilter::SHOW_ALL_FOLDERS || // always show folders?
2755 (folderp->getFiltered(filter_generation) || folderp->hasFilteredDescendants(filter_generation))); // passed filter or has descendants that passed filter
2756 }
2757 if (folderp->getVisible())
2758 {
2759 S32 child_height = 0;
2760 S32 child_width = 0;
2761 S32 child_top = parent_item_height - running_height;
2762
2763 target_height += folderp->arrange( &child_width, &child_height, filter_generation );
2764
2765 mMinWidth = llmax(mMinWidth, child_width);
2766 total_width = llmax( total_width, child_width );
2767 running_height += child_height;
2768 folderp->setOrigin( ICON_PAD, child_top - (*fit)->getRect().getHeight() );
2769 }
2770 }
2771
2772 for (items_t::iterator iter = mItems.begin();
2773 iter != mItems.end();)
2774 {
2775 items_t::iterator iit = iter++;
2776 LLFolderViewItem* itemp = (*iit);
2777 itemp->setVisible(itemp->getFiltered(filter_generation));
2778
2779 if (itemp->getVisible())
2780 {
2781 S32 child_width = 0;
2782 S32 child_height = 0;
2783 S32 child_top = parent_item_height - running_height;
2784
2785 target_height += itemp->arrange( &child_width, &child_height, filter_generation );
2786 itemp->reshape(itemp->getRect().getWidth(), child_height);
2787
2788 mMinWidth = llmax(mMinWidth, child_width);
2789 total_width = llmax( total_width, child_width );
2790 running_height += child_height;
2791 itemp->setOrigin( ICON_PAD, child_top - itemp->getRect().getHeight() );
2792 }
2793 }
2794
2795 S32 dummy_s32;
2796 BOOL dummy_bool;
2797 S32 min_width;
2798 mScrollContainer->calcVisibleSize( &min_width, &dummy_s32, &dummy_bool, &dummy_bool);
2799 reshape( llmax(min_width, total_width), running_height );
2800
2801 S32 new_min_width;
2802 mScrollContainer->calcVisibleSize( &new_min_width, &dummy_s32, &dummy_bool, &dummy_bool);
2803 if (new_min_width != min_width)
2804 {
2805 reshape( llmax(min_width, total_width), running_height );
2806 }
2807
2808 mTargetHeight = (F32)target_height;
2809 return llround(mTargetHeight);
2810}
2811
2812const LLString LLFolderView::getFilterSubString(BOOL trim)
2813{
2814 return mFilter.getFilterSubString(trim);
2815}
2816
2817void LLFolderView::filter( LLInventoryFilter& filter )
2818{
2819 LLFastTimer t2(LLFastTimer::FTM_FILTER);
2820 filter.setFilterCount(llclamp(gSavedSettings.getS32("FilterItemsPerFrame"), 1, 5000));
2821
2822 if (getCompletedFilterGeneration() < filter.getCurrentGeneration())
2823 {
2824 mFiltered = FALSE;
2825 mMinWidth = 0;
2826 LLFolderViewFolder::filter(filter);
2827 }
2828}
2829
2830void LLFolderView::reshape(S32 width, S32 height, BOOL called_from_parent)
2831{
2832 S32 min_width = 0;
2833 S32 dummy_height;
2834 BOOL dummy_bool;
2835 if (mScrollContainer)
2836 {
2837 mScrollContainer->calcVisibleSize( &min_width, &dummy_height, &dummy_bool, &dummy_bool);
2838 }
2839 width = llmax(mMinWidth, min_width);
2840 LLView::reshape(width, height, called_from_parent);
2841}
2842
2843void LLFolderView::addToSelectionList(LLFolderViewItem* item)
2844{
2845 if (item->isSelected())
2846 {
2847 removeFromSelectionList(item);
2848 }
2849 if (mSelectedItems.size())
2850 {
2851 mSelectedItems.back()->setIsCurSelection(FALSE);
2852 }
2853 item->setIsCurSelection(TRUE);
2854 mSelectedItems.push_back(item);
2855}
2856
2857void LLFolderView::removeFromSelectionList(LLFolderViewItem* item)
2858{
2859 if (mSelectedItems.size())
2860 {
2861 mSelectedItems.back()->setIsCurSelection(FALSE);
2862 }
2863
2864 selected_items_t::iterator item_iter;
2865 for (item_iter = mSelectedItems.begin(); item_iter != mSelectedItems.end();)
2866 {
2867 if (*item_iter == item)
2868 {
2869 item_iter = mSelectedItems.erase(item_iter);
2870 }
2871 else
2872 {
2873 ++item_iter;
2874 }
2875 }
2876 if (mSelectedItems.size())
2877 {
2878 mSelectedItems.back()->setIsCurSelection(TRUE);
2879 }
2880}
2881
2882LLFolderViewItem* LLFolderView::getCurSelectedItem( void )
2883{
2884 if(mSelectedItems.size())
2885 {
2886 LLFolderViewItem* itemp = mSelectedItems.back();
2887 llassert(itemp->getIsCurSelection());
2888 return itemp;
2889 }
2890 return NULL;
2891}
2892
2893
2894// Record the selected item and pass it down the hierachy.
2895BOOL LLFolderView::setSelection(LLFolderViewItem* selection, BOOL open,
2896 BOOL take_keyboard_focus)
2897{
2898 if( selection == this )
2899 {
2900 return FALSE;
2901 }
2902
2903 if( selection && take_keyboard_focus)
2904 {
2905 setFocus(TRUE);
2906 }
2907
2908 // clear selection down here because change of keyboard focus can potentially
2909 // affect selection
2910 clearSelection();
2911
2912 if(selection)
2913 {
2914 addToSelectionList(selection);
2915 }
2916
2917 BOOL rv = LLFolderViewFolder::setSelection(selection, open, take_keyboard_focus);
2918 if(open)
2919 {
2920 selection->getParentFolder()->requestArrange();
2921 }
2922
2923 llassert(mSelectedItems.size() <= 1);
2924
2925 mSelectionChanged = TRUE;
2926
2927 return rv;
2928}
2929
2930BOOL LLFolderView::changeSelection(LLFolderViewItem* selection, BOOL selected)
2931{
2932 BOOL rv = FALSE;
2933
2934 // can't select root folder
2935 if(!selection || selection == this)
2936 {
2937 return FALSE;
2938 }
2939
2940 if (!mAllowMultiSelect)
2941 {
2942 clearSelection();
2943 }
2944
2945 selected_items_t::iterator item_iter;
2946 for (item_iter = mSelectedItems.begin(); item_iter != mSelectedItems.end(); ++item_iter)
2947 {
2948 if (*item_iter == selection)
2949 {
2950 break;
2951 }
2952 }
2953
2954 BOOL on_list = (item_iter != mSelectedItems.end());
2955 if (on_list && mSelectedItems.size() == 1)
2956 {
2957 // we are trying to select/deselect the only selected item
2958 return FALSE;
2959 }
2960
2961 if(selected && !on_list)
2962 {
2963 mNumDescendantsSelected++;
2964 addToSelectionList(selection);
2965 }
2966 if(!selected && on_list)
2967 {
2968 mNumDescendantsSelected--;
2969 removeFromSelectionList(selection);
2970 }
2971
2972 rv = LLFolderViewFolder::changeSelection(selection, selected);
2973
2974 mSelectionChanged = TRUE;
2975
2976 return rv;
2977}
2978
2979S32 LLFolderView::extendSelection(LLFolderViewItem* selection, LLFolderViewItem* last_selected, LLDynamicArray<LLFolderViewItem*>& items)
2980{
2981 S32 rv = 0;
2982
2983 // now store resulting selection
2984 if (mAllowMultiSelect)
2985 {
2986 LLFolderViewItem *cur_selection = getCurSelectedItem();
2987 rv = LLFolderViewFolder::extendSelection(selection, cur_selection, items);
2988 for (S32 i = 0; i < items.count(); i++)
2989 {
2990 addToSelectionList(items[i]);
2991 rv++;
2992 }
2993 }
2994 else
2995 {
2996 setSelection(selection, FALSE, FALSE);
2997 rv++;
2998 }
2999
3000 mSelectionChanged = TRUE;
3001 return rv;
3002}
3003
3004void LLFolderView::sanitizeSelection()
3005{
3006 std::vector<LLFolderViewItem*> items_to_remove;
3007 selected_items_t::iterator item_iter;
3008 for (item_iter = mSelectedItems.begin(); item_iter != mSelectedItems.end(); ++item_iter)
3009 {
3010 LLFolderViewItem* item = *item_iter;
3011
3012 BOOL visible = item->getVisible();
3013 LLFolderViewFolder* parent_folder = item->getParentFolder();
3014 while(visible && parent_folder)
3015 {
3016 visible = visible && parent_folder->isOpen() && parent_folder->getVisible();
3017 parent_folder = parent_folder->getParentFolder();
3018 }
3019 if (!visible || item->getNumSelectedDescendants() > 0)
3020 {
3021 // only deselect self if not visible
3022 // check to see if item failed the filter but was checked against most recent generation
3023 if ((!item->getFiltered() && item->getLastFilterGeneration() >= getFilter()->getMinRequiredGeneration())
3024 || (item->getParentFolder() && !item->getParentFolder()->isOpen()))
3025 {
3026 item->recursiveDeselect(TRUE);
3027 items_to_remove.push_back(item);
3028 }
3029 else
3030 {
3031 item->recursiveDeselect(FALSE);
3032 }
3033
3034 selected_items_t::iterator other_item_iter;
3035 for (other_item_iter = mSelectedItems.begin(); other_item_iter != mSelectedItems.end(); ++other_item_iter)
3036 {
3037 LLFolderViewItem* other_item = *other_item_iter;
3038 LLFolderViewFolder* parent_folder = other_item->getParentFolder();
3039 while (parent_folder)
3040 {
3041 if (parent_folder == item)
3042 {
3043 // this is a descendent of the current folder, remove from list
3044 items_to_remove.push_back(other_item);
3045 break;
3046 }
3047 parent_folder = parent_folder->getParentFolder();
3048 }
3049 }
3050 }
3051 }
3052
3053 std::vector<LLFolderViewItem*>::iterator item_it;
3054 for (item_it = items_to_remove.begin(); item_it != items_to_remove.end(); ++item_it )
3055 {
3056 removeFromSelectionList(*item_it);
3057 }
3058}
3059
3060void LLFolderView::clearSelection()
3061{
3062 if (mSelectedItems.size() > 0)
3063 {
3064 recursiveDeselect(FALSE);
3065 mSelectedItems.clear();
3066 }
3067}
3068
3069BOOL LLFolderView::getSelectionList(std::set<LLUUID> &selection)
3070{
3071 selected_items_t::iterator item_it;
3072 for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it)
3073 {
3074 selection.insert((*item_it)->getListener()->getUUID());
3075 }
3076
3077 return (selection.size() != 0);
3078}
3079
3080BOOL LLFolderView::startDrag(LLToolDragAndDrop::ESource source)
3081{
3082 std::vector<EDragAndDropType> types;
3083 std::vector<LLUUID> cargo_ids;
3084 selected_items_t::iterator item_it;
3085 BOOL can_drag = TRUE;
3086 if (!mSelectedItems.empty())
3087 {
3088 for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it)
3089 {
3090 EDragAndDropType type = DAD_NONE;
3091 LLUUID id = LLUUID::null;
3092 can_drag = can_drag && (*item_it)->getListener()->startDrag(&type, &id);
3093
3094 types.push_back(type);
3095 cargo_ids.push_back(id);
3096 }
3097
3098 gToolDragAndDrop->beginMultiDrag(types, cargo_ids, source, mSourceID);
3099 }
3100 return can_drag;
3101}
3102
3103void LLFolderView::commitRename( LLUICtrl* renamer, void* user_data )
3104{
3105 LLFolderView* root = reinterpret_cast<LLFolderView*>(user_data);
3106 if( root )
3107 {
3108 root->finishRenamingItem();
3109 }
3110}
3111
3112void LLFolderView::draw()
3113{
3114 if (mDebugFilters)
3115 {
3116 LLString current_filter_string = llformat("Current Filter: %d, Least Filter: %d, Auto-accept Filter: %d",
3117 mFilter.getCurrentGeneration(), mFilter.getMinRequiredGeneration(), mFilter.getMustPassGeneration());
3118 sSmallFont->renderUTF8(current_filter_string, 0, 2,
3119 mRect.getHeight() - sSmallFont->getLineHeight(), LLColor4(0.5f, 0.5f, 0.8f, 1.f),
3120 LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, S32_MAX, S32_MAX, NULL, FALSE );
3121 }
3122
3123 // if cursor has moved off of me during drag and drop
3124 // close all auto opened folders
3125 if (!mDragAndDropThisFrame)
3126 {
3127 closeAutoOpenedFolders();
3128 }
3129 if(gViewerWindow->hasKeyboardFocus(this) && !getVisible())
3130 {
3131 gViewerWindow->setKeyboardFocus( NULL, NULL );
3132 }
3133
3134 // while dragging, update selection rendering to reflect single/multi drag status
3135 if (gToolDragAndDrop->hasMouseCapture())
3136 {
3137 EAcceptance last_accept = gToolDragAndDrop->getLastAccept();
3138 if (last_accept == ACCEPT_YES_SINGLE || last_accept == ACCEPT_YES_COPY_SINGLE)
3139 {
3140 setShowSingleSelection(TRUE);
3141 }
3142 else
3143 {
3144 setShowSingleSelection(FALSE);
3145 }
3146 }
3147 else
3148 {
3149 setShowSingleSelection(FALSE);
3150 }
3151
3152
3153 if (mSearchTimer.getElapsedTimeF32() > gSavedSettings.getF32("TypeAheadTimeout") || !mSearchString.size())
3154 {
3155 mSearchString.clear();
3156 }
3157
3158 if (hasVisibleChildren() || getShowFolderState() == LLInventoryFilter::SHOW_ALL_FOLDERS)
3159 {
3160 setStatusText("");
3161 }
3162 else
3163 {
3164 if (gInventory.backgroundFetchActive() || mCompletedFilterGeneration < mFilter.getMinRequiredGeneration())
3165 {
3166 setStatusText("Searching...");
3167 sFont->renderUTF8(mStatusText, 0, 2, 1, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP, LLFontGL::NORMAL, S32_MAX, S32_MAX, NULL, FALSE );
3168 }
3169 else
3170 {
3171 setStatusText("No matching items found in inventory.");
3172 sFont->renderUTF8(mStatusText, 0, 2, 1, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP, LLFontGL::NORMAL, S32_MAX, S32_MAX, NULL, FALSE );
3173 }
3174 }
3175
3176 LLFolderViewFolder::draw();
3177
3178 mDragAndDropThisFrame = FALSE;
3179}
3180
3181void LLFolderView::finishRenamingItem( void )
3182{
3183 if(!mRenamer)
3184 {
3185 return;
3186 }
3187 if( mRenameItem )
3188 {
3189 mRenameItem->rename( mRenamer->getText().c_str() );
3190 }
3191
3192 mRenamer->setCommitOnFocusLost( FALSE );
3193 mRenamer->setFocus( FALSE );
3194 mRenamer->setVisible( FALSE );
3195 mRenamer->setCommitOnFocusLost( TRUE );
3196 gViewerWindow->setTopView( NULL, NULL );
3197
3198 if( mRenameItem )
3199 {
3200 setSelectionFromRoot( mRenameItem, TRUE );
3201 mRenameItem = NULL;
3202 }
3203
3204 // List is re-sorted alphabeticly, so scroll to make sure the selected item is visible.
3205 scrollToShowSelection();
3206}
3207
3208void LLFolderView::revertRenamingItem( void )
3209{
3210 mRenamer->setCommitOnFocusLost( FALSE );
3211 mRenamer->setFocus( FALSE );
3212 mRenamer->setVisible( FALSE );
3213 mRenamer->setCommitOnFocusLost( TRUE );
3214 gViewerWindow->setTopView( NULL, NULL );
3215
3216 if( mRenameItem )
3217 {
3218 setSelectionFromRoot( mRenameItem, TRUE );
3219 mRenameItem = NULL;
3220 }
3221}
3222
3223void LLFolderView::removeSelectedItems( void )
3224{
3225 if(getVisible() && mEnabled)
3226 {
3227 // just in case we're removing the renaming item.
3228 mRenameItem = NULL;
3229
3230 // create a temporary structure which we will use to remove
3231 // items, since the removal will futz with internal data
3232 // structures.
3233 LLDynamicArray<LLFolderViewItem*> items;
3234 S32 count = mSelectedItems.size();
3235 if(count == 0) return;
3236 LLFolderViewItem* item = NULL;
3237 selected_items_t::iterator item_it;
3238 for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it)
3239 {
3240 item = *item_it;
3241 if(item->isRemovable())
3242 {
3243 items.put(item);
3244 }
3245 else
3246 {
3247 llinfos << "Cannot delete " << item->getName() << llendl;
3248 return;
3249 }
3250 }
3251
3252 // iterate through the new container.
3253 count = items.count();
3254 LLUUID new_selection_id;
3255 if(count == 1)
3256 {
3257 LLFolderViewItem* item_to_delete = items.get(0);
3258 LLFolderViewFolder* parent = item_to_delete->getParentFolder();
3259 LLFolderViewItem* new_selection = item_to_delete->getNextOpenNode(FALSE);
3260 if (!new_selection)
3261 {
3262 new_selection = item_to_delete->getPreviousOpenNode(FALSE);
3263 }
3264 if(parent)
3265 {
3266 if (parent->removeItem(item_to_delete))
3267 {
3268 // change selection on successful delete
3269 if (new_selection)
3270 {
3271 setSelectionFromRoot(new_selection, new_selection->isOpen(), gViewerWindow->childHasKeyboardFocus(this));
3272 }
3273 else
3274 {
3275 setSelectionFromRoot(NULL, gViewerWindow->childHasKeyboardFocus(this));
3276 }
3277 }
3278 }
3279 arrangeAll();
3280 }
3281 else if (count > 1)
3282 {
3283 LLDynamicArray<LLFolderViewEventListener*> listeners;
3284 LLFolderViewEventListener* listener;
3285 LLFolderViewItem* last_item = items.get(count - 1);
3286 LLFolderViewItem* new_selection = last_item->getNextOpenNode(FALSE);
3287 while(new_selection && new_selection->isSelected())
3288 {
3289 new_selection = new_selection->getNextOpenNode(FALSE);
3290 }
3291 if (!new_selection)
3292 {
3293 new_selection = last_item->getPreviousOpenNode(FALSE);
3294 while (new_selection && new_selection->isSelected())
3295 {
3296 new_selection = new_selection->getPreviousOpenNode(FALSE);
3297 }
3298 }
3299 if (new_selection)
3300 {
3301 setSelectionFromRoot(new_selection, new_selection->isOpen(), gViewerWindow->childHasKeyboardFocus(this));
3302 }
3303 else
3304 {
3305 setSelectionFromRoot(NULL, gViewerWindow->childHasKeyboardFocus(this));
3306 }
3307
3308 for(S32 i = 0; i < count; ++i)
3309 {
3310 listener = items.get(i)->getListener();
3311 if(listener && (listeners.find(listener) == LLDynamicArray<LLFolderViewEventListener*>::FAIL))
3312 {
3313 listeners.put(listener);
3314 }
3315 }
3316 listener = listeners.get(0);
3317 if(listener)
3318 {
3319 listener->removeBatch(listeners);
3320 }
3321 }
3322 arrangeAll();
3323 scrollToShowSelection();
3324 }
3325}
3326
3327// open the selected item.
3328void LLFolderView::openSelectedItems( void )
3329{
3330 if(getVisible() && mEnabled)
3331 {
3332 if (mSelectedItems.size() == 1)
3333 {
3334 mSelectedItems.front()->open();
3335 }
3336 else
3337 {
3338 S32 left, top;
3339 gFloaterView->getNewFloaterPosition(&left, &top);
3340
3341 LLMultiPreview* multi_previewp = new LLMultiPreview(LLRect(left, top, left + 300, top - 100));
3342
3343 LLFloater::setFloaterHost(multi_previewp);
3344
3345 selected_items_t::iterator item_it;
3346 for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it)
3347 {
3348 (*item_it)->open();
3349 }
3350
3351 LLFloater::setFloaterHost(NULL);
3352 multi_previewp->open();
3353 }
3354 }
3355}
3356
3357void LLFolderView::propertiesSelectedItems( void )
3358{
3359 if(getVisible() && mEnabled)
3360 {
3361 if (mSelectedItems.size() == 1)
3362 {
3363 LLFolderViewItem* folder_item = mSelectedItems.front();
3364 if(!folder_item) return;
3365 folder_item->getListener()->showProperties();
3366 }
3367 else
3368 {
3369 S32 left, top;
3370 gFloaterView->getNewFloaterPosition(&left, &top);
3371
3372 LLMultiProperties* multi_propertiesp = new LLMultiProperties(LLRect(left, top, left + 100, top - 100));
3373
3374 LLFloater::setFloaterHost(multi_propertiesp);
3375
3376 selected_items_t::iterator item_it;
3377 for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it)
3378 {
3379 (*item_it)->getListener()->showProperties();
3380 }
3381
3382 LLFloater::setFloaterHost(NULL);
3383 multi_propertiesp->open();
3384 }
3385 }
3386}
3387
3388void LLFolderView::autoOpenItem( LLFolderViewFolder* item )
3389{
3390 if (mAutoOpenItems.check() == item || mAutoOpenItems.getDepth() >= (U32)AUTO_OPEN_STACK_DEPTH)
3391 {
3392 return;
3393 }
3394
3395 // close auto-opened folders
3396 LLFolderViewFolder* close_item = mAutoOpenItems.check();
3397 while (close_item && close_item != item->getParentFolder())
3398 {
3399 mAutoOpenItems.pop();
3400 close_item->setOpenArrangeRecursively(FALSE);
3401 close_item = mAutoOpenItems.check();
3402 }
3403
3404 item->requestArrange();
3405
3406 mAutoOpenItems.push(item);
3407
3408 item->setOpen(TRUE);
3409 scrollToShowItem(item);
3410}
3411
3412void LLFolderView::closeAutoOpenedFolders()
3413{
3414 while (mAutoOpenItems.check())
3415 {
3416 LLFolderViewFolder* close_item = mAutoOpenItems.pop();
3417 close_item->setOpen(FALSE);
3418 }
3419
3420 if (mAutoOpenCandidate)
3421 {
3422 mAutoOpenCandidate->setAutoOpenCountdown(0.f);
3423 }
3424 mAutoOpenCandidate = NULL;
3425 mAutoOpenTimer.stop();
3426}
3427
3428BOOL LLFolderView::autoOpenTest(LLFolderViewFolder* folder)
3429{
3430 if (folder && mAutoOpenCandidate == folder)
3431 {
3432 if (mAutoOpenTimer.getStarted())
3433 {
3434 if (!mAutoOpenCandidate->isOpen())
3435 {
3436 mAutoOpenCandidate->setAutoOpenCountdown(clamp_rescale(mAutoOpenTimer.getElapsedTimeF32(), 0.f, sAutoOpenTime, 0.f, 1.f));
3437 }
3438 if (mAutoOpenTimer.getElapsedTimeF32() > sAutoOpenTime)
3439 {
3440 autoOpenItem(folder);
3441 mAutoOpenTimer.stop();
3442 return TRUE;
3443 }
3444 }
3445 return FALSE;
3446 }
3447
3448 // otherwise new candidate, restart timer
3449 if (mAutoOpenCandidate)
3450 {
3451 mAutoOpenCandidate->setAutoOpenCountdown(0.f);
3452 }
3453 mAutoOpenCandidate = folder;
3454 mAutoOpenTimer.start();
3455 return FALSE;
3456}
3457
3458BOOL LLFolderView::canCopy()
3459{
3460 if (!(getVisible() && mEnabled && (mSelectedItems.size() > 0)))
3461 {
3462 return FALSE;
3463 }
3464
3465 selected_items_t::iterator selected_it;
3466 for (selected_it = mSelectedItems.begin(); selected_it != mSelectedItems.end(); ++selected_it)
3467 {
3468 LLFolderViewItem* item = *selected_it;
3469 if (!item->getListener()->isItemCopyable())
3470 {
3471 return FALSE;
3472 }
3473 }
3474 return TRUE;
3475}
3476
3477// copy selected item
3478void LLFolderView::copy()
3479{
3480 // *NOTE: total hack to clear the inventory clipboard
3481 LLInventoryClipboard::instance().reset();
3482 S32 count = mSelectedItems.size();
3483 if(getVisible() && mEnabled && (count > 0))
3484 {
3485 LLFolderViewEventListener* listener = NULL;
3486 selected_items_t::iterator item_it;
3487 for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it)
3488 {
3489 listener = (*item_it)->getListener();
3490 if(listener)
3491 {
3492 listener->copyToClipboard();
3493 }
3494 }
3495 }
3496 mSearchString.clear();
3497}
3498
3499BOOL LLFolderView::canCut()
3500{
3501 return FALSE;
3502}
3503
3504void LLFolderView::cut()
3505{
3506 // implement Windows-style cut-and-leave
3507}
3508
3509BOOL LLFolderView::canPaste()
3510{
3511 if (mSelectedItems.empty())
3512 {
3513 return FALSE;
3514 }
3515
3516 if(getVisible() && mEnabled)
3517 {
3518 selected_items_t::iterator item_it;
3519 for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it)
3520 {
3521 // *TODO: only check folders and parent folders of items
3522 LLFolderViewItem* item = (*item_it);
3523 LLFolderViewEventListener* listener = item->getListener();
3524 if(!listener || !listener->isClipboardPasteable())
3525 {
3526 LLFolderViewFolder* folderp = item->getParentFolder();
3527 listener = folderp->getListener();
3528 if (!listener || !listener->isClipboardPasteable())
3529 {
3530 return FALSE;
3531 }
3532 }
3533 }
3534 return TRUE;
3535 }
3536 return FALSE;
3537}
3538
3539// paste selected item
3540void LLFolderView::paste()
3541{
3542 if(getVisible() && mEnabled)
3543 {
3544 // find set of unique folders to paste into
3545 std::set<LLFolderViewItem*> folder_set;
3546
3547 selected_items_t::iterator selected_it;
3548 for (selected_it = mSelectedItems.begin(); selected_it != mSelectedItems.end(); ++selected_it)
3549 {
3550 LLFolderViewItem* item = *selected_it;
3551 LLFolderViewEventListener* listener = item->getListener();
3552 if (listener->getInventoryType() != LLInventoryType::IT_CATEGORY)
3553 {
3554 item = item->getParentFolder();
3555 }
3556 folder_set.insert(item);
3557 }
3558
3559 std::set<LLFolderViewItem*>::iterator set_iter;
3560 for(set_iter = folder_set.begin(); set_iter != folder_set.end(); ++set_iter)
3561 {
3562 LLFolderViewEventListener* listener = (*set_iter)->getListener();
3563 if(listener && listener->isClipboardPasteable())
3564 {
3565 listener->pasteFromClipboard();
3566 }
3567 }
3568 }
3569 mSearchString.clear();
3570}
3571
3572// public rename functionality - can only start the process
3573void LLFolderView::startRenamingSelectedItem( void )
3574{
3575 // make sure selection is visible
3576 scrollToShowSelection();
3577
3578 S32 count = mSelectedItems.size();
3579 LLFolderViewItem* item = NULL;
3580 if(count > 0)
3581 {
3582 item = mSelectedItems.front();
3583 }
3584 if(getVisible() && mEnabled && (count == 1) && item && item->getListener() &&
3585 item->getListener()->isItemRenameable())
3586 {
3587 mRenameItem = item;
3588
3589 S32 x = ARROW_SIZE + TEXT_PAD + ICON_WIDTH + ICON_PAD - 1 + item->getIndentation();
3590 S32 y = llfloor(item->getRect().getHeight()-sFont->getLineHeight()-2);
3591 item->localPointToScreen( x, y, &x, &y );
3592 screenPointToLocal( x, y, &x, &y );
3593 mRenamer->setOrigin( x, y );
3594
3595 S32 scroller_height = 0;
3596 S32 scroller_width = gViewerWindow->getWindowWidth();
3597 BOOL dummy_bool;
3598 if (mScrollContainer)
3599 {
3600 mScrollContainer->calcVisibleSize( &scroller_width, &scroller_height, &dummy_bool, &dummy_bool);
3601 }
3602
3603 S32 width = llmax(llmin(item->getRect().getWidth() - x, scroller_width - x - mRect.mLeft), MINIMUM_RENAMER_WIDTH);
3604 S32 height = llfloor(sFont->getLineHeight() + RENAME_HEIGHT_PAD);
3605 mRenamer->reshape( width, height, TRUE );
3606
3607 mRenamer->setText(item->getName());
3608 mRenamer->selectAll();
3609 mRenamer->setVisible( TRUE );
3610 // set focus will fail unless item is visible
3611 mRenamer->setFocus( TRUE );
3612 gViewerWindow->setTopView( mRenamer, top_view_lost );
3613 }
3614}
3615
3616void LLFolderView::setFocus(BOOL focus)
3617{
3618 if (focus)
3619 {
3620 // select "My Inventory" if nothing selected
3621 if (!getCurSelectedItem())
3622 {
3623 LLFolderViewItem* itemp = getItemByID(gAgent.getInventoryRootID());
3624 if (itemp)
3625 {
3626 setSelection(itemp, FALSE, FALSE);
3627 }
3628 }
3629
3630 if (mRenamer->getVisible())
3631 {
3632 //RN: commit rename changes when focus is moved, only revert on ESC
3633 finishRenamingItem();
3634 }
3635 if(!hasFocus())
3636 {
3637 gEditMenuHandler = this;
3638 }
3639 }
3640
3641 LLFolderViewFolder::setFocus(focus);
3642}
3643
3644BOOL LLFolderView::handleKeyHere( KEY key, MASK mask, BOOL called_from_parent )
3645{
3646 BOOL handled = FALSE;
3647
3648 LLView *item = NULL;
3649 if (getChildCount() > 0)
3650 {
3651 item = *(getChildList()->begin());
3652 }
3653
3654 if( getVisible() && mEnabled && !called_from_parent )
3655 {
3656 switch( key )
3657 {
3658 case KEY_F2:
3659 mSearchString.clear();
3660 startRenamingSelectedItem();
3661 handled = TRUE;
3662 break;
3663
3664 case KEY_RETURN:
3665 if (mask == MASK_NONE)
3666 {
3667 if( mRenameItem && mRenamer->getVisible() )
3668 {
3669 finishRenamingItem();
3670 mSearchString.clear();
3671 handled = TRUE;
3672 }
3673 else
3674 {
3675 LLFolderView::openSelectedItems();
3676 handled = TRUE;
3677 }
3678 }
3679 break;
3680
3681 case KEY_ESCAPE:
3682 // mark flag don't commit
3683 if( mRenameItem && mRenamer->getVisible() )
3684 {
3685 revertRenamingItem();
3686 handled = TRUE;
3687 }
3688 else
3689 {
3690 if( gViewerWindow->childHasKeyboardFocus( this ) )
3691 {
3692 gViewerWindow->setKeyboardFocus( NULL, NULL );
3693 }
3694 }
3695 mSearchString.clear();
3696 break;
3697
3698 case KEY_PAGE_UP:
3699 mSearchString.clear();
3700 mScrollContainer->pageUp(30);
3701 handled = TRUE;
3702 break;
3703
3704 case KEY_PAGE_DOWN:
3705 mSearchString.clear();
3706 mScrollContainer->pageDown(30);
3707 handled = TRUE;
3708 break;
3709
3710 case KEY_HOME:
3711 mSearchString.clear();
3712 mScrollContainer->goToTop();
3713 handled = TRUE;
3714 break;
3715
3716 case KEY_END:
3717 mSearchString.clear();
3718 mScrollContainer->goToBottom();
3719 break;
3720
3721 case KEY_DOWN:
3722 if((mSelectedItems.size() > 0) && mScrollContainer)
3723 {
3724 LLFolderViewItem* last_selected = getCurSelectedItem();
3725
3726 if (!mKeyboardSelection)
3727 {
3728 setSelection(last_selected, FALSE, TRUE);
3729 mKeyboardSelection = TRUE;
3730 }
3731
3732 LLFolderViewItem* next = NULL;
3733 if (mask & MASK_SHIFT)
3734 {
3735 // don't shift select down to children of folders (they are implicitly selected through parent)
3736 next = last_selected->getNextOpenNode(FALSE);
3737 if (next)
3738 {
3739 if (next->isSelected())
3740 {
3741 // shrink selection
3742 changeSelectionFromRoot(last_selected, FALSE);
3743 }
3744 else if (last_selected->getParentFolder() == next->getParentFolder())
3745 {
3746 // grow selection
3747 changeSelectionFromRoot(next, TRUE);
3748 }
3749 }
3750 }
3751 else
3752 {
3753 next = last_selected->getNextOpenNode();
3754 if( next )
3755 {
3756 if (next == last_selected)
3757 {
3758 return FALSE;
3759 }
3760 setSelection( next, FALSE, TRUE );
3761 }
3762 }
3763 scrollToShowSelection();
3764 mSearchString.clear();
3765 handled = TRUE;
3766 }
3767 break;
3768
3769 case KEY_UP:
3770 if((mSelectedItems.size() > 0) && mScrollContainer)
3771 {
3772 LLFolderViewItem* last_selected = mSelectedItems.back();
3773
3774 if (!mKeyboardSelection)
3775 {
3776 setSelection(last_selected, FALSE, TRUE);
3777 mKeyboardSelection = TRUE;
3778 }
3779
3780 LLFolderViewItem* prev = NULL;
3781 if (mask & MASK_SHIFT)
3782 {
3783 // don't shift select down to children of folders (they are implicitly selected through parent)
3784 prev = last_selected->getPreviousOpenNode(FALSE);
3785 if (prev)
3786 {
3787 if (prev->isSelected())
3788 {
3789 // shrink selection
3790 changeSelectionFromRoot(last_selected, FALSE);
3791 }
3792 else if (last_selected->getParentFolder() == prev->getParentFolder())
3793 {
3794 // grow selection
3795 changeSelectionFromRoot(prev, TRUE);
3796 }
3797 }
3798 }
3799 else
3800 {
3801 prev = last_selected->getPreviousOpenNode();
3802 if( prev )
3803 {
3804 if (prev == this)
3805 {
3806 return FALSE;
3807 }
3808 setSelection( prev, FALSE, TRUE );
3809 }
3810 }
3811 scrollToShowSelection();
3812 mSearchString.clear();
3813
3814 handled = TRUE;
3815 }
3816 break;
3817
3818 case KEY_RIGHT:
3819 if(mSelectedItems.size())
3820 {
3821 LLFolderViewItem* last_selected = getCurSelectedItem();
3822 last_selected->setOpen( TRUE );
3823 mSearchString.clear();
3824 handled = TRUE;
3825 }
3826 break;
3827
3828 case KEY_LEFT:
3829 if(mSelectedItems.size())
3830 {
3831 LLFolderViewItem* last_selected = getCurSelectedItem();
3832 LLFolderViewItem* parent_folder = last_selected->getParentFolder();
3833 if (!last_selected->isOpen() && parent_folder && parent_folder->getParentFolder())
3834 {
3835 setSelection(parent_folder, FALSE, TRUE);
3836 }
3837 else
3838 {
3839 last_selected->setOpen( FALSE );
3840 }
3841 mSearchString.clear();
3842 scrollToShowSelection();
3843 handled = TRUE;
3844 }
3845 break;
3846 }
3847 }
3848
3849 if (!handled && gFocusMgr.childHasKeyboardFocus(getRoot()))
3850 {
3851 if (key == KEY_BACKSPACE)
3852 {
3853 mSearchTimer.reset();
3854 if (mSearchString.size())
3855 {
3856 mSearchString.erase(mSearchString.size() - 1, 1);
3857 }
3858 search(getCurSelectedItem(), mSearchString.c_str(), FALSE);
3859 handled = TRUE;
3860 }
3861 else if (mask & MASK_CONTROL && key == 'N')
3862 {
3863 LLFolderViewItem* selection = getCurSelectedItem();
3864 if (selection)
3865 {
3866 selection = selection->getNextOpenNode();
3867 }
3868 search(selection, mSearchString.c_str(), FALSE);
3869 mSearchTimer.reset();
3870 handled = TRUE;
3871 }
3872 else if (mask & MASK_CONTROL && key == 'P')
3873 {
3874 LLFolderViewItem* selection = getCurSelectedItem();
3875 if (selection)
3876 {
3877 selection = selection->getPreviousOpenNode();
3878 }
3879 search(selection, mSearchString.c_str(), TRUE);
3880 mSearchTimer.reset();
3881 handled = TRUE;
3882 }
3883 }
3884
3885 if (handled)
3886 {
3887 gViewerWindow->requestFastFrame(this);
3888 }
3889 return handled;
3890}
3891
3892
3893BOOL LLFolderView::handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent)
3894{
3895 if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL
3896 {
3897 return FALSE;
3898 }
3899
3900 if (uni_char > 0x7f)
3901 {
3902 llwarns << "LLFolderView::handleUnicodeCharHere - Don't handle non-ascii yet, aborting" << llendl;
3903 return FALSE;
3904 }
3905
3906 BOOL handled = FALSE;
3907 if (gFocusMgr.childHasKeyboardFocus(getRoot()))
3908 {
3909 //do text search
3910 if (mSearchTimer.getElapsedTimeF32() > gSavedSettings.getF32("TypeAheadTimeout"))
3911 {
3912 mSearchString.clear();
3913 }
3914 mSearchTimer.reset();
3915 if (mSearchString.size() < 128)
3916 {
3917 mSearchString += uni_char;
3918 }
3919 search(getCurSelectedItem(), mSearchString.c_str(), FALSE);
3920
3921 handled = TRUE;
3922 }
3923
3924 if (handled)
3925 {
3926 gViewerWindow->requestFastFrame(this);
3927 }
3928
3929 return handled;
3930}
3931
3932
3933BOOL LLFolderView::canDoDelete()
3934{
3935 if (mSelectedItems.size() == 0) return FALSE;
3936 selected_items_t::iterator item_it;
3937 for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it)
3938 {
3939 if (!(*item_it)->getListener()->isItemRemovable())
3940 {
3941 return FALSE;
3942 }
3943 }
3944 return TRUE;
3945}
3946
3947void LLFolderView::doDelete()
3948{
3949 if(mSelectedItems.size() > 0)
3950 {
3951 removeSelectedItems();
3952 }
3953}
3954
3955
3956BOOL LLFolderView::handleMouseDown( S32 x, S32 y, MASK mask )
3957{
3958 mKeyboardSelection = FALSE;
3959 mSearchString.clear();
3960
3961 setFocus(TRUE);
3962
3963 return LLView::handleMouseDown( x, y, mask );
3964}
3965
3966void LLFolderView::onFocusLost( )
3967{
3968 if( gEditMenuHandler == this )
3969 {
3970 gEditMenuHandler = NULL;
3971 }
3972}
3973
3974BOOL LLFolderView::search(LLFolderViewItem* first_item, const LLString &search_string, BOOL backward)
3975{
3976 // get first selected item
3977 LLFolderViewItem* search_item = first_item;
3978
3979 // make sure search string is upper case
3980 LLString upper_case_string = search_string;
3981 LLString::toUpper(upper_case_string);
3982
3983 // if nothing selected, select first item in folder
3984 if (!first_item)
3985 {
3986 // start from first item
3987 first_item = getNextFromChild(NULL);
3988 }
3989
3990 // search over all open nodes for first substring match (with wrapping)
3991 BOOL found = FALSE;
3992 LLFolderViewItem* original_search_item = search_item;
3993 do
3994 {
3995 // wrap at end
3996 if (!search_item)
3997 {
3998 if (backward)
3999 {
4000 search_item = getPreviousFromChild(NULL);
4001 }
4002 else
4003 {
4004 search_item = getNextFromChild(NULL);
4005 }
4006 if (!search_item || search_item == original_search_item)
4007 {
4008 break;
4009 }
4010 }
4011
4012 const LLString current_item_label(search_item->getSearchableLabel());
4013 S32 search_string_length = llmin(upper_case_string.size(), current_item_label.size());
4014 if (!current_item_label.compare(0, search_string_length, upper_case_string))
4015 {
4016 found = TRUE;
4017 break;
4018 }
4019 if (backward)
4020 {
4021 search_item = search_item->getPreviousOpenNode();
4022 }
4023 else
4024 {
4025 search_item = search_item->getNextOpenNode();
4026 }
4027
4028 } while(search_item != original_search_item);
4029
4030
4031 if (found)
4032 {
4033 setSelection(search_item, FALSE, TRUE);
4034 scrollToShowSelection();
4035 }
4036
4037 return found;
4038}
4039
4040BOOL LLFolderView::handleDoubleClick( S32 x, S32 y, MASK mask )
4041{
4042 if (!getVisible())
4043 {
4044 return FALSE;
4045 }
4046
4047 return LLView::handleDoubleClick( x, y, mask );
4048}
4049
4050BOOL LLFolderView::handleRightMouseDown( S32 x, S32 y, MASK mask )
4051{
4052 // all user operations move keyboard focus to inventory
4053 // this way, we know when to stop auto-updating a search
4054 setFocus(TRUE);
4055
4056 BOOL handled = childrenHandleRightMouseDown(x, y, mask) != NULL;
4057 S32 count = mSelectedItems.size();
4058 LLMenuGL* menu = (LLMenuGL*)LLView::getViewByHandle(mPopupMenuHandle);
4059 if(handled && (count > 0) && menu)
4060 {
4061 //menu->empty();
4062 const LLView::child_list_t *list = menu->getChildList();
4063
4064 LLView::child_list_t::const_iterator menu_itor;
4065 for (menu_itor = list->begin(); menu_itor != list->end(); ++menu_itor)
4066 {
4067 (*menu_itor)->setVisible(TRUE);
4068 (*menu_itor)->setEnabled(TRUE);
4069 }
4070
4071 // Successively filter out invalid options
4072 selected_items_t::iterator item_itor;
4073 U32 flags = FIRST_SELECTED_ITEM;
4074 for (item_itor = mSelectedItems.begin(); item_itor != mSelectedItems.end(); ++item_itor)
4075 {
4076 (*item_itor)->buildContextMenu(*menu, flags);
4077 flags = 0x0;
4078 }
4079
4080 menu->arrange();
4081 menu->updateParent(gMenuHolder);
4082 LLMenuGL::showPopup(this, menu, x, y);
4083 }
4084 else
4085 {
4086 if(menu && menu->getVisible())
4087 {
4088 menu->setVisible(FALSE);
4089 }
4090 setSelection(NULL, FALSE, TRUE);
4091 }
4092 return handled;
4093}
4094
4095BOOL LLFolderView::handleHover( S32 x, S32 y, MASK mask )
4096{
4097 return LLView::handleHover( x, y, mask );
4098}
4099
4100BOOL LLFolderView::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop,
4101 EDragAndDropType cargo_type,
4102 void* cargo_data,
4103 EAcceptance* accept,
4104 LLString& tooltip_msg)
4105{
4106 mDragAndDropThisFrame = TRUE;
4107 BOOL handled = LLView::handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data,
4108 accept, tooltip_msg);
4109
4110 if (handled)
4111 {
4112 lldebugst(LLERR_USER_INPUT) << "dragAndDrop handled by LLFolderView" << llendl;
4113 }
4114
4115 return handled;
4116}
4117
4118BOOL LLFolderView::handleScrollWheel(S32 x, S32 y, S32 clicks)
4119{
4120 if (mScrollContainer)
4121 {
4122 return mScrollContainer->handleScrollWheel(x, y, clicks);
4123 }
4124 return FALSE;
4125}
4126
4127void LLFolderView::deleteAllChildren()
4128{
4129 if(gViewerWindow->hasTopView(mRenamer))
4130 {
4131 gViewerWindow->setTopView(NULL, NULL);
4132 }
4133 LLView::deleteViewByHandle(mPopupMenuHandle);
4134 mPopupMenuHandle = LLViewHandle::sDeadHandle;
4135 mRenamer = NULL;
4136 mRenameItem = NULL;
4137 clearSelection();
4138 LLView::deleteAllChildren();
4139}
4140
4141void LLFolderView::scrollToShowSelection()
4142{
4143 if (mSelectedItems.size())
4144 {
4145 mNeedsScroll = TRUE;
4146 }
4147}
4148
4149// If the parent is scroll containter, scroll it to make the selection
4150// is maximally visible.
4151void LLFolderView::scrollToShowItem(LLFolderViewItem* item)
4152{
4153 // don't scroll to items when mouse is being used to scroll/drag and drop
4154 if (gFocusMgr.childHasMouseCapture(mScrollContainer))
4155 {
4156 mNeedsScroll = FALSE;
4157 return;
4158 }
4159 if(item && mScrollContainer)
4160 {
4161 LLRect local_rect = item->getRect();
4162 LLRect item_scrolled_rect; // item position relative to display area of scroller
4163
4164 S32 icon_height = mIcon.isNull() ? 0 : mIcon->getHeight();
4165 S32 label_height = llround(sFont->getLineHeight());
4166 // when navigating with keyboard, only move top of folders on screen, otherwise show whole folder
4167 S32 max_height_to_show = gFocusMgr.childHasKeyboardFocus(this) ? (llmax( icon_height, label_height ) + ICON_PAD) : local_rect.getHeight();
4168 item->localPointToOtherView(item->getIndentation(), llmax(0, local_rect.getHeight() - max_height_to_show), &item_scrolled_rect.mLeft, &item_scrolled_rect.mBottom, mScrollContainer);
4169 item->localPointToOtherView(local_rect.getWidth(), local_rect.getHeight(), &item_scrolled_rect.mRight, &item_scrolled_rect.mTop, mScrollContainer);
4170
4171 item_scrolled_rect.mRight = llmin(item_scrolled_rect.mLeft + MIN_ITEM_WIDTH_VISIBLE, item_scrolled_rect.mRight);
4172 LLCoordGL scroll_offset(-mScrollContainer->getBorderWidth() - item_scrolled_rect.mLeft,
4173 mScrollContainer->getRect().getHeight() - item_scrolled_rect.mTop - 1);
4174
4175 S32 max_scroll_offset = getVisibleRect().getHeight() - item_scrolled_rect.getHeight();
4176 if (item != mLastScrollItem || // if we're scrolling to focus on a new item
4177 // or the item has just appeared on screen and it wasn't onscreen before
4178 (scroll_offset.mY > 0 && scroll_offset.mY < max_scroll_offset &&
4179 (mLastScrollOffset.mY < 0 || mLastScrollOffset.mY > max_scroll_offset)))
4180 {
4181 // we now have a position on screen that we want to keep stable
4182 // offset of selection relative to top of visible area
4183 mLastScrollOffset = scroll_offset;
4184 mLastScrollItem = item;
4185 }
4186
4187 mScrollContainer->scrollToShowRect( item_scrolled_rect, mLastScrollOffset );
4188
4189 // after scrolling, store new offset
4190 // in case we don't have room to maintain the original position
4191 LLCoordGL new_item_left_top;
4192 item->localPointToOtherView(item->getIndentation(), item->getRect().getHeight(), &new_item_left_top.mX, &new_item_left_top.mY, mScrollContainer);
4193 mLastScrollOffset.set(-mScrollContainer->getBorderWidth() - new_item_left_top.mX, mScrollContainer->getRect().getHeight() - new_item_left_top.mY - 1);
4194 }
4195}
4196
4197LLRect LLFolderView::getVisibleRect()
4198{
4199 S32 visible_height = mScrollContainer->getRect().getHeight();
4200 S32 visible_width = mScrollContainer->getRect().getWidth();
4201 LLRect visible_rect;
4202 visible_rect.setLeftTopAndSize(-mRect.mLeft, visible_height - mRect.mBottom, visible_width, visible_height);
4203 return visible_rect;
4204}
4205
4206BOOL LLFolderView::getShowSelectionContext()
4207{
4208 if (mShowSelectionContext)
4209 {
4210 return TRUE;
4211 }
4212 LLMenuGL* menu = (LLMenuGL*)LLView::getViewByHandle(mPopupMenuHandle);
4213 if (menu && menu->getVisible())
4214 {
4215 return TRUE;
4216 }
4217 return FALSE;
4218}
4219
4220void LLFolderView::setShowSingleSelection(BOOL show)
4221{
4222 if (show != mShowSingleSelection)
4223 {
4224 mMultiSelectionFadeTimer.reset();
4225 mShowSingleSelection = show;
4226 }
4227}
4228
4229void LLFolderView::addItemID(const LLUUID& id, LLFolderViewItem* itemp)
4230{
4231 mItemMap[id] = itemp;
4232}
4233
4234void LLFolderView::removeItemID(const LLUUID& id)
4235{
4236 mItemMap.erase(id);
4237}
4238
4239LLFolderViewItem* LLFolderView::getItemByID(const LLUUID& id)
4240{
4241 if (id.isNull())
4242 {
4243 return this;
4244 }
4245
4246 std::map<LLUUID, LLFolderViewItem*>::iterator map_it;
4247 map_it = mItemMap.find(id);
4248 if (map_it != mItemMap.end())
4249 {
4250 return map_it->second;
4251 }
4252
4253 return NULL;
4254}
4255
4256//static
4257void LLFolderView::idle(void* user_data)
4258{
4259 LLFastTimer t2(LLFastTimer::FTM_INVENTORY);
4260 LLFolderView* self = (LLFolderView*)user_data;
4261
4262 BOOL debug_filters = gSavedSettings.getBOOL("DebugInventoryFilters");
4263 if (debug_filters != self->getDebugFilters())
4264 {
4265 self->mDebugFilters = debug_filters;
4266 self->arrangeAll();
4267 }
4268
4269 self->mFilter.clearModified();
4270 BOOL filter_modified_and_active = self->mCompletedFilterGeneration < self->mFilter.getCurrentGeneration() &&
4271 self->mFilter.isActive();
4272 self->mNeedsAutoSelect = filter_modified_and_active &&
4273 !(gFocusMgr.childHasKeyboardFocus(self) || gFocusMgr.getMouseCapture());
4274
4275 // filter to determine visiblity before arranging
4276 self->filterFromRoot();
4277
4278 self->sanitizeSelection();
4279
4280 // automatically show matching items, and select first one
4281 // do this every frame until user puts keyboard focus into the inventory window
4282 // signaling the end of the automatic update
4283 // only do this when mNeedsFilter is set, meaning filtered items have
4284 // potentially changed
4285 if (self->mNeedsAutoSelect)
4286 {
4287 LLFastTimer t3(LLFastTimer::FTM_AUTO_SELECT);
4288 // select new item only if a filtered item not currently selected
4289 LLFolderViewItem* selected_itemp = self->mSelectedItems.empty() ? NULL : self->mSelectedItems.back();
4290 if ((!selected_itemp || !selected_itemp->getFiltered()) && !self->mAutoSelectOverride)
4291 {
4292 // select first filtered item
4293 LLSelectFirstFilteredItem filter;
4294 self->applyFunctorRecursively(filter);
4295 }
4296 self->scrollToShowSelection();
4297 }
4298
4299 if( self->needsArrange() && self->isInVisibleChain())
4300 {
4301 self->arrangeFromRoot();
4302 }
4303
4304 if (self->mSelectedItems.size() && self->mNeedsScroll)
4305 {
4306 self->scrollToShowItem(self->mSelectedItems.back());
4307 // continue scrolling until animated layout change is done
4308 if (!self->needsArrange() || !self->isInVisibleChain())
4309 {
4310 self->mNeedsScroll = FALSE;
4311 }
4312 }
4313
4314 if (self->mSelectionChanged && self->mSelectCallback)
4315 {
4316 //RN: we use keyboard focus as a proxy for user-explicit actions
4317 self->mSelectCallback(self->mSelectedItems, gFocusMgr.childHasKeyboardFocus(self), self->mUserData);
4318 }
4319 self->mSelectionChanged = FALSE;
4320}
4321
4322void LLFolderView::dumpSelectionInformation()
4323{
4324 llinfos << "LLFolderView::dumpSelectionInformation()" << llendl;
4325 llinfos << "****************************************" << llendl;
4326 selected_items_t::iterator item_it;
4327 for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it)
4328 {
4329 llinfos << " " << (*item_it)->getName() << llendl;
4330 }
4331 llinfos << "****************************************" << llendl;
4332}
4333
4334///----------------------------------------------------------------------------
4335/// Local function definitions
4336///----------------------------------------------------------------------------
4337
4338bool sort_item_name(LLFolderViewItem* a, LLFolderViewItem* b)
4339{
4340 S32 compare = LLString::compareDict(a->getLabel(), b->getLabel());
4341 if (0 == compare)
4342 {
4343 return (a->getCreationDate() > b->getCreationDate());
4344 }
4345 else
4346 {
4347 return (compare < 0);
4348 }
4349}
4350
4351// BUG: This is very very slow. The getCreationDate() is log n in number
4352// of inventory items.
4353bool sort_item_date(LLFolderViewItem* a, LLFolderViewItem* b)
4354{
4355 U32 first_create = a->getCreationDate();
4356 U32 second_create = b->getCreationDate();
4357 if (first_create == second_create)
4358 {
4359 return (LLString::compareDict(a->getLabel(), b->getLabel()) < 0);
4360 }
4361 else
4362 {
4363 return (first_create > second_create);
4364 }
4365}
4366
4367void top_view_lost( LLView* view )
4368{
4369 if( view ) view->setVisible( FALSE );
4370}
4371
4372void delete_selected_item(void* user_data)
4373{
4374 if(user_data)
4375 {
4376 LLFolderView* fv = reinterpret_cast<LLFolderView*>(user_data);
4377 fv->removeSelectedItems();
4378 }
4379}
4380
4381void copy_selected_item(void* user_data)
4382{
4383 if(user_data)
4384 {
4385 LLFolderView* fv = reinterpret_cast<LLFolderView*>(user_data);
4386 fv->copy();
4387 }
4388}
4389
4390void paste_items(void* user_data)
4391{
4392 if(user_data)
4393 {
4394 LLFolderView* fv = reinterpret_cast<LLFolderView*>(user_data);
4395 fv->paste();
4396 }
4397}
4398
4399void open_selected_items(void* user_data)
4400{
4401 if(user_data)
4402 {
4403 LLFolderView* fv = reinterpret_cast<LLFolderView*>(user_data);
4404 fv->openSelectedItems();
4405 }
4406}
4407
4408void properties_selected_items(void* user_data)
4409{
4410 if(user_data)
4411 {
4412 LLFolderView* fv = reinterpret_cast<LLFolderView*>(user_data);
4413 fv->propertiesSelectedItems();
4414 }
4415}
4416
4417///----------------------------------------------------------------------------
4418/// Class LLFolderViewEventListener
4419///----------------------------------------------------------------------------
4420
4421void LLFolderViewEventListener::arrangeAndSet(LLFolderViewItem* focus,
4422 BOOL set_selection,
4423 BOOL take_keyboard_focus)
4424{
4425 if(!focus) return;
4426 LLFolderView* root = focus->getRoot();
4427 focus->getParentFolder()->requestArrange();
4428 if(set_selection)
4429 {
4430 focus->setSelectionFromRoot(focus, TRUE, take_keyboard_focus);
4431 if(root)
4432 {
4433 root->scrollToShowSelection();
4434 }
4435 }
4436}
4437
4438
4439///----------------------------------------------------------------------------
4440/// Class LLInventoryFilter
4441///----------------------------------------------------------------------------
4442LLInventoryFilter::LLInventoryFilter(const LLString& name) :
4443 mName(name),
4444 mModified(FALSE),
4445 mNeedTextRebuild(TRUE)
4446{
4447 mFilterOps.mFilterTypes = 0xffffffff;
4448 mFilterOps.mMinDate = 0;
4449 mFilterOps.mMaxDate = U32_MAX;
4450 mFilterOps.mHoursAgo = 0;
4451 mFilterOps.mShowFolderState = SHOW_NON_EMPTY_FOLDERS;
4452 mFilterOps.mPermissions = PERM_NONE;
4453
4454 mOrder = SO_FOLDERS_BY_NAME; // This gets overridden by a pref immediately
4455
4456 mSubStringMatchOffset = 0;
4457 mFilterSubString = "";
4458 mFilterGeneration = 0;
4459 mMustPassGeneration = S32_MAX;
4460 mMinRequiredGeneration = 0;
4461 mNextFilterGeneration = mFilterGeneration + 1;
4462
4463 mLastLogoff = gSavedPerAccountSettings.getU32("LastLogoff");
4464}
4465
4466LLInventoryFilter::~LLInventoryFilter()
4467{
4468}
4469
4470BOOL LLInventoryFilter::check(LLFolderViewItem* item)
4471{
4472 U32 earliest;
4473
4474 earliest = time_corrected() - mFilterOps.mHoursAgo * 3600;
4475 if (mFilterOps.mMinDate && mFilterOps.mMinDate < earliest)
4476 {
4477 earliest = mFilterOps.mMinDate;
4478 }
4479 else if (!mFilterOps.mHoursAgo)
4480 {
4481 earliest = 0;
4482 }
4483 LLFolderViewEventListener* listener = item->getListener();
4484 mSubStringMatchOffset = mFilterSubString.size() ? item->getSearchableLabel().find(mFilterSubString) : LLString::npos;
4485 BOOL passed = (0x1 << listener->getInventoryType() & mFilterOps.mFilterTypes || listener->getInventoryType() == LLInventoryType::IT_NONE)
4486 && (mFilterSubString.size() == 0 || mSubStringMatchOffset != LLString::npos)
4487 && ((listener->getPermissionMask() & mFilterOps.mPermissions) == mFilterOps.mPermissions)
4488 && (listener->getCreationDate() >= earliest && listener->getCreationDate() <= mFilterOps.mMaxDate);
4489 return passed;
4490}
4491
4492const LLString LLInventoryFilter::getFilterSubString(BOOL trim)
4493{
4494 return mFilterSubString;
4495}
4496
4497std::string::size_type LLInventoryFilter::getStringMatchOffset() const
4498{
4499 return mSubStringMatchOffset;
4500}
4501
4502BOOL LLInventoryFilter::isActive()
4503{
4504 return mFilterOps.mFilterTypes != 0xffffffff
4505 || mFilterSubString.size()
4506 || mFilterOps.mPermissions != PERM_NONE
4507 || mFilterOps.mMinDate != 0
4508 || mFilterOps.mMaxDate != U32_MAX
4509 || mFilterOps.mHoursAgo != 0;
4510}
4511
4512BOOL LLInventoryFilter::isModified()
4513{
4514 return mModified;
4515}
4516
4517BOOL LLInventoryFilter::isModifiedAndClear()
4518{
4519 BOOL ret = mModified;
4520 mModified = FALSE;
4521 return ret;
4522}
4523
4524void LLInventoryFilter::setFilterTypes(U32 types)
4525{
4526 if (mFilterOps.mFilterTypes != types)
4527 {
4528 // keep current items only if no type bits getting turned off
4529 BOOL fewer_bits_set = (mFilterOps.mFilterTypes & ~types);
4530 BOOL more_bits_set = (~mFilterOps.mFilterTypes & types);
4531
4532 mFilterOps.mFilterTypes = types;
4533 if (more_bits_set && fewer_bits_set)
4534 {
4535 // neither less or more restrive, both simultaneously
4536 // so we need to filter from scratch
4537 setModified(FILTER_RESTART);
4538 }
4539 else if (more_bits_set)
4540 {
4541 // target is only one of all requested types so more type bits == less restrictive
4542 setModified(FILTER_LESS_RESTRICTIVE);
4543 }
4544 else if (fewer_bits_set)
4545 {
4546 setModified(FILTER_MORE_RESTRICTIVE);
4547 }
4548
4549 }
4550}
4551
4552void LLInventoryFilter::setFilterSubString(const LLString& string)
4553{
4554 if (mFilterSubString != string)
4555 {
4556 // hitting BACKSPACE, for example
4557 BOOL less_restrictive = mFilterSubString.size() >= string.size() && !mFilterSubString.substr(0, string.size()).compare(string);
4558 // appending new characters
4559 BOOL more_restrictive = mFilterSubString.size() < string.size() && !string.substr(0, mFilterSubString.size()).compare(mFilterSubString);
4560 mFilterSubString = string;
4561 LLString::toUpper(mFilterSubString);
4562 LLString::trimHead(mFilterSubString);
4563
4564 if (less_restrictive)
4565 {
4566 setModified(FILTER_LESS_RESTRICTIVE);
4567 }
4568 else if (more_restrictive)
4569 {
4570 setModified(FILTER_MORE_RESTRICTIVE);
4571 }
4572 else
4573 {
4574 setModified(FILTER_RESTART);
4575 }
4576 }
4577}
4578
4579void LLInventoryFilter::setFilterPermissions(PermissionMask perms)
4580{
4581 if (mFilterOps.mPermissions != perms)
4582 {
4583 // keep current items only if no perm bits getting turned off
4584 BOOL fewer_bits_set = (mFilterOps.mPermissions & ~perms);
4585 BOOL more_bits_set = (~mFilterOps.mPermissions & perms);
4586 mFilterOps.mPermissions = perms;
4587
4588 if (more_bits_set && fewer_bits_set)
4589 {
4590 setModified(FILTER_RESTART);
4591 }
4592 else if (more_bits_set)
4593 {
4594 // target must have all requested permission bits, so more bits == more restrictive
4595 setModified(FILTER_MORE_RESTRICTIVE);
4596 }
4597 else if (fewer_bits_set)
4598 {
4599 setModified(FILTER_LESS_RESTRICTIVE);
4600 }
4601 }
4602}
4603
4604void LLInventoryFilter::setDateRange(U32 min_date, U32 max_date)
4605{
4606 mFilterOps.mHoursAgo = 0;
4607 if (mFilterOps.mMinDate != min_date)
4608 {
4609 mFilterOps.mMinDate = min_date;
4610 setModified();
4611 }
4612 if (mFilterOps.mMaxDate != llmax(mFilterOps.mMinDate, max_date))
4613 {
4614 mFilterOps.mMaxDate = llmax(mFilterOps.mMinDate, max_date);
4615 setModified();
4616 }
4617}
4618
4619void LLInventoryFilter::setDateRangeLastLogoff(BOOL sl)
4620{
4621 if (sl && !isSinceLogoff())
4622 {
4623 setDateRange(mLastLogoff, U32_MAX);
4624 setModified();
4625 }
4626 if (!sl && isSinceLogoff())
4627 {
4628 setDateRange(0, U32_MAX);
4629 setModified();
4630 }
4631}
4632
4633BOOL LLInventoryFilter::isSinceLogoff()
4634{
4635 return mFilterOps.mMinDate == mLastLogoff && mFilterOps.mMaxDate == U32_MAX;
4636}
4637
4638void LLInventoryFilter::setHoursAgo(U32 hours)
4639{
4640 if (mFilterOps.mHoursAgo != hours)
4641 {
4642 // *NOTE: need to cache last filter time, in case filter goes stale
4643 BOOL less_restrictive = (mFilterOps.mMinDate == 0 && mFilterOps.mMaxDate == U32_MAX && hours > mFilterOps.mHoursAgo);
4644 BOOL more_restrictive = (mFilterOps.mMinDate == 0 && mFilterOps.mMaxDate == U32_MAX && hours <= mFilterOps.mHoursAgo);
4645 mFilterOps.mHoursAgo = hours;
4646 mFilterOps.mMinDate = 0;
4647 mFilterOps.mMaxDate = U32_MAX;
4648 if (less_restrictive)
4649 {
4650 setModified(FILTER_LESS_RESTRICTIVE);
4651 }
4652 else if (more_restrictive)
4653 {
4654 setModified(FILTER_MORE_RESTRICTIVE);
4655 }
4656 else
4657 {
4658 setModified(FILTER_RESTART);
4659 }
4660 }
4661}
4662void LLInventoryFilter::setShowFolderState(EFolderShow state)
4663{
4664 if (mFilterOps.mShowFolderState != state)
4665 {
4666 mFilterOps.mShowFolderState = state;
4667 if (state == SHOW_NON_EMPTY_FOLDERS)
4668 {
4669 // showing fewer folders than before
4670 setModified(FILTER_MORE_RESTRICTIVE);
4671 }
4672 else if (state == SHOW_ALL_FOLDERS)
4673 {
4674 // showing same folders as before and then some
4675 setModified(FILTER_LESS_RESTRICTIVE);
4676 }
4677 else
4678 {
4679 setModified();
4680 }
4681 }
4682}
4683
4684void LLInventoryFilter::setSortOrder(U32 order)
4685{
4686 if (mOrder != order)
4687 {
4688 mOrder = order;
4689 setModified();
4690 }
4691}
4692
4693void LLInventoryFilter::markDefault()
4694{
4695 mDefaultFilterOps = mFilterOps;
4696}
4697
4698void LLInventoryFilter::resetDefault()
4699{
4700 mFilterOps = mDefaultFilterOps;
4701 setModified();
4702}
4703
4704void LLInventoryFilter::setModified(EFilterBehavior behavior)
4705{
4706 mModified = TRUE;
4707 mNeedTextRebuild = TRUE;
4708 mFilterGeneration = mNextFilterGeneration++;
4709
4710 if (mFilterBehavior == FILTER_NONE)
4711 {
4712 mFilterBehavior = behavior;
4713 }
4714 else if (mFilterBehavior != behavior)
4715 {
4716 // trying to do both less restrictive and more restrictive filter
4717 // basically means restart from scratch
4718 mFilterBehavior = FILTER_RESTART;
4719 }
4720
4721 if (isActive())
4722 {
4723 // if not keeping current filter results, update last valid as well
4724 switch(mFilterBehavior)
4725 {
4726 case FILTER_RESTART:
4727 mMustPassGeneration = mFilterGeneration;
4728 mMinRequiredGeneration = mFilterGeneration;
4729 break;
4730 case FILTER_LESS_RESTRICTIVE:
4731 mMustPassGeneration = mFilterGeneration;
4732 break;
4733 case FILTER_MORE_RESTRICTIVE:
4734 mMinRequiredGeneration = mFilterGeneration;
4735 // must have passed either current filter generation (meaningless, as it hasn't been run yet)
4736 // or some older generation, so keep the value
4737 mMustPassGeneration = llmin(mMustPassGeneration, mFilterGeneration);
4738 break;
4739 default:
4740 llerrs << "Bad filter behavior specified" << llendl;
4741 }
4742 }
4743 else
4744 {
4745 // shortcut disabled filters to show everything immediately
4746 mMinRequiredGeneration = 0;
4747 mMustPassGeneration = S32_MAX;
4748 }
4749}
4750
4751BOOL LLInventoryFilter::isFilterWith(LLInventoryType::EType t)
4752{
4753 return mFilterOps.mFilterTypes & (0x01 << t);
4754}
4755
4756LLString LLInventoryFilter::getFilterText()
4757{
4758 if (!mNeedTextRebuild)
4759 {
4760 return mFilterText;
4761 }
4762
4763 mNeedTextRebuild = FALSE;
4764 LLString filtered_types;
4765 LLString not_filtered_types;
4766 BOOL filtered_by_type = FALSE;
4767 BOOL filtered_by_all_types = TRUE;
4768 S32 num_filter_types = 0;
4769 mFilterText = "";
4770
4771 if (isFilterWith(LLInventoryType::IT_ANIMATION))
4772 {
4773 filtered_types += " Animations,";
4774 filtered_by_type = TRUE;
4775 num_filter_types++;
4776 }
4777 else
4778 {
4779 not_filtered_types += " Animations,";
4780 filtered_by_all_types = FALSE;
4781 }
4782
4783 if (isFilterWith(LLInventoryType::IT_CALLINGCARD))
4784 {
4785 filtered_types += " Calling Cards,";
4786 filtered_by_type = TRUE;
4787 num_filter_types++;
4788 }
4789 else
4790 {
4791 not_filtered_types += " Calling Cards,";
4792 filtered_by_all_types = FALSE;
4793 }
4794
4795 if (isFilterWith(LLInventoryType::IT_WEARABLE))
4796 {
4797 filtered_types += " Clothing,";
4798 filtered_by_type = TRUE;
4799 num_filter_types++;
4800 }
4801 else
4802 {
4803 not_filtered_types += " Clothing,";
4804 filtered_by_all_types = FALSE;
4805 }
4806
4807 if (isFilterWith(LLInventoryType::IT_GESTURE))
4808 {
4809 filtered_types += " Gestures,";
4810 filtered_by_type = TRUE;
4811 num_filter_types++;
4812 }
4813 else
4814 {
4815 not_filtered_types += " Gestures,";
4816 filtered_by_all_types = FALSE;
4817 }
4818
4819 if (isFilterWith(LLInventoryType::IT_LANDMARK))
4820 {
4821 filtered_types += " Landmarks,";
4822 filtered_by_type = TRUE;
4823 num_filter_types++;
4824 }
4825 else
4826 {
4827 not_filtered_types += " Landmarks,";
4828 filtered_by_all_types = FALSE;
4829 }
4830
4831 if (isFilterWith(LLInventoryType::IT_NOTECARD))
4832 {
4833 filtered_types += " Notecards,";
4834 filtered_by_type = TRUE;
4835 num_filter_types++;
4836 }
4837 else
4838 {
4839 not_filtered_types += " Notecards,";
4840 filtered_by_all_types = FALSE;
4841 }
4842
4843 if (isFilterWith(LLInventoryType::IT_OBJECT) && isFilterWith(LLInventoryType::IT_ATTACHMENT))
4844 {
4845 filtered_types += " Objects,";
4846 filtered_by_type = TRUE;
4847 num_filter_types++;
4848 }
4849 else
4850 {
4851 not_filtered_types += " Objects,";
4852 filtered_by_all_types = FALSE;
4853 }
4854
4855 if (isFilterWith(LLInventoryType::IT_LSL))
4856 {
4857 filtered_types += " Scripts,";
4858 filtered_by_type = TRUE;
4859 num_filter_types++;
4860 }
4861 else
4862 {
4863 not_filtered_types += " Scripts,";
4864 filtered_by_all_types = FALSE;
4865 }
4866
4867 if (isFilterWith(LLInventoryType::IT_SOUND))
4868 {
4869 filtered_types += " Sounds,";
4870 filtered_by_type = TRUE;
4871 num_filter_types++;
4872 }
4873 else
4874 {
4875 not_filtered_types += " Sounds,";
4876 filtered_by_all_types = FALSE;
4877 }
4878
4879 if (isFilterWith(LLInventoryType::IT_TEXTURE))
4880 {
4881 filtered_types += " Textures,";
4882 filtered_by_type = TRUE;
4883 num_filter_types++;
4884 }
4885 else
4886 {
4887 not_filtered_types += " Textures,";
4888 filtered_by_all_types = FALSE;
4889 }
4890
4891 if (isFilterWith(LLInventoryType::IT_SNAPSHOT))
4892 {
4893 filtered_types += " Snapshots,";
4894 filtered_by_type = TRUE;
4895 num_filter_types++;
4896 }
4897 else
4898 {
4899 not_filtered_types += " Snapshots,";
4900 filtered_by_all_types = FALSE;
4901 }
4902
4903 if (!gInventory.backgroundFetchActive() && filtered_by_type && !filtered_by_all_types)
4904 {
4905 mFilterText += " - ";
4906 if (num_filter_types < 5)
4907 {
4908 mFilterText += filtered_types;
4909 }
4910 else
4911 {
4912 mFilterText += "No ";
4913 mFilterText += not_filtered_types;
4914 }
4915 // remove the ',' at the end
4916 mFilterText.erase(mFilterText.size() - 1, 1);
4917 }
4918
4919 if (isSinceLogoff())
4920 {
4921 mFilterText += " - Since Logoff";
4922 }
4923 return mFilterText;
4924}