diff options
Diffstat (limited to 'indra/llui/llscrolllistctrl.cpp')
-rw-r--r-- | indra/llui/llscrolllistctrl.cpp | 2673 |
1 files changed, 2673 insertions, 0 deletions
diff --git a/indra/llui/llscrolllistctrl.cpp b/indra/llui/llscrolllistctrl.cpp new file mode 100644 index 0000000000..5d11973b88 --- /dev/null +++ b/indra/llui/llscrolllistctrl.cpp @@ -0,0 +1,2673 @@ +/** + * @file llscrolllistctrl.cpp + * @brief LLScrollListCtrl base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include <algorithm> + +#include "linden_common.h" +#include "llstl.h" +#include "llboost.h" + +#include "llscrolllistctrl.h" + +#include "indra_constants.h" + +#include "llcheckboxctrl.h" +#include "llclipboard.h" +#include "llfocusmgr.h" +#include "llgl.h" +#include "llglheaders.h" +#include "llresmgr.h" +#include "llscrollbar.h" +#include "llstring.h" +#include "llui.h" +#include "lluictrlfactory.h" +#include "llwindow.h" +#include "llcontrol.h" +#include "llkeyboard.h" + +const S32 LIST_BORDER_PAD = 2; // white space inside the border and to the left of the scrollbar + +U32 LLScrollListCtrl::sSortColumn = 1; +BOOL LLScrollListCtrl::sSortAscending = TRUE; + +// local structures & classes. +struct SortScrollListItem +{ + SortScrollListItem(const S32 sort_col, BOOL sort_ascending) + { + mSortCol = sort_col; + sSortAscending = sort_ascending; + } + + bool operator()(const LLScrollListItem* i1, const LLScrollListItem* i2) + { + const LLScrollListCell *cell1; + const LLScrollListCell *cell2; + + cell1 = i1->getColumn(mSortCol); + cell2 = i2->getColumn(mSortCol); + + S32 order = 1; + if (!sSortAscending) + { + order = -1; + } + + BOOL retval = FALSE; + + if (cell1 && cell2) + { + retval = ((order * LLString::compareDict(cell1->getText(), cell2->getText())) < 0); + } + + return (retval ? TRUE : FALSE); + } + +protected: + S32 mSortCol; + S32 sSortAscending; +}; + + + +// +// LLScrollListIcon +// +LLScrollListIcon::LLScrollListIcon(LLImageGL* icon, S32 width, LLUUID image_id) : +mIcon(icon), mImageUUID(image_id.getString()) +{ + if (width) + { + mWidth = width; + } + else + { + mWidth = icon->getWidth(); + } +} + +LLScrollListIcon::~LLScrollListIcon() +{ +} + +// +// LLScrollListCheck +// +LLScrollListCheck::LLScrollListCheck(LLCheckBoxCtrl* check_box, S32 width) +{ + mCheckBox = check_box; + LLRect rect(mCheckBox->getRect()); + if (width) + { + + rect.mRight = rect.mLeft + width; + mCheckBox->setRect(rect); + mWidth = width; + } + else + { + mWidth = rect.getWidth(); //check_box->getWidth(); + } +} + +LLScrollListCheck::~LLScrollListCheck() +{ + delete mCheckBox; +} + +void LLScrollListCheck::drawToWidth(S32 width, const LLColor4& color, const LLColor4& highlight_color) const +{ + mCheckBox->draw(); + +} + +BOOL LLScrollListCheck::handleClick() +{ + if ( mCheckBox->getEnabled() ) + { + LLCheckBoxCtrl::onButtonPress(mCheckBox); + } + return TRUE; +} + +// +// LLScrollListText +// +U32 LLScrollListText::sCount = 0; + +LLScrollListText::LLScrollListText( const LLString& text, const LLFontGL* font, S32 width, U8 font_style, LLColor4& color, BOOL use_color, BOOL visible) +: mText( text ), + mFont( font ), + mFontStyle( font_style ), + mWidth( width ), + mVisible( visible ), + mHighlightChars( 0 ) +{ + if (use_color) + { + mColor = new LLColor4(); + mColor->setVec(color); + } + else + { + mColor = NULL; + } + + sCount++; + + // initialize rounded rect image + if (!mRoundedRectImage) + { + mRoundedRectImage = LLUI::sImageProvider->getUIImageByID(LLUUID(LLUI::sAssetsGroup->getString("rounded_square.tga"))); + } + + // Yes, that's four dots, because we want it to have a little + // padding, in proportion to the font size. + mEllipsisWidth = (S32)mFont->getWidth("...."); +} + +LLScrollListText::~LLScrollListText() +{ + sCount--; + delete mColor; +} + +void LLScrollListText::setText(const LLString& text) +{ + mText = text; +} + +void LLScrollListText::drawToWidth(S32 width, const LLColor4& color, const LLColor4& highlight_color) const +{ + // If the user has specified a small minimum width, use that. + if (mWidth > 0 && mWidth < width) + { + width = mWidth; + } + + const LLColor4* display_color; + if (mColor) + { + display_color = mColor; + } + else + { + display_color = &color; + } + + if (mHighlightChars > 0) + { + mRoundedRectImage->bind(); + glColor4fv(highlight_color.mV); + gl_segmented_rect_2d_tex(-2, + llround(mFont->getLineHeight()) + 1, + mFont->getWidth(mText.getString(), 0, mHighlightChars) + 1, + 1, + mRoundedRectImage->getWidth(), + mRoundedRectImage->getHeight(), + 16); + } + + // Try to draw the entire string + F32 right_x; + U32 string_chars = mText.length(); + U32 drawn_chars = mFont->render(mText.getWString(), 0, 0, 2, + *display_color, + LLFontGL::LEFT, + LLFontGL::BOTTOM, + mFontStyle, + string_chars, + width - mEllipsisWidth, + &right_x, FALSE); + + // If we didn't get the whole string, abbreviate + if (drawn_chars < string_chars && drawn_chars) + { + mFont->renderUTF8("...", 0, right_x, 0.f, color, LLFontGL::LEFT, LLFontGL::BOTTOM, mFontStyle, + S32_MAX, S32_MAX, NULL, FALSE); + } +} + + +LLScrollListItem::~LLScrollListItem() +{ + std::for_each(mColumns.begin(), mColumns.end(), DeletePointer()); +} + +BOOL LLScrollListItem::handleMouseDown(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + S32 left = 0; + S32 right = 0; + S32 width = 0; + + std::vector<LLScrollListCell *>::iterator iter = mColumns.begin(); + std::vector<LLScrollListCell *>::iterator end = mColumns.end(); + for ( ; iter != end; ++iter) + { + width = (*iter)->getWidth(); + right += width; + if (left <= x && x < right ) + { + handled = (*iter)->handleClick(); + break; + } + + left += width; + } + return handled; +} + +void LLScrollListItem::setNumColumns(S32 columns) +{ + S32 prev_columns = mColumns.size(); + if (columns < prev_columns) + { + std::for_each(mColumns.begin()+columns, mColumns.end(), DeletePointer()); + } + + mColumns.resize(columns); + + for (S32 col = prev_columns; col < columns; ++col) + { + mColumns[col] = NULL; + } +} + +void LLScrollListItem::setColumn( S32 column, LLScrollListCell *cell ) +{ + if (column < (S32)mColumns.size()) + { + delete mColumns[column]; + mColumns[column] = cell; + } + else + { + llerrs << "LLScrollListItem::setColumn: bad column: " << column << llendl; + } +} + +LLString LLScrollListItem::getContentsCSV() +{ + LLString ret; + + S32 count = getNumColumns(); + for (S32 i=0; i<count; ++i) + { + ret += getColumn(i)->getText(); + if (i < count-1) + { + ret += ", "; + } + } + + return ret; +} + +void LLScrollListItem::setEnabled(BOOL b) +{ + if (b != mEnabled) + { + std::vector<LLScrollListCell *>::iterator iter = mColumns.begin(); + std::vector<LLScrollListCell *>::iterator end = mColumns.end(); + for ( ; iter != end; ++iter) + { + (*iter)->setEnabled(b); + } + mEnabled = b; + } +} + +//--------------------------------------------------------------------------- +// LLScrollListCtrl +//--------------------------------------------------------------------------- + +LLScrollListCtrl::LLScrollListCtrl(const LLString& name, const LLRect& rect, + void (*commit_callback)(LLUICtrl* ctrl, void* userdata), + void* callback_user_data, + BOOL allow_multiple_selection, + BOOL show_border + ) + : LLUICtrl(name, rect, TRUE, commit_callback, callback_user_data), + mLineHeight(0), + mScrollLines(0), + mPageLines(0), + mHeadingHeight(20), + mMaxSelectable(0), + mHeadingFont(NULL), + mAllowMultipleSelection( allow_multiple_selection ), + mAllowKeyboardMovement(TRUE), + mCommitOnKeyboardMovement(TRUE), + mCommitOnSelectionChange(FALSE), + mSelectionChanged(FALSE), + mCanSelect(TRUE), + mDisplayColumnButtons(FALSE), + mCollapseEmptyColumns(FALSE), + mIsPopup(FALSE), + mMaxItemCount(INT_MAX), + //mItemCount(0), + mBackgroundVisible( TRUE ), + mDrawStripes(TRUE), + mBgWriteableColor( LLUI::sColorsGroup->getColor( "ScrollBgWriteableColor" ) ), + mBgReadOnlyColor( LLUI::sColorsGroup->getColor( "ScrollBgReadOnlyColor" ) ), + mBgSelectedColor( LLUI::sColorsGroup->getColor("ScrollSelectedBGColor") ), + mBgStripeColor( LLUI::sColorsGroup->getColor("ScrollBGStripeColor") ), + mFgSelectedColor( LLUI::sColorsGroup->getColor("ScrollSelectedFGColor") ), + mFgUnselectedColor( LLUI::sColorsGroup->getColor("ScrollUnselectedColor") ), + mFgDisabledColor( LLUI::sColorsGroup->getColor("ScrollDisabledColor") ), + mHighlightedColor( LLUI::sColorsGroup->getColor("ScrollHighlightedColor") ), + mBorderThickness( 2 ), + mOnDoubleClickCallback( NULL ), + mOnMaximumSelectCallback( NULL ), + mHighlightedItem(-1), + mBorder(NULL), + mDefaultColumn("SIMPLE"), + mSearchColumn(0), + mNumDynamicWidthColumns(0), + mTotalStaticColumnWidth(0), + mDrewSelected(FALSE) +{ + mItemListRect.setOriginAndSize( + mBorderThickness + LIST_BORDER_PAD, + mBorderThickness + LIST_BORDER_PAD, + mRect.getWidth() - 2*( mBorderThickness + LIST_BORDER_PAD ) - SCROLLBAR_SIZE, + mRect.getHeight() - 2*( mBorderThickness + LIST_BORDER_PAD ) ); + + updateLineHeight(); + + mPageLines = mLineHeight? (mItemListRect.getHeight()) / mLineHeight : 0; + + // Init the scrollbar + LLRect scroll_rect; + scroll_rect.setOriginAndSize( + mRect.getWidth() - mBorderThickness - SCROLLBAR_SIZE, + mItemListRect.mBottom, + SCROLLBAR_SIZE, + mItemListRect.getHeight()); + mScrollbar = new LLScrollbar( "Scrollbar", scroll_rect, + LLScrollbar::VERTICAL, + getItemCount(), + mScrollLines, + mPageLines, + &LLScrollListCtrl::onScrollChange, this ); + mScrollbar->setFollowsRight(); + mScrollbar->setFollowsTop(); + mScrollbar->setFollowsBottom(); + mScrollbar->setEnabled( TRUE ); + mScrollbar->setVisible( TRUE ); + addChild(mScrollbar); + + // Border + if (show_border) + { + LLRect border_rect( 0, mRect.getHeight(), mRect.getWidth(), 0 ); + mBorder = new LLViewBorder( "dlg border", border_rect, LLViewBorder::BEVEL_IN, LLViewBorder::STYLE_LINE, 1 ); + addChild(mBorder); + } + + mColumnPadding = 5; + + mLastSelected = NULL; +} + +LLScrollListCtrl::~LLScrollListCtrl() +{ + std::for_each(mItemList.begin(), mItemList.end(), DeletePointer()); + + if( gEditMenuHandler == this ) + { + gEditMenuHandler = NULL; + } +} + + +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(); +} + +// 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; +} + + +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::getFirstSelectedIndex() +{ + S32 CurSelectedIndex = 0; + item_list::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]; +} + +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; +} + + +void LLScrollListCtrl::reshape( S32 width, S32 height, BOOL called_from_parent ) +{ + LLUICtrl::reshape( width, height, called_from_parent ); + + S32 heading_size = (mDisplayColumnButtons ? mHeadingHeight : 0); + + mItemListRect.setOriginAndSize( + mBorderThickness + LIST_BORDER_PAD, + mBorderThickness + LIST_BORDER_PAD, + mRect.getWidth() - 2*( mBorderThickness + LIST_BORDER_PAD ) - SCROLLBAR_SIZE, + mRect.getHeight() - 2*( mBorderThickness + LIST_BORDER_PAD ) - heading_size ); + + mPageLines = mLineHeight? mItemListRect.getHeight() / mLineHeight : 0; + mScrollbar->setVisible(mPageLines < getItemCount()); + mScrollbar->setPageSize( mPageLines ); + + updateColumns(); + updateColumnButtons(); +} + + +// Attempt to size the control to show all items. +// Do not make larger than width or height. +void LLScrollListCtrl::arrange(S32 max_width, S32 max_height) +{ + S32 height = mLineHeight * (getItemCount() + 1); + height = llmin( height, max_height ); + + S32 width = mRect.getWidth(); + + reshape( width, height ); +} + + +LLRect LLScrollListCtrl::getRequiredRect() +{ + S32 height = mLineHeight * (getItemCount() + 1); + S32 width = mRect.getWidth(); + + return LLRect(0, height, width, 0); +} + + +BOOL LLScrollListCtrl::addItem( LLScrollListItem* item, EAddPosition pos ) +{ + BOOL not_too_big = getItemCount() < mMaxItemCount; + if (not_too_big) + { + switch( pos ) + { + case ADD_TOP: + mItemList.push_front(item); + break; + + case ADD_SORTED: + LLScrollListCtrl::sSortColumn = 0; + LLScrollListCtrl::sSortAscending = TRUE; + mItemList.push_back(item); + std::sort(mItemList.begin(), mItemList.end(), SortScrollListItem(sSortColumn, sSortAscending)); + break; + + case ADD_BOTTOM: + mItemList.push_back(item); + break; + + default: + llassert(0); + mItemList.push_back(item); + break; + } + + updateLineHeight(); + mPageLines = mLineHeight ? mItemListRect.getHeight() / mLineHeight : 0; + mScrollbar->setVisible(mPageLines < getItemCount()); + mScrollbar->setPageSize( mPageLines ); + + mScrollbar->setDocSize( getItemCount() ); + } + return not_too_big; +} + + +// Line height is the max height of all the cells in all the items. +void LLScrollListCtrl::updateLineHeight() +{ + const S32 ROW_PAD = 2; + + 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() + ROW_PAD ); + } + } +} + +void LLScrollListCtrl::updateColumns() +{ + mColumnsIndexed.resize(mColumns.size()); + + std::map<LLString, LLScrollListColumn>::iterator column_itor; + for (column_itor = mColumns.begin(); column_itor != mColumns.end(); ++column_itor) + { + LLScrollListColumn *column = &column_itor->second; + if (column->mRelWidth >= 0) + { + column->mWidth = (S32)llround(column->mRelWidth*mItemListRect.getWidth()); + } + else if (column->mDynamicWidth) + { + column->mWidth = (mItemListRect.getWidth() - mTotalStaticColumnWidth) / mNumDynamicWidthColumns; + + } + mColumnsIndexed[column_itor->second.mIndex] = column; + } +} + +void LLScrollListCtrl::updateColumnButtons() +{ + std::map<LLString, LLScrollListColumn>::iterator column_itor; + for (column_itor = mColumns.begin(); column_itor != mColumns.end(); ++column_itor) + { + LLScrollListColumn* column = &column_itor->second; + LLButton *button = column->mButton; + + if (button) + { + mColumnsIndexed[column->mIndex] = column; + + S32 top = mItemListRect.mTop; + S32 left = mItemListRect.mLeft; + { + std::map<LLString, LLScrollListColumn>::iterator itor; + for (itor = mColumns.begin(); itor != mColumns.end(); ++itor) + { + if (itor->second.mIndex < column->mIndex && + itor->second.mWidth > 0) + { + left += itor->second.mWidth + mColumnPadding; + } + } + } + S32 right = left+column->mWidth; + if (column->mIndex != (S32)mColumns.size()-1) + { + right += mColumnPadding; + } + LLRect temp_rect = LLRect(left,top+mHeadingHeight,right,top); + button->setRect(temp_rect); + button->setFont(mHeadingFont); + button->setVisible(mDisplayColumnButtons); + } + } +} + +void LLScrollListCtrl::setDisplayHeading(BOOL display) +{ + mDisplayColumnButtons = display; + + updateColumns(); + + setHeadingHeight(mHeadingHeight); +} + +void LLScrollListCtrl::setHeadingHeight(S32 heading_height) +{ + mHeadingHeight = heading_height; + + reshape(mRect.getWidth(), mRect.getHeight()); + + // Resize + mScrollbar->reshape(SCROLLBAR_SIZE, mItemListRect.getHeight()); + + updateColumnButtons(); +} + +void LLScrollListCtrl::setHeadingFont(const LLFontGL* heading_font) +{ + mHeadingFont = heading_font; + updateColumnButtons(); +} + +void LLScrollListCtrl::setCollapseEmptyColumns(BOOL collapse) +{ + mCollapseEmptyColumns = collapse; +} + +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()) + { + selectItem(itemp); + } + success = TRUE; + } + else + { + deselectItem(itemp); + } + first_item = FALSE; + } + if (mCommitOnSelectionChange) + { + commitIfChanged(); + } + return success; +} + + +BOOL LLScrollListCtrl::selectNthItem( S32 target_index ) +{ + // Deselects all other items + BOOL success = FALSE; + S32 index = 0; + + target_index = llclamp(target_index, 0, (S32)mItemList.size() - 1); + + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem *itemp = *iter; + if( target_index == index ) + { + if( itemp->getEnabled() ) + { + selectItem(itemp); + success = TRUE; + } + } + else + { + deselectItem(itemp); + } + index++; + } + + 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; + } + 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 + } + + LLScrollListItem *cur_itemp = mItemList[index]; + mItemList[index] = mItemList[index - 1]; + mItemList[index - 1] = cur_itemp; +} + + +void LLScrollListCtrl::deleteSingleItem(S32 target_index) +{ + if (target_index >= (S32)mItemList.size()) + { + return; + } + + LLScrollListItem *itemp; + itemp = mItemList[target_index]; + if (itemp == mLastSelected) + { + mLastSelected = NULL; + } + delete itemp; + mItemList.erase(mItemList.begin() + target_index); +} + +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; +} + +void LLScrollListCtrl::highlightNthItem(S32 target_index) +{ + if (mHighlightedItem != target_index) + { + mHighlightedItem = target_index; + } +} + +S32 LLScrollListCtrl::getItemIndex( LLScrollListItem* target_item ) +{ + S32 index = 0; + item_list::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( LLUUID& target_id ) +{ + S32 index = 0; + item_list::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()) + { + selectFirstItem(); + } + else + { + 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, !extend_selection); + } + else + { + reportInvalidInput(); + } + break; + } + + prev_item = cur_item; + } + } + + if ((mCommitOnSelectionChange || mCommitOnKeyboardMovement)) + { + commitIfChanged(); + } + + mSearchString.clear(); +} + + +void LLScrollListCtrl::selectNextItem( BOOL extend_selection) +{ + if (!getFirstSelected()) + { + selectFirstItem(); + } + else + { + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + if (item->getSelected()) + { + if (++iter != mItemList.end()) + { + LLScrollListItem *next_item = *iter; + if (next_item) + { + selectItem(next_item, !extend_selection); + } + else + { + reportInvalidInput(); + } + } + break; + } + } + } + + if ((mCommitOnSelectionChange || 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(); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// "Simple" interface: use this when you're creating a list that contains only unique strings, only +// one of which can be selected at a time. + +LLScrollListItem* LLScrollListCtrl::addSimpleItem(const LLString& item_text, EAddPosition pos, BOOL enabled) +{ + LLScrollListItem* item = NULL; + if (getItemCount() < mMaxItemCount) + { + // simple items have their LLSD data set to their label + item = new LLScrollListItem( LLSD(item_text) ); + item->setEnabled(enabled); + item->addColumn( item_text, gResMgr->getRes( LLFONT_SANSSERIF_SMALL ) ); + addItem( item, pos ); + } + return item; +} + + +// Selects first enabled item of the given name. +// Returns false if item not found. +BOOL LLScrollListCtrl::selectSimpleItem(const LLString& label, BOOL case_sensitive) +{ + //RN: assume no empty items + if (label.empty()) + { + return FALSE; + } + + LLString target_text = label; + if (!case_sensitive) + { + LLString::toLower(target_text); + } + + BOOL found = FALSE; + + item_list::iterator iter; + S32 index = 0; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + // Only select enabled items with matching names + LLString item_text = item->getColumn(0)->getText(); + if (!case_sensitive) + { + LLString::toLower(item_text); + } + BOOL select = !found && item->getEnabled() && item_text == target_text; + if (select) + { + selectItem(item); + } + found = found || select; + index++; + } + + if (mCommitOnSelectionChange) + { + commitIfChanged(); + } + + return found; +} + + +BOOL LLScrollListCtrl::selectSimpleItemByPrefix(const LLString& target, BOOL case_sensitive) +{ + return selectSimpleItemByPrefix(utf8str_to_wstring(target), case_sensitive); +} + +// 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::selectSimpleItemByPrefix(const LLWString& target, BOOL case_sensitive) +{ + 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(mSearchColumn); + BOOL select = cellp ? item->getEnabled() && ('\0' == cellp->getText()[0]) : FALSE; + if (select) + { + selectItem(item); + found = TRUE; + break; + } + } + } + else + { + if (!case_sensitive) + { + // do comparisons in lower case + LLWString::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(mSearchColumn); + if (!cellp) + { + continue; + } + LLWString item_label = utf8str_to_wstring(cellp->getText()); + if (!case_sensitive) + { + LLWString::toLower(item_label); + } + + BOOL select = item->getEnabled() && !item_label.compare(0, target_len, target_trimmed); + + if (select) + { + selectItem(item); + found = TRUE; + break; + } + } + } + + if (mCommitOnSelectionChange) + { + commitIfChanged(); + } + + return found; +} + +const LLString& LLScrollListCtrl::getSimpleSelectedItem(S32 column) const +{ + LLScrollListItem* item; + + item = getFirstSelected(); + if (item) + { + return item->getColumn(column)->getText(); + } + + return LLString::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 LLString& item_text, const LLUUID& id, EAddPosition pos, BOOL enabled, S32 column_width) +{ + LLScrollListItem* item = NULL; + if (getItemCount() < mMaxItemCount) + { + item = new LLScrollListItem( enabled, NULL, id ); + item->addColumn(item_text, gResMgr->getRes(LLFONT_SANSSERIF_SMALL), column_width); + addItem( item, pos ); + } + return item; +} + +LLScrollListItem* LLScrollListCtrl::addSimpleItem(const LLString& item_text, LLSD sd, EAddPosition pos, BOOL enabled, S32 column_width) +{ + LLScrollListItem* item = NULL; + if (getItemCount() < mMaxItemCount) + { + item = new LLScrollListItem( sd ); + item->setEnabled(enabled); + item->addColumn(item_text, gResMgr->getRes(LLFONT_SANSSERIF_SMALL), column_width); + addItem( item, pos ); + } + return item; +} + + +// Select the line or lines that match this UUID +BOOL LLScrollListCtrl::selectByID( const LLUUID& id ) +{ + return selectByValue( LLSD(id) ); +} + +BOOL LLScrollListCtrl::setSelectedByValue(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() && (item->getValue().asString() == value.asString())) + { + if (selected) + { + selectItem(item); + } + else + { + deselectItem(item); + } + found = TRUE; + break; + } + } + + if (mCommitOnSelectionChange) + { + commitIfChanged(); + } + + return found; +} + +BOOL LLScrollListCtrl::isSelected(LLSD value) +{ + item_list::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() +{ + LLScrollListItem* item = getFirstSelected(); + + if (item) + { + return item->getUUID(); + } + + return LLUUID::null; +} + +LLSD LLScrollListCtrl::getSimpleSelectedValue() +{ + LLScrollListItem* item = getFirstSelected(); + + if (item) + { + return item->getValue(); + } + else + { + return LLSD(); + } +} + +void LLScrollListCtrl::drawItems() +{ + S32 x = mItemListRect.mLeft; + S32 y = mItemListRect.mTop - mLineHeight; + + S32 num_page_lines = mPageLines; + + LLRect item_rect; + + LLGLSUIDefault gls_ui; + + { + + S32 cur_x = x; + S32 cur_y = y; + + mDrewSelected = FALSE; + + S32 line = 0; + LLColor4 color; + S32 max_columns = 0; + + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + + item_rect.setOriginAndSize( + cur_x, + cur_y, + mScrollbar->getVisible() ? mItemListRect.getWidth() : mItemListRect.getWidth() + mScrollbar->getRect().getWidth(), + mLineHeight ); + + lldebugs << mItemListRect.getWidth() << llendl; + + if (item->getSelected()) + { + mDrewSelected = TRUE; + } + + max_columns = llmax(max_columns, item->getNumColumns()); + + LLRect bg_rect = item_rect; + // pad background rectangle to separate it from contents + bg_rect.stretch(LIST_BORDER_PAD, 0); + + if( mScrollLines <= line && line < mScrollLines + num_page_lines ) + { + if( item->getSelected() && mCanSelect) + { + // Draw background of selected item + LLGLSNoTexture no_texture; + glColor4fv(mBgSelectedColor.mV); + gl_rect_2d( bg_rect ); + + color = mFgSelectedColor; + } + else if (mHighlightedItem == line && mCanSelect) + { + LLGLSNoTexture no_texture; + glColor4fv(mHighlightedColor.mV); + gl_rect_2d( bg_rect ); + color = (item->getEnabled() ? mFgUnselectedColor : mFgDisabledColor); + } + else + { + color = (item->getEnabled() ? mFgUnselectedColor : mFgDisabledColor); + if (mDrawStripes && (line%2 == 0) && (max_columns > 1)) + { + LLGLSNoTexture no_texture; + glColor4fv(mBgStripeColor.mV); + gl_rect_2d( bg_rect ); + } + } + + S32 line_x = cur_x; + { + S32 num_cols = item->getNumColumns(); + S32 cur_col = 0; + S32 dynamic_width = 0; + S32 dynamic_remainder = 0; + if(mNumDynamicWidthColumns > 0) + { + dynamic_width = (mItemListRect.getWidth() - mTotalStaticColumnWidth) / mNumDynamicWidthColumns; + dynamic_remainder = (mItemListRect.getWidth() - mTotalStaticColumnWidth) % mNumDynamicWidthColumns; + } + for (LLScrollListCell* cell = item->getColumn(0); cur_col < num_cols; cell = item->getColumn(++cur_col)) + { + S32 cell_width = cell->getWidth(); + if(mColumnsIndexed.size() > (U32)cur_col && mColumnsIndexed[cur_col] && mColumnsIndexed[cur_col]->mDynamicWidth) + { + cell_width = dynamic_width + (--dynamic_remainder ? 1 : 0); + cell->setWidth(cell_width); + } + // Two ways a cell could be hidden + if (cell_width < 0 + || !cell->getVisible()) continue; + LLUI::pushMatrix(); + LLUI::translate((F32) cur_x, (F32) cur_y, 0.0f); + S32 space_left = mItemListRect.mRight - cur_x; + LLColor4 highlight_color = LLColor4::white; + F32 type_ahead_timeout = LLUI::sConfigGroup->getF32("TypeAheadTimeout"); + + highlight_color.mV[VALPHA] = clamp_rescale(mSearchTimer.getElapsedTimeF32(), type_ahead_timeout * 0.7f, type_ahead_timeout, 0.4f, 0.f); + cell->drawToWidth( space_left, color, highlight_color ); + LLUI::popMatrix(); + + cur_x += cell_width + mColumnPadding; + + } + } + cur_x = line_x; + cur_y -= mLineHeight; + } + line++; + } + } +} + + +void LLScrollListCtrl::draw() +{ + if( getVisible() ) + { + LLRect background(0, mRect.getHeight(), mRect.getWidth(), 0); + // Draw background + if (mBackgroundVisible) + { + LLGLSNoTexture no_texture; + glColor4fv( getEnabled() ? mBgWriteableColor.mV : mBgReadOnlyColor.mV ); + gl_rect_2d(background); + } + + drawItems(); + + if (mBorder) + { + mBorder->setKeyboardFocusHighlight(gFocusMgr.getKeyboardFocus() == this); + } + + 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 ); + return handled; +} + + +BOOL LLScrollListCtrl::handleMouseDown(S32 x, S32 y, MASK mask) +{ + BOOL handled = LLView::childrenHandleMouseDown(x, y, mask) != NULL; + + if( !handled && mCanSelect) + { + LLScrollListItem* hit_item = hitItem(x, y); + if( hit_item ) + { + if( mAllowMultipleSelection ) + { + if (mask & MASK_SHIFT) + { + if (mLastSelected == NULL) + { + selectItem(hit_item); + } + 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(mCallbackUserData); + } + break; + } + LLScrollListItem *item = *itor; + if (item == hit_item || item == lastSelected) + { + selectItem(item, FALSE); + selecting = !selecting; + } + if (selecting) + { + selectItem(item, FALSE); + } + } + } + } + else if (mask & MASK_CONTROL) + { + if (hit_item->getSelected()) + { + deselectItem(hit_item); + } + else + { + if(!(mMaxSelectable > 0 && getAllSelected().size() >= mMaxSelectable)) + { + selectItem(hit_item, FALSE); + } + else + { + if(mOnMaximumSelectCallback) + { + mOnMaximumSelectCallback(mCallbackUserData); + } + } + } + } + else + { + deselectAllItems(TRUE); + selectItem(hit_item); + } + } + else + { + selectItem(hit_item); + } + + hit_item->handleMouseDown(x - mBorderThickness - LIST_BORDER_PAD, + 1, mask); + // always commit on mousedown + onCommit(); + mSelectionChanged = FALSE; + + // clear search string on mouse operations + mSearchString.clear(); + } + else + { + mLastSelected = NULL; + } + } + + gFocusMgr.setKeyboardFocus(this, NULL); + + return TRUE; +} + +BOOL LLScrollListCtrl::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + //BOOL handled = FALSE; + if(getVisible()) + { + // 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)) + { + if( mCanSelect && mOnDoubleClickCallback ) + { + mOnDoubleClickCallback( mCallbackUserData ); + } + } + } + return TRUE; +} + +LLScrollListItem* LLScrollListCtrl::hitItem( S32 x, S32 y ) +{ + // Excludes disabled items. + LLScrollListItem* hit_item = NULL; + + LLRect item_rect; + item_rect.setLeftTopAndSize( + mItemListRect.mLeft, + mItemListRect.mTop, + mItemListRect.getWidth(), + mLineHeight ); + + int num_page_lines = mPageLines; + + 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; +} + + +BOOL LLScrollListCtrl::handleHover(S32 x,S32 y,MASK mask) +{ + BOOL handled = FALSE; + + if(getVisible()) + { + if (mCanSelect) + { + LLScrollListItem* item = hitItem(x, y); + if (item) + { + highlightNthItem(getItemIndex(item)); + } + else + { + highlightNthItem(-1); + } + } + + handled = LLView::handleHover( x, y, mask ); + + if( !handled ) + { + // Opaque + getWindow()->setCursor(UI_CURSOR_ARROW); + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << llendl; + handled = TRUE; + } + } + return handled; +} + + +BOOL LLScrollListCtrl::handleKeyHere(KEY key,MASK mask, BOOL called_from_parent ) +{ + BOOL handled = FALSE; + + // not called from parent means we have keyboard focus or a child does + if (mCanSelect && !called_from_parent) + { + // Ignore capslock + mask = mask; + + if (mask == MASK_NONE) + { + switch(key) + { + case KEY_UP: + if (mAllowKeyboardMovement || hasFocus()) + { + // commit implicit in call + selectPrevItem(FALSE); + scrollToShowSelected(); + handled = TRUE; + } + break; + case KEY_DOWN: + if (mAllowKeyboardMovement || hasFocus()) + { + // commit implicit in call + selectNextItem(FALSE); + scrollToShowSelected(); + handled = TRUE; + } + break; + case KEY_PAGE_UP: + if (mAllowKeyboardMovement || hasFocus()) + { + selectNthItem(getFirstSelectedIndex() - (mScrollbar->getPageSize() - 1)); + scrollToShowSelected(); + if (mCommitOnKeyboardMovement + && !mCommitOnSelectionChange) + { + onCommit(); + } + handled = TRUE; + } + break; + case KEY_PAGE_DOWN: + if (mAllowKeyboardMovement || hasFocus()) + { + selectNthItem(getFirstSelectedIndex() + (mScrollbar->getPageSize() - 1)); + scrollToShowSelected(); + if (mCommitOnKeyboardMovement + && !mCommitOnSelectionChange) + { + onCommit(); + } + handled = TRUE; + } + break; + case KEY_HOME: + if (mAllowKeyboardMovement || hasFocus()) + { + selectFirstItem(); + scrollToShowSelected(); + if (mCommitOnKeyboardMovement + && !mCommitOnSelectionChange) + { + onCommit(); + } + handled = TRUE; + } + break; + case KEY_END: + if (mAllowKeyboardMovement || hasFocus()) + { + selectNthItem(getItemCount() - 1); + scrollToShowSelected(); + 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 && getVisible()) + { + 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(mSearchColumn); + if (cellp) + { + cellp->highlightText(0); + } + } + } + else if (selectSimpleItemByPrefix(wstring_to_utf8str(mSearchString), FALSE)) + { + // update search string only on successful match + mSearchTimer.reset(); + + // highlight current search on matching item + LLScrollListCell* cellp = getFirstSelected()->getColumn(mSearchColumn); + if (cellp) + { + cellp->highlightText(mSearchString.size()); + } + + 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, BOOL called_from_parent) +{ + if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL + { + return FALSE; + } + + // perform incremental search based on keyboard input + if (mSearchTimer.getElapsedTimeF32() > LLUI::sConfigGroup->getF32("TypeAheadTimeout")) + { + mSearchString.clear(); + } + + // type ahead search is case insensitive + uni_char = LLStringOps::toLower((llwchar)uni_char); + + if (selectSimpleItemByPrefix(wstring_to_utf8str(mSearchString + (llwchar)uni_char), FALSE)) + { + // update search string only on successful match + mSearchString += uni_char; + mSearchTimer.reset(); + + // highlight current search on matching item + LLScrollListCell* cellp = getFirstSelected()->getColumn(mSearchColumn); + if (cellp) + { + cellp->highlightText(mSearchString.size()); + } + + 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(mSearchColumn); + if (cellp) + { + // Only select enabled items with matching first characters + LLWString item_label = utf8str_to_wstring(cellp->getText()); + if (item->getEnabled() && LLStringOps::toLower(item_label[0]) == uni_char) + { + selectItem(item); + cellp->highlightText(1); + mSearchTimer.reset(); + + if (mCommitOnKeyboardMovement + && !mCommitOnSelectionChange) + { + onCommit(); + } + + break; + } + } + + ++iter; + if (iter == mItemList.end()) + { + iter = mItemList.begin(); + } + } + } + + // make sure selected item is on screen + scrollToShowSelected(); + 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, BOOL select_single_item) +{ + if (!itemp) return; + + if (!itemp->getSelected()) + { + if (mLastSelected) + { + LLScrollListCell* cellp = mLastSelected->getColumn(mSearchColumn); + if (cellp) + { + cellp->highlightText(0); + } + } + if (select_single_item) + { + deselectAllItems(TRUE); + } + itemp->setSelected(TRUE); + 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(mSearchColumn); + if (cellp) + { + cellp->highlightText(0); + } + mSelectionChanged = TRUE; + } +} + +void LLScrollListCtrl::commitIfChanged() +{ + if (mSelectionChanged) + { + mSelectionChanged = FALSE; + onCommit(); + } +} + +// Called by scrollbar +//static +void LLScrollListCtrl::onScrollChange( S32 new_pos, LLScrollbar* scrollbar, void* userdata ) +{ + LLScrollListCtrl* self = (LLScrollListCtrl*) userdata; + self->mScrollLines = new_pos; +} + + +// First column is column 0 +void LLScrollListCtrl::sortByColumn(U32 column, BOOL ascending) +{ + LLScrollListCtrl::sSortColumn = column; + LLScrollListCtrl::sSortAscending = ascending; + std::sort(mItemList.begin(), mItemList.end(), SortScrollListItem(sSortColumn, sSortAscending)); +} + +void LLScrollListCtrl::sortByColumn(LLString name, BOOL ascending) +{ + std::map<LLString, LLScrollListColumn>::iterator itor = mColumns.find(name); + if (itor != mColumns.end()) + { + sortByColumn((*itor).second.mIndex, ascending); + } +} + +S32 LLScrollListCtrl::getScrollPos() +{ + return mScrollbar->getDocPos(); +} + + +void LLScrollListCtrl::setScrollPos( S32 pos ) +{ + mScrollbar->setDocPos( pos ); + + onScrollChange(mScrollbar->getDocPos(), mScrollbar, this); +} + + +void LLScrollListCtrl::scrollToShowSelected() +{ + 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 highest = mScrollLines + mPageLines; + + if (index < lowest) + { + // need to scroll to show item + setScrollPos(index); + } + else if (highest <= index) + { + setScrollPos(index - mPageLines + 1); + } +} + +// virtual +LLXMLNodePtr LLScrollListCtrl::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLUICtrl::getXML(); + + // Attributes + + node->createChild("multi_select", TRUE)->setBoolValue(mAllowMultipleSelection); + + node->createChild("draw_border", TRUE)->setBoolValue((mBorder != NULL)); + + node->createChild("draw_heading", TRUE)->setBoolValue(mDisplayColumnButtons); + + node->createChild("background_visible", TRUE)->setBoolValue(mBackgroundVisible); + + node->createChild("draw_stripes", TRUE)->setBoolValue(mDrawStripes); + + node->createChild("column_padding", TRUE)->setIntValue(mColumnPadding); + + addColorXML(node, mBgWriteableColor, "bg_writeable_color", "ScrollBgWriteableColor"); + addColorXML(node, mBgReadOnlyColor, "bg_read_only_color", "ScrollBgReadOnlyColor"); + addColorXML(node, mBgSelectedColor, "bg_selected_color", "ScrollSelectedBGColor"); + addColorXML(node, mBgStripeColor, "bg_stripe_color", "ScrollBGStripeColor"); + addColorXML(node, mFgSelectedColor, "fg_selected_color", "ScrollSelectedFGColor"); + addColorXML(node, mFgUnselectedColor, "fg_unselected_color", "ScrollUnselectedColor"); + addColorXML(node, mFgDisabledColor, "fg_disable_color", "ScrollDisabledColor"); + addColorXML(node, mHighlightedColor, "highlighted_color", "ScrollHighlightedColor"); + + // Contents + + std::map<LLString, LLScrollListColumn>::const_iterator itor; + std::vector<const LLScrollListColumn*> sorted_list; + sorted_list.resize(mColumns.size()); + for (itor = mColumns.begin(); itor != mColumns.end(); ++itor) + { + sorted_list[itor->second.mIndex] = &itor->second; + } + + std::vector<const LLScrollListColumn*>::iterator itor2; + for (itor2 = sorted_list.begin(); itor2 != sorted_list.end(); ++itor2) + { + LLXMLNodePtr child_node = node->createChild("column", FALSE); + const LLScrollListColumn *column = *itor2; + + child_node->createChild("name", TRUE)->setStringValue(column->mName); + child_node->createChild("label", TRUE)->setStringValue(column->mLabel); + child_node->createChild("width", TRUE)->setIntValue(column->mWidth); + } + + return node; +} + +void LLScrollListCtrl::setScrollListParameters(LLXMLNodePtr node) +{ + // James: This is not a good way to do colors. We need a central "UI style" + // manager that sets the colors for ALL scroll lists, buttons, etc. + + LLColor4 color; + if(node->hasAttribute("fg_unselected_color")) + { + LLUICtrlFactory::getAttributeColor(node,"fg_unselected_color", color); + setFgUnselectedColor(color); + } + if(node->hasAttribute("fg_selected_color")) + { + LLUICtrlFactory::getAttributeColor(node,"fg_selected_color", color); + setFgSelectedColor(color); + } + if(node->hasAttribute("bg_selected_color")) + { + LLUICtrlFactory::getAttributeColor(node,"bg_selected_color", color); + setBgSelectedColor(color); + } + if(node->hasAttribute("fg_disable_color")) + { + LLUICtrlFactory::getAttributeColor(node,"fg_disable_color", color); + setFgDisableColor(color); + } + if(node->hasAttribute("bg_writeable_color")) + { + LLUICtrlFactory::getAttributeColor(node,"bg_writeable_color", color); + setBgWriteableColor(color); + } + if(node->hasAttribute("bg_read_only_color")) + { + LLUICtrlFactory::getAttributeColor(node,"bg_read_only_color", color); + setReadOnlyBgColor(color); + } + if (LLUICtrlFactory::getAttributeColor(node,"bg_stripe_color", color)) + { + setBgStripeColor(color); + } + if (LLUICtrlFactory::getAttributeColor(node,"highlighted_color", color)) + { + setHighlightedColor(color); + } + + if(node->hasAttribute("background_visible")) + { + BOOL background_visible; + node->getAttributeBOOL("background_visible", background_visible); + setBackgroundVisible(background_visible); + } + + if(node->hasAttribute("draw_stripes")) + { + BOOL draw_stripes; + node->getAttributeBOOL("draw_stripes", draw_stripes); + setDrawStripes(draw_stripes); + } + + if(node->hasAttribute("column_padding")) + { + S32 column_padding; + node->getAttributeS32("column_padding", column_padding); + setColumnPadding(column_padding); + } +} + +// static +LLView* LLScrollListCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("scroll_list"); + node->getAttributeString("name", name); + + LLRect rect; + createRect(node, rect, parent, LLRect()); + + BOOL multi_select = FALSE; + node->getAttributeBOOL("multi_select", multi_select); + + BOOL draw_border = TRUE; + node->getAttributeBOOL("draw_border", draw_border); + + BOOL draw_heading = FALSE; + node->getAttributeBOOL("draw_heading", draw_heading); + + BOOL collapse_empty_columns = FALSE; + node->getAttributeBOOL("collapse_empty_columns", collapse_empty_columns); + + S32 search_column = 0; + node->getAttributeS32("search_column", search_column); + + LLUICtrlCallback callback = NULL; + + LLScrollListCtrl* scroll_list = new LLScrollListCtrl( + name, + rect, + callback, + NULL, + multi_select, + draw_border); + + scroll_list->setDisplayHeading(draw_heading); + if (node->hasAttribute("heading_height")) + { + S32 heading_height; + node->getAttributeS32("heading_height", heading_height); + scroll_list->setHeadingHeight(heading_height); + } + if (node->hasAttribute("heading_font")) + { + LLString heading_font(""); + node->getAttributeString("heading_font", heading_font); + LLFontGL* gl_font = LLFontGL::fontFromName(heading_font.c_str()); + scroll_list->setHeadingFont(gl_font); + } + scroll_list->setCollapseEmptyColumns(collapse_empty_columns); + + scroll_list->setScrollListParameters(node); + + scroll_list->initFromXML(node, parent); + + scroll_list->setSearchColumn(search_column); + + LLSD columns; + S32 index = 0; + LLXMLNodePtr child; + S32 total_static = 0, num_dynamic = 0; + for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) + { + if (child->hasName("column")) + { + LLString labelname(""); + child->getAttributeString("label", labelname); + + LLString columnname(labelname); + child->getAttributeString("name", columnname); + + LLString sortname(columnname); + child->getAttributeString("sort", sortname); + + LLString imagename; + child->getAttributeString("image", imagename); + + BOOL columndynamicwidth = FALSE; + child->getAttributeBOOL("dynamicwidth", columndynamicwidth); + + S32 columnwidth = -1; + child->getAttributeS32("width", columnwidth); + + if(!columndynamicwidth) total_static += columnwidth; + else ++num_dynamic; + + F32 columnrelwidth = 0.f; + child->getAttributeF32("relwidth", columnrelwidth); + + + columns[index]["name"] = columnname; + columns[index]["sort"] = sortname; + columns[index]["image"] = imagename; + columns[index]["label"] = labelname; + columns[index]["width"] = columnwidth; + columns[index]["relwidth"] = columnrelwidth; + columns[index]["dynamicwidth"] = columndynamicwidth; + index++; + } + } + scroll_list->setNumDynamicColumns(num_dynamic); + scroll_list->setTotalStaticColumnWidth(total_static); + scroll_list->setColumnHeadings(columns); + + for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) + { + if (child->hasName("row")) + { + LLUUID id; + child->getAttributeUUID("id", id); + + LLSD row; + + row["id"] = id; + + S32 column_idx = 0; + LLXMLNodePtr row_child; + for (row_child = child->getFirstChild(); row_child.notNull(); row_child = row_child->getNextSibling()) + { + if (row_child->hasName("column")) + { + LLString value = row_child->getTextContents(); + + LLString columnname(""); + row_child->getAttributeString("name", columnname); + + LLString font(""); + row_child->getAttributeString("font", font); + + LLString font_style(""); + row_child->getAttributeString("font-style", font_style); + + row["columns"][column_idx]["column"] = columnname; + row["columns"][column_idx]["value"] = value; + row["columns"][column_idx]["font"] = font; + row["columns"][column_idx]["font-style"] = font_style; + column_idx++; + } + } + scroll_list->addElement(row); + } + } + + LLString contents = node->getTextContents(); + if (!contents.empty()) + { + typedef boost::tokenizer<boost::char_separator<char> > tokenizer; + boost::char_separator<char> sep("\t\n"); + tokenizer tokens(contents, sep); + tokenizer::iterator token_iter = tokens.begin(); + + while(token_iter != tokens.end()) + { + const char* line = token_iter->c_str(); + scroll_list->addSimpleItem(line); + ++token_iter; + } + } + + return scroll_list; +} + +// LLEditMenuHandler functions + +// virtual +void LLScrollListCtrl::copy() +{ + LLString buffer; + + std::vector<LLScrollListItem*> items = getAllSelected(); + std::vector<LLScrollListItem*>::iterator itor; + for (itor = items.begin(); itor != items.end(); ++itor) + { + buffer += (*itor)->getContentsCSV() + "\n"; + } + gClipboard.copyFromSubstring(utf8str_to_wstring(buffer), 0, buffer.length()); +} + +// virtual +BOOL LLScrollListCtrl::canCopy() +{ + return (getFirstSelected() != NULL); +} + +// virtual +void LLScrollListCtrl::cut() +{ + copy(); + doDelete(); +} + +// virtual +BOOL LLScrollListCtrl::canCut() +{ + return canCopy() && canDoDelete(); +} + +// virtual +void LLScrollListCtrl::doDelete() +{ + // Not yet implemented +} + +// virtual +BOOL LLScrollListCtrl::canDoDelete() +{ + // Not yet implemented + return FALSE; +} + +// 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, FALSE); + } + } + + if (mCommitOnSelectionChange) + { + commitIfChanged(); + } +} + +// virtual +BOOL LLScrollListCtrl::canSelectAll() +{ + return getCanSelect() && mAllowMultipleSelection && !(mMaxSelectable > 0 && mItemList.size() > mMaxSelectable); +} + +// virtual +void LLScrollListCtrl::deselect() +{ + deselectAllItems(); +} + +// virtual +BOOL LLScrollListCtrl::canDeselect() +{ + return getCanSelect(); +} + +void LLScrollListCtrl::addColumn(const LLSD& column, EAddPosition pos) +{ + LLString name = column["name"].asString(); + if (mColumns.size() == 0) + { + mDefaultColumn = 0; + } + if (mColumns.find(name) == mColumns.end()) + { + // Add column + mColumns[name] = LLScrollListColumn(column); + LLScrollListColumn* new_column = &mColumns[name]; + new_column->mParentCtrl = this; + new_column->mIndex = mColumns.size()-1; + + // Add button + if (new_column->mWidth > 0 || new_column->mRelWidth > 0 || new_column->mDynamicWidth) + { + if (new_column->mRelWidth >= 0) + { + new_column->mWidth = (S32)llround(new_column->mRelWidth*mItemListRect.getWidth()); + } + else if(new_column->mDynamicWidth) + { + new_column->mWidth = (mItemListRect.getWidth() - mTotalStaticColumnWidth) / mNumDynamicWidthColumns; + } + S32 top = mItemListRect.mTop; + S32 left = mItemListRect.mLeft; + { + std::map<LLString, LLScrollListColumn>::iterator itor; + for (itor = mColumns.begin(); itor != mColumns.end(); ++itor) + { + if (itor->second.mIndex < new_column->mIndex && + itor->second.mWidth > 0) + { + left += itor->second.mWidth + mColumnPadding; + } + } + } + LLString button_name = "btn_" + name; + S32 right = left+new_column->mWidth; + if (new_column->mIndex != (S32)mColumns.size()-1) + { + right += mColumnPadding; + } + LLRect temp_rect = LLRect(left,top+mHeadingHeight,right,top); + new_column->mButton = new LLSquareButton(button_name, temp_rect, "", mHeadingFont, "", onClickColumn, NULL); + if(column["image"].asString() != "") + { + //new_column->mButton->setScaleImage(false); + new_column->mButton->setImageSelected(column["image"].asString()); + new_column->mButton->setImageUnselected(column["image"].asString()); + } + else + { + new_column->mButton->setLabelSelected(new_column->mLabel); + new_column->mButton->setLabelUnselected(new_column->mLabel); + } + //RN: although it might be useful to change sort order with the keyboard, + // mixing tab stops on child items along with the parent item is not supported yet + new_column->mButton->setTabStop(FALSE); + addChild(new_column->mButton); + new_column->mButton->setVisible(mDisplayColumnButtons); + + + // Move scroll to front + removeChild(mScrollbar); + addChild(mScrollbar); + + new_column->mButton->setCallbackUserData(new_column); + } + } + updateColumns(); +} + +// static +void LLScrollListCtrl::onClickColumn(void *userdata) +{ + LLScrollListColumn *info = (LLScrollListColumn*)userdata; + if (!info) return; + + U32 column_index = info->mIndex; + + LLScrollListColumn* column = info->mParentCtrl->mColumnsIndexed[info->mIndex]; + if (column->mSortingColumn != column->mName) + { + if (info->mParentCtrl->mColumns.find(column->mSortingColumn) != info->mParentCtrl->mColumns.end()) + { + LLScrollListColumn& info_redir = info->mParentCtrl->mColumns[column->mSortingColumn]; + column_index = info_redir.mIndex; + } + } + + // TomY TODO: shouldn't these be non-static members? + bool ascending = true; + if (column_index == LLScrollListCtrl::sSortColumn) + { + ascending = !LLScrollListCtrl::sSortAscending; + } + + info->mParentCtrl->sortByColumn(column_index, ascending); +} + +void LLScrollListCtrl::clearColumns() +{ + std::map<LLString, LLScrollListColumn>::iterator itor; + for (itor = mColumns.begin(); itor != mColumns.end(); ++itor) + { + LLButton *button = itor->second.mButton; + if (button) + { + removeChild(button); + delete button; + } + } + mColumns.clear(); +} + +void LLScrollListCtrl::setColumnLabel(const LLString& column, const LLString& label) +{ + std::map<LLString, LLScrollListColumn>::iterator itor = mColumns.find(column); + if (itor != mColumns.end()) + { + itor->second.mLabel = label; + if (itor->second.mButton) + { + itor->second.mButton->setLabelSelected(label); + itor->second.mButton->setLabelUnselected(label); + } + } +} + +void LLScrollListCtrl::setColumnHeadings(LLSD headings) +{ + mColumns.clear(); + LLSD::array_const_iterator itor; + for (itor = headings.beginArray(); itor != headings.endArray(); ++itor) + { + addColumn(*itor); + } +} + +LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& value, EAddPosition pos, void* userdata) +{ + // ID + LLSD id = value["id"]; + + LLScrollListItem *new_item = new LLScrollListItem(id, userdata); + if (value.has("enabled")) + { + new_item->setEnabled( value["enabled"].asBoolean() ); + } + + new_item->setNumColumns(mColumns.size()); + + // Add any columns we don't already have + LLSD columns = value["columns"]; + LLSD::array_const_iterator itor; + for (itor = columns.beginArray(); itor != columns.endArray(); ++itor) + { + LLString column = (*itor)["column"].asString(); + + if (mColumns.size() == 0) + { + mDefaultColumn = 0; + } + std::map<LLString, LLScrollListColumn>::iterator column_itor = mColumns.find(column); + if (column_itor == mColumns.end()) + { + LLSD new_column; + new_column["name"] = column; + new_column["label"] = column; + new_column["width"] = 0; + addColumn(new_column); + column_itor = mColumns.find(column); + new_item->setNumColumns(mColumns.size()); + } + + S32 index = column_itor->second.mIndex; + S32 width = column_itor->second.mWidth; + + LLSD value = (*itor)["value"]; + LLString fontname = (*itor)["font"].asString(); + LLString fontstyle = (*itor)["font-style"].asString(); + LLString type = (*itor)["type"].asString(); + + const LLFontGL *font = gResMgr->getRes(fontname); + if (!font) + { + font = gResMgr->getRes( LLFONT_SANSSERIF_SMALL ); + } + U8 font_style = LLFontGL::getStyleFromString(fontstyle); + + if (type == "icon") + { + LLUUID image_id = value.asUUID(); + LLImageGL* icon = LLUI::sImageProvider->getUIImageByID(image_id); + new_item->setColumn(index, new LLScrollListIcon(icon, width, image_id)); + } + else if (type == "checkbox") + { + LLCheckBoxCtrl* ctrl = new LLCheckBoxCtrl(value.asString(), + LLRect(0, 0, width, width), "label"); + new_item->setColumn(index, new LLScrollListCheck(ctrl,width)); + } + else + { + new_item->setColumn(index, new LLScrollListText(value.asString(), font, width, font_style)); + } + } + + S32 num_columns = mColumns.size(); + for (S32 column = 0; column < num_columns; ++column) + { + if (new_item->getColumn(column) == NULL) + { + LLScrollListColumn* column_ptr = mColumnsIndexed[column]; + new_item->setColumn(column, new LLScrollListText("", gResMgr->getRes( LLFONT_SANSSERIF_SMALL ), column_ptr->mWidth, LLFontGL::NORMAL)); + } + } + + addItem(new_item, pos); + + return new_item; +} + +LLScrollListItem* LLScrollListCtrl::addSimpleElement(const LLString& value, EAddPosition pos, const LLSD& id) +{ + LLSD entry_id = id; + + if (id.isUndefined()) + { + entry_id = value; + } + + LLScrollListItem *new_item = new LLScrollListItem(entry_id); + + const LLFontGL *font = gResMgr->getRes( LLFONT_SANSSERIF_SMALL ); + + new_item->addColumn(value, font, getRect().getWidth()); + + addItem(new_item, pos); + return new_item; +} + +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) +{ + mSearchString.clear(); + // for tabbing into pristine scroll lists (Finder) + if (!getFirstSelected()) + { + selectFirstItem(); + onCommit(); + } + LLUICtrl::setFocus(b); +} +//virtual +void LLScrollListCtrl::onFocusLost() +{ + if (mIsPopup) + { + if (getParent()) + { + getParent()->onFocusLost(); + } + } +} |