summaryrefslogtreecommitdiff
path: root/indra/llui/llscrolllistctrl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llui/llscrolllistctrl.cpp')
-rw-r--r--indra/llui/llscrolllistctrl.cpp6896
1 files changed, 3448 insertions, 3448 deletions
diff --git a/indra/llui/llscrolllistctrl.cpp b/indra/llui/llscrolllistctrl.cpp
index 535a880120..1868ac9639 100644
--- a/indra/llui/llscrolllistctrl.cpp
+++ b/indra/llui/llscrolllistctrl.cpp
@@ -1,3448 +1,3448 @@
- /**
- * @file llscrolllistctrl.cpp
- * @brief Scroll lists are composed of rows (items), each of which
- * contains columns (cells).
- *
- * $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 "linden_common.h"
-
-#include "llscrolllistctrl.h"
-
-#include <algorithm>
-
-#include "llstl.h"
-#include "llboost.h"
-//#include "indra_constants.h"
-
-#include "llavatarnamecache.h"
-#include "llcheckboxctrl.h"
-#include "llclipboard.h"
-#include "llfocusmgr.h"
-#include "llgl.h" // LLGLSUIDefault()
-#include "lllocalcliprect.h"
-//#include "llrender.h"
-#include "llresmgr.h"
-#include "llscrollbar.h"
-#include "llscrolllistcell.h"
-#include "llstring.h"
-#include "llui.h"
-#include "lluictrlfactory.h"
-#include "llwindow.h"
-#include "llcontrol.h"
-#include "llkeyboard.h"
-#include "llviewborder.h"
-#include "lltextbox.h"
-#include "llsdparam.h"
-#include "llcachename.h"
-#include "llmenugl.h"
-#include "llurlaction.h"
-#include "lltooltip.h"
-
-#include <boost/bind.hpp>
-
-static LLDefaultChildRegistry::Register<LLScrollListCtrl> r("scroll_list");
-
-// local structures & classes.
-struct SortScrollListItem
-{
- SortScrollListItem(const std::vector<std::pair<S32, bool> >& sort_orders,const LLScrollListCtrl::sort_signal_t* sort_signal, bool alternate_sort)
- : mSortOrders(sort_orders)
- , mSortSignal(sort_signal)
- , mAltSort(alternate_sort)
- {}
-
- bool operator()(const LLScrollListItem* i1, const LLScrollListItem* i2)
- {
- // sort over all columns in order specified by mSortOrders
- S32 sort_result = 0;
- for (sort_order_t::const_reverse_iterator it = mSortOrders.rbegin();
- it != mSortOrders.rend(); ++it)
- {
- S32 col_idx = it->first;
- bool sort_ascending = it->second;
-
- S32 order = sort_ascending ? 1 : -1; // ascending or descending sort for this column?
-
- const LLScrollListCell *cell1 = i1->getColumn(col_idx);
- const LLScrollListCell *cell2 = i2->getColumn(col_idx);
- if (cell1 && cell2)
- {
- if(mSortSignal)
- {
- sort_result = order * (*mSortSignal)(col_idx,i1, i2);
- }
- else
- {
- if (mAltSort && !cell1->getAltValue().asString().empty() && !cell2->getAltValue().asString().empty())
- {
- sort_result = order * LLStringUtil::compareDict(cell1->getAltValue().asString(), cell2->getAltValue().asString());
- }
- else
- {
- sort_result = order * LLStringUtil::compareDict(cell1->getValue().asString(), cell2->getValue().asString());
- }
- }
- if (sort_result != 0)
- {
- break; // we have a sort order!
- }
- }
- }
-
- return sort_result < 0;
- }
-
-
- typedef std::vector<std::pair<S32, bool> > sort_order_t;
- const LLScrollListCtrl::sort_signal_t* mSortSignal;
- const sort_order_t& mSortOrders;
- const bool mAltSort;
-};
-
-//---------------------------------------------------------------------------
-// LLScrollListCtrl
-//---------------------------------------------------------------------------
-
-void LLScrollListCtrl::SelectionTypeNames::declareValues()
-{
- declare("row", LLScrollListCtrl::ROW);
- declare("cell", LLScrollListCtrl::CELL);
- declare("header", LLScrollListCtrl::HEADER);
-}
-
-LLScrollListCtrl::Contents::Contents()
-: columns("column"),
- rows("row")
-{
- addSynonym(columns, "columns");
- addSynonym(rows, "rows");
-}
-
-LLScrollListCtrl::Params::Params()
-: multi_select("multi_select", false),
- has_border("draw_border"),
- draw_heading("draw_heading"),
- search_column("search_column", 0),
- selection_type("selection_type", ROW),
- sort_column("sort_column", -1),
- sort_ascending("sort_ascending", true),
- can_sort("can_sort", true),
- mouse_wheel_opaque("mouse_wheel_opaque", false),
- commit_on_keyboard_movement("commit_on_keyboard_movement", true),
- commit_on_selection_change("commit_on_selection_change", false),
- heading_height("heading_height"),
- page_lines("page_lines", 0),
- background_visible("background_visible"),
- draw_stripes("draw_stripes"),
- column_padding("column_padding"),
- row_padding("row_padding", 2),
- fg_unselected_color("fg_unselected_color"),
- fg_selected_color("fg_selected_color"),
- bg_selected_color("bg_selected_color"),
- fg_disable_color("fg_disable_color"),
- bg_writeable_color("bg_writeable_color"),
- bg_readonly_color("bg_readonly_color"),
- bg_stripe_color("bg_stripe_color"),
- hovered_color("hovered_color"),
- highlighted_color("highlighted_color"),
- contents(""),
- scroll_bar_bg_visible("scroll_bar_bg_visible"),
- scroll_bar_bg_color("scroll_bar_bg_color"),
- border("border")
-{}
-
-LLScrollListCtrl::LLScrollListCtrl(const LLScrollListCtrl::Params& p)
-: LLUICtrl(p),
- mLineHeight(0),
- mScrollLines(0),
- mMouseWheelOpaque(p.mouse_wheel_opaque),
- mPageLines(p.page_lines),
- mMaxSelectable(0),
- mAllowKeyboardMovement(true),
- mCommitOnKeyboardMovement(p.commit_on_keyboard_movement),
- mCommitOnSelectionChange(p.commit_on_selection_change),
- mSelectionChanged(false),
- mSelectionType(p.selection_type),
- mNeedsScroll(false),
- mCanSelect(true),
- mCanSort(p.can_sort),
- mColumnsDirty(false),
- mMaxItemCount(INT_MAX),
- mBorderThickness( 2 ),
- mOnDoubleClickCallback( NULL ),
- mOnMaximumSelectCallback( NULL ),
- mOnSortChangedCallback( NULL ),
- mHighlightedItem(-1),
- mBorder(NULL),
- mSortCallback(NULL),
- mCommentTextView(NULL),
- mNumDynamicWidthColumns(0),
- mTotalStaticColumnWidth(0),
- mTotalColumnPadding(0),
- mSorted(false),
- mDirty(false),
- mOriginalSelection(-1),
- mLastSelected(NULL),
- mHeadingHeight(p.heading_height),
- mAllowMultipleSelection(p.multi_select),
- mDisplayColumnHeaders(p.draw_heading),
- mBackgroundVisible(p.background_visible),
- mDrawStripes(p.draw_stripes),
- mBgWriteableColor(p.bg_writeable_color()),
- mBgReadOnlyColor(p.bg_readonly_color()),
- mBgSelectedColor(p.bg_selected_color()),
- mBgStripeColor(p.bg_stripe_color()),
- mFgSelectedColor(p.fg_selected_color()),
- mFgUnselectedColor(p.fg_unselected_color()),
- mFgDisabledColor(p.fg_disable_color()),
- mHighlightedColor(p.highlighted_color()),
- mHoveredColor(p.hovered_color()),
- mSearchColumn(p.search_column),
- mColumnPadding(p.column_padding),
- mRowPadding(p.row_padding),
- mAlternateSort(false),
- mContextMenuType(MENU_NONE),
- mIsFriendSignal(NULL)
-{
- mItemListRect.setOriginAndSize(
- mBorderThickness,
- mBorderThickness,
- getRect().getWidth() - 2 * mBorderThickness,
- getRect().getHeight() - 2 * mBorderThickness );
-
- updateLineHeight();
-
- // Init the scrollbar
- static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
-
- LLRect scroll_rect;
- scroll_rect.setOriginAndSize(
- getRect().getWidth() - mBorderThickness - scrollbar_size,
- mItemListRect.mBottom,
- scrollbar_size,
- mItemListRect.getHeight());
-
- LLScrollbar::Params sbparams;
- sbparams.name("Scrollbar");
- sbparams.rect(scroll_rect);
- sbparams.orientation(LLScrollbar::VERTICAL);
- sbparams.doc_size(getItemCount());
- sbparams.doc_pos(mScrollLines);
- sbparams.page_size( getLinesPerPage() );
- sbparams.change_callback(boost::bind(&LLScrollListCtrl::onScrollChange, this, _1, _2));
- sbparams.follows.flags(FOLLOWS_RIGHT | FOLLOWS_TOP | FOLLOWS_BOTTOM);
- sbparams.visible(false);
- sbparams.bg_visible(p.scroll_bar_bg_visible);
- sbparams.bg_color(p.scroll_bar_bg_color);
- mScrollbar = LLUICtrlFactory::create<LLScrollbar> (sbparams);
- addChild(mScrollbar);
-
- // Border
- if (p.has_border)
- {
- LLRect border_rect = getLocalRect();
- LLViewBorder::Params params = p.border;
- params.rect(border_rect);
- mBorder = LLUICtrlFactory::create<LLViewBorder> (params);
- addChild(mBorder);
- }
-
- // set border *after* rect is fully initialized
- if (mBorder)
- {
- mBorder->setRect(getLocalRect());
- mBorder->reshape(getRect().getWidth(), getRect().getHeight());
- }
-
- if (p.sort_column >= 0)
- {
- sortByColumnIndex(p.sort_column, p.sort_ascending);
- }
-
-
- for (LLInitParam::ParamIterator<LLScrollListColumn::Params>::const_iterator row_it = p.contents.columns.begin();
- row_it != p.contents.columns.end();
- ++row_it)
- {
- addColumn(*row_it);
- }
-
- for (LLInitParam::ParamIterator<LLScrollListItem::Params>::const_iterator row_it = p.contents.rows.begin();
- row_it != p.contents.rows.end();
- ++row_it)
- {
- addRow(*row_it);
- }
-
- LLTextBox::Params text_p;
- text_p.name("comment_text");
- text_p.border_visible(false);
- text_p.rect(mItemListRect);
- text_p.follows.flags(FOLLOWS_ALL);
- // word wrap was added accroding to the EXT-6841
- text_p.wrap(true);
- addChild(LLUICtrlFactory::create<LLTextBox>(text_p));
-}
-
-S32 LLScrollListCtrl::getSearchColumn()
-{
- // search for proper search column
- if (mSearchColumn < 0)
- {
- LLScrollListItem* itemp = getFirstData();
- if (itemp)
- {
- for(S32 column = 0; column < getNumColumns(); column++)
- {
- LLScrollListCell* cell = itemp->getColumn(column);
- if (cell && cell->isText())
- {
- mSearchColumn = column;
- break;
- }
- }
- }
- }
- return llclamp(mSearchColumn, 0, getNumColumns());
-}
-/*virtual*/
-bool LLScrollListCtrl::preProcessChildNode(LLXMLNodePtr child)
-{
- if (child->hasName("column") || child->hasName("row"))
- {
- return true; // skip
- }
- else
- {
- return false;
- }
-}
-
-LLScrollListCtrl::~LLScrollListCtrl()
-{
- delete mSortCallback;
-
- std::for_each(mItemList.begin(), mItemList.end(), DeletePointer());
- mItemList.clear();
- clearColumns(); //clears columns and deletes headers
- delete mIsFriendSignal;
-
- auto menu = mPopupMenuHandle.get();
- if (menu)
- {
- menu->die();
- mPopupMenuHandle.markDead();
- }
-}
-
-
-bool LLScrollListCtrl::setMaxItemCount(S32 max_count)
-{
- if (max_count >= getItemCount())
- {
- mMaxItemCount = max_count;
- }
- return (max_count == mMaxItemCount);
-}
-
-S32 LLScrollListCtrl::isEmpty() const
-{
- return mItemList.empty();
-}
-
-S32 LLScrollListCtrl::getItemCount() const
-{
- return mItemList.size();
-}
-
-bool LLScrollListCtrl::hasSelectedItem() const
-{
- item_list::iterator iter;
- for (iter = mItemList.begin(); iter < mItemList.end(); )
- {
- LLScrollListItem* itemp = *iter;
- if (itemp && itemp->getSelected())
- {
- return true;
- }
- iter++;
- }
- return false;
-}
-
-// virtual LLScrolListInterface function (was deleteAllItems)
-void LLScrollListCtrl::clearRows()
-{
- std::for_each(mItemList.begin(), mItemList.end(), DeletePointer());
- mItemList.clear();
- //mItemCount = 0;
-
- // Scroll the bar back up to the top.
- mScrollbar->setDocParams(0, 0);
-
- mScrollLines = 0;
- mLastSelected = NULL;
- updateLayout();
- mDirty = false;
-}
-
-
-LLScrollListItem* LLScrollListCtrl::getFirstSelected() const
-{
- item_list::const_iterator iter;
- for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
- {
- LLScrollListItem* item = *iter;
- if (item->getSelected())
- {
- return item;
- }
- }
- return NULL;
-}
-
-std::vector<LLScrollListItem*> LLScrollListCtrl::getAllSelected() const
-{
- std::vector<LLScrollListItem*> ret;
- item_list::const_iterator iter;
- for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
- {
- LLScrollListItem* item = *iter;
- if (item->getSelected())
- {
- ret.push_back(item);
- }
- }
- return ret;
-}
-
-S32 LLScrollListCtrl::getNumSelected() const
-{
- S32 numSelected = 0;
-
- for(item_list::const_iterator iter = mItemList.begin(); iter != mItemList.end(); ++iter)
- {
- LLScrollListItem* item = *iter;
- if (item->getSelected())
- {
- ++numSelected;
- }
- }
-
- return numSelected;
-}
-
-S32 LLScrollListCtrl::getFirstSelectedIndex() const
-{
- S32 CurSelectedIndex = 0;
-
- // make sure sort is up to date before returning an index
- updateSort();
-
- item_list::const_iterator iter;
- for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
- {
- LLScrollListItem* item = *iter;
- if (item->getSelected())
- {
- return CurSelectedIndex;
- }
- CurSelectedIndex++;
- }
-
- return -1;
-}
-
-LLScrollListItem* LLScrollListCtrl::getFirstData() const
-{
- if (mItemList.size() == 0)
- {
- return NULL;
- }
- return mItemList[0];
-}
-
-LLScrollListItem* LLScrollListCtrl::getLastData() const
-{
- if (mItemList.size() == 0)
- {
- return NULL;
- }
- return mItemList[mItemList.size() - 1];
-}
-
-std::vector<LLScrollListItem*> LLScrollListCtrl::getAllData() const
-{
- std::vector<LLScrollListItem*> ret;
- item_list::const_iterator iter;
- for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
- {
- LLScrollListItem* item = *iter;
- ret.push_back(item);
- }
- return ret;
-}
-
-// returns first matching item
-LLScrollListItem* LLScrollListCtrl::getItem(const LLSD& sd) const
-{
- std::string string_val = sd.asString();
-
- item_list::const_iterator iter;
- for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
- {
- LLScrollListItem* item = *iter;
- // assumes string representation is good enough for comparison
- if (item->getValue().asString() == string_val)
- {
- return item;
- }
- }
- return NULL;
-}
-
-
-void LLScrollListCtrl::reshape( S32 width, S32 height, bool called_from_parent )
-{
- LLUICtrl::reshape( width, height, called_from_parent );
-
- updateLayout();
-}
-
-void LLScrollListCtrl::updateLayout()
-{
- static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
- // reserve room for column headers, if needed
- S32 heading_size = (mDisplayColumnHeaders ? mHeadingHeight : 0);
- mItemListRect.setOriginAndSize(
- mBorderThickness,
- mBorderThickness,
- getRect().getWidth() - 2 * mBorderThickness,
- getRect().getHeight() - (2 * mBorderThickness ) - heading_size );
-
- if (mCommentTextView == NULL)
- {
- mCommentTextView = getChildView("comment_text");
- }
-
- mCommentTextView->setShape(mItemListRect);
-
- // how many lines of content in a single "page"
- S32 page_lines = getLinesPerPage();
-
- bool scrollbar_visible = mLineHeight * getItemCount() > mItemListRect.getHeight();
- if (scrollbar_visible)
- {
- // provide space on the right for scrollbar
- mItemListRect.mRight = getRect().getWidth() - mBorderThickness - scrollbar_size;
- }
-
- mScrollbar->setOrigin(getRect().getWidth() - mBorderThickness - scrollbar_size, mItemListRect.mBottom);
- mScrollbar->reshape(scrollbar_size, mItemListRect.getHeight() + (mDisplayColumnHeaders ? mHeadingHeight : 0));
- mScrollbar->setPageSize(page_lines);
- mScrollbar->setDocSize( getItemCount() );
- mScrollbar->setVisible(scrollbar_visible);
-
- dirtyColumns();
-}
-
-// Attempt to size the control to show all items.
-// Do not make larger than width or height.
-void LLScrollListCtrl::fitContents(S32 max_width, S32 max_height)
-{
- S32 height = llmin( getRequiredRect().getHeight(), max_height );
- if(mPageLines)
- height = llmin( mPageLines * mLineHeight + 2*mBorderThickness + (mDisplayColumnHeaders ? mHeadingHeight : 0), height );
-
- S32 width = getRect().getWidth();
-
- reshape( width, height );
-}
-
-
-LLRect LLScrollListCtrl::getRequiredRect()
-{
- S32 heading_size = (mDisplayColumnHeaders ? mHeadingHeight : 0);
- S32 height = (mLineHeight * getItemCount())
- + (2 * mBorderThickness )
- + heading_size;
- S32 width = getRect().getWidth();
-
- return LLRect(0, height, width, 0);
-}
-
-
-bool LLScrollListCtrl::addItem( LLScrollListItem* item, EAddPosition pos, bool requires_column )
-{
- bool not_too_big = getItemCount() < mMaxItemCount;
- if (not_too_big)
- {
- switch( pos )
- {
- case ADD_TOP:
- mItemList.push_front(item);
- setNeedsSort();
- break;
-
- case ADD_DEFAULT:
- case ADD_BOTTOM:
- mItemList.push_back(item);
- setNeedsSort();
- break;
-
- default:
- llassert(0);
- mItemList.push_back(item);
- setNeedsSort();
- break;
- }
-
- // create new column on demand
- if (mColumns.empty() && requires_column)
- {
- LLScrollListColumn::Params col_params;
- col_params.name = "default_column";
- col_params.header.label = "";
- col_params.width.dynamic_width = true;
- addColumn(col_params);
- }
-
- S32 num_cols = item->getNumColumns();
- S32 i = 0;
- for (LLScrollListCell* cell = item->getColumn(i); i < num_cols; cell = item->getColumn(++i))
- {
- if (i >= (S32)mColumnsIndexed.size()) break;
-
- cell->setWidth(mColumnsIndexed[i]->getWidth());
- }
-
- updateLineHeightInsert(item);
-
- updateLayout();
- }
-
- return not_too_big;
-}
-
-// NOTE: This is *very* expensive for large lists, especially when we are dirtying the list every frame
-// while receiving a long list of names.
-// *TODO: Use bookkeeping to make this an incramental cost with item additions
-S32 LLScrollListCtrl::calcMaxContentWidth()
-{
- const S32 HEADING_TEXT_PADDING = 25;
- const S32 COLUMN_TEXT_PADDING = 10;
-
- S32 max_item_width = 0;
-
- ordered_columns_t::iterator column_itor;
- for (column_itor = mColumnsIndexed.begin(); column_itor != mColumnsIndexed.end(); ++column_itor)
- {
- LLScrollListColumn* column = *column_itor;
- if (!column) continue;
-
- if (mColumnWidthsDirty)
- {
- // update max content width for this column, by looking at all items
- column->mMaxContentWidth = column->mHeader ? LLFontGL::getFontSansSerifSmall()->getWidth(column->mLabel) + mColumnPadding + HEADING_TEXT_PADDING : 0;
- item_list::iterator iter;
- for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
- {
- LLScrollListCell* cellp = (*iter)->getColumn(column->mIndex);
- if (!cellp) continue;
-
- column->mMaxContentWidth = llmax(LLFontGL::getFontSansSerifSmall()->getWidth(cellp->getValue().asString()) + mColumnPadding + COLUMN_TEXT_PADDING, column->mMaxContentWidth);
- }
- }
- max_item_width += column->mMaxContentWidth;
- }
- mColumnWidthsDirty = false;
-
- return max_item_width;
-}
-
-bool LLScrollListCtrl::updateColumnWidths()
-{
- bool width_changed = false;
- ordered_columns_t::iterator column_itor;
- for (column_itor = mColumnsIndexed.begin(); column_itor != mColumnsIndexed.end(); ++column_itor)
- {
- LLScrollListColumn* column = *column_itor;
- if (!column) continue;
-
- // update column width
- S32 new_width = 0;
- if (column->mRelWidth >= 0)
- {
- new_width = (S32)ll_round(column->mRelWidth*mItemListRect.getWidth());
- }
- else if (column->mDynamicWidth)
- {
- new_width = (mItemListRect.getWidth() - mTotalStaticColumnWidth - mTotalColumnPadding) / mNumDynamicWidthColumns;
- }
- else
- {
- new_width = column->getWidth();
- }
-
- if (column->getWidth() != new_width)
- {
- column->setWidth(new_width);
- width_changed = true;
- }
- }
- return width_changed;
-}
-
-// Line height is the max height of all the cells in all the items.
-void LLScrollListCtrl::updateLineHeight()
-{
- mLineHeight = 0;
- item_list::iterator iter;
- for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
- {
- LLScrollListItem *itemp = *iter;
- S32 num_cols = itemp->getNumColumns();
- S32 i = 0;
- for (const LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i))
- {
- mLineHeight = llmax( mLineHeight, cell->getHeight() + mRowPadding );
- }
- }
-}
-
-// when the only change to line height is from an insert, we needn't scan the entire list
-void LLScrollListCtrl::updateLineHeightInsert(LLScrollListItem* itemp)
-{
- S32 num_cols = itemp->getNumColumns();
- S32 i = 0;
- for (const LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i))
- {
- mLineHeight = llmax( mLineHeight, cell->getHeight() + mRowPadding );
- }
-}
-
-
-void LLScrollListCtrl::updateColumns(bool force_update)
-{
- if (!mColumnsDirty && !force_update)
- return;
-
- mColumnsDirty = false;
-
- bool columns_changed_width = updateColumnWidths();
-
- // update column headers
- std::vector<LLScrollListColumn*>::iterator column_ordered_it;
- S32 left = mItemListRect.mLeft;
- LLScrollColumnHeader* last_header = NULL;
- for (column_ordered_it = mColumnsIndexed.begin(); column_ordered_it != mColumnsIndexed.end(); ++column_ordered_it)
- {
- LLScrollListColumn* column = *column_ordered_it;
- if (!column || column->getWidth() < 0)
- {
- // skip hidden columns
- continue;
- }
-
- if (column->mHeader)
- {
- column->mHeader->updateResizeBars();
-
- last_header = column->mHeader;
- S32 top = mItemListRect.mTop;
- S32 right = left + column->getWidth();
-
- if (column->mIndex != (S32)mColumnsIndexed.size()-1)
- {
- right += mColumnPadding;
- }
- right = llmax(left, llmin(mItemListRect.getWidth(), right));
- S32 header_width = right - left;
-
- last_header->reshape(header_width, mHeadingHeight);
- last_header->translate(
- left - last_header->getRect().mLeft,
- top - last_header->getRect().mBottom);
- last_header->setVisible(mDisplayColumnHeaders && header_width > 0);
- left = right;
- }
- }
-
- bool header_changed_width = false;
- // expand last column header we encountered to full list width
- if (last_header)
- {
- S32 old_width = last_header->getColumn()->getWidth();
- S32 new_width = llmax(0, mItemListRect.mRight - last_header->getRect().mLeft);
- last_header->reshape(new_width, last_header->getRect().getHeight());
- last_header->setVisible(mDisplayColumnHeaders && new_width > 0);
- if (old_width != new_width)
- {
- last_header->getColumn()->setWidth(new_width);
- header_changed_width = true;
- }
- }
-
- // propagate column widths to individual cells
- if (columns_changed_width || force_update)
- {
- item_list::iterator iter;
- for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
- {
- LLScrollListItem *itemp = *iter;
- S32 num_cols = itemp->getNumColumns();
- S32 i = 0;
- for (LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i))
- {
- if (i >= (S32)mColumnsIndexed.size()) break;
-
- cell->setWidth(mColumnsIndexed[i]->getWidth());
- }
- }
- }
- else if (header_changed_width)
- {
- item_list::iterator iter;
- S32 index = last_header->getColumn()->mIndex; // Not always identical to last column!
- for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
- {
- LLScrollListItem *itemp = *iter;
- LLScrollListCell* cell = itemp->getColumn(index);
- if (cell)
- {
- cell->setWidth(last_header->getColumn()->getWidth());
- }
- }
- }
-}
-
-void LLScrollListCtrl::setHeadingHeight(S32 heading_height)
-{
- mHeadingHeight = heading_height;
-
- updateLayout();
-
-}
-void LLScrollListCtrl::setPageLines(S32 new_page_lines)
-{
- mPageLines = new_page_lines;
-
- updateLayout();
-}
-
-bool LLScrollListCtrl::selectFirstItem()
-{
- bool success = false;
-
- // our $%&@#$()^%#$()*^ iterators don't let us check against the first item inside out iteration
- bool first_item = true;
-
- item_list::iterator iter;
- for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
- {
- LLScrollListItem *itemp = *iter;
- if( first_item && itemp->getEnabled() )
- {
- if (!itemp->getSelected())
- {
- switch (mSelectionType)
- {
- case CELL:
- selectItem(itemp, 0);
- break;
- case HEADER:
- case ROW:
- selectItem(itemp, -1);
- }
- }
- success = true;
- mOriginalSelection = 0;
- }
- else
- {
- deselectItem(itemp);
- }
- first_item = false;
- }
- if (mCommitOnSelectionChange)
- {
- commitIfChanged();
- }
- return success;
-}
-
-// Deselects all other items
-// virtual
-bool LLScrollListCtrl::selectNthItem( S32 target_index )
-{
- return selectItemRange(target_index, target_index);
-}
-
-// virtual
-bool LLScrollListCtrl::selectItemRange( S32 first_index, S32 last_index )
-{
- if (mItemList.empty())
- {
- return false;
- }
-
- // make sure sort is up to date
- updateSort();
-
- S32 listlen = (S32)mItemList.size();
- first_index = llclamp(first_index, 0, listlen-1);
-
- if (last_index < 0)
- last_index = listlen-1;
- else
- last_index = llclamp(last_index, first_index, listlen-1);
-
- bool success = false;
- S32 index = 0;
- for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); )
- {
- LLScrollListItem *itemp = *iter;
- if(!itemp)
- {
- iter = mItemList.erase(iter);
- continue ;
- }
-
- if( index >= first_index && index <= last_index )
- {
- if( itemp->getEnabled() )
- {
- // TODO: support range selection for cells
- selectItem(itemp, -1, false);
- success = true;
- }
- }
- else
- {
- deselectItem(itemp);
- }
- index++;
- iter++ ;
- }
-
- if (mCommitOnSelectionChange)
- {
- commitIfChanged();
- }
-
- mSearchString.clear();
-
- return success;
-}
-
-
-void LLScrollListCtrl::swapWithNext(S32 index)
-{
- if (index >= ((S32)mItemList.size() - 1))
- {
- // At end of list, doesn't do anything
- return;
- }
- updateSort();
- LLScrollListItem *cur_itemp = mItemList[index];
- mItemList[index] = mItemList[index + 1];
- mItemList[index + 1] = cur_itemp;
-}
-
-
-void LLScrollListCtrl::swapWithPrevious(S32 index)
-{
- if (index <= 0)
- {
- // At beginning of list, don't do anything
- }
-
- updateSort();
- LLScrollListItem *cur_itemp = mItemList[index];
- mItemList[index] = mItemList[index - 1];
- mItemList[index - 1] = cur_itemp;
-}
-
-
-void LLScrollListCtrl::deleteSingleItem(S32 target_index)
-{
- if (target_index < 0 || target_index >= (S32)mItemList.size())
- {
- return;
- }
-
- updateSort();
-
- LLScrollListItem *itemp;
- itemp = mItemList[target_index];
- if (itemp == mLastSelected)
- {
- mLastSelected = NULL;
- }
- delete itemp;
- mItemList.erase(mItemList.begin() + target_index);
- dirtyColumns();
-}
-
-//FIXME: refactor item deletion
-void LLScrollListCtrl::deleteItems(const LLSD& sd)
-{
- item_list::iterator iter;
- for (iter = mItemList.begin(); iter < mItemList.end(); )
- {
- LLScrollListItem* itemp = *iter;
- if (itemp->getValue().asString() == sd.asString())
- {
- if (itemp == mLastSelected)
- {
- mLastSelected = NULL;
- }
- delete itemp;
- iter = mItemList.erase(iter);
- }
- else
- {
- iter++;
- }
- }
-
- dirtyColumns();
-}
-
-void LLScrollListCtrl::deleteSelectedItems()
-{
- item_list::iterator iter;
- for (iter = mItemList.begin(); iter < mItemList.end(); )
- {
- LLScrollListItem* itemp = *iter;
- if (itemp->getSelected())
- {
- delete itemp;
- iter = mItemList.erase(iter);
- }
- else
- {
- iter++;
- }
- }
- mLastSelected = NULL;
- dirtyColumns();
-}
-
-void LLScrollListCtrl::clearHighlightedItems()
-{
- for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); ++iter)
- {
- (*iter)->setHighlighted(false);
- }
-}
-
-void LLScrollListCtrl::mouseOverHighlightNthItem(S32 target_index)
-{
- if (mHighlightedItem != target_index)
- {
- if (mHighlightedItem >= 0 && mHighlightedItem < mItemList.size())
- {
- mItemList[mHighlightedItem]->setHoverCell(-1);
- }
- mHighlightedItem = target_index;
- }
-}
-
-S32 LLScrollListCtrl::selectMultiple( uuid_vec_t ids )
-{
- item_list::iterator iter;
- S32 count = 0;
- for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
- {
- LLScrollListItem* item = *iter;
- uuid_vec_t::iterator iditr;
- for(iditr = ids.begin(); iditr != ids.end(); ++iditr)
- {
- if (item->getEnabled() && (item->getUUID() == (*iditr)))
- {
- // TODO: support multiple selection for cells
- selectItem(item, -1, false);
- ++count;
- break;
- }
- }
- if(ids.end() != iditr) ids.erase(iditr);
- }
-
- if (mCommitOnSelectionChange)
- {
- commitIfChanged();
- }
- return count;
-}
-
-S32 LLScrollListCtrl::getItemIndex( LLScrollListItem* target_item ) const
-{
- updateSort();
-
- S32 index = 0;
- item_list::const_iterator iter;
- for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
- {
- LLScrollListItem *itemp = *iter;
- if (target_item == itemp)
- {
- return index;
- }
- index++;
- }
- return -1;
-}
-
-S32 LLScrollListCtrl::getItemIndex( const LLUUID& target_id ) const
-{
- updateSort();
-
- S32 index = 0;
- item_list::const_iterator iter;
- for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
- {
- LLScrollListItem *itemp = *iter;
- if (target_id == itemp->getUUID())
- {
- return index;
- }
- index++;
- }
- return -1;
-}
-
-void LLScrollListCtrl::selectPrevItem( bool extend_selection)
-{
- LLScrollListItem* prev_item = NULL;
-
- if (!getFirstSelected())
- {
- // select last item
- selectNthItem(getItemCount() - 1);
- }
- else
- {
- updateSort();
-
- item_list::iterator iter;
- for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
- {
- LLScrollListItem* cur_item = *iter;
-
- if (cur_item->getSelected())
- {
- if (prev_item)
- {
- selectItem(prev_item, cur_item->getSelectedCell(), !extend_selection);
- }
- else
- {
- reportInvalidInput();
- }
- break;
- }
-
- // don't allow navigation to disabled elements
- prev_item = cur_item->getEnabled() ? cur_item : prev_item;
- }
- }
-
- if ((mCommitOnSelectionChange || mCommitOnKeyboardMovement))
- {
- commitIfChanged();
- }
-
- mSearchString.clear();
-}
-
-
-void LLScrollListCtrl::selectNextItem( bool extend_selection)
-{
- LLScrollListItem* next_item = NULL;
-
- if (!getFirstSelected())
- {
- selectFirstItem();
- }
- else
- {
- updateSort();
-
- item_list::reverse_iterator iter;
- for (iter = mItemList.rbegin(); iter != mItemList.rend(); iter++)
- {
- LLScrollListItem* cur_item = *iter;
-
- if (cur_item->getSelected())
- {
- if (next_item)
- {
- selectItem(next_item, cur_item->getSelectedCell(), !extend_selection);
- }
- else
- {
- reportInvalidInput();
- }
- break;
- }
-
- // don't allow navigation to disabled items
- next_item = cur_item->getEnabled() ? cur_item : next_item;
- }
- }
-
- if (mCommitOnKeyboardMovement)
- {
- onCommit();
- }
-
- mSearchString.clear();
-}
-
-
-
-void LLScrollListCtrl::deselectAllItems(bool no_commit_on_change)
-{
- item_list::iterator iter;
- for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
- {
- LLScrollListItem* item = *iter;
- deselectItem(item);
- }
-
- if (mCommitOnSelectionChange && !no_commit_on_change)
- {
- commitIfChanged();
- }
-}
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Use this to add comment text such as "Searching", which ignores column settings of list
-
-void LLScrollListCtrl::setCommentText(const std::string& comment_text)
-{
- getChild<LLTextBox>("comment_text")->setValue(comment_text);
-}
-
-LLScrollListItem* LLScrollListCtrl::addSeparator(EAddPosition pos)
-{
- LLScrollListItem::Params separator_params;
- separator_params.enabled(false);
- LLScrollListCell::Params column_params;
- column_params.type = "icon";
- column_params.value = "menu_separator";
- column_params.color = LLColor4(0.f, 0.f, 0.f, 0.7f);
- column_params.font_halign = LLFontGL::HCENTER;
- separator_params.columns.add(column_params);
- return addRow( separator_params, pos );
-}
-
-// Selects first enabled item of the given name.
-// Returns false if item not found.
-// Calls getItemByLabel in order to combine functionality
-bool LLScrollListCtrl::selectItemByLabel(const std::string& label, bool case_sensitive, S32 column/* = 0*/)
-{
- deselectAllItems(true); // ensure that no stale items are selected, even if we don't find a match
- LLScrollListItem* item = getItemByLabel(label, case_sensitive, column);
-
- bool found = NULL != item;
- if (found)
- {
- selectItem(item, -1);
- }
-
- if (mCommitOnSelectionChange)
- {
- commitIfChanged();
- }
-
- return found;
-}
-
-LLScrollListItem* LLScrollListCtrl::getItemByLabel(const std::string& label, bool case_sensitive, S32 column)
-{
- if (label.empty()) //RN: assume no empty items
- {
- return NULL;
- }
-
- std::string target_text = label;
- if (!case_sensitive)
- {
- LLStringUtil::toLower(target_text);
- }
-
- item_list::iterator iter;
- for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
- {
- LLScrollListItem* item = *iter;
- std::string item_text = item->getColumn(column)->getValue().asString(); // Only select enabled items with matching names
- if (!case_sensitive)
- {
- LLStringUtil::toLower(item_text);
- }
- if(item_text == target_text)
- {
- return item;
- }
- }
- return NULL;
-}
-
-
-bool LLScrollListCtrl::selectItemByPrefix(const std::string& target, bool case_sensitive, S32 column)
-{
- return selectItemByPrefix(utf8str_to_wstring(target), case_sensitive, column);
-}
-
-// Selects first enabled item that has a name where the name's first part matched the target string.
-// Returns false if item not found.
-bool LLScrollListCtrl::selectItemByPrefix(const LLWString& target, bool case_sensitive, S32 column)
-{
- bool found = false;
-
- LLWString target_trimmed( target );
- S32 target_len = target_trimmed.size();
-
- if( 0 == target_len )
- {
- // Is "" a valid choice?
- item_list::iterator iter;
- for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
- {
- LLScrollListItem* item = *iter;
- // Only select enabled items with matching names
- LLScrollListCell* cellp = item->getColumn(column == -1 ? getSearchColumn() : column);
- bool select = cellp ? item->getEnabled() && ('\0' == cellp->getValue().asString()[0]) : false;
- if (select)
- {
- selectItem(item, -1);
- found = true;
- break;
- }
- }
- }
- else
- {
- if (!case_sensitive)
- {
- // do comparisons in lower case
- LLWStringUtil::toLower(target_trimmed);
- }
-
- for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); iter++)
- {
- LLScrollListItem* item = *iter;
-
- // Only select enabled items with matching names
- LLScrollListCell* cellp = item->getColumn(column == -1 ? getSearchColumn() : column);
- if (!cellp)
- {
- continue;
- }
- LLWString item_label = utf8str_to_wstring(cellp->getValue().asString());
- if (!case_sensitive)
- {
- LLWStringUtil::toLower(item_label);
- }
- // remove extraneous whitespace from searchable label
- LLWString trimmed_label = item_label;
- LLWStringUtil::trim(trimmed_label);
-
- bool select = item->getEnabled() && trimmed_label.compare(0, target_trimmed.size(), target_trimmed) == 0;
-
- if (select)
- {
- // find offset of matching text (might have leading whitespace)
- S32 offset = item_label.find(target_trimmed);
- cellp->highlightText(offset, target_trimmed.size());
- selectItem(item, -1);
- found = true;
- break;
- }
- }
- }
-
- if (mCommitOnSelectionChange)
- {
- commitIfChanged();
- }
-
- return found;
-}
-
-U32 LLScrollListCtrl::searchItems(const std::string& substring, bool case_sensitive, bool focus)
-{
- return searchItems(utf8str_to_wstring(substring), case_sensitive, focus);
-}
-
-U32 LLScrollListCtrl::searchItems(const LLWString& substring, bool case_sensitive, bool focus)
-{
- U32 found = 0;
-
- LLWString substring_trimmed(substring);
- S32 len = substring_trimmed.size();
-
- if (0 == len)
- {
- // at the moment search for empty element is not supported
- return 0;
- }
- else
- {
- deselectAllItems(true);
- if (!case_sensitive)
- {
- // do comparisons in lower case
- LLWStringUtil::toLower(substring_trimmed);
- }
-
- for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); iter++)
- {
- LLScrollListItem* item = *iter;
- // Only select enabled items with matching names
- if (!item->getEnabled())
- {
- continue;
- }
- LLScrollListCell* cellp = item->getColumn(getSearchColumn());
- if (!cellp)
- {
- continue;
- }
- LLWString item_label = utf8str_to_wstring(cellp->getValue().asString());
- if (!case_sensitive)
- {
- LLWStringUtil::toLower(item_label);
- }
- // remove extraneous whitespace from searchable label
- LLWStringUtil::trim(item_label);
-
- size_t found_iter = item_label.find(substring_trimmed);
-
- if (found_iter != std::string::npos)
- {
- // find offset of matching text
- cellp->highlightText(found_iter, substring_trimmed.size());
- selectItem(item, -1, false);
-
- found++;
-
- if (!mAllowMultipleSelection)
- {
- break;
- }
- }
- }
- }
-
- if (focus && found != 0)
- {
- mNeedsScroll = true;
- }
-
- if (mCommitOnSelectionChange)
- {
- commitIfChanged();
- }
-
- return found;
-}
-
-const std::string LLScrollListCtrl::getSelectedItemLabel(S32 column) const
-{
- LLScrollListItem* item;
-
- item = getFirstSelected();
- if (item)
- {
- return item->getColumn(column)->getValue().asString();
- }
-
- return LLStringUtil::null;
-}
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// "StringUUID" interface: use this when you're creating a list that contains non-unique strings each of which
-// has an associated, unique UUID, and only one of which can be selected at a time.
-
-LLScrollListItem* LLScrollListCtrl::addStringUUIDItem(const std::string& item_text, const LLUUID& id, EAddPosition pos, bool enabled)
-{
- if (getItemCount() < mMaxItemCount)
- {
- LLScrollListItem::Params item_p;
- item_p.enabled(enabled);
- item_p.value(id);
- item_p.columns.add().value(item_text).type("text");
-
- return addRow( item_p, pos );
- }
- return NULL;
-}
-
-// Select the line or lines that match this UUID
-bool LLScrollListCtrl::selectByID( const LLUUID& id )
-{
- return selectByValue( LLSD(id) );
-}
-
-bool LLScrollListCtrl::setSelectedByValue(const LLSD& value, bool selected)
-{
- bool found = false;
-
- if (selected && !mAllowMultipleSelection) deselectAllItems(true);
-
- item_list::iterator iter;
- for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
- {
- LLScrollListItem* item = *iter;
- if (item->getEnabled())
- {
- if (value.isBinary())
- {
- if (item->getValue().isBinary())
- {
- LLSD::Binary data1 = value.asBinary();
- LLSD::Binary data2 = item->getValue().asBinary();
- found = std::equal(data1.begin(), data1.end(), data2.begin());
- }
- }
- else
- {
- found = item->getValue().asString() == value.asString();
- }
-
- if (found)
- {
- if (selected)
- {
- selectItem(item, -1);
- }
- else
- {
- deselectItem(item);
- }
- break;
- }
- }
- }
-
- if (mCommitOnSelectionChange)
- {
- commitIfChanged();
- }
-
- return found;
-}
-
-bool LLScrollListCtrl::isSelected(const LLSD& value) const
-{
- item_list::const_iterator iter;
- for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
- {
- LLScrollListItem* item = *iter;
- if (item->getValue().asString() == value.asString())
- {
- return item->getSelected();
- }
- }
- return false;
-}
-
-LLUUID LLScrollListCtrl::getStringUUIDSelectedItem() const
-{
- LLScrollListItem* item = getFirstSelected();
-
- if (item)
- {
- return item->getUUID();
- }
-
- return LLUUID::null;
-}
-
-LLSD LLScrollListCtrl::getSelectedValue()
-{
- LLScrollListItem* item = getFirstSelected();
-
- if (item)
- {
- return item->getValue();
- }
- else
- {
- return LLSD();
- }
-}
-
-void LLScrollListCtrl::drawItems()
-{
- S32 x = mItemListRect.mLeft;
- S32 y = mItemListRect.mTop - mLineHeight;
-
- // allow for partial line at bottom
- S32 num_page_lines = getLinesPerPage();
-
- LLRect item_rect;
-
- LLGLSUIDefault gls_ui;
-
- F32 alpha = getDrawContext().mAlpha;
-
- {
- LLLocalClipRect clip(mItemListRect);
-
- S32 cur_y = y;
-
- S32 max_columns = 0;
-
- LLColor4 highlight_color = LLColor4::white; // ex: text inside cells
- static LLUICachedControl<F32> type_ahead_timeout ("TypeAheadTimeout", 0);
- highlight_color.mV[VALPHA] = clamp_rescale(mSearchTimer.getElapsedTimeF32(), type_ahead_timeout * 0.7f, type_ahead_timeout(), 0.4f, 0.f);
-
- S32 first_line = mScrollLines;
- S32 last_line = llmin((S32)mItemList.size() - 1, mScrollLines + getLinesPerPage());
-
- if (first_line >= mItemList.size())
- {
- return;
- }
- item_list::iterator iter;
- for (S32 line = first_line; line <= last_line; line++)
- {
- LLScrollListItem* item = mItemList[line];
-
- item_rect.setOriginAndSize(
- x,
- cur_y,
- mItemListRect.getWidth(),
- mLineHeight );
- item->setRect(item_rect);
-
- max_columns = llmax(max_columns, item->getNumColumns());
-
- LLColor4 fg_color;
- LLColor4 hover_color(LLColor4::transparent);
- LLColor4 select_color(LLColor4::transparent);
-
- if( mScrollLines <= line && line < mScrollLines + num_page_lines )
- {
- fg_color = (item->getEnabled() ? mFgUnselectedColor.get() : mFgDisabledColor.get());
- if( item->getSelected() && mCanSelect)
- {
- if(item->getHighlighted()) // if it's highlighted, average the colors
- {
- select_color = lerp(mBgSelectedColor.get(), mHighlightedColor.get(), 0.5f);
- }
- else // otherwise just select-highlight it
- {
- select_color = mBgSelectedColor.get();
- }
-
- fg_color = (item->getEnabled() ? mFgSelectedColor.get() : mFgDisabledColor.get());
- }
- if (mHighlightedItem == line && mCanSelect)
- {
- if(item->getHighlighted()) // if it's highlighted, average the colors
- {
- hover_color = lerp(mHoveredColor.get(), mHighlightedColor.get(), 0.5f);
- }
- else // otherwise just hover-highlight it
- {
- hover_color = mHoveredColor.get();
- }
- }
- else if (item->getHighlighted())
- {
- hover_color = mHighlightedColor.get();
- }
- else
- {
- if (mDrawStripes && (line % 2 == 0) && (max_columns > 1))
- {
- hover_color = mBgStripeColor.get();
- }
- }
-
- if (!item->getEnabled())
- {
- hover_color = mBgReadOnlyColor.get();
- }
-
- item->draw(item_rect, fg_color % alpha, hover_color% alpha, select_color% alpha, highlight_color % alpha, mColumnPadding);
-
- cur_y -= mLineHeight;
- }
- }
- }
-}
-
-
-void LLScrollListCtrl::draw()
-{
- LLLocalClipRect clip(getLocalRect());
-
- // if user specifies sort, make sure it is maintained
- updateSort();
-
- if (mNeedsScroll)
- {
- scrollToShowSelected();
- mNeedsScroll = false;
- }
- LLRect background(0, getRect().getHeight(), getRect().getWidth(), 0);
- // Draw background
- if (mBackgroundVisible)
- {
- F32 alpha = getCurrentTransparency();
- gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
- gl_rect_2d(background, getEnabled() ? mBgWriteableColor.get() % alpha : mBgReadOnlyColor.get() % alpha );
- }
-
- updateColumns();
-
- getChildView("comment_text")->setVisible(mItemList.empty());
-
- drawItems();
-
- if (mBorder)
- {
- mBorder->setKeyboardFocusHighlight(hasFocus());
- }
-
- LLUICtrl::draw();
-}
-
-void LLScrollListCtrl::setEnabled(bool enabled)
-{
- mCanSelect = enabled;
- setTabStop(enabled);
- mScrollbar->setTabStop(!enabled && mScrollbar->getPageSize() < mScrollbar->getDocSize());
-}
-
-bool LLScrollListCtrl::handleScrollWheel(S32 x, S32 y, S32 clicks)
-{
- bool handled = false;
- // Pretend the mouse is over the scrollbar
- handled = mScrollbar->handleScrollWheel( 0, 0, clicks );
-
- if (mMouseWheelOpaque)
- {
- return true;
- }
-
- return handled;
-}
-
-bool LLScrollListCtrl::handleScrollHWheel(S32 x, S32 y, S32 clicks)
-{
- bool handled = false;
- // Pretend the mouse is over the scrollbar
- handled = mScrollbar->handleScrollHWheel( 0, 0, clicks );
-
- if (mMouseWheelOpaque)
- {
- return true;
- }
-
- return handled;
-}
-
-// *NOTE: Requires a valid row_index and column_index
-LLRect LLScrollListCtrl::getCellRect(S32 row_index, S32 column_index)
-{
- LLRect cell_rect;
- S32 rect_left = getColumnOffsetFromIndex(column_index) + mItemListRect.mLeft;
- S32 rect_bottom = getRowOffsetFromIndex(row_index);
- LLScrollListColumn* columnp = getColumn(column_index);
- cell_rect.setOriginAndSize(rect_left, rect_bottom,
- /*rect_left + */columnp->getWidth(), mLineHeight);
- return cell_rect;
-}
-
-bool LLScrollListCtrl::handleToolTip(S32 x, S32 y, MASK mask)
-{
- S32 column_index = getColumnIndexFromOffset(x);
- LLScrollListColumn* columnp = getColumn(column_index);
-
- if (columnp == NULL) return false;
-
- bool handled = false;
- // show tooltip for full name of hovered item if it has been truncated
- LLScrollListItem* hit_item = hitItem(x, y);
- if (hit_item)
- {
- LLScrollListCell* hit_cell = hit_item->getColumn(column_index);
- if (!hit_cell) return false;
- if (hit_cell
- && hit_cell->isText()
- && hit_cell->needsToolTip())
- {
- S32 row_index = getItemIndex(hit_item);
- LLRect cell_rect = getCellRect(row_index, column_index);
- // Convert rect local to screen coordinates
- LLRect sticky_rect;
- localRectToScreen(cell_rect, &sticky_rect);
-
- // display tooltip exactly over original cell, in same font
- LLToolTipMgr::instance().show(LLToolTip::Params()
- .message(hit_cell->getToolTip())
- .font(LLFontGL::getFontSansSerifSmall())
- .pos(LLCoordGL(sticky_rect.mLeft - 5, sticky_rect.mTop + 6))
- .delay_time(0.2f)
- .sticky_rect(sticky_rect));
- }
- handled = true;
- }
-
- // otherwise, look for a tooltip associated with this column
- LLScrollColumnHeader* headerp = columnp->mHeader;
- if (headerp && !handled)
- {
- handled = headerp->handleToolTip(x, y, mask);
- }
-
- return handled;
-}
-
-bool LLScrollListCtrl::selectItemAt(S32 x, S32 y, MASK mask)
-{
- if (!mCanSelect) return false;
-
- bool selection_changed = false;
-
- LLScrollListItem* hit_item = hitItem(x, y);
-
- if( hit_item )
- {
- if( mAllowMultipleSelection )
- {
- if (mask & MASK_SHIFT)
- {
- if (mLastSelected == NULL)
- {
- selectItem(hit_item, getColumnIndexFromOffset(x));
- }
- else
- {
- // Select everthing between mLastSelected and hit_item
- bool selecting = false;
- item_list::iterator itor;
- // If we multiselect backwards, we'll stomp on mLastSelected,
- // meaning that we never stop selecting until hitting max or
- // the end of the list.
- LLScrollListItem* lastSelected = mLastSelected;
- for (itor = mItemList.begin(); itor != mItemList.end(); ++itor)
- {
- if(mMaxSelectable > 0 && getAllSelected().size() >= mMaxSelectable)
- {
- if(mOnMaximumSelectCallback)
- {
- mOnMaximumSelectCallback();
- }
- break;
- }
- LLScrollListItem *item = *itor;
- if (item == hit_item || item == lastSelected)
- {
- selectItem(item, getColumnIndexFromOffset(x), false);
- selecting = !selecting;
- if (hit_item == lastSelected)
- {
- // stop selecting now, since we just clicked on our last selected item
- selecting = false;
- }
- }
- if (selecting)
- {
- selectItem(item, getColumnIndexFromOffset(x), false);
- }
- }
- }
- }
- else if (mask & MASK_CONTROL)
- {
- if (hit_item->getSelected())
- {
- deselectItem(hit_item);
- }
- else
- {
- if(!(mMaxSelectable > 0 && getAllSelected().size() >= mMaxSelectable))
- {
- selectItem(hit_item, getColumnIndexFromOffset(x), false);
- }
- else
- {
- if(mOnMaximumSelectCallback)
- {
- mOnMaximumSelectCallback();
- }
- }
- }
- }
- else
- {
- deselectAllItems(true);
- selectItem(hit_item, getColumnIndexFromOffset(x));
- }
- }
- else
- {
- selectItem(hit_item, getColumnIndexFromOffset(x));
- }
-
- selection_changed = mSelectionChanged;
- if (mCommitOnSelectionChange)
- {
- commitIfChanged();
- }
-
- // clear search string on mouse operations
- mSearchString.clear();
- }
- else
- {
- //mLastSelected = NULL;
- //deselectAllItems(true);
- }
-
- return selection_changed;
-}
-
-
-bool LLScrollListCtrl::handleMouseDown(S32 x, S32 y, MASK mask)
-{
- bool handled = childrenHandleMouseDown(x, y, mask) != NULL;
-
- if( !handled )
- {
- // set keyboard focus first, in case click action wants to move focus elsewhere
- setFocus(true);
-
- // clear selection changed flag because user is starting a selection operation
- mSelectionChanged = false;
-
- handleClick(x, y, mask);
- }
-
- return true;
-}
-
-bool LLScrollListCtrl::handleMouseUp(S32 x, S32 y, MASK mask)
-{
- if (hasMouseCapture())
- {
- // release mouse capture immediately so
- // scroll to show selected logic will work
- gFocusMgr.setMouseCapture(NULL);
- if(mask == MASK_NONE)
- {
- selectItemAt(x, y, mask);
- mNeedsScroll = true;
- }
- }
-
- // always commit when mouse operation is completed inside list
- if (mItemListRect.pointInRect(x,y))
- {
- mDirty = mDirty || mSelectionChanged;
- mSelectionChanged = false;
- onCommit();
- }
-
- return LLUICtrl::handleMouseUp(x, y, mask);
-}
-
-// virtual
-bool LLScrollListCtrl::handleRightMouseDown(S32 x, S32 y, MASK mask)
-{
- LLScrollListItem *item = hitItem(x, y);
- if (item)
- {
- // check to see if we have a UUID for this row
- std::string id = item->getValue().asString();
- LLUUID uuid(id);
- if (! uuid.isNull() && mContextMenuType != MENU_NONE)
- {
- // set up the callbacks for all of the avatar/group menu items
- // (N.B. callbacks don't take const refs as id is local scope)
- bool is_group = (mContextMenuType == MENU_GROUP);
- LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
- registrar.add("Url.ShowProfile", boost::bind(&LLScrollListCtrl::showProfile, id, is_group));
- registrar.add("Url.SendIM", boost::bind(&LLScrollListCtrl::sendIM, id));
- registrar.add("Url.AddFriend", boost::bind(&LLScrollListCtrl::addFriend, id));
- registrar.add("Url.RemoveFriend", boost::bind(&LLScrollListCtrl::removeFriend, id));
- registrar.add("Url.ReportAbuse", boost::bind(&LLScrollListCtrl::reportAbuse, id, is_group));
- registrar.add("Url.Execute", boost::bind(&LLScrollListCtrl::showNameDetails, id, is_group));
- registrar.add("Url.CopyLabel", boost::bind(&LLScrollListCtrl::copyNameToClipboard, id, is_group));
- registrar.add("Url.CopyUrl", boost::bind(&LLScrollListCtrl::copySLURLToClipboard, id, is_group));
-
- // create the context menu from the XUI file and display it
- std::string menu_name = is_group ? "menu_url_group.xml" : "menu_url_agent.xml";
- auto menu = mPopupMenuHandle.get();
- if (menu)
- {
- menu->die();
- mPopupMenuHandle.markDead();
- }
- llassert(LLMenuGL::sMenuContainer != NULL);
- menu = LLUICtrlFactory::getInstance()->createFromFile<LLContextMenu>(
- menu_name, LLMenuGL::sMenuContainer, LLMenuHolderGL::child_registry_t::instance());
- if (menu)
- {
- mPopupMenuHandle = menu->getHandle();
- if (mIsFriendSignal)
- {
- bool isFriend = *(*mIsFriendSignal)(uuid);
- LLView* addFriendButton = menu->getChild<LLView>("add_friend");
- LLView* removeFriendButton = menu->getChild<LLView>("remove_friend");
-
- if (addFriendButton && removeFriendButton)
- {
- addFriendButton->setEnabled(!isFriend);
- removeFriendButton->setEnabled(isFriend);
- }
- }
-
- menu->show(x, y);
- LLMenuGL::showPopup(this, menu, x, y);
- return true;
- }
- }
- return LLUICtrl::handleRightMouseDown(x, y, mask);
- }
- return false;
-}
-
-void LLScrollListCtrl::showProfile(std::string id, bool is_group)
-{
- // show the resident's profile or the group profile
- std::string sltype = is_group ? "group" : "agent";
- std::string slurl = "secondlife:///app/" + sltype + "/" + id + "/about";
- LLUrlAction::showProfile(slurl);
-}
-
-void LLScrollListCtrl::sendIM(std::string id)
-{
- // send im to the resident
- std::string slurl = "secondlife:///app/agent/" + id + "/about";
- LLUrlAction::sendIM(slurl);
-}
-
-void LLScrollListCtrl::addFriend(std::string id)
-{
- // add resident to friends list
- std::string slurl = "secondlife:///app/agent/" + id + "/about";
- LLUrlAction::addFriend(slurl);
-}
-
-void LLScrollListCtrl::removeFriend(std::string id)
-{
- std::string slurl = "secondlife:///app/agent/" + id + "/about";
- LLUrlAction::removeFriend(slurl);
-}
-
-void LLScrollListCtrl::reportAbuse(std::string id, bool is_group)
-{
- if (!is_group)
- {
- std::string slurl = "secondlife:///app/agent/" + id + "/about";
- LLUrlAction::reportAbuse(slurl);
- }
-}
-
-void LLScrollListCtrl::showNameDetails(std::string id, bool is_group)
-{
- // open the resident's details or the group details
- std::string sltype = is_group ? "group" : "agent";
- std::string slurl = "secondlife:///app/" + sltype + "/" + id + "/about";
- LLUrlAction::clickAction(slurl, true);
-}
-
-void LLScrollListCtrl::copyNameToClipboard(std::string id, bool is_group)
-{
- // copy the name of the avatar or group to the clipboard
- std::string name;
- if (is_group)
- {
- gCacheName->getGroupName(LLUUID(id), name);
- }
- else
- {
- LLAvatarName av_name;
- LLAvatarNameCache::get(LLUUID(id), &av_name);
- name = av_name.getAccountName();
- }
- LLUrlAction::copyURLToClipboard(name);
-}
-
-void LLScrollListCtrl::copySLURLToClipboard(std::string id, bool is_group)
-{
- // copy a SLURL for the avatar or group to the clipboard
- std::string sltype = is_group ? "group" : "agent";
- std::string slurl = "secondlife:///app/" + sltype + "/" + id + "/about";
- LLUrlAction::copyURLToClipboard(slurl);
-}
-
-bool LLScrollListCtrl::handleDoubleClick(S32 x, S32 y, MASK mask)
-{
- //bool handled = false;
- bool handled = handleClick(x, y, mask);
-
- if (!handled)
- {
- // Offer the click to the children, even if we aren't enabled
- // so the scroll bars will work.
- if (NULL == LLView::childrenHandleDoubleClick(x, y, mask))
- {
- // Run the callback only if an item is being double-clicked.
- if( mCanSelect && hitItem(x, y) && mOnDoubleClickCallback )
- {
- mOnDoubleClickCallback();
- }
- }
- }
-
- return true;
-}
-
-bool LLScrollListCtrl::handleClick(S32 x, S32 y, MASK mask)
-{
- // which row was clicked on?
- LLScrollListItem* hit_item = hitItem(x, y);
- if (!hit_item) return false;
-
- // get appropriate cell from that row
- S32 column_index = getColumnIndexFromOffset(x);
- LLScrollListCell* hit_cell = hit_item->getColumn(column_index);
- if (!hit_cell) return false;
-
- // if cell handled click directly (i.e. clicked on an embedded checkbox)
- if (hit_cell->handleClick())
- {
- // if item not currently selected, select it
- if (!hit_item->getSelected())
- {
- selectItemAt(x, y, mask);
- gFocusMgr.setMouseCapture(this);
- mNeedsScroll = true;
- }
-
- // propagate state of cell to rest of selected column
- {
- // propagate value of this cell to other selected items
- // and commit the respective widgets
- LLSD item_value = hit_cell->getValue();
- for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); iter++)
- {
- LLScrollListItem* item = *iter;
- if (item->getSelected())
- {
- LLScrollListCell* cellp = item->getColumn(column_index);
- cellp->setValue(item_value);
- cellp->onCommit();
- if (mLastSelected == NULL)
- {
- break;
- }
- }
- }
- //FIXME: find a better way to signal cell changes
- onCommit();
- }
- // eat click (e.g. do not trigger double click callback)
- return true;
- }
- else
- {
- // treat this as a normal single item selection
- selectItemAt(x, y, mask);
- gFocusMgr.setMouseCapture(this);
- mNeedsScroll = true;
- // do not eat click (allow double click callback)
- return false;
- }
-}
-
-LLScrollListItem* LLScrollListCtrl::hitItem( S32 x, S32 y )
-{
- // Excludes disabled items.
- LLScrollListItem* hit_item = NULL;
-
- updateSort();
-
- LLRect item_rect;
- item_rect.setLeftTopAndSize(
- mItemListRect.mLeft,
- mItemListRect.mTop,
- mItemListRect.getWidth(),
- mLineHeight );
-
- // allow for partial line at bottom
- S32 num_page_lines = getLinesPerPage();
-
- S32 line = 0;
- item_list::iterator iter;
- for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
- {
- LLScrollListItem* item = *iter;
- if( mScrollLines <= line && line < mScrollLines + num_page_lines )
- {
- if( item->getEnabled() && item_rect.pointInRect( x, y ) )
- {
- hit_item = item;
- break;
- }
-
- item_rect.translate(0, -mLineHeight);
- }
- line++;
- }
-
- return hit_item;
-}
-
-S32 LLScrollListCtrl::getColumnIndexFromOffset(S32 x)
-{
- // which column did we hit?
- S32 left = 0;
- S32 right = 0;
- S32 width = 0;
- S32 column_index = 0;
-
- ordered_columns_t::const_iterator iter = mColumnsIndexed.begin();
- ordered_columns_t::const_iterator end = mColumnsIndexed.end();
- for ( ; iter != end; ++iter)
- {
- width = (*iter)->getWidth() + mColumnPadding;
- right += width;
- if (left <= x && x < right )
- {
- break;
- }
-
- // set left for next column as right of current column
- left = right;
- column_index++;
- }
-
- return llclamp(column_index, 0, getNumColumns() - 1);
-}
-
-
-S32 LLScrollListCtrl::getColumnOffsetFromIndex(S32 index)
-{
- S32 column_offset = 0;
- ordered_columns_t::const_iterator iter = mColumnsIndexed.begin();
- ordered_columns_t::const_iterator end = mColumnsIndexed.end();
- for ( ; iter != end; ++iter)
- {
- if (index-- <= 0)
- {
- return column_offset;
- }
- column_offset += (*iter)->getWidth() + mColumnPadding;
- }
-
- // when running off the end, return the rightmost pixel
- return mItemListRect.mRight;
-}
-
-S32 LLScrollListCtrl::getRowOffsetFromIndex(S32 index)
-{
- S32 row_bottom = (mItemListRect.mTop - ((index - mScrollLines + 1) * mLineHeight) );
- return row_bottom;
-}
-
-
-bool LLScrollListCtrl::handleHover(S32 x,S32 y,MASK mask)
-{
- bool handled = false;
-
- if (hasMouseCapture())
- {
- if(mask == MASK_NONE)
- {
- selectItemAt(x, y, mask);
- mNeedsScroll = true;
- }
- }
- else
- if (mCanSelect)
- {
- LLScrollListItem* item = hitItem(x, y);
- if (item)
- {
- mouseOverHighlightNthItem(getItemIndex(item));
- switch (mSelectionType)
- {
- case CELL:
- item->setHoverCell(getColumnIndexFromOffset(x));
- break;
- case HEADER:
- {
- S32 cell = getColumnIndexFromOffset(x);
- if (cell > 0)
- {
- item->setHoverCell(cell);
- }
- else
- {
- item->setHoverCell(-1);
- }
- break;
- }
- case ROW:
- break;
- }
- }
- else
- {
- mouseOverHighlightNthItem(-1);
- }
- }
-
- handled = LLUICtrl::handleHover( x, y, mask );
-
- return handled;
-}
-
-void LLScrollListCtrl::onMouseLeave(S32 x, S32 y, MASK mask)
-{
- // clear mouse highlight
- mouseOverHighlightNthItem(-1);
-}
-
-bool LLScrollListCtrl::handleKeyHere(KEY key,MASK mask )
-{
- bool handled = false;
-
- // not called from parent means we have keyboard focus or a child does
- if (mCanSelect)
- {
- if (mask == MASK_NONE)
- {
- switch(key)
- {
- case KEY_UP:
- if (mAllowKeyboardMovement || hasFocus())
- {
- // commit implicit in call
- selectPrevItem(false);
- mNeedsScroll = true;
- handled = true;
- }
- break;
- case KEY_DOWN:
- if (mAllowKeyboardMovement || hasFocus())
- {
- // commit implicit in call
- selectNextItem(false);
- mNeedsScroll = true;
- handled = true;
- }
- break;
- case KEY_LEFT:
- if (mAllowKeyboardMovement || hasFocus())
- {
- // TODO: support multi-select
- LLScrollListItem *item = getFirstSelected();
- if (item)
- {
- S32 cell = item->getSelectedCell();
- switch (mSelectionType)
- {
- case CELL:
- if (cell < mColumns.size()) cell++;
- break;
- case HEADER:
- if (cell == -1) cell = 1;
- else if (cell > 1 && cell < mColumns.size()) cell++; // skip header
- break;
- case ROW:
- cell = -1;
- break;
- }
- item->setSelectedCell(cell);
- handled = true;
- }
- }
- break;
- case KEY_RIGHT:
- if (mAllowKeyboardMovement || hasFocus())
- {
- // TODO: support multi-select
- LLScrollListItem *item = getFirstSelected();
- if (item)
- {
- S32 cell = item->getSelectedCell();
- switch (mSelectionType)
- {
- case CELL:
- if (cell >= 0) cell--;
- break;
- case HEADER:
- if (cell > 1) cell--;
- else if (cell == 1) cell = -1; // skip header
- break;
- case ROW:
- cell = -1;
- break;
- }
- item->setSelectedCell(cell);
- handled = true;
- }
- }
- break;
- case KEY_PAGE_UP:
- if (mAllowKeyboardMovement || hasFocus())
- {
- selectNthItem(getFirstSelectedIndex() - (mScrollbar->getPageSize() - 1));
- mNeedsScroll = true;
- if (mCommitOnKeyboardMovement
- && !mCommitOnSelectionChange)
- {
- onCommit();
- }
- handled = true;
- }
- break;
- case KEY_PAGE_DOWN:
- if (mAllowKeyboardMovement || hasFocus())
- {
- selectNthItem(getFirstSelectedIndex() + (mScrollbar->getPageSize() - 1));
- mNeedsScroll = true;
- if (mCommitOnKeyboardMovement
- && !mCommitOnSelectionChange)
- {
- onCommit();
- }
- handled = true;
- }
- break;
- case KEY_HOME:
- if (mAllowKeyboardMovement || hasFocus())
- {
- selectFirstItem();
- mNeedsScroll = true;
- if (mCommitOnKeyboardMovement
- && !mCommitOnSelectionChange)
- {
- onCommit();
- }
- handled = true;
- }
- break;
- case KEY_END:
- if (mAllowKeyboardMovement || hasFocus())
- {
- selectNthItem(getItemCount() - 1);
- mNeedsScroll = true;
- if (mCommitOnKeyboardMovement
- && !mCommitOnSelectionChange)
- {
- onCommit();
- }
- handled = true;
- }
- break;
- case KEY_RETURN:
- // JC - Special case: Only claim to have handled it
- // if we're the special non-commit-on-move
- // type. AND we are visible
- if (!mCommitOnKeyboardMovement && mask == MASK_NONE)
- {
- onCommit();
- mSearchString.clear();
- handled = true;
- }
- break;
- case KEY_BACKSPACE:
- mSearchTimer.reset();
- if (mSearchString.size())
- {
- mSearchString.erase(mSearchString.size() - 1, 1);
- }
- if (mSearchString.empty())
- {
- if (getFirstSelected())
- {
- LLScrollListCell* cellp = getFirstSelected()->getColumn(getSearchColumn());
- if (cellp)
- {
- cellp->highlightText(0, 0);
- }
- }
- }
- else if (selectItemByPrefix(wstring_to_utf8str(mSearchString), false))
- {
- mNeedsScroll = true;
- // update search string only on successful match
- mSearchTimer.reset();
-
- if (mCommitOnKeyboardMovement
- && !mCommitOnSelectionChange)
- {
- onCommit();
- }
- }
- break;
- default:
- break;
- }
- }
- // TODO: multiple: shift-up, shift-down, shift-home, shift-end, select all
- }
-
- return handled;
-}
-
-bool LLScrollListCtrl::handleUnicodeCharHere(llwchar uni_char)
-{
- if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL
- {
- return false;
- }
-
- // perform incremental search based on keyboard input
- static LLUICachedControl<F32> type_ahead_timeout ("TypeAheadTimeout", 0);
- if (mSearchTimer.getElapsedTimeF32() > type_ahead_timeout)
- {
- mSearchString.clear();
- }
-
- // type ahead search is case insensitive
- uni_char = LLStringOps::toLower((llwchar)uni_char);
-
- if (selectItemByPrefix(wstring_to_utf8str(mSearchString + (llwchar)uni_char), false))
- {
- // update search string only on successful match
- mNeedsScroll = true;
- mSearchString += uni_char;
- mSearchTimer.reset();
-
- if (mCommitOnKeyboardMovement
- && !mCommitOnSelectionChange)
- {
- onCommit();
- }
- }
- // handle iterating over same starting character
- else if (isRepeatedChars(mSearchString + (llwchar)uni_char) && !mItemList.empty())
- {
- // start from last selected item, in case we previously had a successful match against
- // duplicated characters ('AA' matches 'Aaron')
- item_list::iterator start_iter = mItemList.begin();
- S32 first_selected = getFirstSelectedIndex();
-
- // if we have a selection (> -1) then point iterator at the selected item
- if (first_selected > 0)
- {
- // point iterator to first selected item
- start_iter += first_selected;
- }
-
- // start search at first item after current selection
- item_list::iterator iter = start_iter;
- ++iter;
- if (iter == mItemList.end())
- {
- iter = mItemList.begin();
- }
-
- // loop around once, back to previous selection
- while(iter != start_iter)
- {
- LLScrollListItem* item = *iter;
-
- LLScrollListCell* cellp = item->getColumn(getSearchColumn());
- if (cellp)
- {
- // Only select enabled items with matching first characters
- LLWString item_label = utf8str_to_wstring(cellp->getValue().asString());
- if (item->getEnabled() && LLStringOps::toLower(item_label[0]) == uni_char)
- {
- selectItem(item, -1);
- mNeedsScroll = true;
- cellp->highlightText(0, 1);
- mSearchTimer.reset();
-
- if (mCommitOnKeyboardMovement
- && !mCommitOnSelectionChange)
- {
- onCommit();
- }
-
- break;
- }
- }
-
- ++iter;
- if (iter == mItemList.end())
- {
- iter = mItemList.begin();
- }
- }
- }
-
- return true;
-}
-
-
-void LLScrollListCtrl::reportInvalidInput()
-{
- make_ui_sound("UISndBadKeystroke");
-}
-
-bool LLScrollListCtrl::isRepeatedChars(const LLWString& string) const
-{
- if (string.empty())
- {
- return false;
- }
-
- llwchar first_char = string[0];
-
- for (U32 i = 0; i < string.size(); i++)
- {
- if (string[i] != first_char)
- {
- return false;
- }
- }
-
- return true;
-}
-
-void LLScrollListCtrl::selectItem(LLScrollListItem* itemp, S32 cell, bool select_single_item)
-{
- if (!itemp) return;
-
- if (!itemp->getSelected())
- {
- if (mLastSelected)
- {
- LLScrollListCell* cellp = mLastSelected->getColumn(getSearchColumn());
- if (cellp)
- {
- cellp->highlightText(0, 0);
- }
- }
- if (select_single_item)
- {
- deselectAllItems(true);
- }
- itemp->setSelected(true);
- switch (mSelectionType)
- {
- case CELL:
- itemp->setSelectedCell(cell);
- break;
- case HEADER:
- itemp->setSelectedCell(cell <= 0 ? -1 : cell);
- break;
- case ROW:
- itemp->setSelectedCell(-1);
- break;
- }
- mLastSelected = itemp;
- mSelectionChanged = true;
- }
-}
-
-void LLScrollListCtrl::deselectItem(LLScrollListItem* itemp)
-{
- if (!itemp) return;
-
- if (itemp->getSelected())
- {
- if (mLastSelected == itemp)
- {
- mLastSelected = NULL;
- }
-
- itemp->setSelected(false);
- LLScrollListCell* cellp = itemp->getColumn(getSearchColumn());
- if (cellp)
- {
- cellp->highlightText(0, 0);
- }
- mSelectionChanged = true;
- }
-}
-
-void LLScrollListCtrl::commitIfChanged()
-{
- if (mSelectionChanged)
- {
- mDirty = true;
- mSelectionChanged = false;
- onCommit();
- }
-}
-
-struct SameSortColumn
-{
- SameSortColumn(S32 column) : mColumn(column) {}
- S32 mColumn;
-
- bool operator()(std::pair<S32, bool> sort_column) { return sort_column.first == mColumn; }
-};
-
-bool LLScrollListCtrl::setSort(S32 column_idx, bool ascending)
-{
- LLScrollListColumn* sort_column = getColumn(column_idx);
- if (!sort_column) return false;
-
- sort_column->mSortDirection = ascending ? LLScrollListColumn::ASCENDING : LLScrollListColumn::DESCENDING;
-
- sort_column_t new_sort_column(column_idx, ascending);
-
- setNeedsSort();
-
- if (mSortColumns.empty())
- {
- mSortColumns.push_back(new_sort_column);
- return true;
- }
- else
- {
- // grab current sort column
- sort_column_t cur_sort_column = mSortColumns.back();
-
- // remove any existing sort criterion referencing this column
- // and add the new one
- mSortColumns.erase(remove_if(mSortColumns.begin(), mSortColumns.end(), SameSortColumn(column_idx)), mSortColumns.end());
- mSortColumns.push_back(new_sort_column);
-
- // did the sort criteria change?
- return (cur_sort_column != new_sort_column);
- }
-}
-
-S32 LLScrollListCtrl::getLinesPerPage()
-{
- //if mPageLines is NOT provided display all item
- if (mPageLines)
- {
- return mPageLines;
- }
- else
- {
- return mLineHeight ? mItemListRect.getHeight() / mLineHeight : getItemCount();
- }
-}
-
-
-// Called by scrollbar
-void LLScrollListCtrl::onScrollChange( S32 new_pos, LLScrollbar* scrollbar )
-{
- mScrollLines = new_pos;
-}
-
-
-void LLScrollListCtrl::sortByColumn(const std::string& name, bool ascending)
-{
- column_map_t::iterator itor = mColumns.find(name);
- if (itor != mColumns.end())
- {
- sortByColumnIndex((*itor).second->mIndex, ascending);
- }
-}
-
-// First column is column 0
-void LLScrollListCtrl::sortByColumnIndex(U32 column, bool ascending)
-{
- setSort(column, ascending);
- updateSort();
-}
-
-void LLScrollListCtrl::updateSort() const
-{
- if (hasSortOrder() && !isSorted())
- {
- // do stable sort to preserve any previous sorts
- std::stable_sort(
- mItemList.begin(),
- mItemList.end(),
- SortScrollListItem(mSortColumns,mSortCallback, mAlternateSort));
-
- mSorted = true;
- }
-}
-
-// for one-shot sorts, does not save sort column/order
-void LLScrollListCtrl::sortOnce(S32 column, bool ascending)
-{
- std::vector<std::pair<S32, bool> > sort_column;
- sort_column.push_back(std::make_pair(column, ascending));
-
- // do stable sort to preserve any previous sorts
- std::stable_sort(
- mItemList.begin(),
- mItemList.end(),
- SortScrollListItem(sort_column,mSortCallback,mAlternateSort));
-}
-
-void LLScrollListCtrl::dirtyColumns()
-{
- mColumnsDirty = true;
- mColumnWidthsDirty = true;
-
- // need to keep mColumnsIndexed up to date
- // just in case someone indexes into it immediately
- mColumnsIndexed.resize(mColumns.size());
-
- column_map_t::iterator column_itor;
- for (column_itor = mColumns.begin(); column_itor != mColumns.end(); ++column_itor)
- {
- LLScrollListColumn *column = column_itor->second;
- mColumnsIndexed[column_itor->second->mIndex] = column;
- }
-}
-
-
-S32 LLScrollListCtrl::getScrollPos() const
-{
- return mScrollbar->getDocPos();
-}
-
-
-void LLScrollListCtrl::setScrollPos( S32 pos )
-{
- mScrollbar->setDocPos( pos );
-
- onScrollChange(mScrollbar->getDocPos(), mScrollbar);
-}
-
-
-void LLScrollListCtrl::scrollToShowSelected()
-{
- // don't scroll automatically when capturing mouse input
- // as that will change what is currently under the mouse cursor
- if (hasMouseCapture())
- {
- return;
- }
-
- updateSort();
-
- S32 index = getFirstSelectedIndex();
- if (index < 0)
- {
- return;
- }
-
- LLScrollListItem* item = mItemList[index];
- if (!item)
- {
- // I don't THINK this should ever happen.
- return;
- }
-
- S32 lowest = mScrollLines;
- S32 page_lines = getLinesPerPage();
- S32 highest = mScrollLines + page_lines;
-
- if (index < lowest)
- {
- // need to scroll to show item
- setScrollPos(index);
- }
- else if (highest <= index)
- {
- setScrollPos(index - page_lines + 1);
- }
-}
-
-void LLScrollListCtrl::updateStaticColumnWidth(LLScrollListColumn* col, S32 new_width)
-{
- mTotalStaticColumnWidth += llmax(0, new_width) - llmax(0, col->getWidth());
-}
-
-// LLEditMenuHandler functions
-
-// virtual
-void LLScrollListCtrl::copy()
-{
- std::string buffer;
-
- std::vector<LLScrollListItem*> items = getAllSelected();
- std::vector<LLScrollListItem*>::iterator itor;
- for (itor = items.begin(); itor != items.end(); ++itor)
- {
- buffer += (*itor)->getContentsCSV() + "\n";
- }
- LLClipboard::instance().copyToClipboard(utf8str_to_wstring(buffer), 0, buffer.length());
-}
-
-// virtual
-bool LLScrollListCtrl::canCopy() const
-{
- return (getFirstSelected() != NULL);
-}
-
-// virtual
-void LLScrollListCtrl::cut()
-{
- copy();
- doDelete();
-}
-
-// virtual
-bool LLScrollListCtrl::canCut() const
-{
- return canCopy() && canDoDelete();
-}
-
-// virtual
-void LLScrollListCtrl::selectAll()
-{
- // Deselects all other items
- item_list::iterator iter;
- for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
- {
- LLScrollListItem *itemp = *iter;
- if( itemp->getEnabled() )
- {
- selectItem(itemp, -1, false);
- }
- }
-
- if (mCommitOnSelectionChange)
- {
- commitIfChanged();
- }
-}
-
-// virtual
-bool LLScrollListCtrl::canSelectAll() const
-{
- return getCanSelect() && mAllowMultipleSelection && !(mMaxSelectable > 0 && mItemList.size() > mMaxSelectable);
-}
-
-// virtual
-void LLScrollListCtrl::deselect()
-{
- deselectAllItems();
-}
-
-// virtual
-bool LLScrollListCtrl::canDeselect() const
-{
- return getCanSelect();
-}
-
-void LLScrollListCtrl::addColumn(const LLSD& column, EAddPosition pos)
-{
- LLScrollListColumn::Params p;
- LLParamSDParser parser;
- parser.readSD(column, p);
- addColumn(p, pos);
-}
-
-void LLScrollListCtrl::addColumn(const LLScrollListColumn::Params& column_params, EAddPosition pos)
-{
- if (!column_params.validateBlock()) return;
-
- std::string name = column_params.name;
- // if no column name provided, just use ordinal as name
- if (name.empty())
- {
- name = llformat("%d", mColumnsIndexed.size());
- }
-
- if (mColumns.find(name) == mColumns.end())
- {
- // Add column
- mColumns[name] = new LLScrollListColumn(column_params, this);
- LLScrollListColumn* new_column = mColumns[name];
- new_column->mIndex = mColumns.size()-1;
-
- // Add button
- if (new_column->getWidth() > 0 || new_column->mRelWidth > 0 || new_column->mDynamicWidth)
- {
- if (getNumColumns() > 0)
- {
- mTotalColumnPadding += mColumnPadding;
- }
- if (new_column->mRelWidth >= 0)
- {
- new_column->setWidth((S32)ll_round(new_column->mRelWidth*mItemListRect.getWidth()));
- }
- else if(new_column->mDynamicWidth)
- {
- mNumDynamicWidthColumns++;
- new_column->setWidth((mItemListRect.getWidth() - mTotalStaticColumnWidth - mTotalColumnPadding) / mNumDynamicWidthColumns);
- }
- S32 top = mItemListRect.mTop;
-
- S32 left = mItemListRect.mLeft;
- for (column_map_t::iterator itor = mColumns.begin();
- itor != mColumns.end();
- ++itor)
- {
- if (itor->second->mIndex < new_column->mIndex &&
- itor->second->getWidth() > 0)
- {
- left += itor->second->getWidth() + mColumnPadding;
- }
- }
-
- S32 right = left+new_column->getWidth();
- if (new_column->mIndex != (S32)mColumns.size()-1)
- {
- right += mColumnPadding;
- }
-
- LLRect temp_rect = LLRect(left,top+mHeadingHeight,right,top);
-
- LLScrollColumnHeader::Params params(LLUICtrlFactory::getDefaultParams<LLScrollColumnHeader>());
- params.name = "btn_" + name;
- params.rect = temp_rect;
- params.column = new_column;
- params.tool_tip = column_params.tool_tip;
- params.tab_stop = false;
- params.visible = mDisplayColumnHeaders;
-
- if(column_params.header.image.isProvided())
- {
- params.image_selected = column_params.header.image;
- params.image_unselected = column_params.header.image;
- }
- else
- {
- params.label = column_params.header.label;
- }
-
- new_column->mHeader = LLUICtrlFactory::create<LLScrollColumnHeader>(params);
- addChild(new_column->mHeader);
-
- sendChildToFront(mScrollbar);
- }
- }
-
- dirtyColumns();
-}
-
-// static
-void LLScrollListCtrl::onClickColumn(void *userdata)
-{
- LLScrollListColumn *info = (LLScrollListColumn*)userdata;
- if (!info) return;
-
- LLScrollListCtrl *parent = info->mParentCtrl;
- if (!parent) return;
-
- if (!parent->mCanSort) return;
-
- S32 column_index = info->mIndex;
-
- LLScrollListColumn* column = parent->mColumnsIndexed[info->mIndex];
- bool ascending = column->mSortDirection == LLScrollListColumn::ASCENDING;
- if (column->mSortingColumn != column->mName
- && parent->mColumns.find(column->mSortingColumn) != parent->mColumns.end())
- {
- LLScrollListColumn* info_redir = parent->mColumns[column->mSortingColumn];
- column_index = info_redir->mIndex;
- }
-
- // if this column is the primary sort key, reverse the direction
- if (!parent->mSortColumns.empty() && parent->mSortColumns.back().first == column_index)
- {
- ascending = !parent->mSortColumns.back().second;
- }
-
- parent->sortByColumnIndex(column_index, ascending);
-
- if (parent->mOnSortChangedCallback)
- {
- parent->mOnSortChangedCallback();
- }
-}
-
-std::string LLScrollListCtrl::getSortColumnName()
-{
- LLScrollListColumn* column = mSortColumns.empty() ? NULL : mColumnsIndexed[mSortColumns.back().first];
-
- if (column) return column->mName;
- else return "";
-}
-
-bool LLScrollListCtrl::hasSortOrder() const
-{
- return !mSortColumns.empty();
-}
-
-void LLScrollListCtrl::clearSortOrder()
-{
- mSortColumns.clear();
-}
-
-void LLScrollListCtrl::clearColumns()
-{
- column_map_t::iterator itor;
- for (itor = mColumns.begin(); itor != mColumns.end(); ++itor)
- {
- LLScrollColumnHeader *header = itor->second->mHeader;
- if (header)
- {
- removeChild(header);
- delete header;
- }
- }
- std::for_each(mColumns.begin(), mColumns.end(), DeletePairedPointer());
- mColumns.clear();
- mSortColumns.clear();
- mTotalStaticColumnWidth = 0;
- mTotalColumnPadding = 0;
-
- dirtyColumns(); // Clears mColumnsIndexed
-}
-
-void LLScrollListCtrl::setColumnLabel(const std::string& column, const std::string& label)
-{
- LLScrollListColumn* columnp = getColumn(column);
- if (columnp)
- {
- columnp->mLabel = label;
- if (columnp->mHeader)
- {
- columnp->mHeader->setLabel(label);
- }
- }
-}
-
-LLScrollListColumn* LLScrollListCtrl::getColumn(S32 index)
-{
- if (index < 0 || index >= (S32)mColumnsIndexed.size())
- {
- return NULL;
- }
- return mColumnsIndexed[index];
-}
-
-LLScrollListColumn* LLScrollListCtrl::getColumn(const std::string& name)
-{
- column_map_t::iterator column_itor = mColumns.find(name);
- if (column_itor != mColumns.end())
- {
- return column_itor->second;
- }
- return NULL;
-}
-
-LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& element, EAddPosition pos, void* userdata)
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;
- LLScrollListItem::Params item_params;
- LLParamSDParser parser;
- parser.readSD(element, item_params);
- item_params.userdata = userdata;
- return addRow(item_params, pos);
-}
-
-LLScrollListItem* LLScrollListCtrl::addRow(const LLScrollListItem::Params& item_p, EAddPosition pos)
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;
- LLScrollListItem *new_item = new LLScrollListItem(item_p);
- return addRow(new_item, item_p, pos);
-}
-
-LLScrollListItem* LLScrollListCtrl::addRow(LLScrollListItem *new_item, const LLScrollListItem::Params& item_p, EAddPosition pos)
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;
- if (!item_p.validateBlock() || !new_item) return NULL;
- new_item->setNumColumns(mColumns.size());
-
- // Add any columns we don't already have
- S32 col_index = 0;
-
- for(LLInitParam::ParamIterator<LLScrollListCell::Params>::const_iterator itor = item_p.columns.begin();
- itor != item_p.columns.end();
- ++itor)
- {
- LLScrollListCell::Params cell_p = *itor;
- std::string column = cell_p.column;
-
- // empty columns strings index by ordinal
- if (column.empty())
- {
- column = llformat("%d", col_index);
- }
-
- LLScrollListColumn* columnp = getColumn(column);
-
- // create new column on demand
- if (!columnp)
- {
- LLScrollListColumn::Params new_column;
- new_column.name = column;
- new_column.header.label = column;
-
- // if width supplied for column, use it, otherwise
- // use adaptive width
- if (cell_p.width.isProvided())
- {
- new_column.width.pixel_width = cell_p.width;
- }
- addColumn(new_column);
- columnp = mColumns[column];
- new_item->setNumColumns(mColumns.size());
- }
-
- S32 index = columnp->mIndex;
- if (!cell_p.width.isProvided())
- {
- cell_p.width = columnp->getWidth();
- }
-
- LLScrollListCell* cell = LLScrollListCell::create(cell_p);
-
- if (cell)
- {
- new_item->setColumn(index, cell);
- if (columnp->mHeader
- && cell->isText()
- && !cell->getValue().asString().empty())
- {
- columnp->mHeader->setHasResizableElement(true);
- }
- }
-
- col_index++;
- }
-
- if (item_p.columns.empty())
- {
- if (mColumns.empty())
- {
- LLScrollListColumn::Params new_column;
- new_column.name = "0";
-
- addColumn(new_column);
- new_item->setNumColumns(mColumns.size());
- }
-
- LLScrollListCell* cell = LLScrollListCell::create(LLScrollListCell::Params().value(item_p.value));
- if (cell)
- {
- LLScrollListColumn* columnp = mColumns.begin()->second;
-
- new_item->setColumn(0, cell);
- if (columnp->mHeader
- && cell->isText()
- && !cell->getValue().asString().empty())
- {
- columnp->mHeader->setHasResizableElement(true);
- }
- }
- }
-
- // add dummy cells for missing columns
- for (column_map_t::iterator column_it = mColumns.begin(); column_it != mColumns.end(); ++column_it)
- {
- S32 column_idx = column_it->second->mIndex;
- if (new_item->getColumn(column_idx) == NULL)
- {
- LLScrollListColumn* column_ptr = column_it->second;
- LLScrollListCell::Params cell_p;
- cell_p.width = column_ptr->getWidth();
-
- new_item->setColumn(column_idx, new LLScrollListSpacer(cell_p));
- }
- }
-
- addItem(new_item, pos);
- return new_item;
-}
-
-LLScrollListItem* LLScrollListCtrl::addSimpleElement(const std::string& value, EAddPosition pos, const LLSD& id)
-{
- LLSD entry_id = id;
-
- if (id.isUndefined())
- {
- entry_id = value;
- }
-
- LLScrollListItem::Params item_params;
- item_params.value(entry_id);
- item_params.columns.add()
- .value(value)
- .font(LLFontGL::getFontSansSerifSmall());
-
- return addRow(item_params, pos);
-}
-
-void LLScrollListCtrl::setValue(const LLSD& value )
-{
- LLSD::array_const_iterator itor;
- for (itor = value.beginArray(); itor != value.endArray(); ++itor)
- {
- addElement(*itor);
- }
-}
-
-LLSD LLScrollListCtrl::getValue() const
-{
- LLScrollListItem *item = getFirstSelected();
- if (!item) return LLSD();
- return item->getValue();
-}
-
-bool LLScrollListCtrl::operateOnSelection(EOperation op)
-{
- if (op == OP_DELETE)
- {
- deleteSelectedItems();
- return true;
- }
- else if (op == OP_DESELECT)
- {
- deselectAllItems();
- }
- return false;
-}
-
-bool LLScrollListCtrl::operateOnAll(EOperation op)
-{
- if (op == OP_DELETE)
- {
- clearRows();
- return true;
- }
- else if (op == OP_DESELECT)
- {
- deselectAllItems();
- }
- else if (op == OP_SELECT)
- {
- selectAll();
- }
- return false;
-}
-//virtual
-void LLScrollListCtrl::setFocus(bool b)
-{
- // for tabbing into pristine scroll lists (Finder)
- if (!getFirstSelected())
- {
- selectFirstItem();
- //onCommit(); // SJB: selectFirstItem() will call onCommit() if appropriate
- }
- LLUICtrl::setFocus(b);
-}
-
-
-// virtual
-bool LLScrollListCtrl::isDirty() const
-{
- bool grubby = mDirty;
- if ( !mAllowMultipleSelection )
- {
- grubby = (mOriginalSelection != getFirstSelectedIndex());
- }
- return grubby;
-}
-
-// Clear dirty state
-void LLScrollListCtrl::resetDirty()
-{
- mDirty = false;
- mOriginalSelection = getFirstSelectedIndex();
-}
-
-
-//virtual
-void LLScrollListCtrl::onFocusReceived()
-{
- // forget latent selection changes when getting focus
- mSelectionChanged = false;
- LLUICtrl::onFocusReceived();
-}
-
-//virtual
-void LLScrollListCtrl::onFocusLost()
-{
- if (hasMouseCapture())
- {
- gFocusMgr.setMouseCapture(NULL);
- }
-
- mSearchString.clear();
-
- LLUICtrl::onFocusLost();
-}
-
-boost::signals2::connection LLScrollListCtrl::setIsFriendCallback(const is_friend_signal_t::slot_type& cb)
-{
- if (!mIsFriendSignal)
- {
- mIsFriendSignal = new is_friend_signal_t();
- }
- return mIsFriendSignal->connect(cb);
-}
-
-bool LLScrollListCtrl::highlightMatchingItems(const std::string& filter_str)
-{
- if (filter_str == "" || filter_str == " ")
- {
- clearHighlightedItems();
- return false;
- }
-
- bool res = false;
-
- setHighlightedColor(LLUIColorTable::instance().getColor("SearchableControlHighlightColor", LLColor4::red));
-
- std::string filter_str_lc(filter_str);
- LLStringUtil::toLower(filter_str_lc);
-
- std::vector<LLScrollListItem*> data = getAllData();
- std::vector<LLScrollListItem*>::iterator iter = data.begin();
- while (iter != data.end())
- {
- LLScrollListCell* cell = (*iter)->getColumn(0);
- if (cell)
- {
- std::string value = cell->getValue().asString();
- LLStringUtil::toLower(value);
- if (value.find(filter_str_lc) == std::string::npos)
- {
- (*iter)->setHighlighted(false);
- }
- else
- {
- (*iter)->setHighlighted(true);
- res = true;
- }
- }
- iter++;
- }
- return res;
-}
+ /**
+ * @file llscrolllistctrl.cpp
+ * @brief Scroll lists are composed of rows (items), each of which
+ * contains columns (cells).
+ *
+ * $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 "linden_common.h"
+
+#include "llscrolllistctrl.h"
+
+#include <algorithm>
+
+#include "llstl.h"
+#include "llboost.h"
+//#include "indra_constants.h"
+
+#include "llavatarnamecache.h"
+#include "llcheckboxctrl.h"
+#include "llclipboard.h"
+#include "llfocusmgr.h"
+#include "llgl.h" // LLGLSUIDefault()
+#include "lllocalcliprect.h"
+//#include "llrender.h"
+#include "llresmgr.h"
+#include "llscrollbar.h"
+#include "llscrolllistcell.h"
+#include "llstring.h"
+#include "llui.h"
+#include "lluictrlfactory.h"
+#include "llwindow.h"
+#include "llcontrol.h"
+#include "llkeyboard.h"
+#include "llviewborder.h"
+#include "lltextbox.h"
+#include "llsdparam.h"
+#include "llcachename.h"
+#include "llmenugl.h"
+#include "llurlaction.h"
+#include "lltooltip.h"
+
+#include <boost/bind.hpp>
+
+static LLDefaultChildRegistry::Register<LLScrollListCtrl> r("scroll_list");
+
+// local structures & classes.
+struct SortScrollListItem
+{
+ SortScrollListItem(const std::vector<std::pair<S32, bool> >& sort_orders,const LLScrollListCtrl::sort_signal_t* sort_signal, bool alternate_sort)
+ : mSortOrders(sort_orders)
+ , mSortSignal(sort_signal)
+ , mAltSort(alternate_sort)
+ {}
+
+ bool operator()(const LLScrollListItem* i1, const LLScrollListItem* i2)
+ {
+ // sort over all columns in order specified by mSortOrders
+ S32 sort_result = 0;
+ for (sort_order_t::const_reverse_iterator it = mSortOrders.rbegin();
+ it != mSortOrders.rend(); ++it)
+ {
+ S32 col_idx = it->first;
+ bool sort_ascending = it->second;
+
+ S32 order = sort_ascending ? 1 : -1; // ascending or descending sort for this column?
+
+ const LLScrollListCell *cell1 = i1->getColumn(col_idx);
+ const LLScrollListCell *cell2 = i2->getColumn(col_idx);
+ if (cell1 && cell2)
+ {
+ if(mSortSignal)
+ {
+ sort_result = order * (*mSortSignal)(col_idx,i1, i2);
+ }
+ else
+ {
+ if (mAltSort && !cell1->getAltValue().asString().empty() && !cell2->getAltValue().asString().empty())
+ {
+ sort_result = order * LLStringUtil::compareDict(cell1->getAltValue().asString(), cell2->getAltValue().asString());
+ }
+ else
+ {
+ sort_result = order * LLStringUtil::compareDict(cell1->getValue().asString(), cell2->getValue().asString());
+ }
+ }
+ if (sort_result != 0)
+ {
+ break; // we have a sort order!
+ }
+ }
+ }
+
+ return sort_result < 0;
+ }
+
+
+ typedef std::vector<std::pair<S32, bool> > sort_order_t;
+ const LLScrollListCtrl::sort_signal_t* mSortSignal;
+ const sort_order_t& mSortOrders;
+ const bool mAltSort;
+};
+
+//---------------------------------------------------------------------------
+// LLScrollListCtrl
+//---------------------------------------------------------------------------
+
+void LLScrollListCtrl::SelectionTypeNames::declareValues()
+{
+ declare("row", LLScrollListCtrl::ROW);
+ declare("cell", LLScrollListCtrl::CELL);
+ declare("header", LLScrollListCtrl::HEADER);
+}
+
+LLScrollListCtrl::Contents::Contents()
+: columns("column"),
+ rows("row")
+{
+ addSynonym(columns, "columns");
+ addSynonym(rows, "rows");
+}
+
+LLScrollListCtrl::Params::Params()
+: multi_select("multi_select", false),
+ has_border("draw_border"),
+ draw_heading("draw_heading"),
+ search_column("search_column", 0),
+ selection_type("selection_type", ROW),
+ sort_column("sort_column", -1),
+ sort_ascending("sort_ascending", true),
+ can_sort("can_sort", true),
+ mouse_wheel_opaque("mouse_wheel_opaque", false),
+ commit_on_keyboard_movement("commit_on_keyboard_movement", true),
+ commit_on_selection_change("commit_on_selection_change", false),
+ heading_height("heading_height"),
+ page_lines("page_lines", 0),
+ background_visible("background_visible"),
+ draw_stripes("draw_stripes"),
+ column_padding("column_padding"),
+ row_padding("row_padding", 2),
+ fg_unselected_color("fg_unselected_color"),
+ fg_selected_color("fg_selected_color"),
+ bg_selected_color("bg_selected_color"),
+ fg_disable_color("fg_disable_color"),
+ bg_writeable_color("bg_writeable_color"),
+ bg_readonly_color("bg_readonly_color"),
+ bg_stripe_color("bg_stripe_color"),
+ hovered_color("hovered_color"),
+ highlighted_color("highlighted_color"),
+ contents(""),
+ scroll_bar_bg_visible("scroll_bar_bg_visible"),
+ scroll_bar_bg_color("scroll_bar_bg_color"),
+ border("border")
+{}
+
+LLScrollListCtrl::LLScrollListCtrl(const LLScrollListCtrl::Params& p)
+: LLUICtrl(p),
+ mLineHeight(0),
+ mScrollLines(0),
+ mMouseWheelOpaque(p.mouse_wheel_opaque),
+ mPageLines(p.page_lines),
+ mMaxSelectable(0),
+ mAllowKeyboardMovement(true),
+ mCommitOnKeyboardMovement(p.commit_on_keyboard_movement),
+ mCommitOnSelectionChange(p.commit_on_selection_change),
+ mSelectionChanged(false),
+ mSelectionType(p.selection_type),
+ mNeedsScroll(false),
+ mCanSelect(true),
+ mCanSort(p.can_sort),
+ mColumnsDirty(false),
+ mMaxItemCount(INT_MAX),
+ mBorderThickness( 2 ),
+ mOnDoubleClickCallback( NULL ),
+ mOnMaximumSelectCallback( NULL ),
+ mOnSortChangedCallback( NULL ),
+ mHighlightedItem(-1),
+ mBorder(NULL),
+ mSortCallback(NULL),
+ mCommentTextView(NULL),
+ mNumDynamicWidthColumns(0),
+ mTotalStaticColumnWidth(0),
+ mTotalColumnPadding(0),
+ mSorted(false),
+ mDirty(false),
+ mOriginalSelection(-1),
+ mLastSelected(NULL),
+ mHeadingHeight(p.heading_height),
+ mAllowMultipleSelection(p.multi_select),
+ mDisplayColumnHeaders(p.draw_heading),
+ mBackgroundVisible(p.background_visible),
+ mDrawStripes(p.draw_stripes),
+ mBgWriteableColor(p.bg_writeable_color()),
+ mBgReadOnlyColor(p.bg_readonly_color()),
+ mBgSelectedColor(p.bg_selected_color()),
+ mBgStripeColor(p.bg_stripe_color()),
+ mFgSelectedColor(p.fg_selected_color()),
+ mFgUnselectedColor(p.fg_unselected_color()),
+ mFgDisabledColor(p.fg_disable_color()),
+ mHighlightedColor(p.highlighted_color()),
+ mHoveredColor(p.hovered_color()),
+ mSearchColumn(p.search_column),
+ mColumnPadding(p.column_padding),
+ mRowPadding(p.row_padding),
+ mAlternateSort(false),
+ mContextMenuType(MENU_NONE),
+ mIsFriendSignal(NULL)
+{
+ mItemListRect.setOriginAndSize(
+ mBorderThickness,
+ mBorderThickness,
+ getRect().getWidth() - 2 * mBorderThickness,
+ getRect().getHeight() - 2 * mBorderThickness );
+
+ updateLineHeight();
+
+ // Init the scrollbar
+ static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
+
+ LLRect scroll_rect;
+ scroll_rect.setOriginAndSize(
+ getRect().getWidth() - mBorderThickness - scrollbar_size,
+ mItemListRect.mBottom,
+ scrollbar_size,
+ mItemListRect.getHeight());
+
+ LLScrollbar::Params sbparams;
+ sbparams.name("Scrollbar");
+ sbparams.rect(scroll_rect);
+ sbparams.orientation(LLScrollbar::VERTICAL);
+ sbparams.doc_size(getItemCount());
+ sbparams.doc_pos(mScrollLines);
+ sbparams.page_size( getLinesPerPage() );
+ sbparams.change_callback(boost::bind(&LLScrollListCtrl::onScrollChange, this, _1, _2));
+ sbparams.follows.flags(FOLLOWS_RIGHT | FOLLOWS_TOP | FOLLOWS_BOTTOM);
+ sbparams.visible(false);
+ sbparams.bg_visible(p.scroll_bar_bg_visible);
+ sbparams.bg_color(p.scroll_bar_bg_color);
+ mScrollbar = LLUICtrlFactory::create<LLScrollbar> (sbparams);
+ addChild(mScrollbar);
+
+ // Border
+ if (p.has_border)
+ {
+ LLRect border_rect = getLocalRect();
+ LLViewBorder::Params params = p.border;
+ params.rect(border_rect);
+ mBorder = LLUICtrlFactory::create<LLViewBorder> (params);
+ addChild(mBorder);
+ }
+
+ // set border *after* rect is fully initialized
+ if (mBorder)
+ {
+ mBorder->setRect(getLocalRect());
+ mBorder->reshape(getRect().getWidth(), getRect().getHeight());
+ }
+
+ if (p.sort_column >= 0)
+ {
+ sortByColumnIndex(p.sort_column, p.sort_ascending);
+ }
+
+
+ for (LLInitParam::ParamIterator<LLScrollListColumn::Params>::const_iterator row_it = p.contents.columns.begin();
+ row_it != p.contents.columns.end();
+ ++row_it)
+ {
+ addColumn(*row_it);
+ }
+
+ for (LLInitParam::ParamIterator<LLScrollListItem::Params>::const_iterator row_it = p.contents.rows.begin();
+ row_it != p.contents.rows.end();
+ ++row_it)
+ {
+ addRow(*row_it);
+ }
+
+ LLTextBox::Params text_p;
+ text_p.name("comment_text");
+ text_p.border_visible(false);
+ text_p.rect(mItemListRect);
+ text_p.follows.flags(FOLLOWS_ALL);
+ // word wrap was added accroding to the EXT-6841
+ text_p.wrap(true);
+ addChild(LLUICtrlFactory::create<LLTextBox>(text_p));
+}
+
+S32 LLScrollListCtrl::getSearchColumn()
+{
+ // search for proper search column
+ if (mSearchColumn < 0)
+ {
+ LLScrollListItem* itemp = getFirstData();
+ if (itemp)
+ {
+ for(S32 column = 0; column < getNumColumns(); column++)
+ {
+ LLScrollListCell* cell = itemp->getColumn(column);
+ if (cell && cell->isText())
+ {
+ mSearchColumn = column;
+ break;
+ }
+ }
+ }
+ }
+ return llclamp(mSearchColumn, 0, getNumColumns());
+}
+/*virtual*/
+bool LLScrollListCtrl::preProcessChildNode(LLXMLNodePtr child)
+{
+ if (child->hasName("column") || child->hasName("row"))
+ {
+ return true; // skip
+ }
+ else
+ {
+ return false;
+ }
+}
+
+LLScrollListCtrl::~LLScrollListCtrl()
+{
+ delete mSortCallback;
+
+ std::for_each(mItemList.begin(), mItemList.end(), DeletePointer());
+ mItemList.clear();
+ clearColumns(); //clears columns and deletes headers
+ delete mIsFriendSignal;
+
+ auto menu = mPopupMenuHandle.get();
+ if (menu)
+ {
+ menu->die();
+ mPopupMenuHandle.markDead();
+ }
+}
+
+
+bool LLScrollListCtrl::setMaxItemCount(S32 max_count)
+{
+ if (max_count >= getItemCount())
+ {
+ mMaxItemCount = max_count;
+ }
+ return (max_count == mMaxItemCount);
+}
+
+S32 LLScrollListCtrl::isEmpty() const
+{
+ return mItemList.empty();
+}
+
+S32 LLScrollListCtrl::getItemCount() const
+{
+ return mItemList.size();
+}
+
+bool LLScrollListCtrl::hasSelectedItem() const
+{
+ item_list::iterator iter;
+ for (iter = mItemList.begin(); iter < mItemList.end(); )
+ {
+ LLScrollListItem* itemp = *iter;
+ if (itemp && itemp->getSelected())
+ {
+ return true;
+ }
+ iter++;
+ }
+ return false;
+}
+
+// virtual LLScrolListInterface function (was deleteAllItems)
+void LLScrollListCtrl::clearRows()
+{
+ std::for_each(mItemList.begin(), mItemList.end(), DeletePointer());
+ mItemList.clear();
+ //mItemCount = 0;
+
+ // Scroll the bar back up to the top.
+ mScrollbar->setDocParams(0, 0);
+
+ mScrollLines = 0;
+ mLastSelected = NULL;
+ updateLayout();
+ mDirty = false;
+}
+
+
+LLScrollListItem* LLScrollListCtrl::getFirstSelected() const
+{
+ item_list::const_iterator iter;
+ for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
+ {
+ LLScrollListItem* item = *iter;
+ if (item->getSelected())
+ {
+ return item;
+ }
+ }
+ return NULL;
+}
+
+std::vector<LLScrollListItem*> LLScrollListCtrl::getAllSelected() const
+{
+ std::vector<LLScrollListItem*> ret;
+ item_list::const_iterator iter;
+ for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
+ {
+ LLScrollListItem* item = *iter;
+ if (item->getSelected())
+ {
+ ret.push_back(item);
+ }
+ }
+ return ret;
+}
+
+S32 LLScrollListCtrl::getNumSelected() const
+{
+ S32 numSelected = 0;
+
+ for(item_list::const_iterator iter = mItemList.begin(); iter != mItemList.end(); ++iter)
+ {
+ LLScrollListItem* item = *iter;
+ if (item->getSelected())
+ {
+ ++numSelected;
+ }
+ }
+
+ return numSelected;
+}
+
+S32 LLScrollListCtrl::getFirstSelectedIndex() const
+{
+ S32 CurSelectedIndex = 0;
+
+ // make sure sort is up to date before returning an index
+ updateSort();
+
+ item_list::const_iterator iter;
+ for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
+ {
+ LLScrollListItem* item = *iter;
+ if (item->getSelected())
+ {
+ return CurSelectedIndex;
+ }
+ CurSelectedIndex++;
+ }
+
+ return -1;
+}
+
+LLScrollListItem* LLScrollListCtrl::getFirstData() const
+{
+ if (mItemList.size() == 0)
+ {
+ return NULL;
+ }
+ return mItemList[0];
+}
+
+LLScrollListItem* LLScrollListCtrl::getLastData() const
+{
+ if (mItemList.size() == 0)
+ {
+ return NULL;
+ }
+ return mItemList[mItemList.size() - 1];
+}
+
+std::vector<LLScrollListItem*> LLScrollListCtrl::getAllData() const
+{
+ std::vector<LLScrollListItem*> ret;
+ item_list::const_iterator iter;
+ for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
+ {
+ LLScrollListItem* item = *iter;
+ ret.push_back(item);
+ }
+ return ret;
+}
+
+// returns first matching item
+LLScrollListItem* LLScrollListCtrl::getItem(const LLSD& sd) const
+{
+ std::string string_val = sd.asString();
+
+ item_list::const_iterator iter;
+ for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
+ {
+ LLScrollListItem* item = *iter;
+ // assumes string representation is good enough for comparison
+ if (item->getValue().asString() == string_val)
+ {
+ return item;
+ }
+ }
+ return NULL;
+}
+
+
+void LLScrollListCtrl::reshape( S32 width, S32 height, bool called_from_parent )
+{
+ LLUICtrl::reshape( width, height, called_from_parent );
+
+ updateLayout();
+}
+
+void LLScrollListCtrl::updateLayout()
+{
+ static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
+ // reserve room for column headers, if needed
+ S32 heading_size = (mDisplayColumnHeaders ? mHeadingHeight : 0);
+ mItemListRect.setOriginAndSize(
+ mBorderThickness,
+ mBorderThickness,
+ getRect().getWidth() - 2 * mBorderThickness,
+ getRect().getHeight() - (2 * mBorderThickness ) - heading_size );
+
+ if (mCommentTextView == NULL)
+ {
+ mCommentTextView = getChildView("comment_text");
+ }
+
+ mCommentTextView->setShape(mItemListRect);
+
+ // how many lines of content in a single "page"
+ S32 page_lines = getLinesPerPage();
+
+ bool scrollbar_visible = mLineHeight * getItemCount() > mItemListRect.getHeight();
+ if (scrollbar_visible)
+ {
+ // provide space on the right for scrollbar
+ mItemListRect.mRight = getRect().getWidth() - mBorderThickness - scrollbar_size;
+ }
+
+ mScrollbar->setOrigin(getRect().getWidth() - mBorderThickness - scrollbar_size, mItemListRect.mBottom);
+ mScrollbar->reshape(scrollbar_size, mItemListRect.getHeight() + (mDisplayColumnHeaders ? mHeadingHeight : 0));
+ mScrollbar->setPageSize(page_lines);
+ mScrollbar->setDocSize( getItemCount() );
+ mScrollbar->setVisible(scrollbar_visible);
+
+ dirtyColumns();
+}
+
+// Attempt to size the control to show all items.
+// Do not make larger than width or height.
+void LLScrollListCtrl::fitContents(S32 max_width, S32 max_height)
+{
+ S32 height = llmin( getRequiredRect().getHeight(), max_height );
+ if(mPageLines)
+ height = llmin( mPageLines * mLineHeight + 2*mBorderThickness + (mDisplayColumnHeaders ? mHeadingHeight : 0), height );
+
+ S32 width = getRect().getWidth();
+
+ reshape( width, height );
+}
+
+
+LLRect LLScrollListCtrl::getRequiredRect()
+{
+ S32 heading_size = (mDisplayColumnHeaders ? mHeadingHeight : 0);
+ S32 height = (mLineHeight * getItemCount())
+ + (2 * mBorderThickness )
+ + heading_size;
+ S32 width = getRect().getWidth();
+
+ return LLRect(0, height, width, 0);
+}
+
+
+bool LLScrollListCtrl::addItem( LLScrollListItem* item, EAddPosition pos, bool requires_column )
+{
+ bool not_too_big = getItemCount() < mMaxItemCount;
+ if (not_too_big)
+ {
+ switch( pos )
+ {
+ case ADD_TOP:
+ mItemList.push_front(item);
+ setNeedsSort();
+ break;
+
+ case ADD_DEFAULT:
+ case ADD_BOTTOM:
+ mItemList.push_back(item);
+ setNeedsSort();
+ break;
+
+ default:
+ llassert(0);
+ mItemList.push_back(item);
+ setNeedsSort();
+ break;
+ }
+
+ // create new column on demand
+ if (mColumns.empty() && requires_column)
+ {
+ LLScrollListColumn::Params col_params;
+ col_params.name = "default_column";
+ col_params.header.label = "";
+ col_params.width.dynamic_width = true;
+ addColumn(col_params);
+ }
+
+ S32 num_cols = item->getNumColumns();
+ S32 i = 0;
+ for (LLScrollListCell* cell = item->getColumn(i); i < num_cols; cell = item->getColumn(++i))
+ {
+ if (i >= (S32)mColumnsIndexed.size()) break;
+
+ cell->setWidth(mColumnsIndexed[i]->getWidth());
+ }
+
+ updateLineHeightInsert(item);
+
+ updateLayout();
+ }
+
+ return not_too_big;
+}
+
+// NOTE: This is *very* expensive for large lists, especially when we are dirtying the list every frame
+// while receiving a long list of names.
+// *TODO: Use bookkeeping to make this an incramental cost with item additions
+S32 LLScrollListCtrl::calcMaxContentWidth()
+{
+ const S32 HEADING_TEXT_PADDING = 25;
+ const S32 COLUMN_TEXT_PADDING = 10;
+
+ S32 max_item_width = 0;
+
+ ordered_columns_t::iterator column_itor;
+ for (column_itor = mColumnsIndexed.begin(); column_itor != mColumnsIndexed.end(); ++column_itor)
+ {
+ LLScrollListColumn* column = *column_itor;
+ if (!column) continue;
+
+ if (mColumnWidthsDirty)
+ {
+ // update max content width for this column, by looking at all items
+ column->mMaxContentWidth = column->mHeader ? LLFontGL::getFontSansSerifSmall()->getWidth(column->mLabel) + mColumnPadding + HEADING_TEXT_PADDING : 0;
+ item_list::iterator iter;
+ for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
+ {
+ LLScrollListCell* cellp = (*iter)->getColumn(column->mIndex);
+ if (!cellp) continue;
+
+ column->mMaxContentWidth = llmax(LLFontGL::getFontSansSerifSmall()->getWidth(cellp->getValue().asString()) + mColumnPadding + COLUMN_TEXT_PADDING, column->mMaxContentWidth);
+ }
+ }
+ max_item_width += column->mMaxContentWidth;
+ }
+ mColumnWidthsDirty = false;
+
+ return max_item_width;
+}
+
+bool LLScrollListCtrl::updateColumnWidths()
+{
+ bool width_changed = false;
+ ordered_columns_t::iterator column_itor;
+ for (column_itor = mColumnsIndexed.begin(); column_itor != mColumnsIndexed.end(); ++column_itor)
+ {
+ LLScrollListColumn* column = *column_itor;
+ if (!column) continue;
+
+ // update column width
+ S32 new_width = 0;
+ if (column->mRelWidth >= 0)
+ {
+ new_width = (S32)ll_round(column->mRelWidth*mItemListRect.getWidth());
+ }
+ else if (column->mDynamicWidth)
+ {
+ new_width = (mItemListRect.getWidth() - mTotalStaticColumnWidth - mTotalColumnPadding) / mNumDynamicWidthColumns;
+ }
+ else
+ {
+ new_width = column->getWidth();
+ }
+
+ if (column->getWidth() != new_width)
+ {
+ column->setWidth(new_width);
+ width_changed = true;
+ }
+ }
+ return width_changed;
+}
+
+// Line height is the max height of all the cells in all the items.
+void LLScrollListCtrl::updateLineHeight()
+{
+ mLineHeight = 0;
+ item_list::iterator iter;
+ for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
+ {
+ LLScrollListItem *itemp = *iter;
+ S32 num_cols = itemp->getNumColumns();
+ S32 i = 0;
+ for (const LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i))
+ {
+ mLineHeight = llmax( mLineHeight, cell->getHeight() + mRowPadding );
+ }
+ }
+}
+
+// when the only change to line height is from an insert, we needn't scan the entire list
+void LLScrollListCtrl::updateLineHeightInsert(LLScrollListItem* itemp)
+{
+ S32 num_cols = itemp->getNumColumns();
+ S32 i = 0;
+ for (const LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i))
+ {
+ mLineHeight = llmax( mLineHeight, cell->getHeight() + mRowPadding );
+ }
+}
+
+
+void LLScrollListCtrl::updateColumns(bool force_update)
+{
+ if (!mColumnsDirty && !force_update)
+ return;
+
+ mColumnsDirty = false;
+
+ bool columns_changed_width = updateColumnWidths();
+
+ // update column headers
+ std::vector<LLScrollListColumn*>::iterator column_ordered_it;
+ S32 left = mItemListRect.mLeft;
+ LLScrollColumnHeader* last_header = NULL;
+ for (column_ordered_it = mColumnsIndexed.begin(); column_ordered_it != mColumnsIndexed.end(); ++column_ordered_it)
+ {
+ LLScrollListColumn* column = *column_ordered_it;
+ if (!column || column->getWidth() < 0)
+ {
+ // skip hidden columns
+ continue;
+ }
+
+ if (column->mHeader)
+ {
+ column->mHeader->updateResizeBars();
+
+ last_header = column->mHeader;
+ S32 top = mItemListRect.mTop;
+ S32 right = left + column->getWidth();
+
+ if (column->mIndex != (S32)mColumnsIndexed.size()-1)
+ {
+ right += mColumnPadding;
+ }
+ right = llmax(left, llmin(mItemListRect.getWidth(), right));
+ S32 header_width = right - left;
+
+ last_header->reshape(header_width, mHeadingHeight);
+ last_header->translate(
+ left - last_header->getRect().mLeft,
+ top - last_header->getRect().mBottom);
+ last_header->setVisible(mDisplayColumnHeaders && header_width > 0);
+ left = right;
+ }
+ }
+
+ bool header_changed_width = false;
+ // expand last column header we encountered to full list width
+ if (last_header)
+ {
+ S32 old_width = last_header->getColumn()->getWidth();
+ S32 new_width = llmax(0, mItemListRect.mRight - last_header->getRect().mLeft);
+ last_header->reshape(new_width, last_header->getRect().getHeight());
+ last_header->setVisible(mDisplayColumnHeaders && new_width > 0);
+ if (old_width != new_width)
+ {
+ last_header->getColumn()->setWidth(new_width);
+ header_changed_width = true;
+ }
+ }
+
+ // propagate column widths to individual cells
+ if (columns_changed_width || force_update)
+ {
+ item_list::iterator iter;
+ for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
+ {
+ LLScrollListItem *itemp = *iter;
+ S32 num_cols = itemp->getNumColumns();
+ S32 i = 0;
+ for (LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i))
+ {
+ if (i >= (S32)mColumnsIndexed.size()) break;
+
+ cell->setWidth(mColumnsIndexed[i]->getWidth());
+ }
+ }
+ }
+ else if (header_changed_width)
+ {
+ item_list::iterator iter;
+ S32 index = last_header->getColumn()->mIndex; // Not always identical to last column!
+ for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
+ {
+ LLScrollListItem *itemp = *iter;
+ LLScrollListCell* cell = itemp->getColumn(index);
+ if (cell)
+ {
+ cell->setWidth(last_header->getColumn()->getWidth());
+ }
+ }
+ }
+}
+
+void LLScrollListCtrl::setHeadingHeight(S32 heading_height)
+{
+ mHeadingHeight = heading_height;
+
+ updateLayout();
+
+}
+void LLScrollListCtrl::setPageLines(S32 new_page_lines)
+{
+ mPageLines = new_page_lines;
+
+ updateLayout();
+}
+
+bool LLScrollListCtrl::selectFirstItem()
+{
+ bool success = false;
+
+ // our $%&@#$()^%#$()*^ iterators don't let us check against the first item inside out iteration
+ bool first_item = true;
+
+ item_list::iterator iter;
+ for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
+ {
+ LLScrollListItem *itemp = *iter;
+ if( first_item && itemp->getEnabled() )
+ {
+ if (!itemp->getSelected())
+ {
+ switch (mSelectionType)
+ {
+ case CELL:
+ selectItem(itemp, 0);
+ break;
+ case HEADER:
+ case ROW:
+ selectItem(itemp, -1);
+ }
+ }
+ success = true;
+ mOriginalSelection = 0;
+ }
+ else
+ {
+ deselectItem(itemp);
+ }
+ first_item = false;
+ }
+ if (mCommitOnSelectionChange)
+ {
+ commitIfChanged();
+ }
+ return success;
+}
+
+// Deselects all other items
+// virtual
+bool LLScrollListCtrl::selectNthItem( S32 target_index )
+{
+ return selectItemRange(target_index, target_index);
+}
+
+// virtual
+bool LLScrollListCtrl::selectItemRange( S32 first_index, S32 last_index )
+{
+ if (mItemList.empty())
+ {
+ return false;
+ }
+
+ // make sure sort is up to date
+ updateSort();
+
+ S32 listlen = (S32)mItemList.size();
+ first_index = llclamp(first_index, 0, listlen-1);
+
+ if (last_index < 0)
+ last_index = listlen-1;
+ else
+ last_index = llclamp(last_index, first_index, listlen-1);
+
+ bool success = false;
+ S32 index = 0;
+ for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); )
+ {
+ LLScrollListItem *itemp = *iter;
+ if(!itemp)
+ {
+ iter = mItemList.erase(iter);
+ continue ;
+ }
+
+ if( index >= first_index && index <= last_index )
+ {
+ if( itemp->getEnabled() )
+ {
+ // TODO: support range selection for cells
+ selectItem(itemp, -1, false);
+ success = true;
+ }
+ }
+ else
+ {
+ deselectItem(itemp);
+ }
+ index++;
+ iter++ ;
+ }
+
+ if (mCommitOnSelectionChange)
+ {
+ commitIfChanged();
+ }
+
+ mSearchString.clear();
+
+ return success;
+}
+
+
+void LLScrollListCtrl::swapWithNext(S32 index)
+{
+ if (index >= ((S32)mItemList.size() - 1))
+ {
+ // At end of list, doesn't do anything
+ return;
+ }
+ updateSort();
+ LLScrollListItem *cur_itemp = mItemList[index];
+ mItemList[index] = mItemList[index + 1];
+ mItemList[index + 1] = cur_itemp;
+}
+
+
+void LLScrollListCtrl::swapWithPrevious(S32 index)
+{
+ if (index <= 0)
+ {
+ // At beginning of list, don't do anything
+ }
+
+ updateSort();
+ LLScrollListItem *cur_itemp = mItemList[index];
+ mItemList[index] = mItemList[index - 1];
+ mItemList[index - 1] = cur_itemp;
+}
+
+
+void LLScrollListCtrl::deleteSingleItem(S32 target_index)
+{
+ if (target_index < 0 || target_index >= (S32)mItemList.size())
+ {
+ return;
+ }
+
+ updateSort();
+
+ LLScrollListItem *itemp;
+ itemp = mItemList[target_index];
+ if (itemp == mLastSelected)
+ {
+ mLastSelected = NULL;
+ }
+ delete itemp;
+ mItemList.erase(mItemList.begin() + target_index);
+ dirtyColumns();
+}
+
+//FIXME: refactor item deletion
+void LLScrollListCtrl::deleteItems(const LLSD& sd)
+{
+ item_list::iterator iter;
+ for (iter = mItemList.begin(); iter < mItemList.end(); )
+ {
+ LLScrollListItem* itemp = *iter;
+ if (itemp->getValue().asString() == sd.asString())
+ {
+ if (itemp == mLastSelected)
+ {
+ mLastSelected = NULL;
+ }
+ delete itemp;
+ iter = mItemList.erase(iter);
+ }
+ else
+ {
+ iter++;
+ }
+ }
+
+ dirtyColumns();
+}
+
+void LLScrollListCtrl::deleteSelectedItems()
+{
+ item_list::iterator iter;
+ for (iter = mItemList.begin(); iter < mItemList.end(); )
+ {
+ LLScrollListItem* itemp = *iter;
+ if (itemp->getSelected())
+ {
+ delete itemp;
+ iter = mItemList.erase(iter);
+ }
+ else
+ {
+ iter++;
+ }
+ }
+ mLastSelected = NULL;
+ dirtyColumns();
+}
+
+void LLScrollListCtrl::clearHighlightedItems()
+{
+ for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); ++iter)
+ {
+ (*iter)->setHighlighted(false);
+ }
+}
+
+void LLScrollListCtrl::mouseOverHighlightNthItem(S32 target_index)
+{
+ if (mHighlightedItem != target_index)
+ {
+ if (mHighlightedItem >= 0 && mHighlightedItem < mItemList.size())
+ {
+ mItemList[mHighlightedItem]->setHoverCell(-1);
+ }
+ mHighlightedItem = target_index;
+ }
+}
+
+S32 LLScrollListCtrl::selectMultiple( uuid_vec_t ids )
+{
+ item_list::iterator iter;
+ S32 count = 0;
+ for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
+ {
+ LLScrollListItem* item = *iter;
+ uuid_vec_t::iterator iditr;
+ for(iditr = ids.begin(); iditr != ids.end(); ++iditr)
+ {
+ if (item->getEnabled() && (item->getUUID() == (*iditr)))
+ {
+ // TODO: support multiple selection for cells
+ selectItem(item, -1, false);
+ ++count;
+ break;
+ }
+ }
+ if(ids.end() != iditr) ids.erase(iditr);
+ }
+
+ if (mCommitOnSelectionChange)
+ {
+ commitIfChanged();
+ }
+ return count;
+}
+
+S32 LLScrollListCtrl::getItemIndex( LLScrollListItem* target_item ) const
+{
+ updateSort();
+
+ S32 index = 0;
+ item_list::const_iterator iter;
+ for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
+ {
+ LLScrollListItem *itemp = *iter;
+ if (target_item == itemp)
+ {
+ return index;
+ }
+ index++;
+ }
+ return -1;
+}
+
+S32 LLScrollListCtrl::getItemIndex( const LLUUID& target_id ) const
+{
+ updateSort();
+
+ S32 index = 0;
+ item_list::const_iterator iter;
+ for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
+ {
+ LLScrollListItem *itemp = *iter;
+ if (target_id == itemp->getUUID())
+ {
+ return index;
+ }
+ index++;
+ }
+ return -1;
+}
+
+void LLScrollListCtrl::selectPrevItem( bool extend_selection)
+{
+ LLScrollListItem* prev_item = NULL;
+
+ if (!getFirstSelected())
+ {
+ // select last item
+ selectNthItem(getItemCount() - 1);
+ }
+ else
+ {
+ updateSort();
+
+ item_list::iterator iter;
+ for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
+ {
+ LLScrollListItem* cur_item = *iter;
+
+ if (cur_item->getSelected())
+ {
+ if (prev_item)
+ {
+ selectItem(prev_item, cur_item->getSelectedCell(), !extend_selection);
+ }
+ else
+ {
+ reportInvalidInput();
+ }
+ break;
+ }
+
+ // don't allow navigation to disabled elements
+ prev_item = cur_item->getEnabled() ? cur_item : prev_item;
+ }
+ }
+
+ if ((mCommitOnSelectionChange || mCommitOnKeyboardMovement))
+ {
+ commitIfChanged();
+ }
+
+ mSearchString.clear();
+}
+
+
+void LLScrollListCtrl::selectNextItem( bool extend_selection)
+{
+ LLScrollListItem* next_item = NULL;
+
+ if (!getFirstSelected())
+ {
+ selectFirstItem();
+ }
+ else
+ {
+ updateSort();
+
+ item_list::reverse_iterator iter;
+ for (iter = mItemList.rbegin(); iter != mItemList.rend(); iter++)
+ {
+ LLScrollListItem* cur_item = *iter;
+
+ if (cur_item->getSelected())
+ {
+ if (next_item)
+ {
+ selectItem(next_item, cur_item->getSelectedCell(), !extend_selection);
+ }
+ else
+ {
+ reportInvalidInput();
+ }
+ break;
+ }
+
+ // don't allow navigation to disabled items
+ next_item = cur_item->getEnabled() ? cur_item : next_item;
+ }
+ }
+
+ if (mCommitOnKeyboardMovement)
+ {
+ onCommit();
+ }
+
+ mSearchString.clear();
+}
+
+
+
+void LLScrollListCtrl::deselectAllItems(bool no_commit_on_change)
+{
+ item_list::iterator iter;
+ for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
+ {
+ LLScrollListItem* item = *iter;
+ deselectItem(item);
+ }
+
+ if (mCommitOnSelectionChange && !no_commit_on_change)
+ {
+ commitIfChanged();
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Use this to add comment text such as "Searching", which ignores column settings of list
+
+void LLScrollListCtrl::setCommentText(const std::string& comment_text)
+{
+ getChild<LLTextBox>("comment_text")->setValue(comment_text);
+}
+
+LLScrollListItem* LLScrollListCtrl::addSeparator(EAddPosition pos)
+{
+ LLScrollListItem::Params separator_params;
+ separator_params.enabled(false);
+ LLScrollListCell::Params column_params;
+ column_params.type = "icon";
+ column_params.value = "menu_separator";
+ column_params.color = LLColor4(0.f, 0.f, 0.f, 0.7f);
+ column_params.font_halign = LLFontGL::HCENTER;
+ separator_params.columns.add(column_params);
+ return addRow( separator_params, pos );
+}
+
+// Selects first enabled item of the given name.
+// Returns false if item not found.
+// Calls getItemByLabel in order to combine functionality
+bool LLScrollListCtrl::selectItemByLabel(const std::string& label, bool case_sensitive, S32 column/* = 0*/)
+{
+ deselectAllItems(true); // ensure that no stale items are selected, even if we don't find a match
+ LLScrollListItem* item = getItemByLabel(label, case_sensitive, column);
+
+ bool found = NULL != item;
+ if (found)
+ {
+ selectItem(item, -1);
+ }
+
+ if (mCommitOnSelectionChange)
+ {
+ commitIfChanged();
+ }
+
+ return found;
+}
+
+LLScrollListItem* LLScrollListCtrl::getItemByLabel(const std::string& label, bool case_sensitive, S32 column)
+{
+ if (label.empty()) //RN: assume no empty items
+ {
+ return NULL;
+ }
+
+ std::string target_text = label;
+ if (!case_sensitive)
+ {
+ LLStringUtil::toLower(target_text);
+ }
+
+ item_list::iterator iter;
+ for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
+ {
+ LLScrollListItem* item = *iter;
+ std::string item_text = item->getColumn(column)->getValue().asString(); // Only select enabled items with matching names
+ if (!case_sensitive)
+ {
+ LLStringUtil::toLower(item_text);
+ }
+ if(item_text == target_text)
+ {
+ return item;
+ }
+ }
+ return NULL;
+}
+
+
+bool LLScrollListCtrl::selectItemByPrefix(const std::string& target, bool case_sensitive, S32 column)
+{
+ return selectItemByPrefix(utf8str_to_wstring(target), case_sensitive, column);
+}
+
+// Selects first enabled item that has a name where the name's first part matched the target string.
+// Returns false if item not found.
+bool LLScrollListCtrl::selectItemByPrefix(const LLWString& target, bool case_sensitive, S32 column)
+{
+ bool found = false;
+
+ LLWString target_trimmed( target );
+ S32 target_len = target_trimmed.size();
+
+ if( 0 == target_len )
+ {
+ // Is "" a valid choice?
+ item_list::iterator iter;
+ for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
+ {
+ LLScrollListItem* item = *iter;
+ // Only select enabled items with matching names
+ LLScrollListCell* cellp = item->getColumn(column == -1 ? getSearchColumn() : column);
+ bool select = cellp ? item->getEnabled() && ('\0' == cellp->getValue().asString()[0]) : false;
+ if (select)
+ {
+ selectItem(item, -1);
+ found = true;
+ break;
+ }
+ }
+ }
+ else
+ {
+ if (!case_sensitive)
+ {
+ // do comparisons in lower case
+ LLWStringUtil::toLower(target_trimmed);
+ }
+
+ for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); iter++)
+ {
+ LLScrollListItem* item = *iter;
+
+ // Only select enabled items with matching names
+ LLScrollListCell* cellp = item->getColumn(column == -1 ? getSearchColumn() : column);
+ if (!cellp)
+ {
+ continue;
+ }
+ LLWString item_label = utf8str_to_wstring(cellp->getValue().asString());
+ if (!case_sensitive)
+ {
+ LLWStringUtil::toLower(item_label);
+ }
+ // remove extraneous whitespace from searchable label
+ LLWString trimmed_label = item_label;
+ LLWStringUtil::trim(trimmed_label);
+
+ bool select = item->getEnabled() && trimmed_label.compare(0, target_trimmed.size(), target_trimmed) == 0;
+
+ if (select)
+ {
+ // find offset of matching text (might have leading whitespace)
+ S32 offset = item_label.find(target_trimmed);
+ cellp->highlightText(offset, target_trimmed.size());
+ selectItem(item, -1);
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (mCommitOnSelectionChange)
+ {
+ commitIfChanged();
+ }
+
+ return found;
+}
+
+U32 LLScrollListCtrl::searchItems(const std::string& substring, bool case_sensitive, bool focus)
+{
+ return searchItems(utf8str_to_wstring(substring), case_sensitive, focus);
+}
+
+U32 LLScrollListCtrl::searchItems(const LLWString& substring, bool case_sensitive, bool focus)
+{
+ U32 found = 0;
+
+ LLWString substring_trimmed(substring);
+ S32 len = substring_trimmed.size();
+
+ if (0 == len)
+ {
+ // at the moment search for empty element is not supported
+ return 0;
+ }
+ else
+ {
+ deselectAllItems(true);
+ if (!case_sensitive)
+ {
+ // do comparisons in lower case
+ LLWStringUtil::toLower(substring_trimmed);
+ }
+
+ for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); iter++)
+ {
+ LLScrollListItem* item = *iter;
+ // Only select enabled items with matching names
+ if (!item->getEnabled())
+ {
+ continue;
+ }
+ LLScrollListCell* cellp = item->getColumn(getSearchColumn());
+ if (!cellp)
+ {
+ continue;
+ }
+ LLWString item_label = utf8str_to_wstring(cellp->getValue().asString());
+ if (!case_sensitive)
+ {
+ LLWStringUtil::toLower(item_label);
+ }
+ // remove extraneous whitespace from searchable label
+ LLWStringUtil::trim(item_label);
+
+ size_t found_iter = item_label.find(substring_trimmed);
+
+ if (found_iter != std::string::npos)
+ {
+ // find offset of matching text
+ cellp->highlightText(found_iter, substring_trimmed.size());
+ selectItem(item, -1, false);
+
+ found++;
+
+ if (!mAllowMultipleSelection)
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ if (focus && found != 0)
+ {
+ mNeedsScroll = true;
+ }
+
+ if (mCommitOnSelectionChange)
+ {
+ commitIfChanged();
+ }
+
+ return found;
+}
+
+const std::string LLScrollListCtrl::getSelectedItemLabel(S32 column) const
+{
+ LLScrollListItem* item;
+
+ item = getFirstSelected();
+ if (item)
+ {
+ return item->getColumn(column)->getValue().asString();
+ }
+
+ return LLStringUtil::null;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// "StringUUID" interface: use this when you're creating a list that contains non-unique strings each of which
+// has an associated, unique UUID, and only one of which can be selected at a time.
+
+LLScrollListItem* LLScrollListCtrl::addStringUUIDItem(const std::string& item_text, const LLUUID& id, EAddPosition pos, bool enabled)
+{
+ if (getItemCount() < mMaxItemCount)
+ {
+ LLScrollListItem::Params item_p;
+ item_p.enabled(enabled);
+ item_p.value(id);
+ item_p.columns.add().value(item_text).type("text");
+
+ return addRow( item_p, pos );
+ }
+ return NULL;
+}
+
+// Select the line or lines that match this UUID
+bool LLScrollListCtrl::selectByID( const LLUUID& id )
+{
+ return selectByValue( LLSD(id) );
+}
+
+bool LLScrollListCtrl::setSelectedByValue(const LLSD& value, bool selected)
+{
+ bool found = false;
+
+ if (selected && !mAllowMultipleSelection) deselectAllItems(true);
+
+ item_list::iterator iter;
+ for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
+ {
+ LLScrollListItem* item = *iter;
+ if (item->getEnabled())
+ {
+ if (value.isBinary())
+ {
+ if (item->getValue().isBinary())
+ {
+ LLSD::Binary data1 = value.asBinary();
+ LLSD::Binary data2 = item->getValue().asBinary();
+ found = std::equal(data1.begin(), data1.end(), data2.begin());
+ }
+ }
+ else
+ {
+ found = item->getValue().asString() == value.asString();
+ }
+
+ if (found)
+ {
+ if (selected)
+ {
+ selectItem(item, -1);
+ }
+ else
+ {
+ deselectItem(item);
+ }
+ break;
+ }
+ }
+ }
+
+ if (mCommitOnSelectionChange)
+ {
+ commitIfChanged();
+ }
+
+ return found;
+}
+
+bool LLScrollListCtrl::isSelected(const LLSD& value) const
+{
+ item_list::const_iterator iter;
+ for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
+ {
+ LLScrollListItem* item = *iter;
+ if (item->getValue().asString() == value.asString())
+ {
+ return item->getSelected();
+ }
+ }
+ return false;
+}
+
+LLUUID LLScrollListCtrl::getStringUUIDSelectedItem() const
+{
+ LLScrollListItem* item = getFirstSelected();
+
+ if (item)
+ {
+ return item->getUUID();
+ }
+
+ return LLUUID::null;
+}
+
+LLSD LLScrollListCtrl::getSelectedValue()
+{
+ LLScrollListItem* item = getFirstSelected();
+
+ if (item)
+ {
+ return item->getValue();
+ }
+ else
+ {
+ return LLSD();
+ }
+}
+
+void LLScrollListCtrl::drawItems()
+{
+ S32 x = mItemListRect.mLeft;
+ S32 y = mItemListRect.mTop - mLineHeight;
+
+ // allow for partial line at bottom
+ S32 num_page_lines = getLinesPerPage();
+
+ LLRect item_rect;
+
+ LLGLSUIDefault gls_ui;
+
+ F32 alpha = getDrawContext().mAlpha;
+
+ {
+ LLLocalClipRect clip(mItemListRect);
+
+ S32 cur_y = y;
+
+ S32 max_columns = 0;
+
+ LLColor4 highlight_color = LLColor4::white; // ex: text inside cells
+ static LLUICachedControl<F32> type_ahead_timeout ("TypeAheadTimeout", 0);
+ highlight_color.mV[VALPHA] = clamp_rescale(mSearchTimer.getElapsedTimeF32(), type_ahead_timeout * 0.7f, type_ahead_timeout(), 0.4f, 0.f);
+
+ S32 first_line = mScrollLines;
+ S32 last_line = llmin((S32)mItemList.size() - 1, mScrollLines + getLinesPerPage());
+
+ if (first_line >= mItemList.size())
+ {
+ return;
+ }
+ item_list::iterator iter;
+ for (S32 line = first_line; line <= last_line; line++)
+ {
+ LLScrollListItem* item = mItemList[line];
+
+ item_rect.setOriginAndSize(
+ x,
+ cur_y,
+ mItemListRect.getWidth(),
+ mLineHeight );
+ item->setRect(item_rect);
+
+ max_columns = llmax(max_columns, item->getNumColumns());
+
+ LLColor4 fg_color;
+ LLColor4 hover_color(LLColor4::transparent);
+ LLColor4 select_color(LLColor4::transparent);
+
+ if( mScrollLines <= line && line < mScrollLines + num_page_lines )
+ {
+ fg_color = (item->getEnabled() ? mFgUnselectedColor.get() : mFgDisabledColor.get());
+ if( item->getSelected() && mCanSelect)
+ {
+ if(item->getHighlighted()) // if it's highlighted, average the colors
+ {
+ select_color = lerp(mBgSelectedColor.get(), mHighlightedColor.get(), 0.5f);
+ }
+ else // otherwise just select-highlight it
+ {
+ select_color = mBgSelectedColor.get();
+ }
+
+ fg_color = (item->getEnabled() ? mFgSelectedColor.get() : mFgDisabledColor.get());
+ }
+ if (mHighlightedItem == line && mCanSelect)
+ {
+ if(item->getHighlighted()) // if it's highlighted, average the colors
+ {
+ hover_color = lerp(mHoveredColor.get(), mHighlightedColor.get(), 0.5f);
+ }
+ else // otherwise just hover-highlight it
+ {
+ hover_color = mHoveredColor.get();
+ }
+ }
+ else if (item->getHighlighted())
+ {
+ hover_color = mHighlightedColor.get();
+ }
+ else
+ {
+ if (mDrawStripes && (line % 2 == 0) && (max_columns > 1))
+ {
+ hover_color = mBgStripeColor.get();
+ }
+ }
+
+ if (!item->getEnabled())
+ {
+ hover_color = mBgReadOnlyColor.get();
+ }
+
+ item->draw(item_rect, fg_color % alpha, hover_color% alpha, select_color% alpha, highlight_color % alpha, mColumnPadding);
+
+ cur_y -= mLineHeight;
+ }
+ }
+ }
+}
+
+
+void LLScrollListCtrl::draw()
+{
+ LLLocalClipRect clip(getLocalRect());
+
+ // if user specifies sort, make sure it is maintained
+ updateSort();
+
+ if (mNeedsScroll)
+ {
+ scrollToShowSelected();
+ mNeedsScroll = false;
+ }
+ LLRect background(0, getRect().getHeight(), getRect().getWidth(), 0);
+ // Draw background
+ if (mBackgroundVisible)
+ {
+ F32 alpha = getCurrentTransparency();
+ gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
+ gl_rect_2d(background, getEnabled() ? mBgWriteableColor.get() % alpha : mBgReadOnlyColor.get() % alpha );
+ }
+
+ updateColumns();
+
+ getChildView("comment_text")->setVisible(mItemList.empty());
+
+ drawItems();
+
+ if (mBorder)
+ {
+ mBorder->setKeyboardFocusHighlight(hasFocus());
+ }
+
+ LLUICtrl::draw();
+}
+
+void LLScrollListCtrl::setEnabled(bool enabled)
+{
+ mCanSelect = enabled;
+ setTabStop(enabled);
+ mScrollbar->setTabStop(!enabled && mScrollbar->getPageSize() < mScrollbar->getDocSize());
+}
+
+bool LLScrollListCtrl::handleScrollWheel(S32 x, S32 y, S32 clicks)
+{
+ bool handled = false;
+ // Pretend the mouse is over the scrollbar
+ handled = mScrollbar->handleScrollWheel( 0, 0, clicks );
+
+ if (mMouseWheelOpaque)
+ {
+ return true;
+ }
+
+ return handled;
+}
+
+bool LLScrollListCtrl::handleScrollHWheel(S32 x, S32 y, S32 clicks)
+{
+ bool handled = false;
+ // Pretend the mouse is over the scrollbar
+ handled = mScrollbar->handleScrollHWheel( 0, 0, clicks );
+
+ if (mMouseWheelOpaque)
+ {
+ return true;
+ }
+
+ return handled;
+}
+
+// *NOTE: Requires a valid row_index and column_index
+LLRect LLScrollListCtrl::getCellRect(S32 row_index, S32 column_index)
+{
+ LLRect cell_rect;
+ S32 rect_left = getColumnOffsetFromIndex(column_index) + mItemListRect.mLeft;
+ S32 rect_bottom = getRowOffsetFromIndex(row_index);
+ LLScrollListColumn* columnp = getColumn(column_index);
+ cell_rect.setOriginAndSize(rect_left, rect_bottom,
+ /*rect_left + */columnp->getWidth(), mLineHeight);
+ return cell_rect;
+}
+
+bool LLScrollListCtrl::handleToolTip(S32 x, S32 y, MASK mask)
+{
+ S32 column_index = getColumnIndexFromOffset(x);
+ LLScrollListColumn* columnp = getColumn(column_index);
+
+ if (columnp == NULL) return false;
+
+ bool handled = false;
+ // show tooltip for full name of hovered item if it has been truncated
+ LLScrollListItem* hit_item = hitItem(x, y);
+ if (hit_item)
+ {
+ LLScrollListCell* hit_cell = hit_item->getColumn(column_index);
+ if (!hit_cell) return false;
+ if (hit_cell
+ && hit_cell->isText()
+ && hit_cell->needsToolTip())
+ {
+ S32 row_index = getItemIndex(hit_item);
+ LLRect cell_rect = getCellRect(row_index, column_index);
+ // Convert rect local to screen coordinates
+ LLRect sticky_rect;
+ localRectToScreen(cell_rect, &sticky_rect);
+
+ // display tooltip exactly over original cell, in same font
+ LLToolTipMgr::instance().show(LLToolTip::Params()
+ .message(hit_cell->getToolTip())
+ .font(LLFontGL::getFontEmojiSmall())
+ .pos(LLCoordGL(sticky_rect.mLeft - 5, sticky_rect.mTop + 6))
+ .delay_time(0.2f)
+ .sticky_rect(sticky_rect));
+ }
+ handled = true;
+ }
+
+ // otherwise, look for a tooltip associated with this column
+ LLScrollColumnHeader* headerp = columnp->mHeader;
+ if (headerp && !handled)
+ {
+ handled = headerp->handleToolTip(x, y, mask);
+ }
+
+ return handled;
+}
+
+bool LLScrollListCtrl::selectItemAt(S32 x, S32 y, MASK mask)
+{
+ if (!mCanSelect) return false;
+
+ bool selection_changed = false;
+
+ LLScrollListItem* hit_item = hitItem(x, y);
+
+ if( hit_item )
+ {
+ if( mAllowMultipleSelection )
+ {
+ if (mask & MASK_SHIFT)
+ {
+ if (mLastSelected == NULL)
+ {
+ selectItem(hit_item, getColumnIndexFromOffset(x));
+ }
+ else
+ {
+ // Select everthing between mLastSelected and hit_item
+ bool selecting = false;
+ item_list::iterator itor;
+ // If we multiselect backwards, we'll stomp on mLastSelected,
+ // meaning that we never stop selecting until hitting max or
+ // the end of the list.
+ LLScrollListItem* lastSelected = mLastSelected;
+ for (itor = mItemList.begin(); itor != mItemList.end(); ++itor)
+ {
+ if(mMaxSelectable > 0 && getAllSelected().size() >= mMaxSelectable)
+ {
+ if(mOnMaximumSelectCallback)
+ {
+ mOnMaximumSelectCallback();
+ }
+ break;
+ }
+ LLScrollListItem *item = *itor;
+ if (item == hit_item || item == lastSelected)
+ {
+ selectItem(item, getColumnIndexFromOffset(x), false);
+ selecting = !selecting;
+ if (hit_item == lastSelected)
+ {
+ // stop selecting now, since we just clicked on our last selected item
+ selecting = false;
+ }
+ }
+ if (selecting)
+ {
+ selectItem(item, getColumnIndexFromOffset(x), false);
+ }
+ }
+ }
+ }
+ else if (mask & MASK_CONTROL)
+ {
+ if (hit_item->getSelected())
+ {
+ deselectItem(hit_item);
+ }
+ else
+ {
+ if(!(mMaxSelectable > 0 && getAllSelected().size() >= mMaxSelectable))
+ {
+ selectItem(hit_item, getColumnIndexFromOffset(x), false);
+ }
+ else
+ {
+ if(mOnMaximumSelectCallback)
+ {
+ mOnMaximumSelectCallback();
+ }
+ }
+ }
+ }
+ else
+ {
+ deselectAllItems(true);
+ selectItem(hit_item, getColumnIndexFromOffset(x));
+ }
+ }
+ else
+ {
+ selectItem(hit_item, getColumnIndexFromOffset(x));
+ }
+
+ selection_changed = mSelectionChanged;
+ if (mCommitOnSelectionChange)
+ {
+ commitIfChanged();
+ }
+
+ // clear search string on mouse operations
+ mSearchString.clear();
+ }
+ else
+ {
+ //mLastSelected = NULL;
+ //deselectAllItems(true);
+ }
+
+ return selection_changed;
+}
+
+
+bool LLScrollListCtrl::handleMouseDown(S32 x, S32 y, MASK mask)
+{
+ bool handled = childrenHandleMouseDown(x, y, mask) != NULL;
+
+ if( !handled )
+ {
+ // set keyboard focus first, in case click action wants to move focus elsewhere
+ setFocus(true);
+
+ // clear selection changed flag because user is starting a selection operation
+ mSelectionChanged = false;
+
+ handleClick(x, y, mask);
+ }
+
+ return true;
+}
+
+bool LLScrollListCtrl::handleMouseUp(S32 x, S32 y, MASK mask)
+{
+ if (hasMouseCapture())
+ {
+ // release mouse capture immediately so
+ // scroll to show selected logic will work
+ gFocusMgr.setMouseCapture(NULL);
+ if(mask == MASK_NONE)
+ {
+ selectItemAt(x, y, mask);
+ mNeedsScroll = true;
+ }
+ }
+
+ // always commit when mouse operation is completed inside list
+ if (mItemListRect.pointInRect(x,y))
+ {
+ mDirty = mDirty || mSelectionChanged;
+ mSelectionChanged = false;
+ onCommit();
+ }
+
+ return LLUICtrl::handleMouseUp(x, y, mask);
+}
+
+// virtual
+bool LLScrollListCtrl::handleRightMouseDown(S32 x, S32 y, MASK mask)
+{
+ LLScrollListItem *item = hitItem(x, y);
+ if (item)
+ {
+ // check to see if we have a UUID for this row
+ std::string id = item->getValue().asString();
+ LLUUID uuid(id);
+ if (! uuid.isNull() && mContextMenuType != MENU_NONE)
+ {
+ // set up the callbacks for all of the avatar/group menu items
+ // (N.B. callbacks don't take const refs as id is local scope)
+ bool is_group = (mContextMenuType == MENU_GROUP);
+ LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
+ registrar.add("Url.ShowProfile", boost::bind(&LLScrollListCtrl::showProfile, id, is_group));
+ registrar.add("Url.SendIM", boost::bind(&LLScrollListCtrl::sendIM, id));
+ registrar.add("Url.AddFriend", boost::bind(&LLScrollListCtrl::addFriend, id));
+ registrar.add("Url.RemoveFriend", boost::bind(&LLScrollListCtrl::removeFriend, id));
+ registrar.add("Url.ReportAbuse", boost::bind(&LLScrollListCtrl::reportAbuse, id, is_group));
+ registrar.add("Url.Execute", boost::bind(&LLScrollListCtrl::showNameDetails, id, is_group));
+ registrar.add("Url.CopyLabel", boost::bind(&LLScrollListCtrl::copyNameToClipboard, id, is_group));
+ registrar.add("Url.CopyUrl", boost::bind(&LLScrollListCtrl::copySLURLToClipboard, id, is_group));
+
+ // create the context menu from the XUI file and display it
+ std::string menu_name = is_group ? "menu_url_group.xml" : "menu_url_agent.xml";
+ auto menu = mPopupMenuHandle.get();
+ if (menu)
+ {
+ menu->die();
+ mPopupMenuHandle.markDead();
+ }
+ llassert(LLMenuGL::sMenuContainer != NULL);
+ menu = LLUICtrlFactory::getInstance()->createFromFile<LLContextMenu>(
+ menu_name, LLMenuGL::sMenuContainer, LLMenuHolderGL::child_registry_t::instance());
+ if (menu)
+ {
+ mPopupMenuHandle = menu->getHandle();
+ if (mIsFriendSignal)
+ {
+ bool isFriend = *(*mIsFriendSignal)(uuid);
+ LLView* addFriendButton = menu->getChild<LLView>("add_friend");
+ LLView* removeFriendButton = menu->getChild<LLView>("remove_friend");
+
+ if (addFriendButton && removeFriendButton)
+ {
+ addFriendButton->setEnabled(!isFriend);
+ removeFriendButton->setEnabled(isFriend);
+ }
+ }
+
+ menu->show(x, y);
+ LLMenuGL::showPopup(this, menu, x, y);
+ return true;
+ }
+ }
+ return LLUICtrl::handleRightMouseDown(x, y, mask);
+ }
+ return false;
+}
+
+void LLScrollListCtrl::showProfile(std::string id, bool is_group)
+{
+ // show the resident's profile or the group profile
+ std::string sltype = is_group ? "group" : "agent";
+ std::string slurl = "secondlife:///app/" + sltype + "/" + id + "/about";
+ LLUrlAction::showProfile(slurl);
+}
+
+void LLScrollListCtrl::sendIM(std::string id)
+{
+ // send im to the resident
+ std::string slurl = "secondlife:///app/agent/" + id + "/about";
+ LLUrlAction::sendIM(slurl);
+}
+
+void LLScrollListCtrl::addFriend(std::string id)
+{
+ // add resident to friends list
+ std::string slurl = "secondlife:///app/agent/" + id + "/about";
+ LLUrlAction::addFriend(slurl);
+}
+
+void LLScrollListCtrl::removeFriend(std::string id)
+{
+ std::string slurl = "secondlife:///app/agent/" + id + "/about";
+ LLUrlAction::removeFriend(slurl);
+}
+
+void LLScrollListCtrl::reportAbuse(std::string id, bool is_group)
+{
+ if (!is_group)
+ {
+ std::string slurl = "secondlife:///app/agent/" + id + "/about";
+ LLUrlAction::reportAbuse(slurl);
+ }
+}
+
+void LLScrollListCtrl::showNameDetails(std::string id, bool is_group)
+{
+ // open the resident's details or the group details
+ std::string sltype = is_group ? "group" : "agent";
+ std::string slurl = "secondlife:///app/" + sltype + "/" + id + "/about";
+ LLUrlAction::clickAction(slurl, true);
+}
+
+void LLScrollListCtrl::copyNameToClipboard(std::string id, bool is_group)
+{
+ // copy the name of the avatar or group to the clipboard
+ std::string name;
+ if (is_group)
+ {
+ gCacheName->getGroupName(LLUUID(id), name);
+ }
+ else
+ {
+ LLAvatarName av_name;
+ LLAvatarNameCache::get(LLUUID(id), &av_name);
+ name = av_name.getAccountName();
+ }
+ LLUrlAction::copyURLToClipboard(name);
+}
+
+void LLScrollListCtrl::copySLURLToClipboard(std::string id, bool is_group)
+{
+ // copy a SLURL for the avatar or group to the clipboard
+ std::string sltype = is_group ? "group" : "agent";
+ std::string slurl = "secondlife:///app/" + sltype + "/" + id + "/about";
+ LLUrlAction::copyURLToClipboard(slurl);
+}
+
+bool LLScrollListCtrl::handleDoubleClick(S32 x, S32 y, MASK mask)
+{
+ //bool handled = false;
+ bool handled = handleClick(x, y, mask);
+
+ if (!handled)
+ {
+ // Offer the click to the children, even if we aren't enabled
+ // so the scroll bars will work.
+ if (NULL == LLView::childrenHandleDoubleClick(x, y, mask))
+ {
+ // Run the callback only if an item is being double-clicked.
+ if( mCanSelect && hitItem(x, y) && mOnDoubleClickCallback )
+ {
+ mOnDoubleClickCallback();
+ }
+ }
+ }
+
+ return true;
+}
+
+bool LLScrollListCtrl::handleClick(S32 x, S32 y, MASK mask)
+{
+ // which row was clicked on?
+ LLScrollListItem* hit_item = hitItem(x, y);
+ if (!hit_item) return false;
+
+ // get appropriate cell from that row
+ S32 column_index = getColumnIndexFromOffset(x);
+ LLScrollListCell* hit_cell = hit_item->getColumn(column_index);
+ if (!hit_cell) return false;
+
+ // if cell handled click directly (i.e. clicked on an embedded checkbox)
+ if (hit_cell->handleClick())
+ {
+ // if item not currently selected, select it
+ if (!hit_item->getSelected())
+ {
+ selectItemAt(x, y, mask);
+ gFocusMgr.setMouseCapture(this);
+ mNeedsScroll = true;
+ }
+
+ // propagate state of cell to rest of selected column
+ {
+ // propagate value of this cell to other selected items
+ // and commit the respective widgets
+ LLSD item_value = hit_cell->getValue();
+ for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); iter++)
+ {
+ LLScrollListItem* item = *iter;
+ if (item->getSelected())
+ {
+ LLScrollListCell* cellp = item->getColumn(column_index);
+ cellp->setValue(item_value);
+ cellp->onCommit();
+ if (mLastSelected == NULL)
+ {
+ break;
+ }
+ }
+ }
+ //FIXME: find a better way to signal cell changes
+ onCommit();
+ }
+ // eat click (e.g. do not trigger double click callback)
+ return true;
+ }
+ else
+ {
+ // treat this as a normal single item selection
+ selectItemAt(x, y, mask);
+ gFocusMgr.setMouseCapture(this);
+ mNeedsScroll = true;
+ // do not eat click (allow double click callback)
+ return false;
+ }
+}
+
+LLScrollListItem* LLScrollListCtrl::hitItem( S32 x, S32 y )
+{
+ // Excludes disabled items.
+ LLScrollListItem* hit_item = NULL;
+
+ updateSort();
+
+ LLRect item_rect;
+ item_rect.setLeftTopAndSize(
+ mItemListRect.mLeft,
+ mItemListRect.mTop,
+ mItemListRect.getWidth(),
+ mLineHeight );
+
+ // allow for partial line at bottom
+ S32 num_page_lines = getLinesPerPage();
+
+ S32 line = 0;
+ item_list::iterator iter;
+ for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
+ {
+ LLScrollListItem* item = *iter;
+ if( mScrollLines <= line && line < mScrollLines + num_page_lines )
+ {
+ if( item->getEnabled() && item_rect.pointInRect( x, y ) )
+ {
+ hit_item = item;
+ break;
+ }
+
+ item_rect.translate(0, -mLineHeight);
+ }
+ line++;
+ }
+
+ return hit_item;
+}
+
+S32 LLScrollListCtrl::getColumnIndexFromOffset(S32 x)
+{
+ // which column did we hit?
+ S32 left = 0;
+ S32 right = 0;
+ S32 width = 0;
+ S32 column_index = 0;
+
+ ordered_columns_t::const_iterator iter = mColumnsIndexed.begin();
+ ordered_columns_t::const_iterator end = mColumnsIndexed.end();
+ for ( ; iter != end; ++iter)
+ {
+ width = (*iter)->getWidth() + mColumnPadding;
+ right += width;
+ if (left <= x && x < right )
+ {
+ break;
+ }
+
+ // set left for next column as right of current column
+ left = right;
+ column_index++;
+ }
+
+ return llclamp(column_index, 0, getNumColumns() - 1);
+}
+
+
+S32 LLScrollListCtrl::getColumnOffsetFromIndex(S32 index)
+{
+ S32 column_offset = 0;
+ ordered_columns_t::const_iterator iter = mColumnsIndexed.begin();
+ ordered_columns_t::const_iterator end = mColumnsIndexed.end();
+ for ( ; iter != end; ++iter)
+ {
+ if (index-- <= 0)
+ {
+ return column_offset;
+ }
+ column_offset += (*iter)->getWidth() + mColumnPadding;
+ }
+
+ // when running off the end, return the rightmost pixel
+ return mItemListRect.mRight;
+}
+
+S32 LLScrollListCtrl::getRowOffsetFromIndex(S32 index)
+{
+ S32 row_bottom = (mItemListRect.mTop - ((index - mScrollLines + 1) * mLineHeight) );
+ return row_bottom;
+}
+
+
+bool LLScrollListCtrl::handleHover(S32 x,S32 y,MASK mask)
+{
+ bool handled = false;
+
+ if (hasMouseCapture())
+ {
+ if(mask == MASK_NONE)
+ {
+ selectItemAt(x, y, mask);
+ mNeedsScroll = true;
+ }
+ }
+ else
+ if (mCanSelect)
+ {
+ LLScrollListItem* item = hitItem(x, y);
+ if (item)
+ {
+ mouseOverHighlightNthItem(getItemIndex(item));
+ switch (mSelectionType)
+ {
+ case CELL:
+ item->setHoverCell(getColumnIndexFromOffset(x));
+ break;
+ case HEADER:
+ {
+ S32 cell = getColumnIndexFromOffset(x);
+ if (cell > 0)
+ {
+ item->setHoverCell(cell);
+ }
+ else
+ {
+ item->setHoverCell(-1);
+ }
+ break;
+ }
+ case ROW:
+ break;
+ }
+ }
+ else
+ {
+ mouseOverHighlightNthItem(-1);
+ }
+ }
+
+ handled = LLUICtrl::handleHover( x, y, mask );
+
+ return handled;
+}
+
+void LLScrollListCtrl::onMouseLeave(S32 x, S32 y, MASK mask)
+{
+ // clear mouse highlight
+ mouseOverHighlightNthItem(-1);
+}
+
+bool LLScrollListCtrl::handleKeyHere(KEY key,MASK mask )
+{
+ bool handled = false;
+
+ // not called from parent means we have keyboard focus or a child does
+ if (mCanSelect)
+ {
+ if (mask == MASK_NONE)
+ {
+ switch(key)
+ {
+ case KEY_UP:
+ if (mAllowKeyboardMovement || hasFocus())
+ {
+ // commit implicit in call
+ selectPrevItem(false);
+ mNeedsScroll = true;
+ handled = true;
+ }
+ break;
+ case KEY_DOWN:
+ if (mAllowKeyboardMovement || hasFocus())
+ {
+ // commit implicit in call
+ selectNextItem(false);
+ mNeedsScroll = true;
+ handled = true;
+ }
+ break;
+ case KEY_LEFT:
+ if (mAllowKeyboardMovement || hasFocus())
+ {
+ // TODO: support multi-select
+ LLScrollListItem *item = getFirstSelected();
+ if (item)
+ {
+ S32 cell = item->getSelectedCell();
+ switch (mSelectionType)
+ {
+ case CELL:
+ if (cell < mColumns.size()) cell++;
+ break;
+ case HEADER:
+ if (cell == -1) cell = 1;
+ else if (cell > 1 && cell < mColumns.size()) cell++; // skip header
+ break;
+ case ROW:
+ cell = -1;
+ break;
+ }
+ item->setSelectedCell(cell);
+ handled = true;
+ }
+ }
+ break;
+ case KEY_RIGHT:
+ if (mAllowKeyboardMovement || hasFocus())
+ {
+ // TODO: support multi-select
+ LLScrollListItem *item = getFirstSelected();
+ if (item)
+ {
+ S32 cell = item->getSelectedCell();
+ switch (mSelectionType)
+ {
+ case CELL:
+ if (cell >= 0) cell--;
+ break;
+ case HEADER:
+ if (cell > 1) cell--;
+ else if (cell == 1) cell = -1; // skip header
+ break;
+ case ROW:
+ cell = -1;
+ break;
+ }
+ item->setSelectedCell(cell);
+ handled = true;
+ }
+ }
+ break;
+ case KEY_PAGE_UP:
+ if (mAllowKeyboardMovement || hasFocus())
+ {
+ selectNthItem(getFirstSelectedIndex() - (mScrollbar->getPageSize() - 1));
+ mNeedsScroll = true;
+ if (mCommitOnKeyboardMovement
+ && !mCommitOnSelectionChange)
+ {
+ onCommit();
+ }
+ handled = true;
+ }
+ break;
+ case KEY_PAGE_DOWN:
+ if (mAllowKeyboardMovement || hasFocus())
+ {
+ selectNthItem(getFirstSelectedIndex() + (mScrollbar->getPageSize() - 1));
+ mNeedsScroll = true;
+ if (mCommitOnKeyboardMovement
+ && !mCommitOnSelectionChange)
+ {
+ onCommit();
+ }
+ handled = true;
+ }
+ break;
+ case KEY_HOME:
+ if (mAllowKeyboardMovement || hasFocus())
+ {
+ selectFirstItem();
+ mNeedsScroll = true;
+ if (mCommitOnKeyboardMovement
+ && !mCommitOnSelectionChange)
+ {
+ onCommit();
+ }
+ handled = true;
+ }
+ break;
+ case KEY_END:
+ if (mAllowKeyboardMovement || hasFocus())
+ {
+ selectNthItem(getItemCount() - 1);
+ mNeedsScroll = true;
+ if (mCommitOnKeyboardMovement
+ && !mCommitOnSelectionChange)
+ {
+ onCommit();
+ }
+ handled = true;
+ }
+ break;
+ case KEY_RETURN:
+ // JC - Special case: Only claim to have handled it
+ // if we're the special non-commit-on-move
+ // type. AND we are visible
+ if (!mCommitOnKeyboardMovement && mask == MASK_NONE)
+ {
+ onCommit();
+ mSearchString.clear();
+ handled = true;
+ }
+ break;
+ case KEY_BACKSPACE:
+ mSearchTimer.reset();
+ if (mSearchString.size())
+ {
+ mSearchString.erase(mSearchString.size() - 1, 1);
+ }
+ if (mSearchString.empty())
+ {
+ if (getFirstSelected())
+ {
+ LLScrollListCell* cellp = getFirstSelected()->getColumn(getSearchColumn());
+ if (cellp)
+ {
+ cellp->highlightText(0, 0);
+ }
+ }
+ }
+ else if (selectItemByPrefix(wstring_to_utf8str(mSearchString), false))
+ {
+ mNeedsScroll = true;
+ // update search string only on successful match
+ mSearchTimer.reset();
+
+ if (mCommitOnKeyboardMovement
+ && !mCommitOnSelectionChange)
+ {
+ onCommit();
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ // TODO: multiple: shift-up, shift-down, shift-home, shift-end, select all
+ }
+
+ return handled;
+}
+
+bool LLScrollListCtrl::handleUnicodeCharHere(llwchar uni_char)
+{
+ if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL
+ {
+ return false;
+ }
+
+ // perform incremental search based on keyboard input
+ static LLUICachedControl<F32> type_ahead_timeout ("TypeAheadTimeout", 0);
+ if (mSearchTimer.getElapsedTimeF32() > type_ahead_timeout)
+ {
+ mSearchString.clear();
+ }
+
+ // type ahead search is case insensitive
+ uni_char = LLStringOps::toLower((llwchar)uni_char);
+
+ if (selectItemByPrefix(wstring_to_utf8str(mSearchString + (llwchar)uni_char), false))
+ {
+ // update search string only on successful match
+ mNeedsScroll = true;
+ mSearchString += uni_char;
+ mSearchTimer.reset();
+
+ if (mCommitOnKeyboardMovement
+ && !mCommitOnSelectionChange)
+ {
+ onCommit();
+ }
+ }
+ // handle iterating over same starting character
+ else if (isRepeatedChars(mSearchString + (llwchar)uni_char) && !mItemList.empty())
+ {
+ // start from last selected item, in case we previously had a successful match against
+ // duplicated characters ('AA' matches 'Aaron')
+ item_list::iterator start_iter = mItemList.begin();
+ S32 first_selected = getFirstSelectedIndex();
+
+ // if we have a selection (> -1) then point iterator at the selected item
+ if (first_selected > 0)
+ {
+ // point iterator to first selected item
+ start_iter += first_selected;
+ }
+
+ // start search at first item after current selection
+ item_list::iterator iter = start_iter;
+ ++iter;
+ if (iter == mItemList.end())
+ {
+ iter = mItemList.begin();
+ }
+
+ // loop around once, back to previous selection
+ while(iter != start_iter)
+ {
+ LLScrollListItem* item = *iter;
+
+ LLScrollListCell* cellp = item->getColumn(getSearchColumn());
+ if (cellp)
+ {
+ // Only select enabled items with matching first characters
+ LLWString item_label = utf8str_to_wstring(cellp->getValue().asString());
+ if (item->getEnabled() && LLStringOps::toLower(item_label[0]) == uni_char)
+ {
+ selectItem(item, -1);
+ mNeedsScroll = true;
+ cellp->highlightText(0, 1);
+ mSearchTimer.reset();
+
+ if (mCommitOnKeyboardMovement
+ && !mCommitOnSelectionChange)
+ {
+ onCommit();
+ }
+
+ break;
+ }
+ }
+
+ ++iter;
+ if (iter == mItemList.end())
+ {
+ iter = mItemList.begin();
+ }
+ }
+ }
+
+ return true;
+}
+
+
+void LLScrollListCtrl::reportInvalidInput()
+{
+ make_ui_sound("UISndBadKeystroke");
+}
+
+bool LLScrollListCtrl::isRepeatedChars(const LLWString& string) const
+{
+ if (string.empty())
+ {
+ return false;
+ }
+
+ llwchar first_char = string[0];
+
+ for (U32 i = 0; i < string.size(); i++)
+ {
+ if (string[i] != first_char)
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void LLScrollListCtrl::selectItem(LLScrollListItem* itemp, S32 cell, bool select_single_item)
+{
+ if (!itemp) return;
+
+ if (!itemp->getSelected())
+ {
+ if (mLastSelected)
+ {
+ LLScrollListCell* cellp = mLastSelected->getColumn(getSearchColumn());
+ if (cellp)
+ {
+ cellp->highlightText(0, 0);
+ }
+ }
+ if (select_single_item)
+ {
+ deselectAllItems(true);
+ }
+ itemp->setSelected(true);
+ switch (mSelectionType)
+ {
+ case CELL:
+ itemp->setSelectedCell(cell);
+ break;
+ case HEADER:
+ itemp->setSelectedCell(cell <= 0 ? -1 : cell);
+ break;
+ case ROW:
+ itemp->setSelectedCell(-1);
+ break;
+ }
+ mLastSelected = itemp;
+ mSelectionChanged = true;
+ }
+}
+
+void LLScrollListCtrl::deselectItem(LLScrollListItem* itemp)
+{
+ if (!itemp) return;
+
+ if (itemp->getSelected())
+ {
+ if (mLastSelected == itemp)
+ {
+ mLastSelected = NULL;
+ }
+
+ itemp->setSelected(false);
+ LLScrollListCell* cellp = itemp->getColumn(getSearchColumn());
+ if (cellp)
+ {
+ cellp->highlightText(0, 0);
+ }
+ mSelectionChanged = true;
+ }
+}
+
+void LLScrollListCtrl::commitIfChanged()
+{
+ if (mSelectionChanged)
+ {
+ mDirty = true;
+ mSelectionChanged = false;
+ onCommit();
+ }
+}
+
+struct SameSortColumn
+{
+ SameSortColumn(S32 column) : mColumn(column) {}
+ S32 mColumn;
+
+ bool operator()(std::pair<S32, bool> sort_column) { return sort_column.first == mColumn; }
+};
+
+bool LLScrollListCtrl::setSort(S32 column_idx, bool ascending)
+{
+ LLScrollListColumn* sort_column = getColumn(column_idx);
+ if (!sort_column) return false;
+
+ sort_column->mSortDirection = ascending ? LLScrollListColumn::ASCENDING : LLScrollListColumn::DESCENDING;
+
+ sort_column_t new_sort_column(column_idx, ascending);
+
+ setNeedsSort();
+
+ if (mSortColumns.empty())
+ {
+ mSortColumns.push_back(new_sort_column);
+ return true;
+ }
+ else
+ {
+ // grab current sort column
+ sort_column_t cur_sort_column = mSortColumns.back();
+
+ // remove any existing sort criterion referencing this column
+ // and add the new one
+ mSortColumns.erase(remove_if(mSortColumns.begin(), mSortColumns.end(), SameSortColumn(column_idx)), mSortColumns.end());
+ mSortColumns.push_back(new_sort_column);
+
+ // did the sort criteria change?
+ return (cur_sort_column != new_sort_column);
+ }
+}
+
+S32 LLScrollListCtrl::getLinesPerPage()
+{
+ //if mPageLines is NOT provided display all item
+ if (mPageLines)
+ {
+ return mPageLines;
+ }
+ else
+ {
+ return mLineHeight ? mItemListRect.getHeight() / mLineHeight : getItemCount();
+ }
+}
+
+
+// Called by scrollbar
+void LLScrollListCtrl::onScrollChange( S32 new_pos, LLScrollbar* scrollbar )
+{
+ mScrollLines = new_pos;
+}
+
+
+void LLScrollListCtrl::sortByColumn(const std::string& name, bool ascending)
+{
+ column_map_t::iterator itor = mColumns.find(name);
+ if (itor != mColumns.end())
+ {
+ sortByColumnIndex((*itor).second->mIndex, ascending);
+ }
+}
+
+// First column is column 0
+void LLScrollListCtrl::sortByColumnIndex(U32 column, bool ascending)
+{
+ setSort(column, ascending);
+ updateSort();
+}
+
+void LLScrollListCtrl::updateSort() const
+{
+ if (hasSortOrder() && !isSorted())
+ {
+ // do stable sort to preserve any previous sorts
+ std::stable_sort(
+ mItemList.begin(),
+ mItemList.end(),
+ SortScrollListItem(mSortColumns,mSortCallback, mAlternateSort));
+
+ mSorted = true;
+ }
+}
+
+// for one-shot sorts, does not save sort column/order
+void LLScrollListCtrl::sortOnce(S32 column, bool ascending)
+{
+ std::vector<std::pair<S32, bool> > sort_column;
+ sort_column.push_back(std::make_pair(column, ascending));
+
+ // do stable sort to preserve any previous sorts
+ std::stable_sort(
+ mItemList.begin(),
+ mItemList.end(),
+ SortScrollListItem(sort_column,mSortCallback,mAlternateSort));
+}
+
+void LLScrollListCtrl::dirtyColumns()
+{
+ mColumnsDirty = true;
+ mColumnWidthsDirty = true;
+
+ // need to keep mColumnsIndexed up to date
+ // just in case someone indexes into it immediately
+ mColumnsIndexed.resize(mColumns.size());
+
+ column_map_t::iterator column_itor;
+ for (column_itor = mColumns.begin(); column_itor != mColumns.end(); ++column_itor)
+ {
+ LLScrollListColumn *column = column_itor->second;
+ mColumnsIndexed[column_itor->second->mIndex] = column;
+ }
+}
+
+
+S32 LLScrollListCtrl::getScrollPos() const
+{
+ return mScrollbar->getDocPos();
+}
+
+
+void LLScrollListCtrl::setScrollPos( S32 pos )
+{
+ mScrollbar->setDocPos( pos );
+
+ onScrollChange(mScrollbar->getDocPos(), mScrollbar);
+}
+
+
+void LLScrollListCtrl::scrollToShowSelected()
+{
+ // don't scroll automatically when capturing mouse input
+ // as that will change what is currently under the mouse cursor
+ if (hasMouseCapture())
+ {
+ return;
+ }
+
+ updateSort();
+
+ S32 index = getFirstSelectedIndex();
+ if (index < 0)
+ {
+ return;
+ }
+
+ LLScrollListItem* item = mItemList[index];
+ if (!item)
+ {
+ // I don't THINK this should ever happen.
+ return;
+ }
+
+ S32 lowest = mScrollLines;
+ S32 page_lines = getLinesPerPage();
+ S32 highest = mScrollLines + page_lines;
+
+ if (index < lowest)
+ {
+ // need to scroll to show item
+ setScrollPos(index);
+ }
+ else if (highest <= index)
+ {
+ setScrollPos(index - page_lines + 1);
+ }
+}
+
+void LLScrollListCtrl::updateStaticColumnWidth(LLScrollListColumn* col, S32 new_width)
+{
+ mTotalStaticColumnWidth += llmax(0, new_width) - llmax(0, col->getWidth());
+}
+
+// LLEditMenuHandler functions
+
+// virtual
+void LLScrollListCtrl::copy()
+{
+ std::string buffer;
+
+ std::vector<LLScrollListItem*> items = getAllSelected();
+ std::vector<LLScrollListItem*>::iterator itor;
+ for (itor = items.begin(); itor != items.end(); ++itor)
+ {
+ buffer += (*itor)->getContentsCSV() + "\n";
+ }
+ LLClipboard::instance().copyToClipboard(utf8str_to_wstring(buffer), 0, buffer.length());
+}
+
+// virtual
+bool LLScrollListCtrl::canCopy() const
+{
+ return (getFirstSelected() != NULL);
+}
+
+// virtual
+void LLScrollListCtrl::cut()
+{
+ copy();
+ doDelete();
+}
+
+// virtual
+bool LLScrollListCtrl::canCut() const
+{
+ return canCopy() && canDoDelete();
+}
+
+// virtual
+void LLScrollListCtrl::selectAll()
+{
+ // Deselects all other items
+ item_list::iterator iter;
+ for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
+ {
+ LLScrollListItem *itemp = *iter;
+ if( itemp->getEnabled() )
+ {
+ selectItem(itemp, -1, false);
+ }
+ }
+
+ if (mCommitOnSelectionChange)
+ {
+ commitIfChanged();
+ }
+}
+
+// virtual
+bool LLScrollListCtrl::canSelectAll() const
+{
+ return getCanSelect() && mAllowMultipleSelection && !(mMaxSelectable > 0 && mItemList.size() > mMaxSelectable);
+}
+
+// virtual
+void LLScrollListCtrl::deselect()
+{
+ deselectAllItems();
+}
+
+// virtual
+bool LLScrollListCtrl::canDeselect() const
+{
+ return getCanSelect();
+}
+
+void LLScrollListCtrl::addColumn(const LLSD& column, EAddPosition pos)
+{
+ LLScrollListColumn::Params p;
+ LLParamSDParser parser;
+ parser.readSD(column, p);
+ addColumn(p, pos);
+}
+
+void LLScrollListCtrl::addColumn(const LLScrollListColumn::Params& column_params, EAddPosition pos)
+{
+ if (!column_params.validateBlock()) return;
+
+ std::string name = column_params.name;
+ // if no column name provided, just use ordinal as name
+ if (name.empty())
+ {
+ name = llformat("%d", mColumnsIndexed.size());
+ }
+
+ if (mColumns.find(name) == mColumns.end())
+ {
+ // Add column
+ mColumns[name] = new LLScrollListColumn(column_params, this);
+ LLScrollListColumn* new_column = mColumns[name];
+ new_column->mIndex = mColumns.size()-1;
+
+ // Add button
+ if (new_column->getWidth() > 0 || new_column->mRelWidth > 0 || new_column->mDynamicWidth)
+ {
+ if (getNumColumns() > 0)
+ {
+ mTotalColumnPadding += mColumnPadding;
+ }
+ if (new_column->mRelWidth >= 0)
+ {
+ new_column->setWidth((S32)ll_round(new_column->mRelWidth*mItemListRect.getWidth()));
+ }
+ else if(new_column->mDynamicWidth)
+ {
+ mNumDynamicWidthColumns++;
+ new_column->setWidth((mItemListRect.getWidth() - mTotalStaticColumnWidth - mTotalColumnPadding) / mNumDynamicWidthColumns);
+ }
+ S32 top = mItemListRect.mTop;
+
+ S32 left = mItemListRect.mLeft;
+ for (column_map_t::iterator itor = mColumns.begin();
+ itor != mColumns.end();
+ ++itor)
+ {
+ if (itor->second->mIndex < new_column->mIndex &&
+ itor->second->getWidth() > 0)
+ {
+ left += itor->second->getWidth() + mColumnPadding;
+ }
+ }
+
+ S32 right = left+new_column->getWidth();
+ if (new_column->mIndex != (S32)mColumns.size()-1)
+ {
+ right += mColumnPadding;
+ }
+
+ LLRect temp_rect = LLRect(left,top+mHeadingHeight,right,top);
+
+ LLScrollColumnHeader::Params params(LLUICtrlFactory::getDefaultParams<LLScrollColumnHeader>());
+ params.name = "btn_" + name;
+ params.rect = temp_rect;
+ params.column = new_column;
+ params.tool_tip = column_params.tool_tip;
+ params.tab_stop = false;
+ params.visible = mDisplayColumnHeaders;
+
+ if(column_params.header.image.isProvided())
+ {
+ params.image_selected = column_params.header.image;
+ params.image_unselected = column_params.header.image;
+ }
+ else
+ {
+ params.label = column_params.header.label;
+ }
+
+ new_column->mHeader = LLUICtrlFactory::create<LLScrollColumnHeader>(params);
+ addChild(new_column->mHeader);
+
+ sendChildToFront(mScrollbar);
+ }
+ }
+
+ dirtyColumns();
+}
+
+// static
+void LLScrollListCtrl::onClickColumn(void *userdata)
+{
+ LLScrollListColumn *info = (LLScrollListColumn*)userdata;
+ if (!info) return;
+
+ LLScrollListCtrl *parent = info->mParentCtrl;
+ if (!parent) return;
+
+ if (!parent->mCanSort) return;
+
+ S32 column_index = info->mIndex;
+
+ LLScrollListColumn* column = parent->mColumnsIndexed[info->mIndex];
+ bool ascending = column->mSortDirection == LLScrollListColumn::ASCENDING;
+ if (column->mSortingColumn != column->mName
+ && parent->mColumns.find(column->mSortingColumn) != parent->mColumns.end())
+ {
+ LLScrollListColumn* info_redir = parent->mColumns[column->mSortingColumn];
+ column_index = info_redir->mIndex;
+ }
+
+ // if this column is the primary sort key, reverse the direction
+ if (!parent->mSortColumns.empty() && parent->mSortColumns.back().first == column_index)
+ {
+ ascending = !parent->mSortColumns.back().second;
+ }
+
+ parent->sortByColumnIndex(column_index, ascending);
+
+ if (parent->mOnSortChangedCallback)
+ {
+ parent->mOnSortChangedCallback();
+ }
+}
+
+std::string LLScrollListCtrl::getSortColumnName()
+{
+ LLScrollListColumn* column = mSortColumns.empty() ? NULL : mColumnsIndexed[mSortColumns.back().first];
+
+ if (column) return column->mName;
+ else return "";
+}
+
+bool LLScrollListCtrl::hasSortOrder() const
+{
+ return !mSortColumns.empty();
+}
+
+void LLScrollListCtrl::clearSortOrder()
+{
+ mSortColumns.clear();
+}
+
+void LLScrollListCtrl::clearColumns()
+{
+ column_map_t::iterator itor;
+ for (itor = mColumns.begin(); itor != mColumns.end(); ++itor)
+ {
+ LLScrollColumnHeader *header = itor->second->mHeader;
+ if (header)
+ {
+ removeChild(header);
+ delete header;
+ }
+ }
+ std::for_each(mColumns.begin(), mColumns.end(), DeletePairedPointer());
+ mColumns.clear();
+ mSortColumns.clear();
+ mTotalStaticColumnWidth = 0;
+ mTotalColumnPadding = 0;
+
+ dirtyColumns(); // Clears mColumnsIndexed
+}
+
+void LLScrollListCtrl::setColumnLabel(const std::string& column, const std::string& label)
+{
+ LLScrollListColumn* columnp = getColumn(column);
+ if (columnp)
+ {
+ columnp->mLabel = label;
+ if (columnp->mHeader)
+ {
+ columnp->mHeader->setLabel(label);
+ }
+ }
+}
+
+LLScrollListColumn* LLScrollListCtrl::getColumn(S32 index)
+{
+ if (index < 0 || index >= (S32)mColumnsIndexed.size())
+ {
+ return NULL;
+ }
+ return mColumnsIndexed[index];
+}
+
+LLScrollListColumn* LLScrollListCtrl::getColumn(const std::string& name)
+{
+ column_map_t::iterator column_itor = mColumns.find(name);
+ if (column_itor != mColumns.end())
+ {
+ return column_itor->second;
+ }
+ return NULL;
+}
+
+LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& element, EAddPosition pos, void* userdata)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;
+ LLScrollListItem::Params item_params;
+ LLParamSDParser parser;
+ parser.readSD(element, item_params);
+ item_params.userdata = userdata;
+ return addRow(item_params, pos);
+}
+
+LLScrollListItem* LLScrollListCtrl::addRow(const LLScrollListItem::Params& item_p, EAddPosition pos)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;
+ LLScrollListItem *new_item = new LLScrollListItem(item_p);
+ return addRow(new_item, item_p, pos);
+}
+
+LLScrollListItem* LLScrollListCtrl::addRow(LLScrollListItem *new_item, const LLScrollListItem::Params& item_p, EAddPosition pos)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;
+ if (!item_p.validateBlock() || !new_item) return NULL;
+ new_item->setNumColumns(mColumns.size());
+
+ // Add any columns we don't already have
+ S32 col_index = 0;
+
+ for(LLInitParam::ParamIterator<LLScrollListCell::Params>::const_iterator itor = item_p.columns.begin();
+ itor != item_p.columns.end();
+ ++itor)
+ {
+ LLScrollListCell::Params cell_p = *itor;
+ std::string column = cell_p.column;
+
+ // empty columns strings index by ordinal
+ if (column.empty())
+ {
+ column = llformat("%d", col_index);
+ }
+
+ LLScrollListColumn* columnp = getColumn(column);
+
+ // create new column on demand
+ if (!columnp)
+ {
+ LLScrollListColumn::Params new_column;
+ new_column.name = column;
+ new_column.header.label = column;
+
+ // if width supplied for column, use it, otherwise
+ // use adaptive width
+ if (cell_p.width.isProvided())
+ {
+ new_column.width.pixel_width = cell_p.width;
+ }
+ addColumn(new_column);
+ columnp = mColumns[column];
+ new_item->setNumColumns(mColumns.size());
+ }
+
+ S32 index = columnp->mIndex;
+ if (!cell_p.width.isProvided())
+ {
+ cell_p.width = columnp->getWidth();
+ }
+
+ LLScrollListCell* cell = LLScrollListCell::create(cell_p);
+
+ if (cell)
+ {
+ new_item->setColumn(index, cell);
+ if (columnp->mHeader
+ && cell->isText()
+ && !cell->getValue().asString().empty())
+ {
+ columnp->mHeader->setHasResizableElement(true);
+ }
+ }
+
+ col_index++;
+ }
+
+ if (item_p.columns.empty())
+ {
+ if (mColumns.empty())
+ {
+ LLScrollListColumn::Params new_column;
+ new_column.name = "0";
+
+ addColumn(new_column);
+ new_item->setNumColumns(mColumns.size());
+ }
+
+ LLScrollListCell* cell = LLScrollListCell::create(LLScrollListCell::Params().value(item_p.value));
+ if (cell)
+ {
+ LLScrollListColumn* columnp = mColumns.begin()->second;
+
+ new_item->setColumn(0, cell);
+ if (columnp->mHeader
+ && cell->isText()
+ && !cell->getValue().asString().empty())
+ {
+ columnp->mHeader->setHasResizableElement(true);
+ }
+ }
+ }
+
+ // add dummy cells for missing columns
+ for (column_map_t::iterator column_it = mColumns.begin(); column_it != mColumns.end(); ++column_it)
+ {
+ S32 column_idx = column_it->second->mIndex;
+ if (new_item->getColumn(column_idx) == NULL)
+ {
+ LLScrollListColumn* column_ptr = column_it->second;
+ LLScrollListCell::Params cell_p;
+ cell_p.width = column_ptr->getWidth();
+
+ new_item->setColumn(column_idx, new LLScrollListSpacer(cell_p));
+ }
+ }
+
+ addItem(new_item, pos);
+ return new_item;
+}
+
+LLScrollListItem* LLScrollListCtrl::addSimpleElement(const std::string& value, EAddPosition pos, const LLSD& id)
+{
+ LLSD entry_id = id;
+
+ if (id.isUndefined())
+ {
+ entry_id = value;
+ }
+
+ LLScrollListItem::Params item_params;
+ item_params.value(entry_id);
+ item_params.columns.add()
+ .value(value)
+ .font(LLFontGL::getFontEmojiSmall());
+
+ return addRow(item_params, pos);
+}
+
+void LLScrollListCtrl::setValue(const LLSD& value )
+{
+ LLSD::array_const_iterator itor;
+ for (itor = value.beginArray(); itor != value.endArray(); ++itor)
+ {
+ addElement(*itor);
+ }
+}
+
+LLSD LLScrollListCtrl::getValue() const
+{
+ LLScrollListItem *item = getFirstSelected();
+ if (!item) return LLSD();
+ return item->getValue();
+}
+
+bool LLScrollListCtrl::operateOnSelection(EOperation op)
+{
+ if (op == OP_DELETE)
+ {
+ deleteSelectedItems();
+ return true;
+ }
+ else if (op == OP_DESELECT)
+ {
+ deselectAllItems();
+ }
+ return false;
+}
+
+bool LLScrollListCtrl::operateOnAll(EOperation op)
+{
+ if (op == OP_DELETE)
+ {
+ clearRows();
+ return true;
+ }
+ else if (op == OP_DESELECT)
+ {
+ deselectAllItems();
+ }
+ else if (op == OP_SELECT)
+ {
+ selectAll();
+ }
+ return false;
+}
+//virtual
+void LLScrollListCtrl::setFocus(bool b)
+{
+ // for tabbing into pristine scroll lists (Finder)
+ if (!getFirstSelected())
+ {
+ selectFirstItem();
+ //onCommit(); // SJB: selectFirstItem() will call onCommit() if appropriate
+ }
+ LLUICtrl::setFocus(b);
+}
+
+
+// virtual
+bool LLScrollListCtrl::isDirty() const
+{
+ bool grubby = mDirty;
+ if ( !mAllowMultipleSelection )
+ {
+ grubby = (mOriginalSelection != getFirstSelectedIndex());
+ }
+ return grubby;
+}
+
+// Clear dirty state
+void LLScrollListCtrl::resetDirty()
+{
+ mDirty = false;
+ mOriginalSelection = getFirstSelectedIndex();
+}
+
+
+//virtual
+void LLScrollListCtrl::onFocusReceived()
+{
+ // forget latent selection changes when getting focus
+ mSelectionChanged = false;
+ LLUICtrl::onFocusReceived();
+}
+
+//virtual
+void LLScrollListCtrl::onFocusLost()
+{
+ if (hasMouseCapture())
+ {
+ gFocusMgr.setMouseCapture(NULL);
+ }
+
+ mSearchString.clear();
+
+ LLUICtrl::onFocusLost();
+}
+
+boost::signals2::connection LLScrollListCtrl::setIsFriendCallback(const is_friend_signal_t::slot_type& cb)
+{
+ if (!mIsFriendSignal)
+ {
+ mIsFriendSignal = new is_friend_signal_t();
+ }
+ return mIsFriendSignal->connect(cb);
+}
+
+bool LLScrollListCtrl::highlightMatchingItems(const std::string& filter_str)
+{
+ if (filter_str == "" || filter_str == " ")
+ {
+ clearHighlightedItems();
+ return false;
+ }
+
+ bool res = false;
+
+ setHighlightedColor(LLUIColorTable::instance().getColor("SearchableControlHighlightColor", LLColor4::red));
+
+ std::string filter_str_lc(filter_str);
+ LLStringUtil::toLower(filter_str_lc);
+
+ std::vector<LLScrollListItem*> data = getAllData();
+ std::vector<LLScrollListItem*>::iterator iter = data.begin();
+ while (iter != data.end())
+ {
+ LLScrollListCell* cell = (*iter)->getColumn(0);
+ if (cell)
+ {
+ std::string value = cell->getValue().asString();
+ LLStringUtil::toLower(value);
+ if (value.find(filter_str_lc) == std::string::npos)
+ {
+ (*iter)->setHighlighted(false);
+ }
+ else
+ {
+ (*iter)->setHighlighted(true);
+ res = true;
+ }
+ }
+ iter++;
+ }
+ return res;
+}