From 1bd52dfbdc3607bbd9ea86c715ce63b17d5a557f Mon Sep 17 00:00:00 2001
From: Richard Linden <none@none>
Date: Mon, 2 Jul 2012 17:57:29 -0700
Subject: [PATCH] CHUI-101 WIP Make LLFolderView general purpose refactored
 source files, moving logic into llfolderviewmodel*.cpp filter logic tweaks
 purging and moving inventory now properly cleans up view model

---
 indra/newview/CMakeLists.txt                 |   3 +
 indra/newview/llfolderview.cpp               |  27 +--
 indra/newview/llfolderviewitem.cpp           |  19 +-
 indra/newview/llfolderviewitem.h             |  10 +-
 indra/newview/llfolderviewmodel.cpp          |  54 +++++
 indra/newview/llfolderviewmodel.h            |  25 ++-
 indra/newview/llfolderviewmodelinventory.cpp | 225 +++++++++++++++++++
 indra/newview/llfolderviewmodelinventory.h   | 107 +++++++++
 indra/newview/llinventoryfilter.cpp          |  24 --
 indra/newview/llinventorypanel.cpp           | 201 +----------------
 indra/newview/llinventorypanel.h             |  78 +------
 indra/newview/lltexturectrl.cpp              |   4 +-
 12 files changed, 429 insertions(+), 348 deletions(-)
 create mode 100644 indra/newview/llfolderviewmodel.cpp
 create mode 100644 indra/newview/llfolderviewmodelinventory.cpp
 create mode 100644 indra/newview/llfolderviewmodelinventory.h

diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 4f447fd35b8..dee37bd5e8c 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -253,6 +253,8 @@ set(viewer_SOURCE_FILES
     llfloaterworldmap.cpp
     llfolderview.cpp
     llfolderviewitem.cpp
+    llfolderviewmodel.cpp
+    llfolderviewmodelinventory.cpp
     llfollowcam.cpp
     llfriendcard.cpp
     llgesturelistener.cpp
@@ -809,6 +811,7 @@ set(viewer_HEADER_FILES
     llfloaterworldmap.h
     llfolderview.h
     llfolderviewmodel.h
+    llfolderviewmodelinventory.h
     llfolderviewitem.h
     llfollowcam.h
     llfriendcard.h
diff --git a/indra/newview/llfolderview.cpp b/indra/newview/llfolderview.cpp
index 5844c58e094..90c78d98b0f 100644
--- a/indra/newview/llfolderview.cpp
+++ b/indra/newview/llfolderview.cpp
@@ -259,7 +259,7 @@ LLFolderView::LLFolderView(const Params& p)
 	menu->setBackgroundColor(LLUIColorTable::instance().getColor("MenuPopupBgColor"));
 	mPopupMenuHandle = menu->getHandle();
 
-	mListener->openItem();
+	mViewModelItem->openItem();
 }
 
 // Destroys the object
@@ -363,11 +363,7 @@ void LLFolderView::filter( LLFolderViewFilter& filter )
 	LLFastTimer t2(FTM_FILTER);
 	filter.setFilterCount(llclamp(gSavedSettings.getS32("FilterItemsPerFrame"), 1, 5000));
 
-	if (getLastFilterGeneration() < filter.getCurrentGeneration())
-	{
-		mMinWidth = 0;
-		getViewModelItem()->filter(filter);
-	}
+	getViewModelItem()->filter(filter);
 }
 
 void LLFolderView::reshape(S32 width, S32 height, BOOL called_from_parent)
@@ -706,20 +702,11 @@ void LLFolderView::draw()
 
 	if (hasVisibleChildren())
 	{
-		mStatusText.clear();
 		mStatusTextBox->setVisible( FALSE );
 	}
 	else if (mShowEmptyMessage)
 	{
-		if (!mViewModel->contentsReady() || getLastFilterGeneration() < getFolderViewModel()->getFilter()->getFirstSuccessGeneration())
-		{
-			mStatusText = LLTrans::getString("Searching");
-		}
-		else
-		{
-			mStatusText = getFolderViewModel()->getFilter()->getEmptyLookupMessage();
-		}
-		mStatusTextBox->setValue(mStatusText);
+		mStatusTextBox->setValue(getFolderViewModel()->getStatusText());
 		mStatusTextBox->setVisible( TRUE );
 		
 		// firstly reshape message textbox with current size. This is necessary to
@@ -1971,7 +1958,7 @@ void LLFolderView::doIdle()
 		scrollToShowSelection();
 	}
 
-	BOOL filter_finished = getLastFilterGeneration() >= getFolderViewModel()->getFilter()->getCurrentGeneration() 
+	BOOL filter_finished = getViewModelItem()->passedFilter()
 						&& mViewModel->contentsReady();
 	if (filter_finished 
 		|| gFocusMgr.childHasKeyboardFocus(inventory_panel) 
@@ -2264,9 +2251,3 @@ S32 LLFolderView::getItemHeight()
 	}
 	return 0;
 }
-
-//TODO RN: move to llfolderviewmodel.cpp file
-bool LLFolderViewModelCommon::needsSort(LLFolderViewModelItem* item)
-{
-	return item->getSortVersion() < mTargetSortVersion;
-}
diff --git a/indra/newview/llfolderviewitem.cpp b/indra/newview/llfolderviewitem.cpp
index 685a4cbf497..ac389c9189d 100644
--- a/indra/newview/llfolderviewitem.cpp
+++ b/indra/newview/llfolderviewitem.cpp
@@ -120,12 +120,12 @@ LLFolderViewItem::LLFolderViewItem(const LLFolderViewItem::Params& p)
 	mDragAndDropTarget(FALSE),
 	mLabel(p.name),
 	mRoot(p.root),
-	mListener(p.listener),
+	mViewModelItem(p.listener),
 	mIsMouseOverTitle(false)
 {
-	if (mListener)
+	if (mViewModelItem)
 	{
-		mListener->setFolderViewItem(this);
+		mViewModelItem->setFolderViewItem(this);
 	}
 }
 
@@ -138,8 +138,8 @@ BOOL LLFolderViewItem::postBuild()
 // Destroys the object
 LLFolderViewItem::~LLFolderViewItem( void )
 {
-	delete mListener;
-	mListener = NULL;
+	delete mViewModelItem;
+	mViewModelItem = NULL;
 }
 
 LLFolderView* LLFolderViewItem::getRoot()
@@ -837,12 +837,6 @@ LLFolderViewModelInterface* LLFolderViewItem::getFolderViewModel( void )
 	return getRoot()->getFolderViewModel();
 }
 
-S32 LLFolderViewItem::getLastFilterGeneration() const
-{
-	return getViewModelItem()->getLastFilterGeneration();
-}
-
-
 
 ///----------------------------------------------------------------------------
 /// Class LLFolderViewFolder
@@ -1446,6 +1440,7 @@ void LLFolderViewFolder::extractItem( LLFolderViewItem* item )
 		mItems.erase(it);
 	}
 	//item has been removed, need to update filter
+	getViewModelItem()->removeChild(item->getViewModelItem());
 	getViewModelItem()->dirtyFilter();
 	//because an item is going away regardless of filter status, force rearrange
 	requestArrange();
@@ -1638,7 +1633,7 @@ BOOL LLFolderViewFolder::handleDragAndDropFromChild(MASK mask,
 													EAcceptance* accept,
 													std::string& tooltip_msg)
 {
-	BOOL accepted = mListener->dragOrDrop(mask,drop,c_type,cargo_data, tooltip_msg);
+	BOOL accepted = mViewModelItem->dragOrDrop(mask,drop,c_type,cargo_data, tooltip_msg);
 	if (accepted) 
 	{
 		mDragAndDropTarget = TRUE;
diff --git a/indra/newview/llfolderviewitem.h b/indra/newview/llfolderviewitem.h
index a3c92a55e8d..581ec7239e3 100644
--- a/indra/newview/llfolderviewitem.h
+++ b/indra/newview/llfolderviewitem.h
@@ -92,13 +92,12 @@ class LLFolderViewItem : public LLView
 	S32							mLabelWidth;
 	bool						mLabelWidthDirty;
 	LLFolderViewFolder*			mParentFolder;
-	LLFolderViewModelItem*		mListener;
+	LLFolderViewModelItem*		mViewModelItem;
 	BOOL						mIsCurSelection;
 	BOOL						mSelectPending;
 	LLFontGL::StyleFlags		mLabelStyle;
 	std::string					mLabelSuffix;
 	LLUIImagePtr				mIcon;
-	std::string					mStatusText;
 	LLUIImagePtr				mIconOpen;
 	LLUIImagePtr				mIconOverlay;
 	BOOL						mHasVisibleChildren;
@@ -136,9 +135,6 @@ class LLFolderViewItem : public LLView
 	virtual S32 arrange( S32* width, S32* height );
 	virtual S32 getItemHeight();
 
-	// updates filter serial number and optionally propagated value up to root
-	S32		getLastFilterGeneration() const;
-
 	// If 'selection' is 'this' then note that otherwise ignore.
 	// Returns TRUE if this item ends up being selected.
 	virtual BOOL setSelection(LLFolderViewItem* selection, BOOL openitem, BOOL take_keyboard_focus);
@@ -202,8 +198,8 @@ class LLFolderViewItem : public LLView
 	LLFolderViewItem* getNextOpenNode( BOOL include_children = TRUE );
 	LLFolderViewItem* getPreviousOpenNode( BOOL include_children = TRUE );
 
-	const LLFolderViewModelItem* getViewModelItem( void ) const { return mListener; }
-	LLFolderViewModelItem* getViewModelItem( void ) { return mListener; }
+	const LLFolderViewModelItem* getViewModelItem( void ) const { return mViewModelItem; }
+	LLFolderViewModelItem* getViewModelItem( void ) { return mViewModelItem; }
 
 	const LLFolderViewModelInterface* getFolderViewModel( void ) const;
 	LLFolderViewModelInterface* getFolderViewModel( void );
diff --git a/indra/newview/llfolderviewmodel.cpp b/indra/newview/llfolderviewmodel.cpp
new file mode 100644
index 00000000000..92db84156e5
--- /dev/null
+++ b/indra/newview/llfolderviewmodel.cpp
@@ -0,0 +1,54 @@
+/** 
+ * @file llfolderviewmodel.cpp
+ * @brief Implementation of the view model collection of classes.
+ *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ * 
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llfolderviewmodel.h"
+#include "lltrans.h"
+#include "llviewercontrol.h"
+
+bool LLFolderViewModelCommon::needsSort(LLFolderViewModelItem* item)
+{
+	return item->getSortVersion() < mTargetSortVersion;
+}
+
+std::string LLFolderViewModelCommon::getStatusText()
+{
+	if (!contentsReady() || mFolderView->getViewModelItem()->getLastFilterGeneration() < getFilter()->getCurrentGeneration())
+	{
+		return LLTrans::getString("Searching");
+	}
+	else
+	{
+		return getFilter()->getEmptyLookupMessage();
+	}
+}
+
+void LLFolderViewModelCommon::filter()
+{
+	getFilter()->setFilterCount(llclamp(gSavedSettings.getS32("FilterItemsPerFrame"), 1, 5000));
+	mFolderView->getViewModelItem()->filter(*getFilter());
+}
diff --git a/indra/newview/llfolderviewmodel.h b/indra/newview/llfolderviewmodel.h
index 5304613219f..8a16ec3eff6 100644
--- a/indra/newview/llfolderviewmodel.h
+++ b/indra/newview/llfolderviewmodel.h
@@ -28,6 +28,7 @@
 #include "lldarray.h"	// *TODO: convert to std::vector
 #include "llfoldertype.h"
 #include "llfontgl.h"	// just for StyleFlags enum
+#include "llfolderview.h"
 #include "llfolderviewitem.h"
 #include "llinventorytype.h"
 #include "llpermissionsflags.h"
@@ -122,11 +123,13 @@ class LLFolderViewModelInterface
 	virtual void requestSortAll() = 0;
 
 	virtual void sort(class LLFolderViewFolder*) = 0;
+	virtual void filter() = 0;
 
 	virtual bool contentsReady() = 0;
 	virtual void setFolderView(LLFolderView* folder_view) = 0;
 	virtual LLFolderViewFilter* getFilter() = 0;
 	virtual const LLFolderViewFilter* getFilter() const = 0;
+	virtual std::string getStatusText() = 0;
 };
 
 class LLFolderViewModelCommon : public LLFolderViewModelInterface
@@ -142,6 +145,8 @@ class LLFolderViewModelCommon : public LLFolderViewModelInterface
 		// sort everything
 		mTargetSortVersion++;
 	}
+	virtual std::string getStatusText();
+	virtual void filter();
 
 	void setFolderView(LLFolderView* folder_view) { mFolderView = folder_view;}
 
@@ -177,6 +182,7 @@ class LLFolderViewModel : public LLFolderViewModelCommon
 	// add getStatusText and isFiltering()
 	virtual bool contentsReady()					{ return true; }
 
+
 	struct ViewModelCompare
 	{
 		ViewModelCompare(const SortType& sorter)
@@ -272,6 +278,7 @@ class LLFolderViewModelItem
 	
 	virtual bool hasChildren() const = 0;
 	virtual void addChild(LLFolderViewModelItem* child) = 0;
+	virtual void removeChild(LLFolderViewModelItem* child) = 0;
 
 	// This method will be called to determine if a drop can be
 	// performed, and will set drop to TRUE if a drop is
@@ -305,7 +312,9 @@ class LLFolderViewModelItemCommon : public LLFolderViewModelItem
 		mLastFilterGeneration(-1),
 		mMostFilteredDescendantGeneration(-1),
 		mParent(NULL)
-	{}
+	{
+		std::for_each(mChildren.begin(), mChildren.end(), DeletePointer());
+	}
 
 	void requestSort() { mSortVersion = -1; }
 	S32 getSortVersion() { return mSortVersion; }
@@ -315,13 +324,23 @@ class LLFolderViewModelItemCommon : public LLFolderViewModelItem
 	void dirtyFilter()
 	{
 		mLastFilterGeneration = -1;
+
 		// bubble up dirty flag all the way to root
 		if (mParent)
 		{
 			mParent->dirtyFilter();
-		}
+		}	
+	}
+	virtual void addChild(LLFolderViewModelItem* child) 
+	{ 
+		mChildren.push_back(child); 
+		child->setParent(this); 
+	}
+	virtual void removeChild(LLFolderViewModelItem* child) 
+	{ 
+		mChildren.remove(child); 
+		child->setParent(NULL); 
 	}
-	virtual void addChild(LLFolderViewModelItem* child) { mChildren.push_back(child); child->setParent(this); }
 
 protected:
 	virtual void setParent(LLFolderViewModelItem* parent) { mParent = parent; }
diff --git a/indra/newview/llfolderviewmodelinventory.cpp b/indra/newview/llfolderviewmodelinventory.cpp
new file mode 100644
index 00000000000..7ee1a10b150
--- /dev/null
+++ b/indra/newview/llfolderviewmodelinventory.cpp
@@ -0,0 +1,225 @@
+/* 
+ * @file llfolderviewmodelinventory.cpp
+ * @brief Implementation of the inventory-specific view model
+ *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ * 
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+#include "llfolderviewmodelinventory.h"
+#include "llinventorymodelbackgroundfetch.h"
+#include "llinventorypanel.h"
+
+//
+// class LLFolderViewModelInventory
+//
+static LLFastTimer::DeclareTimer FTM_INVENTORY_SORT("Sort");
+
+void LLFolderViewModelInventory::sort( LLFolderViewFolder* folder )
+{
+	LLFastTimer _(FTM_INVENTORY_SORT);
+
+	if (!needsSort(folder->getViewModelItem())) return;
+
+	LLFolderViewModelItemInventory* modelp =   static_cast<LLFolderViewModelItemInventory*>(folder->getViewModelItem());
+	if (modelp->getUUID().isNull()) return;
+
+	for (std::list<LLFolderViewFolder*>::iterator it =   folder->getFoldersBegin(), end_it = folder->getFoldersEnd();
+		it != end_it;
+		++it)
+	{
+		LLFolderViewFolder* child_folderp = *it;
+		sort(child_folderp);
+
+		if (child_folderp->getFoldersCount() > 0)
+		{
+			time_t most_recent_folder_time =
+				static_cast<LLFolderViewModelItemInventory*>((*child_folderp->getFoldersBegin())->getViewModelItem())->getCreationDate();
+			LLFolderViewModelItemInventory* modelp =   static_cast<LLFolderViewModelItemInventory*>(child_folderp->getViewModelItem());
+			if (most_recent_folder_time > modelp->getCreationDate())
+			{
+				modelp->setCreationDate(most_recent_folder_time);
+			}
+		}
+		if (child_folderp->getItemsCount() > 0)			
+		{
+			time_t most_recent_item_time =
+				static_cast<LLFolderViewModelItemInventory*>((*child_folderp->getItemsBegin())->getViewModelItem())->getCreationDate();
+
+			LLFolderViewModelItemInventory* modelp =   static_cast<LLFolderViewModelItemInventory*>(child_folderp->getViewModelItem());
+			if (most_recent_item_time > modelp->getCreationDate())
+			{
+				modelp->setCreationDate(most_recent_item_time);
+			}
+		}
+	}
+	base_t::sort(folder);
+}
+
+bool LLFolderViewModelInventory::contentsReady()
+{
+	return !LLInventoryModelBackgroundFetch::instance().folderFetchActive();
+}
+
+void LLFolderViewModelItemInventory::requestSort()
+{
+	LLFolderViewModelItemCommon::requestSort();
+	if (mRootViewModel->getSorter().isByDate())
+	{
+		// sort by date potentially affects parent folders which use a date
+		// derived from newest item in them
+		if (mParent)
+		{
+			mParent->requestSort();
+		}
+	}
+}
+
+bool LLFolderViewModelItemInventory::potentiallyVisible()
+{
+	return passedFilter() // we've passed the filter
+		|| getLastFilterGeneration() < mRootViewModel->getFilter()->getFirstSuccessGeneration() // or we don't know yet
+		|| descendantsPassedFilter();
+}
+
+bool LLFolderViewModelItemInventory::passedFilter(S32 filter_generation) 
+{ 
+	if (filter_generation < 0 && mRootViewModel) 
+		filter_generation = mRootViewModel->getFilter()->getFirstSuccessGeneration();
+
+	return mPassedFolderFilter 
+		&& mLastFilterGeneration >= filter_generation
+		&& (mPassedFilter || descendantsPassedFilter(filter_generation));
+}
+
+bool LLFolderViewModelItemInventory::descendantsPassedFilter(S32 filter_generation)
+{ 
+	if (filter_generation < 0) filter_generation = mRootViewModel->getFilter()->getFirstSuccessGeneration();
+	return mMostFilteredDescendantGeneration >= filter_generation; 
+}
+
+void LLFolderViewModelItemInventory::setPassedFilter(bool passed, bool passed_folder, S32 filter_generation)
+{
+	mPassedFilter = passed;
+	mPassedFolderFilter = passed_folder;
+	mLastFilterGeneration = filter_generation;
+}
+
+bool LLFolderViewModelItemInventory::filterChildItem( LLFolderViewModelItem* item, LLFolderViewFilter& filter )
+{
+	bool passed_filter_before = item->passedFilter();
+	S32 filter_generation = filter.getCurrentGeneration();
+	S32 must_pass_generation = filter.getFirstRequiredGeneration();
+
+	if (item->getLastFilterGeneration() < filter_generation)
+	{
+		if (item->getLastFilterGeneration() >= must_pass_generation 
+			&& !item->passedFilter(must_pass_generation))
+		{
+			// failed to pass an earlier filter that was a subset of the current one
+			// go ahead and flag this item as done
+			item->filter(filter);
+			if (item->passedFilter())
+			{
+				llerrs << "Invalid shortcut in inventory filtering!" << llendl;
+			}
+			item->setPassedFilter(false, false, filter_generation);
+		}
+		else
+		{
+			item->filter( filter );
+		}
+	}
+
+	// track latest generation to pass any child items, for each folder up to root
+	if (item->passedFilter())
+	{
+		LLFolderViewModelItemInventory* view_model = this;
+		
+		while(view_model && view_model->mMostFilteredDescendantGeneration < filter_generation)
+		{
+			view_model->mMostFilteredDescendantGeneration = filter_generation;
+			view_model = static_cast<LLFolderViewModelItemInventory*>(view_model->mParent);
+		}
+		
+		return !passed_filter_before;
+	}
+	else // !item->passedfilter()
+	{
+		return passed_filter_before;
+	}
+}
+
+bool LLFolderViewModelItemInventory::filter( LLFolderViewFilter& filter)
+{
+	bool changed = false;
+
+	if(!mChildren.empty()
+		&& (getLastFilterGeneration() < filter.getFirstRequiredGeneration() // haven't checked descendants against minimum required generation to pass
+			|| descendantsPassedFilter(filter.getFirstRequiredGeneration()))) // or at least one descendant has passed the minimum requirement
+	{
+		// now query children
+		for (child_list_t::iterator iter = mChildren.begin();
+			iter != mChildren.end() && filter.getFilterCount() > 0;
+			++iter)
+		{
+			changed |= filterChildItem((*iter), filter);
+		}
+	}
+
+	if (changed)
+	{
+		//TODO RN: ensure this still happens, but without dependency on folderview
+		LLFolderViewFolder* folder = static_cast<LLFolderViewFolder*>(mFolderViewItem);
+		folder->requestArrange();
+	}
+
+	// if we didn't use all filter iterations
+	// that means we filtered all of our descendants
+	// so filter ourselves now
+	if (filter.getFilterCount() > 0)
+	{
+		filter.decrementFilterCount();
+
+		const BOOL passed_filter = filter.check(this);
+		const BOOL passed_filter_folder = (getInventoryType() == LLInventoryType::IT_CATEGORY) 
+								? filter.checkFolder(this)
+								: true;
+
+		setPassedFilter(passed_filter, passed_filter_folder, filter.getCurrentGeneration());
+		//TODO RN: create interface for string highlighting
+		//mStringMatchOffset = filter.getStringMatchOffset(this);
+	}
+	return changed;
+}
+
+LLFolderViewModelInventory* LLInventoryPanel::getFolderViewModel()
+{
+	return &mInventoryViewModel;
+}
+
+
+const LLFolderViewModelInventory* LLInventoryPanel::getFolderViewModel() const
+{
+	return &mInventoryViewModel;
+}
+
diff --git a/indra/newview/llfolderviewmodelinventory.h b/indra/newview/llfolderviewmodelinventory.h
new file mode 100644
index 00000000000..a8fe3f57ea9
--- /dev/null
+++ b/indra/newview/llfolderviewmodelinventory.h
@@ -0,0 +1,107 @@
+/** 
+ * @file llfolderviewmodelinventory.h
+ * @brief view model implementation specific to inventory
+ * class definition
+ *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ * 
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLFOLDERVIEWMODELINVENTORY_H
+#define LL_LLFOLDERVIEWMODELINVENTORY_H
+
+#include "llinventoryfilter.h"
+
+class LLFolderViewModelItemInventory
+	:	public LLFolderViewModelItemCommon
+{
+public:
+	LLFolderViewModelItemInventory()
+		:	mRootViewModel(NULL)
+	{}
+	void setRootViewModel(class LLFolderViewModelInventory* root_view_model)
+	{
+		mRootViewModel = root_view_model;
+	}
+	virtual const LLUUID& getUUID() const = 0;
+	virtual time_t getCreationDate() const = 0;	// UTC seconds
+	virtual void setCreationDate(time_t creation_date_utc) = 0;
+	virtual PermissionMask getPermissionMask() const = 0;
+	virtual LLFolderType::EType getPreferredType() const = 0;
+	virtual void showProperties(void) = 0;
+	virtual BOOL isItemInTrash( void) const { return FALSE; } // TODO: make   into pure virtual.
+	virtual BOOL isUpToDate() const = 0;
+	virtual bool hasChildren() const = 0;
+	virtual LLInventoryType::EType getInventoryType() const = 0;
+	virtual void performAction(LLInventoryModel* model, std::string action)   = 0;
+	virtual LLWearableType::EType getWearableType() const = 0;
+	virtual EInventorySortGroup getSortGroup() const = 0;
+	virtual LLInventoryObject* getInventoryObject() const = 0;
+	virtual void requestSort();
+	virtual bool potentiallyVisible();
+	virtual bool passedFilter(S32 filter_generation = -1);
+	virtual bool descendantsPassedFilter(S32 filter_generation = -1);
+	virtual void setPassedFilter(bool filtered, bool filtered_folder, S32 filter_generation);
+	virtual bool filter( LLFolderViewFilter& filter);
+	virtual bool filterChildItem( LLFolderViewModelItem* item, LLFolderViewFilter& filter);
+protected:
+	class LLFolderViewModelInventory* mRootViewModel;
+};
+
+class LLInventorySort
+{
+public:
+	LLInventorySort(U32 order = 0)
+		:	mSortOrder(order),
+		mByDate(false),
+		mSystemToTop(false),
+		mFoldersByName(false)
+	{
+		mByDate = (order & LLInventoryFilter::SO_DATE);
+		mSystemToTop = (order & LLInventoryFilter::SO_SYSTEM_FOLDERS_TO_TOP);
+		mFoldersByName = (order & LLInventoryFilter::SO_FOLDERS_BY_NAME);
+	}
+
+	bool isByDate() const { return mByDate; }
+	U32 getSortOrder() const { return mSortOrder; }
+
+	bool operator()(const LLFolderViewModelItemInventory* const& a, const LLFolderViewModelItemInventory* const& b) const;
+private:
+	U32  mSortOrder;
+	bool mByDate;
+	bool mSystemToTop;
+	bool mFoldersByName;
+};
+
+class LLFolderViewModelInventory
+	:	public LLFolderViewModel<LLInventorySort,   LLFolderViewModelItemInventory, LLFolderViewModelItemInventory,   LLInventoryFilter>
+{
+public:
+	typedef LLFolderViewModel<LLInventorySort,   LLFolderViewModelItemInventory, LLFolderViewModelItemInventory,   LLInventoryFilter> base_t;
+
+	virtual ~LLFolderViewModelInventory() {}
+
+	void sort(LLFolderViewFolder* folder);
+
+	bool contentsReady();
+
+};
+#endif // LL_LLFOLDERVIEWMODELINVENTORY_H
diff --git a/indra/newview/llinventoryfilter.cpp b/indra/newview/llinventoryfilter.cpp
index 6a331303228..3f38d80a396 100644
--- a/indra/newview/llinventoryfilter.cpp
+++ b/indra/newview/llinventoryfilter.cpp
@@ -779,14 +779,12 @@ const std::string& LLInventoryFilter::getFilterText()
 
 	if (isFilterObjectTypesWith(LLInventoryType::IT_ANIMATION))
 	{
-		//filtered_types += " Animations,";
 		filtered_types += LLTrans::getString("Animations");
 		filtered_by_type = TRUE;
 		num_filter_types++;
 	}
 	else
 	{
-		//not_filtered_types += " Animations,";
 		not_filtered_types += LLTrans::getString("Animations");
 
 		filtered_by_all_types = FALSE;
@@ -794,140 +792,120 @@ const std::string& LLInventoryFilter::getFilterText()
 
 	if (isFilterObjectTypesWith(LLInventoryType::IT_CALLINGCARD))
 	{
-		//filtered_types += " Calling Cards,";
 		filtered_types += LLTrans::getString("Calling Cards");
 		filtered_by_type = TRUE;
 		num_filter_types++;
 	}
 	else
 	{
-		//not_filtered_types += " Calling Cards,";
 		not_filtered_types += LLTrans::getString("Calling Cards");
 		filtered_by_all_types = FALSE;
 	}
 
 	if (isFilterObjectTypesWith(LLInventoryType::IT_WEARABLE))
 	{
-		//filtered_types += " Clothing,";
 		filtered_types +=  LLTrans::getString("Clothing");
 		filtered_by_type = TRUE;
 		num_filter_types++;
 	}
 	else
 	{
-		//not_filtered_types += " Clothing,";
 		not_filtered_types +=  LLTrans::getString("Clothing");
 		filtered_by_all_types = FALSE;
 	}
 
 	if (isFilterObjectTypesWith(LLInventoryType::IT_GESTURE))
 	{
-		//filtered_types += " Gestures,";
 		filtered_types +=  LLTrans::getString("Gestures");
 		filtered_by_type = TRUE;
 		num_filter_types++;
 	}
 	else
 	{
-		//not_filtered_types += " Gestures,";
 		not_filtered_types +=  LLTrans::getString("Gestures");
 		filtered_by_all_types = FALSE;
 	}
 
 	if (isFilterObjectTypesWith(LLInventoryType::IT_LANDMARK))
 	{
-		//filtered_types += " Landmarks,";
 		filtered_types +=  LLTrans::getString("Landmarks");
 		filtered_by_type = TRUE;
 		num_filter_types++;
 	}
 	else
 	{
-		//not_filtered_types += " Landmarks,";
 		not_filtered_types +=  LLTrans::getString("Landmarks");
 		filtered_by_all_types = FALSE;
 	}
 
 	if (isFilterObjectTypesWith(LLInventoryType::IT_NOTECARD))
 	{
-		//filtered_types += " Notecards,";
 		filtered_types +=  LLTrans::getString("Notecards");
 		filtered_by_type = TRUE;
 		num_filter_types++;
 	}
 	else
 	{
-		//not_filtered_types += " Notecards,";
 		not_filtered_types +=  LLTrans::getString("Notecards");
 		filtered_by_all_types = FALSE;
 	}
 
 	if (isFilterObjectTypesWith(LLInventoryType::IT_OBJECT) && isFilterObjectTypesWith(LLInventoryType::IT_ATTACHMENT))
 	{
-		//filtered_types += " Objects,";
 		filtered_types +=  LLTrans::getString("Objects");
 		filtered_by_type = TRUE;
 		num_filter_types++;
 	}
 	else
 	{
-		//not_filtered_types += " Objects,";
 		not_filtered_types +=  LLTrans::getString("Objects");
 		filtered_by_all_types = FALSE;
 	}
 
 	if (isFilterObjectTypesWith(LLInventoryType::IT_LSL))
 	{
-		//filtered_types += " Scripts,";
 		filtered_types +=  LLTrans::getString("Scripts");
 		filtered_by_type = TRUE;
 		num_filter_types++;
 	}
 	else
 	{
-		//not_filtered_types += " Scripts,";
 		not_filtered_types +=  LLTrans::getString("Scripts");
 		filtered_by_all_types = FALSE;
 	}
 
 	if (isFilterObjectTypesWith(LLInventoryType::IT_SOUND))
 	{
-		//filtered_types += " Sounds,";
 		filtered_types +=  LLTrans::getString("Sounds");
 		filtered_by_type = TRUE;
 		num_filter_types++;
 	}
 	else
 	{
-		//not_filtered_types += " Sounds,";
 		not_filtered_types +=  LLTrans::getString("Sounds");
 		filtered_by_all_types = FALSE;
 	}
 
 	if (isFilterObjectTypesWith(LLInventoryType::IT_TEXTURE))
 	{
-		//filtered_types += " Textures,";
 		filtered_types +=  LLTrans::getString("Textures");
 		filtered_by_type = TRUE;
 		num_filter_types++;
 	}
 	else
 	{
-		//not_filtered_types += " Textures,";
 		not_filtered_types +=  LLTrans::getString("Textures");
 		filtered_by_all_types = FALSE;
 	}
 
 	if (isFilterObjectTypesWith(LLInventoryType::IT_SNAPSHOT))
 	{
-		//filtered_types += " Snapshots,";
 		filtered_types +=  LLTrans::getString("Snapshots");
 		filtered_by_type = TRUE;
 		num_filter_types++;
 	}
 	else
 	{
-		//not_filtered_types += " Snapshots,";
 		not_filtered_types +=  LLTrans::getString("Snapshots");
 		filtered_by_all_types = FALSE;
 	}
@@ -943,7 +921,6 @@ const std::string& LLInventoryFilter::getFilterText()
 		}
 		else
 		{
-			//mFilterText += "No ";
 			mFilterText += LLTrans::getString("No Filters");
 			mFilterText += not_filtered_types;
 		}
@@ -953,7 +930,6 @@ const std::string& LLInventoryFilter::getFilterText()
 
 	if (isSinceLogoff())
 	{
-		//mFilterText += " - Since Logoff";
 		mFilterText += LLTrans::getString("Since Logoff");
 	}
 	return mFilterText;
diff --git a/indra/newview/llinventorypanel.cpp b/indra/newview/llinventorypanel.cpp
index e4cabcc9880..b5fcf364dd4 100644
--- a/indra/newview/llinventorypanel.cpp
+++ b/indra/newview/llinventorypanel.cpp
@@ -56,58 +56,6 @@ const std::string LLInventoryPanel::RECENTITEMS_SORT_ORDER = std::string("Recent
 const std::string LLInventoryPanel::INHERIT_SORT_ORDER = std::string("");
 static const LLInventoryFVBridgeBuilder INVENTORY_BRIDGE_BUILDER;
 
-//
-// class LLFolderViewModelInventory
-//
-static LLFastTimer::DeclareTimer FTM_INVENTORY_SORT("Sort");
-
-void LLFolderViewModelInventory::sort( LLFolderViewFolder* folder )
-{
-	LLFastTimer _(FTM_INVENTORY_SORT);
-
-	if (!needsSort(folder->getViewModelItem())) return;
-
-	LLFolderViewModelItemInventory* modelp =   static_cast<LLFolderViewModelItemInventory*>(folder->getViewModelItem());
-	if (modelp->getUUID().isNull()) return;
-
-	for (std::list<LLFolderViewFolder*>::iterator it =   folder->getFoldersBegin(), end_it = folder->getFoldersEnd();
-		it != end_it;
-		++it)
-	{
-		LLFolderViewFolder* child_folderp = *it;
-		sort(child_folderp);
-
-		if (child_folderp->getFoldersCount() > 0)
-		{
-			time_t most_recent_folder_time =
-				static_cast<LLFolderViewModelItemInventory*>((*child_folderp->getFoldersBegin())->getViewModelItem())->getCreationDate();
-			LLFolderViewModelItemInventory* modelp =   static_cast<LLFolderViewModelItemInventory*>(child_folderp->getViewModelItem());
-			if (most_recent_folder_time > modelp->getCreationDate())
-			{
-				modelp->setCreationDate(most_recent_folder_time);
-			}
-		}
-		if (child_folderp->getItemsCount() > 0)			
-		{
-			time_t most_recent_item_time =
-				static_cast<LLFolderViewModelItemInventory*>((*child_folderp->getItemsBegin())->getViewModelItem())->getCreationDate();
-
-			LLFolderViewModelItemInventory* modelp =   static_cast<LLFolderViewModelItemInventory*>(child_folderp->getViewModelItem());
-			if (most_recent_item_time > modelp->getCreationDate())
-			{
-				modelp->setCreationDate(most_recent_item_time);
-			}
-		}
-	}
-	base_t::sort(folder);
-}
-
-bool LLFolderViewModelInventory::contentsReady()
-{
-	return !LLInventoryModelBackgroundFetch::instance().folderFetchActive();
-}
-
-
 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // Class LLInventoryPanelObserver
 //
@@ -580,8 +528,8 @@ void LLInventoryPanel::modelChanged(U32 mask)
 			else if (!model_item && view_item)
 			{
 				// Remove the item's UI.
-				view_item->destroyView();
                 removeItemID(viewmodel_item->getUUID());
+				view_item->destroyView();
 			}
 		}
 	}
@@ -1344,150 +1292,3 @@ LLInventoryRecentItemsPanel::LLInventoryRecentItemsPanel( const Params& params)
 	// replace bridge builder to have necessary View bridges.
 	mInvFVBridgeBuilder = &RECENT_ITEMS_BUILDER;
 }
-
-
-void LLFolderViewModelItemInventory::requestSort()
-{
-	LLFolderViewModelItemCommon::requestSort();
-	if (mRootViewModel->getSorter().isByDate())
-	{
-		// sort by date potentially affects parent folders which use a date
-		// derived from newest item in them
-		if (mParent)
-		{
-			mParent->requestSort();
-		}
-	}
-}
-
-bool LLFolderViewModelItemInventory::potentiallyVisible()
-{
-	return passedFilter() // we've passed the filter
-		|| getLastFilterGeneration() < mRootViewModel->getFilter()->getFirstSuccessGeneration() // or we don't know yet
-		|| descendantsPassedFilter();
-}
-
-bool LLFolderViewModelItemInventory::passedFilter(S32 filter_generation) 
-{ 
-	if (filter_generation < 0) filter_generation = mRootViewModel->getFilter()->getFirstSuccessGeneration();
-	return mPassedFolderFilter 
-		&& mLastFilterGeneration >= filter_generation
-		&& (mPassedFilter || descendantsPassedFilter(filter_generation));
-}
-
-bool LLFolderViewModelItemInventory::descendantsPassedFilter(S32 filter_generation)
-{ 
-	if (filter_generation < 0) filter_generation = mRootViewModel->getFilter()->getFirstSuccessGeneration();
-	return mMostFilteredDescendantGeneration >= filter_generation; 
-}
-
-void LLFolderViewModelItemInventory::setPassedFilter(bool passed, bool passed_folder, S32 filter_generation)
-{
-	mPassedFilter = passed;
-	mPassedFolderFilter = passed_folder;
-	mLastFilterGeneration = filter_generation;
-}
-
-bool LLFolderViewModelItemInventory::filterChildItem( LLFolderViewModelItem* item, LLFolderViewFilter& filter )
-{
-	bool passed_filter_before = item->passedFilter();
-	S32 filter_generation = filter.getCurrentGeneration();
-	S32 must_pass_generation = filter.getFirstRequiredGeneration();
-	bool changed = false;
-
-	// mMostFilteredDescendantGeneration might have been reset
-	// in which case we need to update it even for folders that
-	// don't need to be filtered anymore
-	if (item->getLastFilterGeneration() < filter_generation)
-	{
-		if (item->getLastFilterGeneration() >= must_pass_generation && 
-			!item->passedFilter(must_pass_generation))
-		{
-			// failed to pass an earlier filter that was a subset of the current one
-			// go ahead and flag this item as done
-			item->setPassedFilter(false, false, filter_generation);
-		}
-		else
-		{
-			changed |= item->filter( filter );
-		}
-	}
-
-	// track latest generation to pass any child items
-	if (item->passedFilter())
-	{
-		LLFolderViewModelItemInventory* view_model = this;
-		
-		while(view_model && view_model->mMostFilteredDescendantGeneration < filter_generation)
-		{
-			view_model->mMostFilteredDescendantGeneration = filter_generation;
-			view_model = static_cast<LLFolderViewModelItemInventory*>(view_model->mParent);
-		}
-	}
-
-	changed |= (item->passedFilter() != passed_filter_before);
-	if (changed)
-	{
-		//TODO RN: ensure this still happens, but without dependency on folderview
-		LLFolderViewFolder* parent = mFolderViewItem->getParentFolder();
-		if (parent) parent->requestArrange();
-	}
-
-	return changed;
-}
-
-bool LLFolderViewModelItemInventory::filter( LLFolderViewFilter& filter)
-{
-	bool changed = false;
-
-	if(!mChildren.empty()
-		&& (getLastFilterGeneration() < filter.getFirstRequiredGeneration() // haven't checked descendants against minimum required generation to pass
-			|| descendantsPassedFilter(filter.getFirstRequiredGeneration()))) // or at least one descendant has passed the minimum requirement
-	{
-		// now query children
-		for (child_list_t::iterator iter = mChildren.begin();
-			iter != mChildren.end() && filter.getFilterCount() > 0;
-			++iter)
-		{
-			changed |= filterChildItem((*iter), filter);
-		}
-	}
-
-	// if we didn't use all filter iterations
-	// that means we filtered all of our descendants
-	// so filter ourselves now
-	if (filter.getFilterCount() > 0)
-	{
-		const BOOL previous_passed_filter = mPassedFilter;
-		const BOOL passed_filter = filter.check(this);
-		const BOOL passed_filter_folder = (getInventoryType() == LLInventoryType::IT_CATEGORY) 
-								? filter.checkFolder(this)
-								: true;
-
-		// If our visibility will change as a result of this filter, then
-		// we need to be rearranged in our parent folder
-		LLFolderViewFolder* parent_folder = mFolderViewItem->getParentFolder();
-		if (parent_folder && passed_filter != previous_passed_filter)
-		{
-			parent_folder->requestArrange();
-		}
-	
-		setPassedFilter(passed_filter, passed_filter_folder, filter.getCurrentGeneration());
-		//TODO RN: create interface for string highlighting
-		//mStringMatchOffset = filter.getStringMatchOffset(this);
-		filter.decrementFilterCount();
-	}
-	return changed;
-}
-
-LLFolderViewModelInventory* LLInventoryPanel::getFolderViewModel()
-{
-	return &mInventoryViewModel;
-}
-
-
-const LLFolderViewModelInventory* LLInventoryPanel::getFolderViewModel() const
-{
-	return &mInventoryViewModel;
-}
-
diff --git a/indra/newview/llinventorypanel.h b/indra/newview/llinventorypanel.h
index 3195d9a3694..a62b97aa7d3 100644
--- a/indra/newview/llinventorypanel.h
+++ b/indra/newview/llinventorypanel.h
@@ -31,6 +31,7 @@
 #include "llassetstorage.h"
 #include "lldarray.h"
 #include "llfolderviewitem.h"
+#include "llfolderviewmodelinventory.h"
 #include "llfloater.h"
 #include "llinventory.h"
 #include "llinventoryfilter.h"
@@ -42,83 +43,6 @@
 class LLInvFVBridge;
 class LLInventoryFVBridgeBuilder;
 class LLInvPanelComplObserver;
-class LLFolderViewModelInventory;
-
-class LLFolderViewModelItemInventory
-	:	public LLFolderViewModelItemCommon
-{
-public:
-	LLFolderViewModelItemInventory()
-	:	mRootViewModel(NULL)
-	{}
-	void setRootViewModel(LLFolderViewModelInventory* root_view_model)
-	{
-		mRootViewModel = root_view_model;
-	}
-	virtual const LLUUID& getUUID() const = 0;
-	virtual time_t getCreationDate() const = 0;	// UTC seconds
-	virtual void setCreationDate(time_t creation_date_utc) = 0;
-	virtual PermissionMask getPermissionMask() const = 0;
-	virtual LLFolderType::EType getPreferredType() const = 0;
-	virtual void showProperties(void) = 0;
-	virtual BOOL isItemInTrash( void) const { return FALSE; } // TODO: make   into pure virtual.
-	virtual BOOL isUpToDate() const = 0;
-	virtual bool hasChildren() const = 0;
-	virtual LLInventoryType::EType getInventoryType() const = 0;
-	virtual void performAction(LLInventoryModel* model, std::string action)   = 0;
-	virtual LLWearableType::EType getWearableType() const = 0;
-	virtual EInventorySortGroup getSortGroup() const = 0;
-	virtual LLInventoryObject* getInventoryObject() const = 0;
-	virtual void requestSort();
-	virtual bool potentiallyVisible();
-	virtual bool passedFilter(S32 filter_generation = -1);
-	virtual bool descendantsPassedFilter(S32 filter_generation = -1);
-	virtual void setPassedFilter(bool filtered, bool filtered_folder, S32 filter_generation);
-	virtual bool filter( LLFolderViewFilter& filter);
-	virtual bool filterChildItem( LLFolderViewModelItem* item, LLFolderViewFilter& filter);
-protected:
-	LLFolderViewModelInventory* mRootViewModel;
-};
-
-class LLInventorySort
-{
-public:
-	LLInventorySort(U32 order = 0)
-	:	mSortOrder(order),
-		mByDate(false),
-		mSystemToTop(false),
-		mFoldersByName(false)
-	{
-		mByDate = (order & LLInventoryFilter::SO_DATE);
-		mSystemToTop = (order & LLInventoryFilter::SO_SYSTEM_FOLDERS_TO_TOP);
-		mFoldersByName = (order & LLInventoryFilter::SO_FOLDERS_BY_NAME);
-	}
-
-	bool isByDate() const { return mByDate; }
-	U32 getSortOrder() const { return mSortOrder; }
-
-	bool operator()(const LLFolderViewModelItemInventory* const& a, const LLFolderViewModelItemInventory* const& b) const;
-private:
-	U32  mSortOrder;
-	bool mByDate;
-	bool mSystemToTop;
-	bool mFoldersByName;
-};
-
-class LLFolderViewModelInventory
-	:	public LLFolderViewModel<LLInventorySort,   LLFolderViewModelItemInventory, LLFolderViewModelItemInventory,   LLInventoryFilter>
-{
-public:
-	typedef LLFolderViewModel<LLInventorySort,   LLFolderViewModelItemInventory, LLFolderViewModelItemInventory,   LLInventoryFilter> base_t;
-
-	virtual ~LLFolderViewModelInventory() {}
-
-	void sort(LLFolderViewFolder* folder);
-
-	bool contentsReady();
-
-};
-
 
 class LLInventoryPanel : public LLPanel
 {
diff --git a/indra/newview/lltexturectrl.cpp b/indra/newview/lltexturectrl.cpp
index 61a0331b72a..4a9e106687c 100644
--- a/indra/newview/lltexturectrl.cpp
+++ b/indra/newview/lltexturectrl.cpp
@@ -623,9 +623,9 @@ void LLFloaterTexturePicker::draw()
 		LLFolderView* folder_view = mInventoryPanel->getRootFolder();
 		if (!folder_view) return;
 
-		LLInventoryFilter* filter = static_cast<LLFolderViewModelInventory*>(folder_view->getFolderViewModel())->getFilter();
+		LLFolderViewFilter* filter = static_cast<LLFolderViewModelInventory*>(folder_view->getFolderViewModel())->getFilter();
 
-		bool is_filter_active = folder_view->getLastFilterGeneration() < filter->getCurrentGeneration() &&
+		bool is_filter_active = folder_view->getViewModelItem()->getLastFilterGeneration() < filter->getCurrentGeneration() &&
 				filter->isNotDefault();
 
 		// After inventory panel filter is applied we have to update
-- 
GitLab