From 40b476dbb2aee494d57dce81427b74ac19b1428d Mon Sep 17 00:00:00 2001
From: Sergei Litovchuk <slitovchuk@productengine.com>
Date: Sat, 29 May 2010 00:06:34 +0300
Subject: [PATCH] EXT-7198, EXT-7491 FIXED Added mutli-selection support for
 outfit items and outfit tabs selection support. - Added selecting multiple
 items from more than one accordion tab. - Integrated context menus from
 Vadim's patch for EXT-6726 Appearance SP menus (WIP tier 2). - Added
 selection to accordion control to use it instead of accordion tab focus in
 cases when focus is lost and outfit tab should stay selected. - Changed
 "Wear" button behavior: "Wear" puts on currently selected outfit (as the
 tooltip reads "Wear selected outfit"). There is always an  outfit selected
 when the accordion is focused so for now there are no cases when only some
 items are selected. Separate items can be worn from context menu. - Added
 moving accordion tab selection with right click. Fixed (EXT-7491) Right click
 on an accordion title should move selection to it.

Reviewed by Neal Orman at https://codereview.productengine.com/secondlife/r/437/

--HG--
branch : product-engine
---
 indra/llui/llaccordionctrl.cpp                | 24 ++++++
 indra/llui/llaccordionctrl.h                  |  1 +
 indra/llui/llaccordionctrltab.cpp             | 29 ++++++-
 indra/llui/llaccordionctrltab.h               |  7 ++
 indra/newview/llinventoryitemslist.h          |  3 +
 indra/newview/lloutfitslist.cpp               | 78 +++++++++++++++----
 indra/newview/lloutfitslist.h                 |  7 +-
 .../default/xui/en/outfit_accordion_tab.xml   |  2 +
 8 files changed, 134 insertions(+), 17 deletions(-)

diff --git a/indra/llui/llaccordionctrl.cpp b/indra/llui/llaccordionctrl.cpp
index 5d1d57cbb28..8e0245c451c 100644
--- a/indra/llui/llaccordionctrl.cpp
+++ b/indra/llui/llaccordionctrl.cpp
@@ -65,6 +65,7 @@ LLAccordionCtrl::LLAccordionCtrl(const Params& params):LLPanel(params)
  , mFitParent(params.fit_parent)
  , mAutoScrolling( false )
  , mAutoScrollRate( 0.f )
+ , mSelectedTab( NULL )
 {
   mSingleExpansion = params.single_expansion;
 	if(mFitParent && !mSingleExpansion)
@@ -76,6 +77,7 @@ LLAccordionCtrl::LLAccordionCtrl(const Params& params):LLPanel(params)
 LLAccordionCtrl::LLAccordionCtrl() : LLPanel()
  , mAutoScrolling( false )
  , mAutoScrollRate( 0.f )
+ , mSelectedTab( NULL )
 {
 	mSingleExpansion = false;
 	mFitParent = false;
@@ -689,6 +691,28 @@ S32	LLAccordionCtrl::notifyParent(const LLSD& info)
 			}
 			return 0;
 		}
+		else if(str_action == "select_current")
+		{
+			for(size_t i=0;i<mAccordionTabs.size();++i)
+			{
+				// Set selection to the currently focused tab.
+				if(mAccordionTabs[i]->hasFocus())
+				{
+					if (mAccordionTabs[i] != mSelectedTab)
+					{
+						if (mSelectedTab)
+						{
+							mSelectedTab->setSelected(false);
+						}
+						mSelectedTab = mAccordionTabs[i];
+						mSelectedTab->setSelected(true);
+					}
+
+					return 1;
+				}
+			}
+			return 0;
+		}
 	}
 	else if (info.has("scrollToShowRect"))
 	{
diff --git a/indra/llui/llaccordionctrl.h b/indra/llui/llaccordionctrl.h
index ab7d6548ca8..a029201c90d 100644
--- a/indra/llui/llaccordionctrl.h
+++ b/indra/llui/llaccordionctrl.h
@@ -130,6 +130,7 @@ class LLAccordionCtrl: public LLPanel
 	bool			mFitParent;
 	bool			mAutoScrolling;
 	F32				mAutoScrollRate;
+	LLAccordionCtrlTab* mSelectedTab;
 };
 
 
diff --git a/indra/llui/llaccordionctrltab.cpp b/indra/llui/llaccordionctrltab.cpp
index b09c108ec3c..83e67980a3c 100644
--- a/indra/llui/llaccordionctrltab.cpp
+++ b/indra/llui/llaccordionctrltab.cpp
@@ -76,6 +76,8 @@ class LLAccordionCtrlTab::LLAccordionCtrlTabHeader : public LLUICtrl
 	std::string getTitle();
 	void	setTitle(const std::string& title, const std::string& hl);
 
+	void	setSelected(bool is_selected) { mIsSelected = is_selected; }
+
 	virtual void onMouseEnter(S32 x, S32 y, MASK mask);
 	virtual void onMouseLeave(S32 x, S32 y, MASK mask);
 	virtual BOOL handleKey(KEY key, MASK mask, BOOL called_from_parent);
@@ -103,6 +105,7 @@ class LLAccordionCtrlTab::LLAccordionCtrlTabHeader : public LLUICtrl
 	LLUIColor mHeaderBGColor;
 
 	bool mNeedsHighlight;
+	bool mIsSelected;
 
 	LLFrameTimer mAutoOpenTimer;
 };
@@ -115,7 +118,8 @@ LLAccordionCtrlTab::LLAccordionCtrlTabHeader::LLAccordionCtrlTabHeader(
 	const LLAccordionCtrlTabHeader::Params& p)
 : LLUICtrl(p)
 , mHeaderBGColor(p.header_bg_color())
-,mNeedsHighlight(false),
+, mNeedsHighlight(false)
+, mIsSelected(false),
 	mImageCollapsed(p.header_collapse_img),
 	mImageCollapsedPressed(p.header_collapse_img_pressed),
 	mImageExpanded(p.header_expand_img),
@@ -187,7 +191,7 @@ void LLAccordionCtrlTab::LLAccordionCtrlTabHeader::draw()
 	// Only show green "focus" background image if the accordion is open,
 	// because the user's mental model of focus is that it goes away after
 	// the accordion is closed.
-	if (getParent()->hasFocus()
+	if (getParent()->hasFocus() || mIsSelected
 		/*&& !(collapsible && !expanded)*/ // WHY??
 		)
 	{
@@ -301,6 +305,7 @@ LLAccordionCtrlTab::Params::Params()
 	,header_image_focused("header_image_focused")
 	,header_text_color("header_text_color")
 	,fit_panel("fit_panel",true)
+	,selection_enabled("selection_enabled", false)
 {
 	mouse_opaque(false);
 }
@@ -331,6 +336,11 @@ LLAccordionCtrlTab::LLAccordionCtrlTab(const LLAccordionCtrlTab::Params&p)
 	mHeader = LLUICtrlFactory::create<LLAccordionCtrlTabHeader>(headerParams);
 	addChild(mHeader, 1);
 
+	if (p.selection_enabled)
+	{
+		LLFocusableElement::setFocusReceivedCallback(boost::bind(&LLAccordionCtrlTab::selectOnFocusReceived, this));
+	}
+
 	reshape(100, 200,FALSE);
 }
 
@@ -498,6 +508,15 @@ boost::signals2::connection LLAccordionCtrlTab::setFocusLostCallback(const focus
 	return boost::signals2::connection();
 }
 
+void LLAccordionCtrlTab::setSelected(bool is_selected)
+{
+	LLAccordionCtrlTabHeader* header = findChild<LLAccordionCtrlTabHeader>(DD_HEADER_NAME);
+	if (header)
+	{
+		header->setSelected(is_selected);
+	}
+}
+
 LLView*	LLAccordionCtrlTab::findContainerView()
 {
 	for(child_list_const_iter_t it = getChildList()->begin(); 
@@ -513,6 +532,11 @@ LLView*	LLAccordionCtrlTab::findContainerView()
 	return NULL;
 }
 
+void LLAccordionCtrlTab::selectOnFocusReceived()
+{
+	if (getParent()) // A parent may not be set if tabs are added dynamically.
+		getParent()->notifyParent(LLSD().with("action", "select_current"));
+}
 
 S32 LLAccordionCtrlTab::getHeaderHeight()
 {
@@ -713,6 +737,7 @@ void LLAccordionCtrlTab::showAndFocusHeader()
 {
 	LLAccordionCtrlTabHeader* header = getChild<LLAccordionCtrlTabHeader>(DD_HEADER_NAME);	
 	header->setFocus(true);
+	header->setSelected(true);
 
 	LLRect screen_rc;
 	LLRect selected_rc = header->getRect();
diff --git a/indra/llui/llaccordionctrltab.h b/indra/llui/llaccordionctrltab.h
index f5b7fd0af6e..83a9024a74a 100644
--- a/indra/llui/llaccordionctrltab.h
+++ b/indra/llui/llaccordionctrltab.h
@@ -88,6 +88,8 @@ class LLAccordionCtrlTab : public LLUICtrl
 
 		Optional<bool>			fit_panel;
 
+		Optional<bool>			selection_enabled;
+
 		Optional<S32>			padding_left;
 		Optional<S32>			padding_right;
 		Optional<S32>			padding_top;
@@ -121,6 +123,8 @@ class LLAccordionCtrlTab : public LLUICtrl
 	boost::signals2::connection setFocusReceivedCallback(const focus_signal_t::slot_type& cb);
 	boost::signals2::connection setFocusLostCallback(const focus_signal_t::slot_type& cb);
 
+	void setSelected(bool is_selected);
+
 	bool getCollapsible() {return mCollapsible;};
 
 	void setCollapsible(bool collapsible) {mCollapsible = collapsible;};
@@ -199,6 +203,9 @@ class LLAccordionCtrlTab : public LLUICtrl
 	void drawChild(const LLRect& root_rect,LLView* child);
 
 	LLView* findContainerView	();
+
+	void selectOnFocusReceived();
+
 private:
 
 	class LLAccordionCtrlTabHeader;
diff --git a/indra/newview/llinventoryitemslist.h b/indra/newview/llinventoryitemslist.h
index 807952948b9..03ad7c21845 100644
--- a/indra/newview/llinventoryitemslist.h
+++ b/indra/newview/llinventoryitemslist.h
@@ -133,6 +133,9 @@ class LLPanelInventoryListItemBase : public LLPanel
 	/** Get the description of a corresponding inventory item */
 	const std::string& getDescription() const { return mItem->getDescription(); }
 
+	/** Get the associated inventory item */
+	LLViewerInventoryItem* getItem() const { return mItem; }
+
 	virtual ~LLPanelInventoryListItemBase(){}
 
 protected:
diff --git a/indra/newview/lloutfitslist.cpp b/indra/newview/lloutfitslist.cpp
index 17a2db7a433..8f189a1e9f6 100644
--- a/indra/newview/lloutfitslist.cpp
+++ b/indra/newview/lloutfitslist.cpp
@@ -82,7 +82,6 @@ LLOutfitsList::LLOutfitsList()
 	:	LLPanel()
 	,	mAccordion(NULL)
 	,	mListCommands(NULL)
-	,	mSelectedList(NULL)
 {
 	mCategoriesObserver = new LLInventoryCategoriesObserver();
 	gInventory.addObserver(mCategoriesObserver);
@@ -208,6 +207,8 @@ void LLOutfitsList::refreshList(const LLUUID& category_id)
 		// Setting list refresh callback to apply filter on list change.
 		list->setRefreshCompleteCallback(boost::bind(&LLOutfitsList::onFilteredWearableItemsListRefresh, this, _1));
 
+		list->setRightMouseDownCallback(boost::bind(&LLOutfitsList::onWearableItemsListRightClick, this, _1, _2, _3));
+
 		// Fetch the new outfit contents.
 		cat->fetch();
 
@@ -237,23 +238,27 @@ void LLOutfitsList::refreshList(const LLUUID& category_id)
 		outfits_map_t::iterator outfits_iter = mOutfitsMap.find((*iter));
 		if (outfits_iter != mOutfitsMap.end())
 		{
-			// An outfit is removed from the list. Do the following:
-			// 1. Remove outfit accordion tab from accordion.
-			mAccordion->removeCollapsibleCtrl(outfits_iter->second);
-
 			const LLUUID& outfit_id = outfits_iter->first;
+			LLAccordionCtrlTab* tab = outfits_iter->second;
 
-			// 2. Remove outfit category from observer to stop monitoring its changes.
+			// An outfit is removed from the list. Do the following:
+			// 1. Remove outfit category from observer to stop monitoring its changes.
 			mCategoriesObserver->removeCategory(outfit_id);
 
-			// 3. Reset selection if selected outfit is being removed.
-			if (mSelectedOutfitUUID == outfit_id)
+			// 2. Remove selected lists map entry.
+			mSelectedListsMap.erase(outfit_id);
+
+			// 3. Reset currently selected outfit id if it is being removed.
+			if (outfit_id == mSelectedOutfitUUID)
 			{
-				changeOutfitSelection(NULL, LLUUID());
+				mSelectedOutfitUUID = LLUUID();
 			}
 
 			// 4. Remove category UUID to accordion tab mapping.
 			mOutfitsMap.erase(outfits_iter);
+
+			// 5. Remove outfit tab from accordion.
+			mAccordion->removeCollapsibleCtrl(tab);
 		}
 	}
 
@@ -283,6 +288,8 @@ void LLOutfitsList::onSelectionChange(LLUICtrl* ctrl)
 
 void LLOutfitsList::performAction(std::string action)
 {
+	if (mSelectedOutfitUUID.isNull()) return;
+
 	LLViewerInventoryCategory* cat = gInventory.getCategory(mSelectedOutfitUUID);
 	if (!cat) return;
 
@@ -367,14 +374,28 @@ void LLOutfitsList::updateOutfitTab(const LLUUID& category_id)
 
 void LLOutfitsList::changeOutfitSelection(LLWearableItemsList* list, const LLUUID& category_id)
 {
-	// Reset selection in previously selected tab
-	// if a new one is selected.
-	if (list && mSelectedList && mSelectedList != list)
+	MASK mask = gKeyboard->currentMask(TRUE);
+
+	// Reset selection in all previously selected tabs except for the current
+	// if new selection is started.
+	if (list && !(mask & MASK_CONTROL))
 	{
-		mSelectedList->resetSelection();
+		for (wearables_lists_map_t::iterator iter = mSelectedListsMap.begin();
+				iter != mSelectedListsMap.end();
+				++iter)
+		{
+			LLWearableItemsList* selected_list = (*iter).second;
+			if (selected_list != list)
+			{
+				selected_list->resetSelection();
+			}
+		}
+
+		// Clear current selection.
+		mSelectedListsMap.clear();
 	}
 
-	mSelectedList = list;
+	mSelectedListsMap.insert(wearables_lists_map_value_t(category_id, list));
 	mSelectedOutfitUUID = category_id;
 }
 
@@ -494,6 +515,13 @@ void LLOutfitsList::onAccordionTabRightClick(LLUICtrl* ctrl, S32 x, S32 y, const
 		S32 header_bottom = tab->getLocalRect().getHeight() - tab->getHeaderHeight();
 		if(y >= header_bottom)
 		{
+			// Focus tab header to trigger tab selection change.
+			LLUICtrl* header = tab->findChild<LLUICtrl>("dd_header");
+			if (header)
+			{
+				header->setFocus(TRUE);
+			}
+
 			uuid_vec_t selected_uuids;
 			selected_uuids.push_back(cat_id);
 			mOutfitMenu->show(ctrl, selected_uuids, x, y);
@@ -501,4 +529,26 @@ void LLOutfitsList::onAccordionTabRightClick(LLUICtrl* ctrl, S32 x, S32 y, const
 	}
 }
 
+void LLOutfitsList::onWearableItemsListRightClick(LLUICtrl* ctrl, S32 x, S32 y)
+{
+	LLWearableItemsList* list = dynamic_cast<LLWearableItemsList*>(ctrl);
+	if (!list) return;
+
+	uuid_vec_t selected_uuids;
+
+	// Collect seleted items from all selected lists.
+	for (wearables_lists_map_t::iterator iter = mSelectedListsMap.begin();
+			iter != mSelectedListsMap.end();
+			++iter)
+	{
+		uuid_vec_t uuids;
+		(*iter).second->getSelectedUUIDs(uuids);
+
+		S32 prev_size = selected_uuids.size();
+		selected_uuids.resize(prev_size + uuids.size());
+		std::copy(uuids.begin(), uuids.end(), selected_uuids.begin() + prev_size);
+	}
+
+	LLWearableItemsList::ContextMenu::instance().show(list, selected_uuids, x, y);
+}
 // EOF
diff --git a/indra/newview/lloutfitslist.h b/indra/newview/lloutfitslist.h
index b6b3d6ae468..1da7360c2ef 100644
--- a/indra/newview/lloutfitslist.h
+++ b/indra/newview/lloutfitslist.h
@@ -108,12 +108,17 @@ class LLOutfitsList : public LLPanel, public LLInventoryObserver
 
 	void onAccordionTabRightClick(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& cat_id);
 
+	void onWearableItemsListRightClick(LLUICtrl* ctrl, S32 x, S32 y);
+
 	LLInventoryCategoriesObserver* 	mCategoriesObserver;
 
 	LLAccordionCtrl*				mAccordion;
 	LLPanel*						mListCommands;
 
-	LLWearableItemsList*			mSelectedList;
+	typedef	std::map<LLUUID, LLWearableItemsList*>		wearables_lists_map_t;
+	typedef wearables_lists_map_t::value_type			wearables_lists_map_value_t;
+	wearables_lists_map_t			mSelectedListsMap;
+
 	LLUUID							mSelectedOutfitUUID;
 
 	std::string 					mFilterSubString;
diff --git a/indra/newview/skins/default/xui/en/outfit_accordion_tab.xml b/indra/newview/skins/default/xui/en/outfit_accordion_tab.xml
index 066992b25da..44437d01ebb 100644
--- a/indra/newview/skins/default/xui/en/outfit_accordion_tab.xml
+++ b/indra/newview/skins/default/xui/en/outfit_accordion_tab.xml
@@ -8,6 +8,7 @@
  height="45"
  layout="topleft"
  name="Mockup Tab"
+ selection_enabled="true"
  title="Mockup Tab"
  translate="false"
  width="0">
@@ -18,5 +19,6 @@
      multi_select="true"
      name="wearable_items_list"
      translate="false"
+     use_internal_context_menu="false"
     />
 </accordion_tab>
-- 
GitLab