From c9101f603a7969f4d46a7d8d911fa8805d926548 Mon Sep 17 00:00:00 2001
From: Eugene Mutavchi <emutavchi@productengine.com>
Date: Fri, 20 Nov 2009 20:27:53 +0200
Subject: Implemented low task EXT-1153(FlatListView should support keyboard)

--HG--
branch : product-engine
---
 indra/llui/llflatlistview.cpp       | 241 ++++++++++++++++++++++++++++++++++++
 indra/llui/llflatlistview.h         |  25 ++++
 indra/newview/llpanelpeople.cpp     |   7 ++
 indra/newview/llparticipantlist.cpp |   3 +
 4 files changed, 276 insertions(+)

(limited to 'indra')

diff --git a/indra/llui/llflatlistview.cpp b/indra/llui/llflatlistview.cpp
index 19f203b80c..8de3a8a96f 100644
--- a/indra/llui/llflatlistview.cpp
+++ b/indra/llui/llflatlistview.cpp
@@ -94,6 +94,9 @@ bool LLFlatListView::addItem(LLPanel * item, const LLSD& value /*= LLUUID::null*
 	item->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4));
 	item->setRightMouseDownCallback(boost::bind(&LLFlatListView::onItemRightMouseClick, this, new_pair, _4));
 
+	// Children don't accept the focus
+	item->setTabStop(false);
+
 	rearrangeItems();
 	notifyParentItemsRectChanged();
 	return true;
@@ -282,6 +285,9 @@ void LLFlatListView::resetSelection(bool no_commit_on_deselection /*= false*/)
 	{
 		onCommit();
 	}
+
+	// Stretch selected items rect to ensure it won't be clipped
+	mSelectedItemsBorder->setRect(getSelectedItemsRect().stretch(-1));
 }
 
 void LLFlatListView::setNoItemsCommentText(const std::string& comment_text)
@@ -381,8 +387,34 @@ LLFlatListView::LLFlatListView(const LLFlatListView::Params& p)
 	//we don't need to stretch in vertical direction on reshaping by a parent
 	//no bottom following!
 	mItemsPanel->setFollows(FOLLOWS_LEFT | FOLLOWS_RIGHT | FOLLOWS_TOP);
+
+	LLViewBorder::Params params;
+	params.name("scroll border");
+	params.rect(getSelectedItemsRect());
+	params.visible(false);
+	params.bevel_style(LLViewBorder::BEVEL_IN);
+	mSelectedItemsBorder = LLUICtrlFactory::create<LLViewBorder> (params);
+	mItemsPanel->addChild( mSelectedItemsBorder );
 };
 
+// virtual
+void LLFlatListView::draw()
+{
+	// Highlight border if a child of this container has keyboard focus
+	if( mSelectedItemsBorder->getVisible() )
+	{
+		mSelectedItemsBorder->setKeyboardFocusHighlight( hasFocus() );
+	}
+	LLScrollContainer::draw();
+}
+
+// virtual
+BOOL LLFlatListView::postBuild()
+{
+	setTabStop(true);
+	return LLScrollContainer::postBuild();
+}
+
 void LLFlatListView::rearrangeItems()
 {
 	static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
@@ -444,6 +476,9 @@ void LLFlatListView::rearrangeItems()
 		// move top for next item in list
 		item_new_top -= (rc.getHeight() + mItemPad);
 	}
+
+	// Stretch selected items rect to ensure it won't be clipped
+	mSelectedItemsBorder->setRect(getSelectedItemsRect().stretch(-1));
 }
 
 void LLFlatListView::onItemMouseClick(item_pair_t* item_pair, MASK mask)
@@ -473,6 +508,64 @@ void LLFlatListView::onItemRightMouseClick(item_pair_t* item_pair, MASK mask)
 	onItemMouseClick(item_pair, mask);
 }
 
+BOOL LLFlatListView::handleKeyHere(KEY key, MASK mask)
+{
+	BOOL reset_selection = (mask != MASK_SHIFT);
+	BOOL handled = FALSE;
+	switch (key)
+	{
+		case KEY_RETURN:
+		{
+			if (mSelectedItemPairs.size() && mask == MASK_NONE)
+			{
+				mOnReturnSignal(this, getValue());
+				handled = TRUE;
+			}
+			break;
+		}
+		case KEY_UP:
+		{
+			if ( !selectNextItemPair(true, reset_selection) && reset_selection)
+			{
+				// If case we are in accordion tab notify parent to go to the previous accordion
+				notifyParent(LLSD().insert("action","select_prev"));
+			}
+			break;
+		}
+		case KEY_DOWN:
+		{
+			if ( !selectNextItemPair(false, reset_selection) && reset_selection)
+			{
+				// If case we are in accordion tab notify parent to go to the next accordion
+				notifyParent(LLSD().insert("action","select_next"));
+			}
+			break;
+		}
+		case 'A':
+		{
+			if(MASK_CONTROL & mask)
+			{
+				selectAll();
+				handled = TRUE;
+			}
+			break;
+		}
+		default:
+			break;
+	}
+
+	if ( key == KEY_UP || key == KEY_DOWN )
+	{
+		LLRect selcted_rect = getLastSelectedItemRect().stretch(1);
+		LLRect visible_rect = getVisibleContentRect();
+		if ( !visible_rect.contains (selcted_rect) )
+			scrollToShowRect(selcted_rect);
+		handled = TRUE;
+	}
+
+	return handled ? handled : LLScrollContainer::handleKeyHere(key, mask);
+}
+
 LLFlatListView::item_pair_t* LLFlatListView::getItemPair(LLPanel* item) const
 {
 	llassert(item);
@@ -552,6 +645,143 @@ bool LLFlatListView::selectItemPair(item_pair_t* item_pair, bool select)
 		onCommit();
 	}
 
+	setFocus(TRUE);
+
+	// Stretch selected items rect to ensure it won't be clipped
+	mSelectedItemsBorder->setRect(getSelectedItemsRect().stretch(-1));
+
+	return true;
+}
+
+LLRect LLFlatListView::getLastSelectedItemRect()
+{
+	if (!mSelectedItemPairs.size())
+	{
+		return LLRect::null;
+	}
+
+	return mSelectedItemPairs.back()->first->getRect();
+}
+
+LLRect LLFlatListView::getSelectedItemsRect()
+{
+	if (!mSelectedItemPairs.size())
+	{
+		return LLRect::null;
+	}
+	LLRect rc = getLastSelectedItemRect();
+	for ( pairs_const_iterator_t
+			  it = mSelectedItemPairs.begin(),
+			  it_end = mSelectedItemPairs.end();
+		  it != it_end; ++it )
+	{
+		rc.unionWith((*it)->first->getRect());
+	}
+	return rc;
+}
+
+// virtual
+bool LLFlatListView::selectNextItemPair(bool is_up_direction, bool reset_selection)
+{
+	// No items - no actions!
+	if ( !mItemPairs.size() )
+		return false;
+
+	item_pair_t* cur_sel_pair = NULL;
+	item_pair_t* to_sel_pair = NULL;
+
+	if ( mSelectedItemPairs.size() )
+	{
+		// Take the last selected pair
+		cur_sel_pair = mSelectedItemPairs.back();
+	}
+	else
+	{
+		// If there weren't selected items then choose the first one bases on given direction
+		cur_sel_pair = (is_up_direction) ? mItemPairs.back() : mItemPairs.front();
+		// Force selection to first item
+		to_sel_pair = cur_sel_pair;
+	}
+
+	// Bases on given direction choose next item to select
+	if ( is_up_direction )
+	{
+		// Find current selected item position in mItemPairs list
+		pairs_list_t::reverse_iterator sel_it = std::find(mItemPairs.rbegin(), mItemPairs.rend(), cur_sel_pair);
+
+		for (;++sel_it != mItemPairs.rend();)
+		{
+			// skip invisible items
+			if ( (*sel_it)->first->getVisible() )
+			{
+				to_sel_pair = *sel_it;
+				break;
+			}
+		}
+	}
+	else
+	{
+		// Find current selected item position in mItemPairs list
+		pairs_list_t::iterator sel_it = std::find(mItemPairs.begin(), mItemPairs.end(), cur_sel_pair);
+
+		for (;++sel_it != mItemPairs.end();)
+		{
+			// skip invisible items
+			if ( (*sel_it)->first->getVisible() )
+			{
+				to_sel_pair = *sel_it;
+				break;
+			}
+		}
+	}
+
+	if ( to_sel_pair )
+	{
+		bool select = true;
+
+		if ( reset_selection )
+		{
+			// Reset current selection if we were asked about it
+			resetSelection();
+		}
+		else
+		{
+			// If item already selected and no reset request than we should deselect last selected item.
+			select = (mSelectedItemPairs.end() == std::find(mSelectedItemPairs.begin(), mSelectedItemPairs.end(), to_sel_pair));
+		}
+
+		// Select/Deselect next item
+		selectItemPair(select ? to_sel_pair : cur_sel_pair, select);
+
+		return true;
+	}
+	return false;
+}
+
+bool LLFlatListView::selectAll()
+{
+	if (!mAllowSelection)
+		return false;
+
+	mSelectedItemPairs.clear();
+
+	for (pairs_const_iterator_t it= mItemPairs.begin(); it != mItemPairs.end(); ++it)
+	{
+		item_pair_t* item_pair = *it;
+		mSelectedItemPairs.push_back(item_pair);
+		//a way of notifying panel of selection state changes
+		LLPanel* item = item_pair->first;
+		item->setValue(SELECTED_EVENT);
+	}
+
+	if (mCommitOnSelectionChange)
+	{
+		onCommit();
+	}
+
+	// Stretch selected items rect to ensure it won't be clipped
+	mSelectedItemsBorder->setRect(getSelectedItemsRect().stretch(-1));
+
 	return true;
 }
 
@@ -670,4 +900,15 @@ void LLFlatListView::getValues(std::vector<LLSD>& values) const
 	}
 }
 
+// virtual
+void LLFlatListView::onFocusReceived()
+{
+	mSelectedItemsBorder->setVisible(TRUE);
+}
+// virtual
+void LLFlatListView::onFocusLost()
+{
+	mSelectedItemsBorder->setVisible(FALSE);
+}
+
 //EOF
diff --git a/indra/llui/llflatlistview.h b/indra/llui/llflatlistview.h
index 97772bc677..eac947a0d7 100644
--- a/indra/llui/llflatlistview.h
+++ b/indra/llui/llflatlistview.h
@@ -113,6 +113,10 @@ public:
 	
 	virtual ~LLFlatListView() { clear(); };
 
+	/**
+	 * Connects callback to signal called when Return key is pressed.
+	 */
+	boost::signals2::connection setReturnCallback( const commit_signal_t::slot_type& cb ) { return mOnReturnSignal.connect(cb); }
 
 	/** Overridden LLPanel's reshape, height is ignored, the list sets its height to accommodate all items */
 	virtual void reshape(S32 width, S32 height, BOOL called_from_parent  = TRUE);
@@ -318,6 +322,10 @@ protected:
 
 	virtual bool selectItemPair(item_pair_t* item_pair, bool select);
 
+	virtual bool selectNextItemPair(bool is_up_direction, bool reset_selection);
+
+	virtual bool selectAll();
+
 	virtual bool isSelected(item_pair_t* item_pair) const;
 
 	virtual bool removeItemPair(item_pair_t* item_pair);
@@ -331,6 +339,19 @@ protected:
 	 */
 	void notifyParentItemsRectChanged();
 
+	virtual BOOL handleKeyHere(KEY key, MASK mask);
+
+	virtual BOOL postBuild();
+
+	virtual void onFocusReceived();
+
+	virtual void onFocusLost();
+
+	virtual void draw();
+
+	LLRect getLastSelectedItemRect();
+
+	LLRect getSelectedItemsRect();
 
 private:
 
@@ -381,6 +402,10 @@ private:
 	LLRect mPrevNotifyParentRect;
 
 	LLTextBox* mNoItemsCommentTextbox;
+
+	LLViewBorder* mSelectedItemsBorder;
+
+	commit_signal_t	mOnReturnSignal;
 };
 
 #endif
diff --git a/indra/newview/llpanelpeople.cpp b/indra/newview/llpanelpeople.cpp
index 4dc8872557..29f7cc1851 100644
--- a/indra/newview/llpanelpeople.cpp
+++ b/indra/newview/llpanelpeople.cpp
@@ -536,8 +536,15 @@ BOOL LLPanelPeople::postBuild()
 	mNearbyList->setCommitCallback(boost::bind(&LLPanelPeople::onAvatarListCommitted, this, mNearbyList));
 	mRecentList->setCommitCallback(boost::bind(&LLPanelPeople::onAvatarListCommitted, this, mRecentList));
 
+	// Set openning IM as default on return action for avatar lists
+	mOnlineFriendList->setReturnCallback(boost::bind(&LLPanelPeople::onImButtonClicked, this));
+	mAllFriendList->setReturnCallback(boost::bind(&LLPanelPeople::onImButtonClicked, this));
+	mNearbyList->setReturnCallback(boost::bind(&LLPanelPeople::onImButtonClicked, this));
+	mRecentList->setReturnCallback(boost::bind(&LLPanelPeople::onImButtonClicked, this));
+
 	mGroupList->setDoubleClickCallback(boost::bind(&LLPanelPeople::onChatButtonClicked, this));
 	mGroupList->setCommitCallback(boost::bind(&LLPanelPeople::updateButtons, this));
+	mGroupList->setReturnCallback(boost::bind(&LLPanelPeople::onChatButtonClicked, this));
 
 	LLAccordionCtrlTab* accordion_tab = getChild<LLAccordionCtrlTab>("tab_all");
 	accordion_tab->setDropDownStateChangedCallback(
diff --git a/indra/newview/llparticipantlist.cpp b/indra/newview/llparticipantlist.cpp
index 13bd059d45..4ee9cba69c 100644
--- a/indra/newview/llparticipantlist.cpp
+++ b/indra/newview/llparticipantlist.cpp
@@ -65,6 +65,8 @@ LLParticipantList::LLParticipantList(LLSpeakerMgr* data_source, LLAvatarList* av
 	mAvatarList->setNoItemsCommentText(LLTrans::getString("LoadingData"));
 	mAvatarList->setDoubleClickCallback(boost::bind(&LLParticipantList::onAvatarListDoubleClicked, this, mAvatarList));
 	mAvatarList->setRefreshCompleteCallback(boost::bind(&LLParticipantList::onAvatarListRefreshed, this, _1, _2));
+    // Set onAvatarListDoubleClicked as default on_return action.
+	mAvatarList->setReturnCallback(boost::bind(&LLParticipantList::onAvatarListDoubleClicked, this, mAvatarList));
 
 	mParticipantListMenu = new LLParticipantListMenu(*this);
 	mAvatarList->setContextMenu(mParticipantListMenu);
@@ -99,6 +101,7 @@ void LLParticipantList::setSpeakingIndicatorsVisible(BOOL visible)
 
 void LLParticipantList::onAvatarListDoubleClicked(LLAvatarList* list)
 {
+	// NOTE(EM): Should we check if there is multiple selection and start conference if it is so?
 	LLUUID clicked_id = list->getSelectedUUID();
 
 	if (clicked_id.isNull() || clicked_id == gAgent.getID())
-- 
cgit v1.2.3