diff options
Diffstat (limited to 'indra/llui/llscrolllistctrl.cpp')
-rw-r--r-- | indra/llui/llscrolllistctrl.cpp | 908 |
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); +} |