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.cpp908
1 files changed, 736 insertions, 172 deletions
diff --git a/indra/llui/llscrolllistctrl.cpp b/indra/llui/llscrolllistctrl.cpp
index 35d5affa5d..98dddfb542 100644
--- a/indra/llui/llscrolllistctrl.cpp
+++ b/indra/llui/llscrolllistctrl.cpp
@@ -29,8 +29,11 @@
#include "llwindow.h"
#include "llcontrol.h"
#include "llkeyboard.h"
+#include "llresizebar.h"
const S32 LIST_BORDER_PAD = 2; // white space inside the border and to the left of the scrollbar
+const S32 MIN_COLUMN_WIDTH = 20;
+const S32 LIST_SNAP_PADDING = 5;
// local structures & classes.
struct SortScrollListItem
@@ -254,7 +257,7 @@ LLScrollListItem::~LLScrollListItem()
std::for_each(mColumns.begin(), mColumns.end(), DeletePointer());
}
-BOOL LLScrollListItem::handleMouseDown(S32 x, S32 y, MASK mask)
+BOOL LLScrollListItem::handleClick(S32 x, S32 y, MASK mask)
{
BOOL handled = FALSE;
@@ -355,14 +358,13 @@ LLScrollListCtrl::LLScrollListCtrl(const LLString& name, const LLRect& rect,
mPageLines(0),
mHeadingHeight(20),
mMaxSelectable(0),
- mHeadingFont(NULL),
mAllowMultipleSelection( allow_multiple_selection ),
mAllowKeyboardMovement(TRUE),
mCommitOnKeyboardMovement(TRUE),
mCommitOnSelectionChange(FALSE),
mSelectionChanged(FALSE),
mCanSelect(TRUE),
- mDisplayColumnButtons(FALSE),
+ mDisplayColumnHeaders(FALSE),
mCollapseEmptyColumns(FALSE),
mIsPopup(FALSE),
mMaxItemCount(INT_MAX),
@@ -377,21 +379,20 @@ LLScrollListCtrl::LLScrollListCtrl(const LLString& name, const LLRect& rect,
mFgUnselectedColor( LLUI::sColorsGroup->getColor("ScrollUnselectedColor") ),
mFgDisabledColor( LLUI::sColorsGroup->getColor("ScrollDisabledColor") ),
mHighlightedColor( LLUI::sColorsGroup->getColor("ScrollHighlightedColor") ),
+ mHighlightedItem(-1),
mBorderThickness( 2 ),
mOnDoubleClickCallback( NULL ),
mOnMaximumSelectCallback( NULL ),
mOnSortChangedCallback( NULL ),
- mHighlightedItem(-1),
+ mDrewSelected(FALSE),
mBorder(NULL),
- mDefaultColumn("SIMPLE"),
mSearchColumn(0),
+ mDefaultColumn("SIMPLE"),
mNumDynamicWidthColumns(0),
mTotalStaticColumnWidth(0),
- mSortColumn(0),
- mSortAscending(TRUE),
-
- mDrewSelected(FALSE)
+ mSortColumn(-1),
+ mSortAscending(TRUE)
{
mItemListRect.setOriginAndSize(
mBorderThickness + LIST_BORDER_PAD,
@@ -478,6 +479,7 @@ void LLScrollListCtrl::clearRows()
mScrollLines = 0;
mLastSelected = NULL;
+ updateMaxContentWidth(NULL);
}
@@ -527,7 +529,6 @@ S32 LLScrollListCtrl::getFirstSelectedIndex()
return -1;
}
-
LLScrollListItem* LLScrollListCtrl::getFirstData() const
{
if (mItemList.size() == 0)
@@ -537,6 +538,15 @@ LLScrollListItem* LLScrollListCtrl::getFirstData() const
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;
@@ -554,7 +564,7 @@ void LLScrollListCtrl::reshape( S32 width, S32 height, BOOL called_from_parent )
{
LLUICtrl::reshape( width, height, called_from_parent );
- S32 heading_size = (mDisplayColumnButtons ? mHeadingHeight : 0);
+ S32 heading_size = (mDisplayColumnHeaders ? mHeadingHeight : 0);
mItemListRect.setOriginAndSize(
mBorderThickness + LIST_BORDER_PAD,
@@ -567,10 +577,8 @@ void LLScrollListCtrl::reshape( S32 width, S32 height, BOOL called_from_parent )
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)
@@ -623,14 +631,56 @@ BOOL LLScrollListCtrl::addItem( LLScrollListItem* item, EAddPosition pos )
updateLineHeight();
mPageLines = mLineHeight ? mItemListRect.getHeight() / mLineHeight : 0;
- mScrollbar->setVisible(mPageLines < getItemCount());
+ BOOL scrollbar_visible = mPageLines < getItemCount();
+
+ if (scrollbar_visible != mScrollbar->getVisible())
+ {
+ mScrollbar->setVisible(mPageLines < getItemCount());
+ updateColumns();
+ }
mScrollbar->setPageSize( mPageLines );
mScrollbar->setDocSize( getItemCount() );
+
+ updateMaxContentWidth(item);
}
+
return not_too_big;
}
+void LLScrollListCtrl::updateMaxContentWidth(LLScrollListItem* added_item)
+{
+ const S32 HEADING_TEXT_PADDING = 30;
+ const S32 COLUMN_TEXT_PADDING = 20;
+
+ std::map<LLString, LLScrollListColumn>::iterator column_itor;
+ for (column_itor = mColumns.begin(); column_itor != mColumns.end(); ++column_itor)
+ {
+ LLScrollListColumn* column = &column_itor->second;
+
+ if (!added_item)
+ {
+ // update on all items
+ column->mMaxContentWidth = column->mHeader ? LLFontGL::sSansSerifSmall->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::sSansSerifSmall->getWidth(cellp->getText()) + mColumnPadding + COLUMN_TEXT_PADDING, column->mMaxContentWidth);
+ }
+ }
+ else
+ {
+ LLScrollListCell* cellp = added_item->getColumn(column->mIndex);
+ if (!cellp) continue;
+
+ column->mMaxContentWidth = llmax(LLFontGL::sSansSerifSmall->getWidth(cellp->getText()) + mColumnPadding + COLUMN_TEXT_PADDING, column->mMaxContentWidth);
+ }
+ }
+}
+
// Line height is the max height of all the cells in all the items.
void LLScrollListCtrl::updateLineHeight()
@@ -659,60 +709,82 @@ void LLScrollListCtrl::updateColumns()
for (column_itor = mColumns.begin(); column_itor != mColumns.end(); ++column_itor)
{
LLScrollListColumn *column = &column_itor->second;
+ S32 new_width = column->mWidth;
if (column->mRelWidth >= 0)
{
- column->mWidth = (S32)llround(column->mRelWidth*mItemListRect.getWidth());
+ new_width = (S32)llround(column->mRelWidth*mItemListRect.getWidth());
}
else if (column->mDynamicWidth)
{
- column->mWidth = (mItemListRect.getWidth() - mTotalStaticColumnWidth) / mNumDynamicWidthColumns;
-
+ new_width = (mItemListRect.getWidth() - mTotalStaticColumnWidth) / mNumDynamicWidthColumns;
+ }
+
+ if (new_width != column->mWidth)
+ {
+ column->mWidth = new_width;
}
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)
+ item_list::iterator iter;
+ for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
{
- LLScrollListColumn* column = &column_itor->second;
- LLButton *button = column->mButton;
-
- if (button)
+ LLScrollListItem *itemp = *iter;
+ S32 num_cols = itemp->getNumColumns();
+ S32 i = 0;
+ for (LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i))
{
- mColumnsIndexed[column->mIndex] = column;
+ if (i >= (S32)mColumnsIndexed.size()) break;
+
+ cell->setWidth(mColumnsIndexed[i]->mWidth);
+ }
+ }
+ // update headers
+ std::vector<LLScrollListColumn*>::iterator column_ordered_it;
+ S32 left = mItemListRect.mLeft;
+ LLColumnHeader* last_header = NULL;
+ for (column_ordered_it = mColumnsIndexed.begin(); column_ordered_it != mColumnsIndexed.end(); ++column_ordered_it)
+ {
+ if ((*column_ordered_it)->mWidth <= 0)
+ {
+ // skip hidden columns
+ }
+ LLScrollListColumn* column = *column_ordered_it;
+
+ if (column->mHeader)
+ {
+ last_header = column->mHeader;
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)
+ S32 right = left + column->mWidth;
+
+ if (column->mIndex != (S32)mColumnsIndexed.size()-1)
{
right += mColumnPadding;
}
- LLRect temp_rect = LLRect(left,top+mHeadingHeight,right,top);
- button->setRect(temp_rect);
- button->setFont(mHeadingFont);
- button->setVisible(mDisplayColumnButtons);
+ 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;
}
}
+
+ // expand last column header we encountered to full list width
+ if (last_header)
+ {
+ S32 header_strip_width = mItemListRect.getWidth() + (mScrollbar->getVisible() ? 0 : SCROLLBAR_SIZE);
+ S32 new_width = llmax(0, mItemListRect.mLeft + header_strip_width - last_header->getRect().mLeft);
+ last_header->reshape(new_width, last_header->getRect().getHeight());
+ }
}
void LLScrollListCtrl::setDisplayHeading(BOOL display)
{
- mDisplayColumnButtons = display;
+ mDisplayColumnHeaders = display;
updateColumns();
@@ -726,15 +798,7 @@ void LLScrollListCtrl::setHeadingHeight(S32 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();
+ mScrollbar->reshape(SCROLLBAR_SIZE, mItemListRect.getHeight() + (mDisplayColumnHeaders ? mHeadingHeight : 0));
}
void LLScrollListCtrl::setCollapseEmptyColumns(BOOL collapse)
@@ -854,6 +918,7 @@ void LLScrollListCtrl::deleteSingleItem(S32 target_index)
}
delete itemp;
mItemList.erase(mItemList.begin() + target_index);
+ updateMaxContentWidth(NULL);
}
void LLScrollListCtrl::deleteSelectedItems()
@@ -873,6 +938,7 @@ void LLScrollListCtrl::deleteSelectedItems()
}
}
mLastSelected = NULL;
+ updateMaxContentWidth(NULL);
}
void LLScrollListCtrl::highlightNthItem(S32 target_index)
@@ -880,6 +946,7 @@ void LLScrollListCtrl::highlightNthItem(S32 target_index)
if (mHighlightedItem != target_index)
{
mHighlightedItem = target_index;
+ llinfos << "Highlighting item " << target_index << llendl;
}
}
@@ -1467,109 +1534,154 @@ BOOL LLScrollListCtrl::handleScrollWheel(S32 x, S32 y, S32 clicks)
return handled;
}
-
-BOOL LLScrollListCtrl::handleMouseDown(S32 x, S32 y, MASK mask)
+BOOL LLScrollListCtrl::selectItemAt(S32 x, S32 y, MASK mask)
{
- BOOL handled = LLView::childrenHandleMouseDown(x, y, mask) != NULL;
+ if (!mCanSelect) return FALSE;
- // set keyboard focus first, in case click action wants to move focus elsewhere
- setFocus(TRUE);
+ BOOL selection_changed = FALSE;
- if( !handled && mCanSelect)
+ LLScrollListItem* hit_item = hitItem(x, y);
+ if( hit_item )
{
- LLScrollListItem* hit_item = hitItem(x, y);
- if( hit_item )
+ if( mAllowMultipleSelection )
{
- if( mAllowMultipleSelection )
+ if (mask & MASK_SHIFT)
{
- if (mask & MASK_SHIFT)
+ if (mLastSelected == NULL)
{
- if (mLastSelected == NULL)
- {
- selectItem(hit_item);
- }
- else
+ 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)
{
- // 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(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)
+ if(mOnMaximumSelectCallback)
{
- selectItem(item, FALSE);
+ 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)
+ }
+ else if (mask & MASK_CONTROL)
+ {
+ if (hit_item->getSelected())
{
- if (hit_item->getSelected())
+ deselectItem(hit_item);
+ }
+ else
+ {
+ if(!(mMaxSelectable > 0 && getAllSelected().size() >= mMaxSelectable))
{
- deselectItem(hit_item);
+ selectItem(hit_item, FALSE);
}
else
{
- if(!(mMaxSelectable > 0 && getAllSelected().size() >= mMaxSelectable))
+ if(mOnMaximumSelectCallback)
{
- selectItem(hit_item, FALSE);
- }
- else
- {
- if(mOnMaximumSelectCallback)
- {
- mOnMaximumSelectCallback(mCallbackUserData);
- }
+ mOnMaximumSelectCallback(mCallbackUserData);
}
}
}
- else
- {
- deselectAllItems(TRUE);
- selectItem(hit_item);
- }
}
else
{
+ deselectAllItems(TRUE);
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;
+ selectItem(hit_item);
}
+
+ hit_item->handleClick(x - mBorderThickness - LIST_BORDER_PAD,
+ 1, mask);
+
+ 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 = LLView::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 so because user is starting a selection operation
+ mSelectionChanged = FALSE;
+
+ gFocusMgr.setMouseCapture(this);
+ selectItemAt(x, y, mask);
}
return TRUE;
}
+BOOL LLScrollListCtrl::handleMouseUp(S32 x, S32 y, MASK mask)
+{
+ if (hasMouseCapture())
+ {
+ if(mask == MASK_NONE)
+ {
+ selectItemAt(x, y, mask);
+ }
+ }
+
+ if (hasMouseCapture())
+ {
+ gFocusMgr.setMouseCapture(NULL);
+ }
+
+ // always commit when mouse operation is completed inside list
+ // this only needs to be done for lists that don't commit on selection change
+ if (!mCommitOnSelectionChange && pointInView(x,y))
+ {
+ mSelectionChanged = FALSE;
+ onCommit();
+ }
+
+ return LLUICtrl::handleMouseUp(x, y, mask);
+}
+
BOOL LLScrollListCtrl::handleDoubleClick(S32 x, S32 y, MASK mask)
{
//BOOL handled = FALSE;
@@ -1628,31 +1740,35 @@ BOOL LLScrollListCtrl::handleHover(S32 x,S32 y,MASK mask)
{
BOOL handled = FALSE;
- if(getVisible())
+ if (hasMouseCapture())
{
- if (mCanSelect)
+ if(mask == MASK_NONE)
{
- LLScrollListItem* item = hitItem(x, y);
- if (item)
- {
- highlightNthItem(getItemIndex(item));
- }
- else
- {
- highlightNthItem(-1);
- }
+ selectItemAt(x, y, mask);
}
-
- handled = LLView::handleHover( x, y, mask );
-
- if( !handled )
+ }
+ else if (mCanSelect)
+ {
+ LLScrollListItem* item = hitItem(x, y);
+ if (item)
+ {
+ highlightNthItem(getItemIndex(item));
+ }
+ else
{
- // Opaque
- getWindow()->setCursor(UI_CURSOR_ARROW);
- lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << llendl;
- handled = TRUE;
+ highlightNthItem(-1);
}
}
+
+ handled = LLUICtrl::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;
}
@@ -1954,7 +2070,7 @@ void LLScrollListCtrl::deselectItem(LLScrollListItem* itemp)
void LLScrollListCtrl::commitIfChanged()
{
- if (mSelectionChanged)
+ if (mSelectionChanged && !hasMouseCapture())
{
mSelectionChanged = FALSE;
onCommit();
@@ -1973,9 +2089,18 @@ void LLScrollListCtrl::onScrollChange( S32 new_pos, LLScrollbar* scrollbar, void
// First column is column 0
void LLScrollListCtrl::sortByColumn(U32 column, BOOL ascending)
{
- mSortColumn = column;
- mSortAscending = ascending;
- std::sort(mItemList.begin(), mItemList.end(), SortScrollListItem(mSortColumn, mSortAscending));
+ if (mSortColumn != column)
+ {
+ mSortColumn = column;
+ std::sort(mItemList.begin(), mItemList.end(), SortScrollListItem(mSortColumn, mSortAscending));
+ }
+
+ // just reverse the list if changing sort order
+ if(mSortAscending != ascending)
+ {
+ std::reverse(mItemList.begin(), mItemList.end());
+ mSortAscending = ascending;
+ }
}
void LLScrollListCtrl::sortByColumn(LLString name, BOOL ascending)
@@ -2047,7 +2172,7 @@ LLXMLNodePtr LLScrollListCtrl::getXML(bool save_children) const
node->createChild("draw_border", TRUE)->setBoolValue((mBorder != NULL));
- node->createChild("draw_heading", TRUE)->setBoolValue(mDisplayColumnButtons);
+ node->createChild("draw_heading", TRUE)->setBoolValue(mDisplayColumnHeaders);
node->createChild("background_visible", TRUE)->setBoolValue(mBackgroundVisible);
@@ -2196,13 +2321,6 @@ LLView* LLScrollListCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFac
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);
@@ -2408,7 +2526,7 @@ BOOL LLScrollListCtrl::canDeselect()
void LLScrollListCtrl::addColumn(const LLSD& column, EAddPosition pos)
{
LLString name = column["name"].asString();
- if (mColumns.size() == 0)
+ if (mColumns.empty())
{
mDefaultColumn = 0;
}
@@ -2451,30 +2569,28 @@ void LLScrollListCtrl::addColumn(const LLSD& column, EAddPosition pos)
right += mColumnPadding;
}
LLRect temp_rect = LLRect(left,top+mHeadingHeight,right,top);
- new_column->mButton = new LLSquareButton(button_name, temp_rect, "", mHeadingFont, "", onClickColumn, NULL);
+ new_column->mHeader = new LLColumnHeader(button_name, temp_rect, new_column);
if(column["image"].asString() != "")
{
- //new_column->mButton->setScaleImage(false);
- new_column->mButton->setImageSelected(column["image"].asString());
- new_column->mButton->setImageUnselected(column["image"].asString());
+ //new_column->mHeader->setScaleImage(false);
+ new_column->mHeader->setImage(column["image"].asString());
}
else
{
- new_column->mButton->setLabelSelected(new_column->mLabel);
- new_column->mButton->setLabelUnselected(new_column->mLabel);
+ new_column->mHeader->setLabel(new_column->mLabel);
+ //new_column->mHeader->setLabel(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);
+ new_column->mHeader->setTabStop(FALSE);
+ addChild(new_column->mHeader);
+ new_column->mHeader->setVisible(mDisplayColumnHeaders);
// Move scroll to front
removeChild(mScrollbar);
addChild(mScrollbar);
-
- new_column->mButton->setCallbackUserData(new_column);
+
}
}
updateColumns();
@@ -2517,7 +2633,7 @@ void LLScrollListCtrl::onClickColumn(void *userdata)
std::string LLScrollListCtrl::getSortColumnName()
{
- LLScrollListColumn* column = mColumnsIndexed[mSortColumn];
+ LLScrollListColumn* column = mSortColumn >= 0 ? mColumnsIndexed[mSortColumn] : NULL;
if (column) return column->mName;
else return "";
@@ -2528,11 +2644,11 @@ void LLScrollListCtrl::clearColumns()
std::map<LLString, LLScrollListColumn>::iterator itor;
for (itor = mColumns.begin(); itor != mColumns.end(); ++itor)
{
- LLButton *button = itor->second.mButton;
- if (button)
+ LLColumnHeader *header = itor->second.mHeader;
+ if (header)
{
- removeChild(button);
- delete button;
+ removeChild(header);
+ delete header;
}
}
mColumns.clear();
@@ -2544,14 +2660,22 @@ void LLScrollListCtrl::setColumnLabel(const LLString& column, const LLString& la
if (itor != mColumns.end())
{
itor->second.mLabel = label;
- if (itor->second.mButton)
+ if (itor->second.mHeader)
{
- itor->second.mButton->setLabelSelected(label);
- itor->second.mButton->setLabelUnselected(label);
+ itor->second.mHeader->setLabel(label);
}
}
}
+LLScrollListColumn* LLScrollListCtrl::getColumn(S32 index)
+{
+ if (index < 0 || index >= (S32)mColumnsIndexed.size())
+ {
+ return NULL;
+ }
+ return mColumnsIndexed[index];
+}
+
void LLScrollListCtrl::setColumnHeadings(LLSD headings)
{
mColumns.clear();
@@ -2629,6 +2753,10 @@ LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& value, EAddPosition p
else
{
new_item->setColumn(index, new LLScrollListText(value.asString(), font, width, font_style, font_alignment));
+ if (column_itor->second.mHeader && !value.asString().empty())
+ {
+ column_itor->second.mHeader->setHasResizableElement(TRUE);
+ }
}
}
@@ -2725,6 +2853,14 @@ void LLScrollListCtrl::setFocus(BOOL b)
}
LLUICtrl::setFocus(b);
}
+
+//virtual
+void LLScrollListCtrl::onFocusReceived()
+{
+ // forget latent selection changes when getting focus
+ mSelectionChanged = FALSE;
+}
+
//virtual
void LLScrollListCtrl::onFocusLost()
{
@@ -2735,5 +2871,433 @@ void LLScrollListCtrl::onFocusLost()
getParent()->onFocusLost();
}
}
+ if (hasMouseCapture())
+ {
+ gFocusMgr.setMouseCapture(NULL);
+ }
+ LLUICtrl::onFocusLost();
+}
+
+LLColumnHeader::LLColumnHeader(const LLString& label, const LLRect &rect, LLScrollListColumn* column, const LLFontGL* fontp) :
+ LLComboBox(label, rect, label, NULL, NULL),
+ mColumn(column),
+ mOrigLabel(label),
+ mShowSortOptions(FALSE),
+ mHasResizableElement(FALSE)
+{
+ mListPosition = LLComboBox::ABOVE;
+ setCommitCallback(onSelectSort);
+ setCallbackUserData(this);
+ mButton->setTabStop(FALSE);
+ // require at least two frames between mouse down and mouse up event to capture intentional "hold" not just bad framerate
+ mButton->setHeldDownDelay(LLUI::sConfigGroup->getF32("ColumnHeaderDropDownDelay"), 2);
+ mButton->setHeldDownCallback(onHeldDown);
+ mButton->setClickedCallback(onClick);
+ mButton->setMouseDownCallback(onMouseDown);
+
+ mButton->setCallbackUserData(this);
+
+ mAscendingText = "[LOW]...[HIGH](Ascending)";
+ mDescendingText = "[HIGH]...[LOW](Descending)";
+
+ LLSD row;
+ row["columns"][0]["column"] = "label";
+ row["columns"][0]["value"] = mAscendingText.getString();
+ row["columns"][0]["font"] = "SANSSERIF_SMALL";
+ row["columns"][0]["width"] = 80;
+
+ row["columns"][1]["column"] = "arrow";
+ row["columns"][1]["type"] = "icon";
+ row["columns"][1]["value"] = LLUI::sAssetsGroup->getString("up_arrow.tga");
+ row["columns"][1]["width"] = 20;
+
+ mList->addElement(row);
+
+ row["columns"][0]["column"] = "label";
+ row["columns"][0]["type"] = "text";
+ row["columns"][0]["value"] = mDescendingText.getString();
+ row["columns"][0]["font"] = "SANSSERIF_SMALL";
+ row["columns"][0]["width"] = 80;
+
+ row["columns"][1]["column"] = "arrow";
+ row["columns"][1]["type"] = "icon";
+ row["columns"][1]["value"] = LLUI::sAssetsGroup->getString("down_arrow.tga");
+ row["columns"][1]["width"] = 20;
+
+ mList->addElement(row);
+
+ mList->reshape(llmax(mList->getRect().getWidth(), 110, mRect.getWidth()), mList->getRect().getHeight());
+
+ // resize handles on left and right
+ const S32 RESIZE_BAR_THICKNESS = 3;
+ mResizeBar = new LLResizeBar(
+ "resizebar",
+ LLRect( mRect.getWidth() - RESIZE_BAR_THICKNESS, mRect.getHeight(), mRect.getWidth(), 0),
+ MIN_COLUMN_WIDTH, mRect.getHeight(), LLResizeBar::RIGHT );
+ addChild(mResizeBar);
+
+ mResizeBar->setEnabled(FALSE);
+}
+
+LLColumnHeader::~LLColumnHeader()
+{
+}
+
+void LLColumnHeader::draw()
+{
+ if( getVisible() )
+ {
+ mDrawArrow = !mColumn->mLabel.empty() && mColumn->mParentCtrl->getSortColumnName() == mColumn->mSortingColumn;
+
+ BOOL is_ascending = mColumn->mParentCtrl->getSortAscending();
+ mArrowImage = is_ascending ? LLUI::sImageProvider->getUIImageByID(LLUUID(LLUI::sAssetsGroup->getString("up_arrow.tga")))
+ : LLUI::sImageProvider->getUIImageByID(LLUUID(LLUI::sAssetsGroup->getString("down_arrow.tga")));
+
+ //BOOL clip = mRect.mRight > mColumn->mParentCtrl->getItemListRect().getWidth();
+ //LLGLEnable scissor_test(clip ? GL_SCISSOR_TEST : GL_FALSE);
+
+ //LLRect column_header_local_rect(-mRect.mLeft, mRect.getHeight(), mColumn->mParentCtrl->getItemListRect().getWidth() - mRect.mLeft, 0);
+ //LLUI::setScissorRegionLocal(column_header_local_rect);
+
+ // Draw children
+ LLComboBox::draw();
+
+ if (mList->getVisible())
+ {
+ // sync sort order with list selection every frame
+ mColumn->mParentCtrl->sortByColumn(mColumn->mSortingColumn, getCurrentIndex() == 0);
+ }
+
+ }
+}
+
+BOOL LLColumnHeader::handleDoubleClick(S32 x, S32 y, MASK mask)
+{
+ if (canResize() && mResizeBar->getRect().pointInRect(x, y))
+ {
+ // reshape column to max content width
+ LLRect column_rect = getRect();
+ column_rect.mRight = column_rect.mLeft + mColumn->mMaxContentWidth;
+ userSetShape(column_rect);
+ }
+ else
+ {
+ onClick(this);
+ }
+ return TRUE;
+}
+
+void LLColumnHeader::setImage(const LLString &image_name)
+{
+ if (mButton)
+ {
+ mButton->setImageSelected(image_name);
+ mButton->setImageUnselected(image_name);
+ }
+}
+
+//static
+void LLColumnHeader::onClick(void* user_data)
+{
+ LLColumnHeader* headerp = (LLColumnHeader*)user_data;
+ if (!headerp) return;
+
+ LLScrollListColumn* column = headerp->mColumn;
+ if (!column) return;
+
+ if (headerp->mList->getVisible())
+ {
+ headerp->hideList();
+ }
+
+ LLScrollListCtrl::onClickColumn(column);
+
+ // propage new sort order to sort order list
+ headerp->mList->selectNthItem(column->mParentCtrl->getSortAscending() ? 0 : 1);
+}
+
+//static
+void LLColumnHeader::onMouseDown(void* user_data)
+{
+ // for now, do nothing but block the normal showList() behavior
+ return;
+}
+
+//static
+void LLColumnHeader::onHeldDown(void* user_data)
+{
+ LLColumnHeader* headerp = (LLColumnHeader*)user_data;
+ headerp->showList();
+}
+
+void LLColumnHeader::showList()
+{
+ if (mShowSortOptions)
+ {
+ //LLSD item_val = mColumn->mParentCtrl->getFirstData()->getValue();
+ mOrigLabel = mButton->getLabelSelected();
+
+ // move sort column over to this column and do initial sort
+ mColumn->mParentCtrl->sortByColumn(mColumn->mSortingColumn, mColumn->mParentCtrl->getSortAscending());
+
+ LLString low_item_text;
+ LLString high_item_text;
+
+ LLScrollListItem* itemp = mColumn->mParentCtrl->getFirstData();
+ if (itemp)
+ {
+ LLScrollListCell* cell = itemp->getColumn(mColumn->mIndex);
+ if (cell && cell->isText())
+ {
+ if (mColumn->mParentCtrl->getSortAscending())
+ {
+ low_item_text = cell->getText();
+ }
+ else
+ {
+ high_item_text = cell->getText();
+ }
+ }
+ }
+
+ itemp = mColumn->mParentCtrl->getLastData();
+ if (itemp)
+ {
+ LLScrollListCell* cell = itemp->getColumn(mColumn->mIndex);
+ if (cell && cell->isText())
+ {
+ if (mColumn->mParentCtrl->getSortAscending())
+ {
+ high_item_text = cell->getText();
+ }
+ else
+ {
+ low_item_text = cell->getText();
+ }
+ }
+ }
+
+ LLString::truncate(low_item_text, 3);
+ LLString::truncate(high_item_text, 3);
+
+ LLString ascending_string;
+ LLString descending_string;
+
+ if (low_item_text.empty() || high_item_text.empty())
+ {
+ ascending_string = "Ascending";
+ descending_string = "Descending";
+ }
+ else
+ {
+ mAscendingText.setArg("[LOW]", low_item_text);
+ mAscendingText.setArg("[HIGH]", high_item_text);
+ mDescendingText.setArg("[LOW]", low_item_text);
+ mDescendingText.setArg("[HIGH]", high_item_text);
+ ascending_string = mAscendingText.getString();
+ descending_string = mDescendingText.getString();
+ }
+
+ S32 text_width = LLFontGL::sSansSerifSmall->getWidth(ascending_string);
+ text_width = llmax(text_width, LLFontGL::sSansSerifSmall->getWidth(descending_string)) + 10;
+ text_width = llmax(text_width, mRect.getWidth() - 30);
+
+ mList->getColumn(0)->mWidth = text_width;
+ ((LLScrollListText*)mList->getFirstData()->getColumn(0))->setText(ascending_string);
+ ((LLScrollListText*)mList->getLastData()->getColumn(0))->setText(descending_string);
+
+ mList->reshape(llmax(text_width + 30, 110, mRect.getWidth()), mList->getRect().getHeight());
+
+ LLComboBox::showList();
+ }
}
+//static
+void LLColumnHeader::onSelectSort(LLUICtrl* ctrl, void* user_data)
+{
+ LLColumnHeader* headerp = (LLColumnHeader*)user_data;
+ if (!headerp) return;
+
+ LLScrollListColumn* column = headerp->mColumn;
+ if (!column) return;
+ LLScrollListCtrl *parent = column->mParentCtrl;
+ if (!parent) return;
+
+ if (headerp->getCurrentIndex() == 0)
+ {
+ // ascending
+ parent->sortByColumn(column->mSortingColumn, TRUE);
+ }
+ else
+ {
+ // descending
+ parent->sortByColumn(column->mSortingColumn, FALSE);
+ }
+
+ // restore original column header
+ headerp->setLabel(headerp->mOrigLabel);
+}
+
+LLView* LLColumnHeader::findSnapEdge(S32& new_edge_val, const LLCoordGL& mouse_dir, ESnapEdge snap_edge, ESnapType snap_type, S32 threshold, S32 padding)
+{
+ // this logic assumes dragging on right
+ llassert(snap_edge == SNAP_RIGHT);
+
+ // use higher snap threshold for column headers
+ threshold = llmin(threshold, 15);
+
+ LLRect snap_rect = getSnapRect();
+
+ S32 snap_delta = mColumn->mMaxContentWidth - snap_rect.getWidth();
+
+ // x coord growing means column growing, so same signs mean we're going in right direction
+ if (llabs(snap_delta) <= threshold && mouse_dir.mX * snap_delta > 0 )
+ {
+ new_edge_val = snap_rect.mRight + snap_delta;
+ }
+ else
+ {
+ LLScrollListColumn* next_column = mColumn->mParentCtrl->getColumn(mColumn->mIndex + 1);
+ while (next_column)
+ {
+ if (next_column->mHeader)
+ {
+ snap_delta = (next_column->mHeader->getSnapRect().mRight - next_column->mMaxContentWidth) - snap_rect.mRight;
+ if (llabs(snap_delta) <= threshold && mouse_dir.mX * snap_delta > 0 )
+ {
+ new_edge_val = snap_rect.mRight + snap_delta;
+ }
+ break;
+ }
+ next_column = mColumn->mParentCtrl->getColumn(next_column->mIndex + 1);
+ }
+ }
+
+ return this;
+}
+
+void LLColumnHeader::userSetShape(const LLRect& new_rect)
+{
+ S32 new_width = new_rect.getWidth();
+ S32 delta_width = new_width - (mRect.getWidth() + mColumn->mParentCtrl->getColumnPadding());
+
+ if (delta_width != 0)
+ {
+ S32 remaining_width = delta_width;
+ S32 col;
+ for (col = mColumn->mIndex + 1; col < mColumn->mParentCtrl->getNumColumns(); col++)
+ {
+ LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col);
+ if (!columnp) break;
+
+ if (columnp->mHeader && columnp->mHeader->canResize())
+ {
+ // how many pixels in width can this column afford to give up?
+ S32 resize_buffer_amt = llmax(0, columnp->mWidth - MIN_COLUMN_WIDTH);
+
+ // user shrinking column, need to add width to other columns
+ if (delta_width < 0)
+ {
+ if (!columnp->mDynamicWidth && columnp->mWidth > 0)
+ {
+ // statically sized column, give all remaining width to this column
+ columnp->mWidth -= remaining_width;
+ if (columnp->mRelWidth > 0.f)
+ {
+ columnp->mRelWidth = (F32)columnp->mWidth / (F32)mColumn->mParentCtrl->getItemListRect().getWidth();
+ }
+ }
+ break;
+ }
+ else
+ {
+ // user growing column, need to take width from other columns
+ remaining_width -= resize_buffer_amt;
+
+ if (!columnp->mDynamicWidth && columnp->mWidth > 0)
+ {
+ columnp->mWidth -= llmin(columnp->mWidth - MIN_COLUMN_WIDTH, delta_width);
+ if (columnp->mRelWidth > 0.f)
+ {
+ columnp->mRelWidth = (F32)columnp->mWidth / (F32)mColumn->mParentCtrl->getItemListRect().getWidth();
+ }
+ }
+
+ if (remaining_width <= 0)
+ {
+ // width sucked up from neighboring columns, done
+ break;
+ }
+ }
+ }
+ }
+
+ // clamp resize amount to maximum that can be absorbed by other columns
+ if (delta_width > 0)
+ {
+ delta_width -= llmax(remaining_width, 0);
+ }
+
+ // propagate constrained delta_width to new width for this column
+ new_width = mRect.getWidth() + delta_width - mColumn->mParentCtrl->getColumnPadding();
+
+ // use requested width
+ mColumn->mWidth = new_width;
+
+ // update proportional spacing
+ if (mColumn->mRelWidth > 0.f)
+ {
+ mColumn->mRelWidth = (F32)new_width / (F32)mColumn->mParentCtrl->getItemListRect().getWidth();
+ }
+
+ // tell scroll list to layout columns again
+ mColumn->mParentCtrl->updateColumns();
+ }
+}
+
+void LLColumnHeader::setHasResizableElement(BOOL resizable)
+{
+ // for now, dynamically spaced columns can't be resized
+ if (mColumn->mDynamicWidth) return;
+
+ if (resizable != mHasResizableElement)
+ {
+ mHasResizableElement = resizable;
+
+ S32 num_resizable_columns = 0;
+ S32 col;
+ for (col = 0; col < mColumn->mParentCtrl->getNumColumns(); col++)
+ {
+ LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col);
+ if (columnp->mHeader && columnp->mHeader->canResize())
+ {
+ num_resizable_columns++;
+ }
+ }
+
+ S32 num_resizers_enabled = 0;
+
+ // now enable/disable resize handles on resizable columns if we have at least two
+ for (col = 0; col < mColumn->mParentCtrl->getNumColumns(); col++)
+ {
+ LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col);
+ if (!columnp->mHeader) continue;
+ BOOL enable = num_resizable_columns >= 2 && num_resizers_enabled < (num_resizable_columns - 1) && columnp->mHeader->canResize();
+ columnp->mHeader->enableResizeBar(enable);
+ if (enable)
+ {
+ num_resizers_enabled++;
+ }
+ }
+ }
+}
+
+void LLColumnHeader::enableResizeBar(BOOL enable)
+{
+ mResizeBar->setEnabled(enable);
+}
+
+BOOL LLColumnHeader::canResize()
+{
+ return getVisible() && (mHasResizableElement || mColumn->mDynamicWidth);
+}