aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/linden/indra/llui/llscrolllistctrl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'linden/indra/llui/llscrolllistctrl.cpp')
-rw-r--r--linden/indra/llui/llscrolllistctrl.cpp1048
1 files changed, 674 insertions, 374 deletions
diff --git a/linden/indra/llui/llscrolllistctrl.cpp b/linden/indra/llui/llscrolllistctrl.cpp
index b80787f..d65ebd2 100644
--- a/linden/indra/llui/llscrolllistctrl.cpp
+++ b/linden/indra/llui/llscrolllistctrl.cpp
@@ -12,12 +12,12 @@
12 * ("GPL"), unless you have obtained a separate licensing agreement 12 * ("GPL"), unless you have obtained a separate licensing agreement
13 * ("Other License"), formally executed by you and Linden Lab. Terms of 13 * ("Other License"), formally executed by you and Linden Lab. Terms of
14 * the GPL can be found in doc/GPL-license.txt in this distribution, or 14 * the GPL can be found in doc/GPL-license.txt in this distribution, or
15 * online at http://secondlife.com/developers/opensource/gplv2 15 * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
16 * 16 *
17 * There are special exceptions to the terms and conditions of the GPL as 17 * There are special exceptions to the terms and conditions of the GPL as
18 * it is applied to this Source Code. View the full text of the exception 18 * it is applied to this Source Code. View the full text of the exception
19 * in the file doc/FLOSS-exception.txt in this software distribution, or 19 * in the file doc/FLOSS-exception.txt in this software distribution, or
20 * online at http://secondlife.com/developers/opensource/flossexception 20 * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception
21 * 21 *
22 * By copying, modifying or distributing this software, you acknowledge 22 * By copying, modifying or distributing this software, you acknowledge
23 * that you have read and understood your obligations described above, 23 * that you have read and understood your obligations described above,
@@ -61,73 +61,101 @@ const S32 LIST_SNAP_PADDING = 5;
61// local structures & classes. 61// local structures & classes.
62struct SortScrollListItem 62struct SortScrollListItem
63{ 63{
64 SortScrollListItem(const S32 sort_col, BOOL sort_ascending) 64 SortScrollListItem(const std::vector<std::pair<S32, BOOL> >& sort_orders)
65 { 65 : mSortOrders(sort_orders)
66 mSortCol = sort_col; 66 {}
67 mSortAscending = sort_ascending;
68 }
69 67
70 bool operator()(const LLScrollListItem* i1, const LLScrollListItem* i2) 68 bool operator()(const LLScrollListItem* i1, const LLScrollListItem* i2)
71 { 69 {
72 const LLScrollListCell *cell1; 70 if ( mSortOrders.empty() ) return true;
73 const LLScrollListCell *cell2; 71
74 72 const LLScrollListCell *cell1 = NULL;
75 cell1 = i1->getColumn(mSortCol); 73 const LLScrollListCell *cell2 = NULL;
76 cell2 = i2->getColumn(mSortCol);
77 74
78 S32 order = 1; 75 sort_order_t::const_reverse_iterator end_it = mSortOrders.rend();
79 if (!mSortAscending) 76 sort_order_t::const_reverse_iterator it;
77
78 // sort over all columns in order specified by mSortOrders
79 S32 sort_result = 0;
80 for (it = mSortOrders.rbegin(); it != end_it; ++it)
80 { 81 {
81 order = -1; 82 S32 col_idx = it->first;
82 } 83 BOOL sort_ascending = it->second;
83 84
84 BOOL retval = FALSE; 85 cell1 = i1->getColumn(col_idx);
86 cell2 = i2->getColumn(col_idx);
87 // ascending or descending sort for this column?
88 S32 order = 1;
89 if (!sort_ascending)
90 {
91 order = -1;
92 }
85 93
86 if (cell1 && cell2) 94 if (cell1 && cell2)
87 { 95 {
88 retval = ((order * LLString::compareDict(cell1->getText(), cell2->getText())) < 0); 96 sort_result = (order * LLString::compareDict(cell1->getValue().asString(), cell2->getValue().asString()));
97 if (sort_result != 0)
98 {
99 // we have a sort order!
100 break;
101 }
102 }
89 } 103 }
90 104
91 return (retval ? TRUE : FALSE); 105 return sort_result < 0;
92 } 106 }
93 107
94protected: 108 typedef std::vector<std::pair<S32, BOOL> > sort_order_t;
95 S32 mSortCol; 109 const sort_order_t& mSortOrders;
96 S32 mSortAscending;
97}; 110};
98 111
99 112
100
101// 113//
102// LLScrollListIcon 114// LLScrollListIcon
103// 115//
104LLScrollListIcon::LLScrollListIcon(LLImageGL* icon, S32 width, LLUUID image_id) 116LLScrollListIcon::LLScrollListIcon(const LLUUID& icon_id, S32 width)
105 : mIcon(icon), 117 : LLScrollListCell(width),
106 mImageUUID(image_id.asString()), 118 mColor(LLColor4::white),
107 mColor(LLColor4::white) 119 mImageUUID(icon_id)
108{ 120{
109 if (width) 121 // don't use default image specified by LLUUID::null, use no image in that case
110 { 122 mIcon = icon_id.isNull() ? NULL : LLUI::sImageProvider->getImageByID(icon_id);
111 mWidth = width;
112 }
113 else
114 {
115 mWidth = icon->getWidth();
116 }
117} 123}
118 124
119LLScrollListIcon::~LLScrollListIcon() 125LLScrollListIcon::~LLScrollListIcon()
120{ 126{
121} 127}
122 128
129void LLScrollListIcon::setValue(LLSD value)
130{
131 mImageUUID = value.asUUID();
132 // don't use default image specified by LLUUID::null, use no image in that case
133 mIcon = mImageUUID.isNull() ? NULL : LLUI::sImageProvider->getImageByID(mImageUUID);
134}
135
136
123void LLScrollListIcon::setColor(const LLColor4& color) 137void LLScrollListIcon::setColor(const LLColor4& color)
124{ 138{
125 mColor = color; 139 mColor = color;
126} 140}
127 141
128void LLScrollListIcon::drawToWidth(S32 width, const LLColor4& color, const LLColor4& highlight_color) const 142S32 LLScrollListIcon::getWidth() const
143{
144 // if no specified fix width, use width of icon
145 if (mWidth == 0)
146 {
147 return mIcon->getWidth();
148 }
149 return mWidth;
150}
151
152
153void LLScrollListIcon::draw(const LLColor4& color, const LLColor4& highlight_color) const
129{ 154{
130 gl_draw_image(0, 0, mIcon, mColor); 155 if (mIcon)
156 {
157 gl_draw_image(0, 0, mIcon, mColor);
158 }
131} 159}
132 160
133// 161//
@@ -155,32 +183,32 @@ LLScrollListCheck::~LLScrollListCheck()
155 delete mCheckBox; 183 delete mCheckBox;
156} 184}
157 185
158void LLScrollListCheck::drawToWidth(S32 width, const LLColor4& color, const LLColor4& highlight_color) const 186void LLScrollListCheck::draw(const LLColor4& color, const LLColor4& highlight_color) const
159{ 187{
160 mCheckBox->draw(); 188 mCheckBox->draw();
161
162} 189}
163 190
164BOOL LLScrollListCheck::handleClick() 191BOOL LLScrollListCheck::handleClick()
165{ 192{
166 if ( mCheckBox->getEnabled() ) 193 if (mCheckBox->getEnabled())
167 { 194 {
168 LLCheckBoxCtrl::onButtonPress(mCheckBox); 195 mCheckBox->toggle();
169 } 196 }
197 // don't change selection when clicking on embedded checkbox
170 return TRUE; 198 return TRUE;
171} 199}
172 200
173// 201//
174// LLScrollListSeparator 202// LLScrollListSeparator
175// 203//
176LLScrollListSeparator::LLScrollListSeparator(S32 width) : mWidth(width) 204LLScrollListSeparator::LLScrollListSeparator(S32 width) : LLScrollListCell(width)
177{ 205{
178} 206}
179 207
180void LLScrollListSeparator::drawToWidth(S32 width, const LLColor4& color, const LLColor4& highlight_color) const 208void LLScrollListSeparator::draw(const LLColor4& color, const LLColor4& highlight_color) const
181{ 209{
182 //*FIXME: use dynamic item heights and make separators narrow, and inactive 210 //*FIXME: use dynamic item heights and make separators narrow, and inactive
183 gl_line_2d(5, 8, llmax(5, width - 5), 8, color); 211 gl_line_2d(5, 8, llmax(5, getWidth() - 5), 8, color);
184} 212}
185 213
186// 214//
@@ -189,11 +217,11 @@ void LLScrollListSeparator::drawToWidth(S32 width, const LLColor4& color, const
189U32 LLScrollListText::sCount = 0; 217U32 LLScrollListText::sCount = 0;
190 218
191LLScrollListText::LLScrollListText( const LLString& text, const LLFontGL* font, S32 width, U8 font_style, LLFontGL::HAlign font_alignment, LLColor4& color, BOOL use_color, BOOL visible) 219LLScrollListText::LLScrollListText( const LLString& text, const LLFontGL* font, S32 width, U8 font_style, LLFontGL::HAlign font_alignment, LLColor4& color, BOOL use_color, BOOL visible)
192: mText( text ), 220: LLScrollListCell(width),
221 mText( text ),
193 mFont( font ), 222 mFont( font ),
194 mFontStyle( font_style ), 223 mFontStyle( font_style ),
195 mFontAlignment( font_alignment ), 224 mFontAlignment( font_alignment ),
196 mWidth( width ),
197 mVisible( visible ), 225 mVisible( visible ),
198 mHighlightCount( 0 ), 226 mHighlightCount( 0 ),
199 mHighlightOffset( 0 ) 227 mHighlightOffset( 0 )
@@ -213,7 +241,7 @@ LLScrollListText::LLScrollListText( const LLString& text, const LLFontGL* font,
213 // initialize rounded rect image 241 // initialize rounded rect image
214 if (!mRoundedRectImage) 242 if (!mRoundedRectImage)
215 { 243 {
216 mRoundedRectImage = LLUI::sImageProvider->getUIImageByID(LLUUID(LLUI::sAssetsGroup->getString("rounded_square.tga"))); 244 mRoundedRectImage = LLUI::sImageProvider->getImageByID(LLUUID(LLUI::sAssetsGroup->getString("rounded_square.tga")));
217 } 245 }
218} 246}
219 247
@@ -223,6 +251,12 @@ LLScrollListText::~LLScrollListText()
223 delete mColor; 251 delete mColor;
224} 252}
225 253
254S32 LLScrollListText::getContentWidth() const
255{
256 return mFont->getWidth(mText.getString());
257}
258
259
226void LLScrollListText::setColor(const LLColor4& color) 260void LLScrollListText::setColor(const LLColor4& color)
227{ 261{
228 if (!mColor) 262 if (!mColor)
@@ -237,14 +271,8 @@ void LLScrollListText::setText(const LLStringExplicit& text)
237 mText = text; 271 mText = text;
238} 272}
239 273
240void LLScrollListText::drawToWidth(S32 width, const LLColor4& color, const LLColor4& highlight_color) const 274void LLScrollListText::draw(const LLColor4& color, const LLColor4& highlight_color) const
241{ 275{
242 // If the user has specified a small minimum width, use that.
243 if (mWidth > 0 && mWidth < width)
244 {
245 width = mWidth;
246 }
247
248 const LLColor4* display_color; 276 const LLColor4* display_color;
249 if (mColor) 277 if (mColor)
250 { 278 {
@@ -266,10 +294,10 @@ void LLScrollListText::drawToWidth(S32 width, const LLColor4& color, const LLCol
266 left = mFont->getWidth(mText.getString(), 0, mHighlightOffset); 294 left = mFont->getWidth(mText.getString(), 0, mHighlightOffset);
267 break; 295 break;
268 case LLFontGL::RIGHT: 296 case LLFontGL::RIGHT:
269 left = width - mFont->getWidth(mText.getString(), mHighlightOffset, S32_MAX); 297 left = getWidth() - mFont->getWidth(mText.getString(), mHighlightOffset, S32_MAX);
270 break; 298 break;
271 case LLFontGL::HCENTER: 299 case LLFontGL::HCENTER:
272 left = (width - mFont->getWidth(mText.getString())) / 2; 300 left = (getWidth() - mFont->getWidth(mText.getString())) / 2;
273 break; 301 break;
274 } 302 }
275 gl_segmented_rect_2d_tex(left - 2, 303 gl_segmented_rect_2d_tex(left - 2,
@@ -291,10 +319,10 @@ void LLScrollListText::drawToWidth(S32 width, const LLColor4& color, const LLCol
291 start_x = 0.f; 319 start_x = 0.f;
292 break; 320 break;
293 case LLFontGL::RIGHT: 321 case LLFontGL::RIGHT:
294 start_x = (F32)width; 322 start_x = (F32)getWidth();
295 break; 323 break;
296 case LLFontGL::HCENTER: 324 case LLFontGL::HCENTER:
297 start_x = (F32)width * 0.5f; 325 start_x = (F32)getWidth() * 0.5f;
298 break; 326 break;
299 } 327 }
300 mFont->render(mText.getWString(), 0, 328 mFont->render(mText.getWString(), 0,
@@ -304,8 +332,10 @@ void LLScrollListText::drawToWidth(S32 width, const LLColor4& color, const LLCol
304 LLFontGL::BOTTOM, 332 LLFontGL::BOTTOM,
305 mFontStyle, 333 mFontStyle,
306 string_chars, 334 string_chars,
307 width, 335 getWidth(),
308 &right_x, FALSE, TRUE); 336 &right_x,
337 FALSE,
338 TRUE);
309} 339}
310 340
311 341
@@ -314,31 +344,6 @@ LLScrollListItem::~LLScrollListItem()
314 std::for_each(mColumns.begin(), mColumns.end(), DeletePointer()); 344 std::for_each(mColumns.begin(), mColumns.end(), DeletePointer());
315} 345}
316 346
317BOOL LLScrollListItem::handleClick(S32 x, S32 y, MASK mask)
318{
319 BOOL handled = FALSE;
320
321 S32 left = 0;
322 S32 right = 0;
323 S32 width = 0;
324
325 std::vector<LLScrollListCell *>::iterator iter = mColumns.begin();
326 std::vector<LLScrollListCell *>::iterator end = mColumns.end();
327 for ( ; iter != end; ++iter)
328 {
329 width = (*iter)->getWidth();
330 right += width;
331 if (left <= x && x < right )
332 {
333 handled = (*iter)->handleClick();
334 break;
335 }
336
337 left += width;
338 }
339 return handled;
340}
341
342void LLScrollListItem::setNumColumns(S32 columns) 347void LLScrollListItem::setNumColumns(S32 columns)
343{ 348{
344 S32 prev_columns = mColumns.size(); 349 S32 prev_columns = mColumns.size();
@@ -375,7 +380,7 @@ LLString LLScrollListItem::getContentsCSV()
375 S32 count = getNumColumns(); 380 S32 count = getNumColumns();
376 for (S32 i=0; i<count; ++i) 381 for (S32 i=0; i<count; ++i)
377 { 382 {
378 ret += getColumn(i)->getText(); 383 ret += getColumn(i)->getValue().asString();
379 if (i < count-1) 384 if (i < count-1)
380 { 385 {
381 ret += ", "; 386 ret += ", ";
@@ -385,17 +390,107 @@ LLString LLScrollListItem::getContentsCSV()
385 return ret; 390 return ret;
386} 391}
387 392
393void LLScrollListItem::draw(const LLRect& rect, const LLColor4& fg_color, const LLColor4& bg_color, const LLColor4& highlight_color, S32 column_padding)
394{
395 // draw background rect
396 LLRect bg_rect = rect;
397 // pad background rectangle to separate it from contents
398 bg_rect.stretch(LIST_BORDER_PAD, 0);
399 {
400 LLGLSNoTexture no_texture;
401 glColor4fv(bg_color.mV);
402 gl_rect_2d( bg_rect );
403 }
404
405 S32 cur_x = rect.mLeft;
406 S32 num_cols = getNumColumns();
407 S32 cur_col = 0;
408
409 for (LLScrollListCell* cell = getColumn(0); cur_col < num_cols; cell = getColumn(++cur_col))
410 {
411 // Two ways a cell could be hidden
412 if (cell->getWidth() < 0
413 || !cell->getVisible()) continue;
414
415 LLUI::pushMatrix();
416 {
417 LLUI::translate((F32) cur_x, (F32) rect.mBottom, 0.0f);
418
419 cell->draw( fg_color, highlight_color );
420 }
421 LLUI::popMatrix();
422
423 cur_x += cell->getWidth() + column_padding;
424 }
425}
426
427
388void LLScrollListItem::setEnabled(BOOL b) 428void LLScrollListItem::setEnabled(BOOL b)
389{ 429{
390 if (b != mEnabled) 430 mEnabled = b;
431}
432
433//---------------------------------------------------------------------------
434// LLScrollListItemComment
435//---------------------------------------------------------------------------
436LLScrollListItemComment::LLScrollListItemComment(const LLString& comment_string, const LLColor4& color)
437: LLScrollListItem(FALSE),
438 mColor(color)
439{
440 addColumn( comment_string, gResMgr->getRes( LLFONT_SANSSERIF_SMALL ) );
441}
442
443void LLScrollListItemComment::draw(const LLRect& rect, const LLColor4& fg_color, const LLColor4& bg_color, const LLColor4& highlight_color, S32 column_padding)
444{
445 LLScrollListCell* cell = getColumn(0);
446 if (cell)
447 {
448 // Two ways a cell could be hidden
449 if (cell->getWidth() < 0
450 || !cell->getVisible()) return;
451
452 LLUI::pushMatrix();
453 {
454 LLUI::translate((F32)rect.mLeft, (F32)rect.mBottom, 0.0f);
455
456 // force first cell to be width of entire item
457 cell->setWidth(rect.getWidth());
458 cell->draw( mColor, highlight_color );
459 }
460 LLUI::popMatrix();
461 }
462}
463
464//---------------------------------------------------------------------------
465// LLScrollListItemSeparator
466//---------------------------------------------------------------------------
467LLScrollListItemSeparator::LLScrollListItemSeparator()
468: LLScrollListItem(FALSE)
469{
470 LLScrollListSeparator* cell = new LLScrollListSeparator(0);
471 setNumColumns(1);
472 setColumn(0, cell);
473}
474
475void LLScrollListItemSeparator::draw(const LLRect& rect, const LLColor4& fg_color, const LLColor4& bg_color, const LLColor4& highlight_color, S32 column_padding)
476{
477 //TODO* move LLScrollListSeparator::draw into here and get rid of it
478 LLScrollListCell* cell = getColumn(0);
479 if (cell)
391 { 480 {
392 std::vector<LLScrollListCell *>::iterator iter = mColumns.begin(); 481 // Two ways a cell could be hidden
393 std::vector<LLScrollListCell *>::iterator end = mColumns.end(); 482 if (cell->getWidth() < 0
394 for ( ; iter != end; ++iter) 483 || !cell->getVisible()) return;
484
485 LLUI::pushMatrix();
395 { 486 {
396 (*iter)->setEnabled(b); 487 LLUI::translate((F32)rect.mLeft, (F32)rect.mBottom, 0.0f);
488
489 // force first cell to be width of entire item
490 cell->setWidth(rect.getWidth());
491 cell->draw( fg_color, highlight_color );
397 } 492 }
398 mEnabled = b; 493 LLUI::popMatrix();
399 } 494 }
400} 495}
401 496
@@ -423,10 +518,8 @@ LLScrollListCtrl::LLScrollListCtrl(const LLString& name, const LLRect& rect,
423 mNeedsScroll(FALSE), 518 mNeedsScroll(FALSE),
424 mCanSelect(TRUE), 519 mCanSelect(TRUE),
425 mDisplayColumnHeaders(FALSE), 520 mDisplayColumnHeaders(FALSE),
426 mCollapseEmptyColumns(FALSE),
427 mIsPopup(FALSE),
428 mMaxItemCount(INT_MAX), 521 mMaxItemCount(INT_MAX),
429 //mItemCount(0), 522 mMaxContentWidth(0),
430 mBackgroundVisible( TRUE ), 523 mBackgroundVisible( TRUE ),
431 mDrawStripes(TRUE), 524 mDrawStripes(TRUE),
432 mBgWriteableColor( LLUI::sColorsGroup->getColor( "ScrollBgWriteableColor" ) ), 525 mBgWriteableColor( LLUI::sColorsGroup->getColor( "ScrollBgWriteableColor" ) ),
@@ -443,12 +536,9 @@ LLScrollListCtrl::LLScrollListCtrl(const LLString& name, const LLRect& rect,
443 mOnSortChangedCallback( NULL ), 536 mOnSortChangedCallback( NULL ),
444 mHighlightedItem(-1), 537 mHighlightedItem(-1),
445 mBorder(NULL), 538 mBorder(NULL),
446 mDefaultColumn("SIMPLE"),
447 mSearchColumn(0), 539 mSearchColumn(0),
448 mNumDynamicWidthColumns(0), 540 mNumDynamicWidthColumns(0),
449 mTotalStaticColumnWidth(0), 541 mTotalStaticColumnWidth(0),
450 mSortColumn(-1),
451 mSortAscending(TRUE),
452 mSorted(TRUE), 542 mSorted(TRUE),
453 mDirty(FALSE), 543 mDirty(FALSE),
454 mOriginalSelection(-1), 544 mOriginalSelection(-1),
@@ -457,7 +547,7 @@ LLScrollListCtrl::LLScrollListCtrl(const LLString& name, const LLRect& rect,
457 mItemListRect.setOriginAndSize( 547 mItemListRect.setOriginAndSize(
458 mBorderThickness + LIST_BORDER_PAD, 548 mBorderThickness + LIST_BORDER_PAD,
459 mBorderThickness + LIST_BORDER_PAD, 549 mBorderThickness + LIST_BORDER_PAD,
460 mRect.getWidth() - 2*( mBorderThickness + LIST_BORDER_PAD ) - SCROLLBAR_SIZE, 550 mRect.getWidth() - 2*( mBorderThickness + LIST_BORDER_PAD ),
461 mRect.getHeight() - 2*( mBorderThickness + LIST_BORDER_PAD ) ); 551 mRect.getHeight() - 2*( mBorderThickness + LIST_BORDER_PAD ) );
462 552
463 updateLineHeight(); 553 updateLineHeight();
@@ -481,7 +571,8 @@ LLScrollListCtrl::LLScrollListCtrl(const LLString& name, const LLRect& rect,
481 mScrollbar->setFollowsTop(); 571 mScrollbar->setFollowsTop();
482 mScrollbar->setFollowsBottom(); 572 mScrollbar->setFollowsBottom();
483 mScrollbar->setEnabled( TRUE ); 573 mScrollbar->setEnabled( TRUE );
484 mScrollbar->setVisible( TRUE ); 574 // scrollbar is visible only when needed
575 mScrollbar->setVisible(FALSE);
485 addChild(mScrollbar); 576 addChild(mScrollbar);
486 577
487 // Border 578 // Border
@@ -539,7 +630,8 @@ void LLScrollListCtrl::clearRows()
539 630
540 mScrollLines = 0; 631 mScrollLines = 0;
541 mLastSelected = NULL; 632 mLastSelected = NULL;
542 updateMaxContentWidth(NULL); 633 calcMaxContentWidth(NULL);
634 updateLayout();
543 mDirty = FALSE; 635 mDirty = FALSE;
544} 636}
545 637
@@ -620,38 +712,64 @@ std::vector<LLScrollListItem*> LLScrollListCtrl::getAllData() const
620 return ret; 712 return ret;
621} 713}
622 714
715// returns first matching item
716LLScrollListItem* LLScrollListCtrl::getItem(const LLSD& sd) const
717{
718 LLString string_val = sd.asString();
719
720 item_list::const_iterator iter;
721 for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
722 {
723 LLScrollListItem* item = *iter;
724 // assumes string representation is good enough for comparison
725 if (item->getValue().asString() == string_val)
726 {
727 return item;
728 }
729 }
730 return NULL;
731}
732
623 733
624void LLScrollListCtrl::reshape( S32 width, S32 height, BOOL called_from_parent ) 734void LLScrollListCtrl::reshape( S32 width, S32 height, BOOL called_from_parent )
625{ 735{
626 S32 old_height = mRect.getHeight();
627 LLUICtrl::reshape( width, height, called_from_parent ); 736 LLUICtrl::reshape( width, height, called_from_parent );
628 737
629 S32 heading_size = (mDisplayColumnHeaders ? mHeadingHeight : 0); 738 updateLayout();
739}
630 740
741void LLScrollListCtrl::updateLayout()
742{
743 // reserve room for column headers, if needed
744 S32 heading_size = (mDisplayColumnHeaders ? mHeadingHeight : 0);
631 mItemListRect.setOriginAndSize( 745 mItemListRect.setOriginAndSize(
632 mBorderThickness + LIST_BORDER_PAD, 746 mBorderThickness + LIST_BORDER_PAD,
633 mBorderThickness + LIST_BORDER_PAD, 747 mBorderThickness + LIST_BORDER_PAD,
634 mRect.getWidth() - 2*( mBorderThickness + LIST_BORDER_PAD ) - SCROLLBAR_SIZE, 748 mRect.getWidth() - 2*( mBorderThickness + LIST_BORDER_PAD ),
635 mRect.getHeight() - 2*( mBorderThickness + LIST_BORDER_PAD ) - heading_size ); 749 mRect.getHeight() - 2*( mBorderThickness + LIST_BORDER_PAD ) - heading_size );
636 750
751 // how many lines of content in a single "page"
637 mPageLines = mLineHeight? mItemListRect.getHeight() / mLineHeight : 0; 752 mPageLines = mLineHeight? mItemListRect.getHeight() / mLineHeight : 0;
638 if(old_height < height && getScrollPos() == mScrollbar->getDocPosMax()) 753 BOOL scrollbar_visible = getItemCount() > mPageLines;
754 if (scrollbar_visible)
639 { 755 {
640 setScrollPos(mScrollbar->getDocPosMax()); 756 // provide space on the right for scrollbar
757 mItemListRect.mRight = mRect.getWidth() - ( mBorderThickness + LIST_BORDER_PAD ) - SCROLLBAR_SIZE;
641 } 758 }
642 mScrollbar->setVisible(mPageLines < getItemCount()); 759
760 mScrollbar->reshape(SCROLLBAR_SIZE, mItemListRect.getHeight() + (mDisplayColumnHeaders ? mHeadingHeight : 0));
643 mScrollbar->setPageSize( mPageLines ); 761 mScrollbar->setPageSize( mPageLines );
644 762 mScrollbar->setDocSize( getItemCount() );
763 mScrollbar->setVisible(scrollbar_visible);
764
645 updateColumns(); 765 updateColumns();
646} 766}
647 767
648// Attempt to size the control to show all items. 768// Attempt to size the control to show all items.
649// Do not make larger than width or height. 769// Do not make larger than width or height.
650void LLScrollListCtrl::arrange(S32 max_width, S32 max_height) 770void LLScrollListCtrl::fitContents(S32 max_width, S32 max_height)
651{ 771{
652 S32 height = mLineHeight * (getItemCount() + 1); 772 S32 height = llmin( getRequiredRect().getHeight(), max_height );
653 height = llmin( height, max_height );
654
655 S32 width = mRect.getWidth(); 773 S32 width = mRect.getWidth();
656 774
657 reshape( width, height ); 775 reshape( width, height );
@@ -660,14 +778,17 @@ void LLScrollListCtrl::arrange(S32 max_width, S32 max_height)
660 778
661LLRect LLScrollListCtrl::getRequiredRect() 779LLRect LLScrollListCtrl::getRequiredRect()
662{ 780{
663 S32 height = mLineHeight * (getItemCount() + 1); 781 S32 heading_size = (mDisplayColumnHeaders ? mHeadingHeight : 0);
782 S32 height = (mLineHeight * getItemCount())
783 + (2 * ( mBorderThickness + LIST_BORDER_PAD ))
784 + heading_size;
664 S32 width = mRect.getWidth(); 785 S32 width = mRect.getWidth();
665 786
666 return LLRect(0, height, width, 0); 787 return LLRect(0, height, width, 0);
667} 788}
668 789
669 790
670BOOL LLScrollListCtrl::addItem( LLScrollListItem* item, EAddPosition pos ) 791BOOL LLScrollListCtrl::addItem( LLScrollListItem* item, EAddPosition pos, BOOL requires_column )
671{ 792{
672 BOOL not_too_big = getItemCount() < mMaxItemCount; 793 BOOL not_too_big = getItemCount() < mMaxItemCount;
673 if (not_too_big) 794 if (not_too_big)
@@ -680,15 +801,22 @@ BOOL LLScrollListCtrl::addItem( LLScrollListItem* item, EAddPosition pos )
680 break; 801 break;
681 802
682 case ADD_SORTED: 803 case ADD_SORTED:
683 if (mSortColumn == -1)
684 { 804 {
685 mSortColumn = 0; 805 // sort by column 0, in ascending order
686 mSortAscending = TRUE; 806 std::vector<sort_column_t> single_sort_column;
687 } 807 single_sort_column.push_back(std::make_pair(0, TRUE));
688 mItemList.push_back(item); 808
689 std::sort(mItemList.begin(), mItemList.end(), SortScrollListItem(mSortColumn, mSortAscending)); 809 mItemList.push_back(item);
690 break; 810 std::stable_sort(
691 811 mItemList.begin(),
812 mItemList.end(),
813 SortScrollListItem(single_sort_column));
814
815 // ADD_SORTED just sorts by first column...
816 // this might not match user sort criteria, so flag list as being in unsorted state
817 setSorted(FALSE);
818 break;
819 }
692 case ADD_BOTTOM: 820 case ADD_BOTTOM:
693 mItemList.push_back(item); 821 mItemList.push_back(item);
694 setSorted(FALSE); 822 setSorted(FALSE);
@@ -701,34 +829,42 @@ BOOL LLScrollListCtrl::addItem( LLScrollListItem* item, EAddPosition pos )
701 break; 829 break;
702 } 830 }
703 831
704 updateLineHeightInsert(item); 832 // create new column on demand
705 mPageLines = mLineHeight ? mItemListRect.getHeight() / mLineHeight : 0; 833 if (mColumns.empty() && requires_column)
706 BOOL scrollbar_visible = mPageLines < getItemCount();
707
708 if (scrollbar_visible != mScrollbar->getVisible())
709 { 834 {
710 mScrollbar->setVisible(mPageLines < getItemCount()); 835 LLSD new_column;
711 updateColumns(); 836 new_column["name"] = "default_column";
837 new_column["label"] = "";
838 new_column["dynamicwidth"] = TRUE;
839 addColumn(new_column);
712 } 840 }
713 mScrollbar->setPageSize( mPageLines );
714
715 mScrollbar->setDocSize( getItemCount() );
716 841
717 updateMaxContentWidth(item); 842 updateLineHeightInsert(item);
843 calcMaxContentWidth(item);
844
845 updateLayout();
718 } 846 }
719 847
720 return not_too_big; 848 return not_too_big;
721} 849}
722 850
723void LLScrollListCtrl::updateMaxContentWidth(LLScrollListItem* added_item) 851void LLScrollListCtrl::calcMaxContentWidth(LLScrollListItem* added_item)
724{ 852{
725 const S32 HEADING_TEXT_PADDING = 30; 853 const S32 HEADING_TEXT_PADDING = 30;
726 const S32 COLUMN_TEXT_PADDING = 20; 854 const S32 COLUMN_TEXT_PADDING = 20;
727 855
728 std::map<LLString, LLScrollListColumn>::iterator column_itor; 856 if (added_item == NULL)
729 for (column_itor = mColumns.begin(); column_itor != mColumns.end(); ++column_itor)
730 { 857 {
731 LLScrollListColumn* column = &column_itor->second; 858 mMaxContentWidth = 0;
859 }
860
861 S32 item_content_width = 0;
862
863 ordered_columns_t::iterator column_itor;
864 for (column_itor = mColumnsIndexed.begin(); column_itor != mColumnsIndexed.end(); ++column_itor)
865 {
866 LLScrollListColumn* column = *column_itor;
867 if (!column) continue;
732 868
733 if (!added_item) 869 if (!added_item)
734 { 870 {
@@ -740,7 +876,7 @@ void LLScrollListCtrl::updateMaxContentWidth(LLScrollListItem* added_item)
740 LLScrollListCell* cellp = (*iter)->getColumn(column->mIndex); 876 LLScrollListCell* cellp = (*iter)->getColumn(column->mIndex);
741 if (!cellp) continue; 877 if (!cellp) continue;
742 878
743 column->mMaxContentWidth = llmax(LLFontGL::sSansSerifSmall->getWidth(cellp->getText()) + mColumnPadding + COLUMN_TEXT_PADDING, column->mMaxContentWidth); 879 column->mMaxContentWidth = llmax(LLFontGL::sSansSerifSmall->getWidth(cellp->getValue().asString()) + mColumnPadding + COLUMN_TEXT_PADDING, column->mMaxContentWidth);
744 } 880 }
745 } 881 }
746 else 882 else
@@ -748,9 +884,13 @@ void LLScrollListCtrl::updateMaxContentWidth(LLScrollListItem* added_item)
748 LLScrollListCell* cellp = added_item->getColumn(column->mIndex); 884 LLScrollListCell* cellp = added_item->getColumn(column->mIndex);
749 if (!cellp) continue; 885 if (!cellp) continue;
750 886
751 column->mMaxContentWidth = llmax(LLFontGL::sSansSerifSmall->getWidth(cellp->getText()) + mColumnPadding + COLUMN_TEXT_PADDING, column->mMaxContentWidth); 887 column->mMaxContentWidth = llmax(LLFontGL::sSansSerifSmall->getWidth(cellp->getValue().asString()) + mColumnPadding + COLUMN_TEXT_PADDING, column->mMaxContentWidth);
752 } 888 }
889
890 item_content_width += column->mMaxContentWidth;
753 } 891 }
892
893 mMaxContentWidth = llmax(mMaxContentWidth, item_content_width);
754} 894}
755 895
756const S32 SCROLL_LIST_ROW_PAD = 2; 896const S32 SCROLL_LIST_ROW_PAD = 2;
@@ -789,7 +929,6 @@ void LLScrollListCtrl::updateColumns()
789 mColumnsIndexed.resize(mColumns.size()); 929 mColumnsIndexed.resize(mColumns.size());
790 930
791 std::map<LLString, LLScrollListColumn>::iterator column_itor; 931 std::map<LLString, LLScrollListColumn>::iterator column_itor;
792 bool first_dynamic = true;
793 for (column_itor = mColumns.begin(); column_itor != mColumns.end(); ++column_itor) 932 for (column_itor = mColumns.begin(); column_itor != mColumns.end(); ++column_itor)
794 { 933 {
795 LLScrollListColumn *column = &column_itor->second; 934 LLScrollListColumn *column = &column_itor->second;
@@ -801,11 +940,6 @@ void LLScrollListCtrl::updateColumns()
801 else if (column->mDynamicWidth) 940 else if (column->mDynamicWidth)
802 { 941 {
803 new_width = (mItemListRect.getWidth() - mTotalStaticColumnWidth) / mNumDynamicWidthColumns; 942 new_width = (mItemListRect.getWidth() - mTotalStaticColumnWidth) / mNumDynamicWidthColumns;
804 if(first_dynamic)
805 {
806 first_dynamic = false;
807 new_width += (mScrollbar->getVisible() ? 0 : SCROLLBAR_SIZE);
808 }
809 } 943 }
810 944
811 if (new_width != column->mWidth) 945 if (new_width != column->mWidth)
@@ -854,48 +988,38 @@ void LLScrollListCtrl::updateColumns()
854 } 988 }
855 right = llmax(left, llmin(mItemListRect.getWidth(), right)); 989 right = llmax(left, llmin(mItemListRect.getWidth(), right));
856 S32 header_width = right - left; 990 S32 header_width = right - left;
857 991
858 last_header->reshape(header_width, mHeadingHeight); 992 last_header->reshape(header_width, mHeadingHeight);
859 last_header->translate(left - last_header->getRect().mLeft, top - last_header->getRect().mBottom); 993 last_header->translate(
994 left - last_header->getRect().mLeft,
995 top - last_header->getRect().mBottom);
860 last_header->setVisible(mDisplayColumnHeaders && header_width > 0); 996 last_header->setVisible(mDisplayColumnHeaders && header_width > 0);
861 left = right; 997 left = right;
862 } 998 }
863 } 999 }
864 1000
865 // expand last column header we encountered to full list width 1001 // expand last column header we encountered to full list width
866
867 if (last_header) 1002 if (last_header)
868 { 1003 {
869 S32 header_strip_width = mItemListRect.getWidth() + (mScrollbar->getVisible() ? 0 : SCROLLBAR_SIZE); 1004 S32 new_width = llmax(0, mItemListRect.mRight - last_header->getRect().mLeft);
870 S32 new_width = llmax(0, mItemListRect.mLeft + header_strip_width - last_header->getRect().mLeft);
871 last_header->reshape(new_width, last_header->getRect().getHeight()); 1005 last_header->reshape(new_width, last_header->getRect().getHeight());
872 last_header->setVisible(mDisplayColumnHeaders && new_width > 0); 1006 last_header->setVisible(mDisplayColumnHeaders && new_width > 0);
873 } 1007 }
874
875} 1008}
876 1009
877void LLScrollListCtrl::setDisplayHeading(BOOL display) 1010void LLScrollListCtrl::setDisplayHeading(BOOL display)
878{ 1011{
879 mDisplayColumnHeaders = display; 1012 mDisplayColumnHeaders = display;
880 1013
881 updateColumns(); 1014 updateLayout();
882
883 setHeadingHeight(mHeadingHeight);
884} 1015}
885 1016
886void LLScrollListCtrl::setHeadingHeight(S32 heading_height) 1017void LLScrollListCtrl::setHeadingHeight(S32 heading_height)
887{ 1018{
888 mHeadingHeight = heading_height; 1019 mHeadingHeight = heading_height;
889 1020
890 reshape(mRect.getWidth(), mRect.getHeight()); 1021 updateLayout();
891
892 // Resize
893 mScrollbar->reshape(SCROLLBAR_SIZE, mItemListRect.getHeight() + (mDisplayColumnHeaders ? mHeadingHeight : 0));
894}
895 1022
896void LLScrollListCtrl::setCollapseEmptyColumns(BOOL collapse)
897{
898 mCollapseEmptyColumns = collapse;
899} 1023}
900 1024
901BOOL LLScrollListCtrl::selectFirstItem() 1025BOOL LLScrollListCtrl::selectFirstItem()
@@ -934,6 +1058,8 @@ BOOL LLScrollListCtrl::selectFirstItem()
934 1058
935BOOL LLScrollListCtrl::selectNthItem( S32 target_index ) 1059BOOL LLScrollListCtrl::selectNthItem( S32 target_index )
936{ 1060{
1061 if (mItemList.empty()) return FALSE;
1062
937 // Deselects all other items 1063 // Deselects all other items
938 BOOL success = FALSE; 1064 BOOL success = FALSE;
939 S32 index = 0; 1065 S32 index = 0;
@@ -1012,7 +1138,32 @@ void LLScrollListCtrl::deleteSingleItem(S32 target_index)
1012 } 1138 }
1013 delete itemp; 1139 delete itemp;
1014 mItemList.erase(mItemList.begin() + target_index); 1140 mItemList.erase(mItemList.begin() + target_index);
1015 updateMaxContentWidth(NULL); 1141 calcMaxContentWidth(NULL);
1142}
1143
1144//FIXME: refactor item deletion
1145void LLScrollListCtrl::deleteItems(const LLSD& sd)
1146{
1147 item_list::iterator iter;
1148 for (iter = mItemList.begin(); iter < mItemList.end(); )
1149 {
1150 LLScrollListItem* itemp = *iter;
1151 if (itemp->getValue().asString() == sd.asString())
1152 {
1153 if (itemp == mLastSelected)
1154 {
1155 mLastSelected = NULL;
1156 }
1157 delete itemp;
1158 mItemList.erase(iter++);
1159 }
1160 else
1161 {
1162 iter++;
1163 }
1164 }
1165
1166 calcMaxContentWidth(NULL);
1016} 1167}
1017 1168
1018void LLScrollListCtrl::deleteSelectedItems() 1169void LLScrollListCtrl::deleteSelectedItems()
@@ -1032,7 +1183,7 @@ void LLScrollListCtrl::deleteSelectedItems()
1032 } 1183 }
1033 } 1184 }
1034 mLastSelected = NULL; 1185 mLastSelected = NULL;
1035 updateMaxContentWidth(NULL); 1186 calcMaxContentWidth(NULL);
1036} 1187}
1037 1188
1038void LLScrollListCtrl::highlightNthItem(S32 target_index) 1189void LLScrollListCtrl::highlightNthItem(S32 target_index)
@@ -1108,7 +1259,8 @@ void LLScrollListCtrl::selectPrevItem( BOOL extend_selection)
1108 1259
1109 if (!getFirstSelected()) 1260 if (!getFirstSelected())
1110 { 1261 {
1111 selectFirstItem(); 1262 // select last item
1263 selectNthItem(getItemCount() - 1);
1112 } 1264 }
1113 else 1265 else
1114 { 1266 {
@@ -1130,7 +1282,8 @@ void LLScrollListCtrl::selectPrevItem( BOOL extend_selection)
1130 break; 1282 break;
1131 } 1283 }
1132 1284
1133 prev_item = cur_item; 1285 // don't allow navigation to disabled elements
1286 prev_item = cur_item->getEnabled() ? cur_item : prev_item;
1134 } 1287 }
1135 } 1288 }
1136 1289
@@ -1145,32 +1298,34 @@ void LLScrollListCtrl::selectPrevItem( BOOL extend_selection)
1145 1298
1146void LLScrollListCtrl::selectNextItem( BOOL extend_selection) 1299void LLScrollListCtrl::selectNextItem( BOOL extend_selection)
1147{ 1300{
1301 LLScrollListItem* next_item = NULL;
1302
1148 if (!getFirstSelected()) 1303 if (!getFirstSelected())
1149 { 1304 {
1150 selectFirstItem(); 1305 selectFirstItem();
1151 } 1306 }
1152 else 1307 else
1153 { 1308 {
1154 item_list::iterator iter; 1309 item_list::reverse_iterator iter;
1155 for (iter = mItemList.begin(); iter != mItemList.end(); iter++) 1310 for (iter = mItemList.rbegin(); iter != mItemList.rend(); iter++)
1156 { 1311 {
1157 LLScrollListItem* item = *iter; 1312 LLScrollListItem* cur_item = *iter;
1158 if (item->getSelected()) 1313
1314 if (cur_item->getSelected())
1159 { 1315 {
1160 if (++iter != mItemList.end()) 1316 if (next_item)
1161 { 1317 {
1162 LLScrollListItem *next_item = *iter; 1318 selectItem(next_item, !extend_selection);
1163 if (next_item) 1319 }
1164 { 1320 else
1165 selectItem(next_item, !extend_selection); 1321 {
1166 } 1322 reportInvalidInput();
1167 else
1168 {
1169 reportInvalidInput();
1170 }
1171 } 1323 }
1172 break; 1324 break;
1173 } 1325 }
1326
1327 // don't allow navigation to disabled items
1328 next_item = cur_item->getEnabled() ? cur_item : next_item;
1174 } 1329 }
1175 } 1330 }
1176 1331
@@ -1200,27 +1355,31 @@ void LLScrollListCtrl::deselectAllItems(BOOL no_commit_on_change)
1200} 1355}
1201 1356
1202/////////////////////////////////////////////////////////////////////////////////////////////////// 1357///////////////////////////////////////////////////////////////////////////////////////////////////
1203// "Simple" interface: use this when you're creating a list that contains only unique strings, only 1358// Use this to add comment text such as "Searching", which ignores column settings of list
1204// one of which can be selected at a time.
1205 1359
1206LLScrollListItem* LLScrollListCtrl::addSimpleItem(const LLString& item_text, EAddPosition pos, BOOL enabled) 1360LLScrollListItem* LLScrollListCtrl::addCommentText(const LLString& comment_text, EAddPosition pos)
1207{ 1361{
1208 LLScrollListItem* item = NULL; 1362 LLScrollListItem* item = NULL;
1209 if (getItemCount() < mMaxItemCount) 1363 if (getItemCount() < mMaxItemCount)
1210 { 1364 {
1211 // simple items have their LLSD data set to their label 1365 // simple items have their LLSD data set to their label
1212 item = new LLScrollListItem( LLSD(item_text) ); 1366 // always draw comment text with "enabled" color
1213 item->setEnabled(enabled); 1367 item = new LLScrollListItemComment( comment_text, mFgUnselectedColor );
1214 item->addColumn( item_text, gResMgr->getRes( LLFONT_SANSSERIF_SMALL ) ); 1368 addItem( item, pos, FALSE );
1215 addItem( item, pos );
1216 } 1369 }
1217 return item; 1370 return item;
1218} 1371}
1219 1372
1373LLScrollListItem* LLScrollListCtrl::addSeparator(EAddPosition pos)
1374{
1375 LLScrollListItem* item = new LLScrollListItemSeparator();
1376 addItem(item, pos, FALSE);
1377 return item;
1378}
1220 1379
1221// Selects first enabled item of the given name. 1380// Selects first enabled item of the given name.
1222// Returns false if item not found. 1381// Returns false if item not found.
1223BOOL LLScrollListCtrl::selectSimpleItem(const LLString& label, BOOL case_sensitive) 1382BOOL LLScrollListCtrl::selectItemByLabel(const LLString& label, BOOL case_sensitive)
1224{ 1383{
1225 //RN: assume no empty items 1384 //RN: assume no empty items
1226 if (label.empty()) 1385 if (label.empty())
@@ -1242,7 +1401,7 @@ BOOL LLScrollListCtrl::selectSimpleItem(const LLString& label, BOOL case_sensiti
1242 { 1401 {
1243 LLScrollListItem* item = *iter; 1402 LLScrollListItem* item = *iter;
1244 // Only select enabled items with matching names 1403 // Only select enabled items with matching names
1245 LLString item_text = item->getColumn(0)->getText(); 1404 LLString item_text = item->getColumn(0)->getValue().asString();
1246 if (!case_sensitive) 1405 if (!case_sensitive)
1247 { 1406 {
1248 LLString::toLower(item_text); 1407 LLString::toLower(item_text);
@@ -1265,14 +1424,14 @@ BOOL LLScrollListCtrl::selectSimpleItem(const LLString& label, BOOL case_sensiti
1265} 1424}
1266 1425
1267 1426
1268BOOL LLScrollListCtrl::selectSimpleItemByPrefix(const LLString& target, BOOL case_sensitive) 1427BOOL LLScrollListCtrl::selectItemByPrefix(const LLString& target, BOOL case_sensitive)
1269{ 1428{
1270 return selectSimpleItemByPrefix(utf8str_to_wstring(target), case_sensitive); 1429 return selectItemByPrefix(utf8str_to_wstring(target), case_sensitive);
1271} 1430}
1272 1431
1273// Selects first enabled item that has a name where the name's first part matched the target string. 1432// Selects first enabled item that has a name where the name's first part matched the target string.
1274// Returns false if item not found. 1433// Returns false if item not found.
1275BOOL LLScrollListCtrl::selectSimpleItemByPrefix(const LLWString& target, BOOL case_sensitive) 1434BOOL LLScrollListCtrl::selectItemByPrefix(const LLWString& target, BOOL case_sensitive)
1276{ 1435{
1277 BOOL found = FALSE; 1436 BOOL found = FALSE;
1278 1437
@@ -1288,7 +1447,7 @@ BOOL LLScrollListCtrl::selectSimpleItemByPrefix(const LLWString& target, BOOL ca
1288 LLScrollListItem* item = *iter; 1447 LLScrollListItem* item = *iter;
1289 // Only select enabled items with matching names 1448 // Only select enabled items with matching names
1290 LLScrollListCell* cellp = item->getColumn(mSearchColumn); 1449 LLScrollListCell* cellp = item->getColumn(mSearchColumn);
1291 BOOL select = cellp ? item->getEnabled() && ('\0' == cellp->getText()[0]) : FALSE; 1450 BOOL select = cellp ? item->getEnabled() && ('\0' == cellp->getValue().asString()[0]) : FALSE;
1292 if (select) 1451 if (select)
1293 { 1452 {
1294 selectItem(item); 1453 selectItem(item);
@@ -1315,7 +1474,7 @@ BOOL LLScrollListCtrl::selectSimpleItemByPrefix(const LLWString& target, BOOL ca
1315 { 1474 {
1316 continue; 1475 continue;
1317 } 1476 }
1318 LLWString item_label = utf8str_to_wstring(cellp->getText()); 1477 LLWString item_label = utf8str_to_wstring(cellp->getValue().asString());
1319 if (!case_sensitive) 1478 if (!case_sensitive)
1320 { 1479 {
1321 LLWString::toLower(item_label); 1480 LLWString::toLower(item_label);
@@ -1346,14 +1505,14 @@ BOOL LLScrollListCtrl::selectSimpleItemByPrefix(const LLWString& target, BOOL ca
1346 return found; 1505 return found;
1347} 1506}
1348 1507
1349const LLString& LLScrollListCtrl::getSimpleSelectedItem(S32 column) const 1508const LLString LLScrollListCtrl::getSelectedItemLabel(S32 column) const
1350{ 1509{
1351 LLScrollListItem* item; 1510 LLScrollListItem* item;
1352 1511
1353 item = getFirstSelected(); 1512 item = getFirstSelected();
1354 if (item) 1513 if (item)
1355 { 1514 {
1356 return item->getColumn(column)->getText(); 1515 return item->getColumn(column)->getValue().asString();
1357 } 1516 }
1358 1517
1359 return LLString::null; 1518 return LLString::null;
@@ -1375,20 +1534,6 @@ LLScrollListItem* LLScrollListCtrl::addStringUUIDItem(const LLString& item_text,
1375 return item; 1534 return item;
1376} 1535}
1377 1536
1378LLScrollListItem* LLScrollListCtrl::addSimpleItem(const LLString& item_text, LLSD sd, EAddPosition pos, BOOL enabled, S32 column_width)
1379{
1380 LLScrollListItem* item = NULL;
1381 if (getItemCount() < mMaxItemCount)
1382 {
1383 item = new LLScrollListItem( sd );
1384 item->setEnabled(enabled);
1385 item->addColumn(item_text, gResMgr->getRes(LLFONT_SANSSERIF_SMALL), column_width);
1386 addItem( item, pos );
1387 }
1388 return item;
1389}
1390
1391
1392// Select the line or lines that match this UUID 1537// Select the line or lines that match this UUID
1393BOOL LLScrollListCtrl::selectByID( const LLUUID& id ) 1538BOOL LLScrollListCtrl::selectByID( const LLUUID& id )
1394{ 1539{
@@ -1454,7 +1599,7 @@ LLUUID LLScrollListCtrl::getStringUUIDSelectedItem()
1454 return LLUUID::null; 1599 return LLUUID::null;
1455} 1600}
1456 1601
1457LLSD LLScrollListCtrl::getSimpleSelectedValue() 1602LLSD LLScrollListCtrl::getSelectedValue()
1458{ 1603{
1459 LLScrollListItem* item = getFirstSelected(); 1604 LLScrollListItem* item = getFirstSelected();
1460 1605
@@ -1481,28 +1626,28 @@ void LLScrollListCtrl::drawItems()
1481 LLGLSUIDefault gls_ui; 1626 LLGLSUIDefault gls_ui;
1482 1627
1483 { 1628 {
1484 LLRect clip_rect = mItemListRect; 1629 LLLocalClipRect clip(mItemListRect);
1485 if(!mScrollbar->getVisible()) clip_rect.mRight += SCROLLBAR_SIZE;
1486 LLLocalClipRect clip(clip_rect);
1487 1630
1488 S32 cur_x = x;
1489 S32 cur_y = y; 1631 S32 cur_y = y;
1490 1632
1491 mDrewSelected = FALSE; 1633 mDrewSelected = FALSE;
1492 1634
1493 S32 line = 0; 1635 S32 line = 0;
1494 LLColor4 color;
1495 S32 max_columns = 0; 1636 S32 max_columns = 0;
1496 1637
1638 LLColor4 highlight_color = LLColor4::white;
1639 F32 type_ahead_timeout = LLUI::sConfigGroup->getF32("TypeAheadTimeout");
1640 highlight_color.mV[VALPHA] = clamp_rescale(mSearchTimer.getElapsedTimeF32(), type_ahead_timeout * 0.7f, type_ahead_timeout, 0.4f, 0.f);
1641
1497 item_list::iterator iter; 1642 item_list::iterator iter;
1498 for (iter = mItemList.begin(); iter != mItemList.end(); iter++) 1643 for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
1499 { 1644 {
1500 LLScrollListItem* item = *iter; 1645 LLScrollListItem* item = *iter;
1501 1646
1502 item_rect.setOriginAndSize( 1647 item_rect.setOriginAndSize(
1503 cur_x, 1648 x,
1504 cur_y, 1649 cur_y,
1505 mScrollbar->getVisible() ? mItemListRect.getWidth() : mItemListRect.getWidth() + SCROLLBAR_SIZE, 1650 mItemListRect.getWidth(),
1506 mLineHeight ); 1651 mLineHeight );
1507 1652
1508 //llinfos << item_rect.getWidth() << llendl; 1653 //llinfos << item_rect.getWidth() << llendl;
@@ -1514,85 +1659,37 @@ void LLScrollListCtrl::drawItems()
1514 1659
1515 max_columns = llmax(max_columns, item->getNumColumns()); 1660 max_columns = llmax(max_columns, item->getNumColumns());
1516 1661
1517 LLRect bg_rect = item_rect; 1662 LLColor4 fg_color;
1518 // pad background rectangle to separate it from contents 1663 LLColor4 bg_color(LLColor4::transparent);
1519 bg_rect.stretch(LIST_BORDER_PAD, 0);
1520 1664
1521 if( mScrollLines <= line && line < mScrollLines + num_page_lines ) 1665 if( mScrollLines <= line && line < mScrollLines + num_page_lines )
1522 { 1666 {
1667 fg_color = (item->getEnabled() ? mFgUnselectedColor : mFgDisabledColor);
1523 if( item->getSelected() && mCanSelect) 1668 if( item->getSelected() && mCanSelect)
1524 { 1669 {
1525 // Draw background of selected item 1670 // Draw background of selected item
1526 LLGLSNoTexture no_texture; 1671 bg_color = mBgSelectedColor;
1527 glColor4fv(mBgSelectedColor.mV); 1672 fg_color = (item->getEnabled() ? mFgSelectedColor : mFgDisabledColor);
1528 gl_rect_2d( bg_rect );
1529
1530 color = mFgSelectedColor;
1531 } 1673 }
1532 else if (mHighlightedItem == line && mCanSelect) 1674 else if (mHighlightedItem == line && mCanSelect)
1533 { 1675 {
1534 LLGLSNoTexture no_texture; 1676 bg_color = mHighlightedColor;
1535 glColor4fv(mHighlightedColor.mV);
1536 gl_rect_2d( bg_rect );
1537 color = (item->getEnabled() ? mFgUnselectedColor : mFgDisabledColor);
1538 } 1677 }
1539 else 1678 else
1540 { 1679 {
1541 color = (item->getEnabled() ? mFgUnselectedColor : mFgDisabledColor); 1680 if (mDrawStripes && (line % 2 == 0) && (max_columns > 1))
1542 if (mDrawStripes && (line%2 == 0) && (max_columns > 1))
1543 { 1681 {
1544 LLGLSNoTexture no_texture; 1682 bg_color = mBgStripeColor;
1545 glColor4fv(mBgStripeColor.mV);
1546 gl_rect_2d( bg_rect );
1547 } 1683 }
1548 } 1684 }
1549 1685
1550 S32 line_x = cur_x; 1686 if (!item->getEnabled())
1551 { 1687 {
1552 S32 num_cols = item->getNumColumns(); 1688 bg_color = mBgReadOnlyColor;
1553 S32 cur_col = 0; 1689 }
1554 S32 dynamic_width = 0;
1555 S32 dynamic_remainder = 0;
1556 bool first_dynamic = true;
1557 if(mNumDynamicWidthColumns > 0)
1558 {
1559 dynamic_width = (mItemListRect.getWidth() - mTotalStaticColumnWidth) / mNumDynamicWidthColumns;
1560 dynamic_remainder = (mItemListRect.getWidth() - mTotalStaticColumnWidth) % mNumDynamicWidthColumns;
1561 }
1562 1690
1563 for (LLScrollListCell* cell = item->getColumn(0); cur_col < num_cols; cell = item->getColumn(++cur_col)) 1691 item->draw(item_rect, fg_color, bg_color, highlight_color, mColumnPadding);
1564 {
1565 S32 cell_width = cell->getWidth();
1566
1567 if(mColumnsIndexed.size() > (U32)cur_col && mColumnsIndexed[cur_col] && mColumnsIndexed[cur_col]->mDynamicWidth)
1568 {
1569 cell_width = dynamic_width + (--dynamic_remainder ? 1 : 0);
1570 if(first_dynamic)
1571 {
1572 cell_width += mScrollbar->getVisible() ? 0 : SCROLLBAR_SIZE;
1573 first_dynamic = false;
1574 }
1575 cell->setWidth(cell_width);
1576 }
1577 // Two ways a cell could be hidden
1578 if (cell_width < 0
1579 || !cell->getVisible()) continue;
1580
1581 LLUI::pushMatrix();
1582 LLUI::translate((F32) cur_x, (F32) cur_y, 0.0f);
1583 S32 space_left = mItemListRect.mRight - cur_x;
1584 LLColor4 highlight_color = LLColor4::white;
1585 F32 type_ahead_timeout = LLUI::sConfigGroup->getF32("TypeAheadTimeout");
1586
1587 highlight_color.mV[VALPHA] = clamp_rescale(mSearchTimer.getElapsedTimeF32(), type_ahead_timeout * 0.7f, type_ahead_timeout, 0.4f, 0.f);
1588 cell->drawToWidth( space_left, color, highlight_color );
1589 LLUI::popMatrix();
1590
1591 cur_x += cell_width + mColumnPadding;
1592 1692
1593 }
1594 }
1595 cur_x = line_x;
1596 cur_y -= mLineHeight; 1693 cur_y -= mLineHeight;
1597 } 1694 }
1598 line++; 1695 line++;
@@ -1605,6 +1702,12 @@ void LLScrollListCtrl::draw()
1605{ 1702{
1606 if( getVisible() ) 1703 if( getVisible() )
1607 { 1704 {
1705 // if user specifies sort, make sure it is maintained
1706 if (needsSorting() && !isSorted())
1707 {
1708 sortItems();
1709 }
1710
1608 if (mNeedsScroll) 1711 if (mNeedsScroll)
1609 { 1712 {
1610 scrollToShowSelected(); 1713 scrollToShowSelected();
@@ -1645,6 +1748,54 @@ BOOL LLScrollListCtrl::handleScrollWheel(S32 x, S32 y, S32 clicks)
1645 return handled; 1748 return handled;
1646} 1749}
1647 1750
1751BOOL LLScrollListCtrl::handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect_screen)
1752{
1753 S32 column_index = getColumnIndexFromOffset(x);
1754 LLScrollListColumn* columnp = getColumn(column_index);
1755
1756 if (columnp == NULL) return FALSE;
1757
1758 BOOL handled = FALSE;
1759 // show tooltip for full name of hovered item if it has been truncated
1760 LLScrollListItem* hit_item = hitItem(x, y);
1761 if (hit_item)
1762 {
1763 LLScrollListCell* hit_cell = hit_item->getColumn(column_index);
1764 if (!hit_cell) return FALSE;
1765 S32 cell_required_width = hit_cell->getContentWidth();
1766 if (hit_cell
1767 && hit_cell->isText()
1768 && cell_required_width > columnp->mWidth)
1769 {
1770
1771 S32 rect_left = getColumnOffsetFromIndex(column_index) + mItemListRect.mLeft;
1772 S32 rect_bottom = getRowOffsetFromIndex(getItemIndex(hit_item));
1773 LLRect cell_rect;
1774 cell_rect.setOriginAndSize(rect_left, rect_bottom, rect_left + columnp->mWidth, mLineHeight);
1775 // Convert rect local to screen coordinates
1776 localPointToScreen(
1777 cell_rect.mLeft, cell_rect.mBottom,
1778 &(sticky_rect_screen->mLeft), &(sticky_rect_screen->mBottom) );
1779 localPointToScreen(
1780 cell_rect.mRight, cell_rect.mTop,
1781 &(sticky_rect_screen->mRight), &(sticky_rect_screen->mTop) );
1782
1783 msg = hit_cell->getValue().asString();
1784 handled = TRUE;
1785 }
1786 }
1787
1788 // otherwise, look for a tooltip associated with this column
1789 LLColumnHeader* headerp = columnp->mHeader;
1790 if (headerp && !handled)
1791 {
1792 headerp->handleToolTip(x, y, msg, sticky_rect_screen);
1793 handled = !msg.empty();
1794 }
1795
1796 return handled;
1797}
1798
1648BOOL LLScrollListCtrl::selectItemAt(S32 x, S32 y, MASK mask) 1799BOOL LLScrollListCtrl::selectItemAt(S32 x, S32 y, MASK mask)
1649{ 1800{
1650 if (!mCanSelect) return FALSE; 1801 if (!mCanSelect) return FALSE;
@@ -1652,6 +1803,7 @@ BOOL LLScrollListCtrl::selectItemAt(S32 x, S32 y, MASK mask)
1652 BOOL selection_changed = FALSE; 1803 BOOL selection_changed = FALSE;
1653 1804
1654 LLScrollListItem* hit_item = hitItem(x, y); 1805 LLScrollListItem* hit_item = hitItem(x, y);
1806
1655 if( hit_item ) 1807 if( hit_item )
1656 { 1808 {
1657 if( mAllowMultipleSelection ) 1809 if( mAllowMultipleSelection )
@@ -1686,6 +1838,11 @@ BOOL LLScrollListCtrl::selectItemAt(S32 x, S32 y, MASK mask)
1686 { 1838 {
1687 selectItem(item, FALSE); 1839 selectItem(item, FALSE);
1688 selecting = !selecting; 1840 selecting = !selecting;
1841 if (hit_item == lastSelected)
1842 {
1843 // stop selecting now, since we just clicked on our last selected item
1844 selecting = FALSE;
1845 }
1689 } 1846 }
1690 if (selecting) 1847 if (selecting)
1691 { 1848 {
@@ -1726,9 +1883,6 @@ BOOL LLScrollListCtrl::selectItemAt(S32 x, S32 y, MASK mask)
1726 selectItem(hit_item); 1883 selectItem(hit_item);
1727 } 1884 }
1728 1885
1729 hit_item->handleClick(x - mBorderThickness - LIST_BORDER_PAD,
1730 1, mask);
1731
1732 selection_changed = mSelectionChanged; 1886 selection_changed = mSelectionChanged;
1733 if (mCommitOnSelectionChange) 1887 if (mCommitOnSelectionChange)
1734 { 1888 {
@@ -1750,19 +1904,17 @@ BOOL LLScrollListCtrl::selectItemAt(S32 x, S32 y, MASK mask)
1750 1904
1751BOOL LLScrollListCtrl::handleMouseDown(S32 x, S32 y, MASK mask) 1905BOOL LLScrollListCtrl::handleMouseDown(S32 x, S32 y, MASK mask)
1752{ 1906{
1753 BOOL handled = LLView::childrenHandleMouseDown(x, y, mask) != NULL; 1907 BOOL handled = childrenHandleMouseDown(x, y, mask) != NULL;
1754 1908
1755 if( !handled ) 1909 if( !handled )
1756 { 1910 {
1757 // set keyboard focus first, in case click action wants to move focus elsewhere 1911 // set keyboard focus first, in case click action wants to move focus elsewhere
1758 setFocus(TRUE); 1912 setFocus(TRUE);
1759 1913
1760 // clear selection changed flag so because user is starting a selection operation 1914 // clear selection changed flag because user is starting a selection operation
1761 mSelectionChanged = FALSE; 1915 mSelectionChanged = FALSE;
1762 1916
1763 gFocusMgr.setMouseCapture(this); 1917 handleClick(x, y, mask);
1764 selectItemAt(x, y, mask);
1765 mNeedsScroll = TRUE;
1766 } 1918 }
1767 1919
1768 return TRUE; 1920 return TRUE;
@@ -1798,19 +1950,79 @@ BOOL LLScrollListCtrl::handleDoubleClick(S32 x, S32 y, MASK mask)
1798 //BOOL handled = FALSE; 1950 //BOOL handled = FALSE;
1799 if(getVisible()) 1951 if(getVisible())
1800 { 1952 {
1801 // Offer the click to the children, even if we aren't enabled 1953 BOOL handled = handleClick(x, y, mask);
1802 // so the scroll bars will work. 1954
1803 if (NULL == LLView::childrenHandleDoubleClick(x, y, mask)) 1955 if (!handled)
1804 { 1956 {
1805 if( mCanSelect && mOnDoubleClickCallback ) 1957 // Offer the click to the children, even if we aren't enabled
1958 // so the scroll bars will work.
1959 if (NULL == LLView::childrenHandleDoubleClick(x, y, mask))
1806 { 1960 {
1807 mOnDoubleClickCallback( mCallbackUserData ); 1961 if( mCanSelect && mOnDoubleClickCallback )
1962 {
1963 mOnDoubleClickCallback( mCallbackUserData );
1964 }
1808 } 1965 }
1809 } 1966 }
1810 } 1967 }
1811 return TRUE; 1968 return TRUE;
1812} 1969}
1813 1970
1971BOOL LLScrollListCtrl::handleClick(S32 x, S32 y, MASK mask)
1972{
1973 // which row was clicked on?
1974 LLScrollListItem* hit_item = hitItem(x, y);
1975 if (!hit_item) return FALSE;
1976
1977 // get appropriate cell from that row
1978 S32 column_index = getColumnIndexFromOffset(x);
1979 LLScrollListCell* hit_cell = hit_item->getColumn(column_index);
1980 if (!hit_cell) return FALSE;
1981
1982 // if cell handled click directly (i.e. clicked on an embedded checkbox)
1983 if (hit_cell->handleClick())
1984 {
1985 // if item not currently selected, select it
1986 if (!hit_item->getSelected())
1987 {
1988 selectItemAt(x, y, mask);
1989 gFocusMgr.setMouseCapture(this);
1990 mNeedsScroll = TRUE;
1991 }
1992 // otherwise we already have this item selected
1993 // so propagate state of cell to rest of selected column
1994 else
1995 {
1996 // propagate value of this cell to other selected items
1997 // and commit the respective widgets
1998 LLSD item_value = hit_cell->getValue();
1999 for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); iter++)
2000 {
2001 LLScrollListItem* item = *iter;
2002 if (item->getSelected())
2003 {
2004 LLScrollListCell* cellp = item->getColumn(column_index);
2005 cellp->setValue(item_value);
2006 cellp->onCommit();
2007 }
2008 }
2009 //FIXME: find a better way to signal cell changes
2010 onCommit();
2011 }
2012 // eat click (e.g. do not trigger double click callback)
2013 return TRUE;
2014 }
2015 else
2016 {
2017 // treat this as a normal single item selection
2018 selectItemAt(x, y, mask);
2019 gFocusMgr.setMouseCapture(this);
2020 mNeedsScroll = TRUE;
2021 // do not eat click (allow double click callback)
2022 return FALSE;
2023 }
2024}
2025
1814LLScrollListItem* LLScrollListCtrl::hitItem( S32 x, S32 y ) 2026LLScrollListItem* LLScrollListCtrl::hitItem( S32 x, S32 y )
1815{ 2027{
1816 // Excludes disabled items. 2028 // Excludes disabled items.
@@ -1847,6 +2059,59 @@ LLScrollListItem* LLScrollListCtrl::hitItem( S32 x, S32 y )
1847 return hit_item; 2059 return hit_item;
1848} 2060}
1849 2061
2062S32 LLScrollListCtrl::getColumnIndexFromOffset(S32 x)
2063{
2064 // which column did we hit?
2065 S32 left = 0;
2066 S32 right = 0;
2067 S32 width = 0;
2068 S32 column_index = 0;
2069
2070 ordered_columns_t::const_iterator iter = mColumnsIndexed.begin();
2071 ordered_columns_t::const_iterator end = mColumnsIndexed.end();
2072 for ( ; iter != end; ++iter)
2073 {
2074 width = (*iter)->mWidth + mColumnPadding;
2075 right += width;
2076 if (left <= x && x < right )
2077 {
2078 break;
2079 }
2080
2081 // set left for next column as right of current column
2082 left = right;
2083 column_index++;
2084 }
2085
2086 return llclamp(column_index, 0, getNumColumns() - 1);
2087}
2088
2089
2090S32 LLScrollListCtrl::getColumnOffsetFromIndex(S32 index)
2091{
2092 S32 column_offset = 0;
2093 ordered_columns_t::const_iterator iter = mColumnsIndexed.begin();
2094 ordered_columns_t::const_iterator end = mColumnsIndexed.end();
2095 for ( ; iter != end; ++iter)
2096 {
2097 if (index-- <= 0)
2098 {
2099 return column_offset;
2100 }
2101 column_offset += (*iter)->mWidth + mColumnPadding;
2102 }
2103
2104 // when running off the end, return the rightmost pixel
2105 return mItemListRect.mRight;
2106}
2107
2108S32 LLScrollListCtrl::getRowOffsetFromIndex(S32 index)
2109{
2110 S32 row_bottom = ((mItemListRect.mTop - (index - mScrollLines)) * mLineHeight)
2111 - mLineHeight;
2112 return row_bottom;
2113}
2114
1850 2115
1851BOOL LLScrollListCtrl::handleHover(S32 x,S32 y,MASK mask) 2116BOOL LLScrollListCtrl::handleHover(S32 x,S32 y,MASK mask)
1852{ 2117{
@@ -1860,7 +2125,8 @@ BOOL LLScrollListCtrl::handleHover(S32 x,S32 y,MASK mask)
1860 mNeedsScroll = TRUE; 2125 mNeedsScroll = TRUE;
1861 } 2126 }
1862 } 2127 }
1863 else if (mCanSelect) 2128 else
2129 if (mCanSelect)
1864 { 2130 {
1865 LLScrollListItem* item = hitItem(x, y); 2131 LLScrollListItem* item = hitItem(x, y);
1866 if (item) 2132 if (item)
@@ -1875,13 +2141,6 @@ BOOL LLScrollListCtrl::handleHover(S32 x,S32 y,MASK mask)
1875 2141
1876 handled = LLUICtrl::handleHover( x, y, mask ); 2142 handled = LLUICtrl::handleHover( x, y, mask );
1877 2143
1878 //if( !handled )
1879 //{
1880 // // Opaque
1881 // getWindow()->setCursor(UI_CURSOR_ARROW);
1882 // lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << llendl;
1883 // handled = TRUE;
1884 //}
1885 return handled; 2144 return handled;
1886} 2145}
1887 2146
@@ -1998,7 +2257,7 @@ BOOL LLScrollListCtrl::handleKeyHere(KEY key,MASK mask, BOOL called_from_parent
1998 } 2257 }
1999 } 2258 }
2000 } 2259 }
2001 else if (selectSimpleItemByPrefix(wstring_to_utf8str(mSearchString), FALSE)) 2260 else if (selectItemByPrefix(wstring_to_utf8str(mSearchString), FALSE))
2002 { 2261 {
2003 mNeedsScroll = TRUE; 2262 mNeedsScroll = TRUE;
2004 // update search string only on successful match 2263 // update search string only on successful match
@@ -2037,7 +2296,7 @@ BOOL LLScrollListCtrl::handleUnicodeCharHere(llwchar uni_char, BOOL called_from_
2037 // type ahead search is case insensitive 2296 // type ahead search is case insensitive
2038 uni_char = LLStringOps::toLower((llwchar)uni_char); 2297 uni_char = LLStringOps::toLower((llwchar)uni_char);
2039 2298
2040 if (selectSimpleItemByPrefix(wstring_to_utf8str(mSearchString + (llwchar)uni_char), FALSE)) 2299 if (selectItemByPrefix(wstring_to_utf8str(mSearchString + (llwchar)uni_char), FALSE))
2041 { 2300 {
2042 // update search string only on successful match 2301 // update search string only on successful match
2043 mNeedsScroll = TRUE; 2302 mNeedsScroll = TRUE;
@@ -2082,7 +2341,7 @@ BOOL LLScrollListCtrl::handleUnicodeCharHere(llwchar uni_char, BOOL called_from_
2082 if (cellp) 2341 if (cellp)
2083 { 2342 {
2084 // Only select enabled items with matching first characters 2343 // Only select enabled items with matching first characters
2085 LLWString item_label = utf8str_to_wstring(cellp->getText()); 2344 LLWString item_label = utf8str_to_wstring(cellp->getValue().asString());
2086 if (item->getEnabled() && LLStringOps::toLower(item_label[0]) == uni_char) 2345 if (item->getEnabled() && LLStringOps::toLower(item_label[0]) == uni_char)
2087 { 2346 {
2088 selectItem(item); 2347 selectItem(item);
@@ -2176,7 +2435,7 @@ void LLScrollListCtrl::deselectItem(LLScrollListItem* itemp)
2176 LLScrollListCell* cellp = itemp->getColumn(mSearchColumn); 2435 LLScrollListCell* cellp = itemp->getColumn(mSearchColumn);
2177 if (cellp) 2436 if (cellp)
2178 { 2437 {
2179 cellp->highlightText(0, 0); 2438 cellp->highlightText(0, 0);
2180 } 2439 }
2181 mSelectionChanged = TRUE; 2440 mSelectionChanged = TRUE;
2182 } 2441 }
@@ -2202,38 +2461,52 @@ BOOL LLScrollListCtrl::isSorted()
2202 return mSorted; 2461 return mSorted;
2203} 2462}
2204 2463
2205// Called by scrollbar 2464struct SameSortColumn
2206//static
2207void LLScrollListCtrl::onScrollChange( S32 new_pos, LLScrollbar* scrollbar, void* userdata )
2208{ 2465{
2209 LLScrollListCtrl* self = (LLScrollListCtrl*) userdata; 2466 SameSortColumn(S32 column) : mColumn(column) {}
2210 self->mScrollLines = new_pos; 2467 S32 mColumn;
2211}
2212 2468
2469 bool operator()(std::pair<S32, BOOL> sort_column) { return sort_column.first == mColumn; }
2470};
2213 2471
2214// First column is column 0 2472BOOL LLScrollListCtrl::setSort(S32 column, BOOL ascending)
2215void LLScrollListCtrl::sortByColumn(U32 column, BOOL ascending)
2216{ 2473{
2217 if (!mSorted || mSortColumn != column) 2474 sort_column_t new_sort_column(column, ascending);
2475
2476 if (mSortColumns.empty())
2218 { 2477 {
2219 mSortColumn = column; 2478 mSortColumns.push_back(new_sort_column);
2220 std::sort(mItemList.begin(), mItemList.end(), SortScrollListItem(mSortColumn, mSortAscending)); 2479 return TRUE;
2221 setSorted(TRUE);
2222 } 2480 }
2481 else
2482 {
2483 // grab current sort column
2484 sort_column_t cur_sort_column = mSortColumns.back();
2485
2486 // remove any existing sort criterion referencing this column
2487 // and add the new one
2488 mSortColumns.erase(remove_if(mSortColumns.begin(), mSortColumns.end(), SameSortColumn(column)), mSortColumns.end());
2489 mSortColumns.push_back(new_sort_column);
2223 2490
2224 // just reverse the list if changing sort order 2491 // did the sort criteria change?
2225 if(mSortAscending != ascending) 2492 return (cur_sort_column != new_sort_column);
2226 {
2227 std::reverse(mItemList.begin(), mItemList.end());
2228 mSortAscending = ascending;
2229 } 2493 }
2230} 2494}
2231 2495
2496// Called by scrollbar
2497//static
2498void LLScrollListCtrl::onScrollChange( S32 new_pos, LLScrollbar* scrollbar, void* userdata )
2499{
2500 LLScrollListCtrl* self = (LLScrollListCtrl*) userdata;
2501 self->mScrollLines = new_pos;
2502}
2503
2504
2232void LLScrollListCtrl::sortByColumn(LLString name, BOOL ascending) 2505void LLScrollListCtrl::sortByColumn(LLString name, BOOL ascending)
2233{ 2506{
2234 if (name.empty()) 2507 if (name.empty())
2235 { 2508 {
2236 sortByColumn(mSortColumn, mSortAscending); 2509 sortItems();
2237 return; 2510 return;
2238 } 2511 }
2239 2512
@@ -2244,6 +2517,26 @@ void LLScrollListCtrl::sortByColumn(LLString name, BOOL ascending)
2244 } 2517 }
2245} 2518}
2246 2519
2520// First column is column 0
2521void LLScrollListCtrl::sortByColumn(U32 column, BOOL ascending)
2522{
2523 if (setSort(column, ascending))
2524 {
2525 sortItems();
2526 }
2527}
2528
2529void LLScrollListCtrl::sortItems()
2530{
2531 // do stable sort to preserve any previous sorts
2532 std::stable_sort(
2533 mItemList.begin(),
2534 mItemList.end(),
2535 SortScrollListItem(mSortColumns));
2536
2537 setSorted(TRUE);
2538}
2539
2247S32 LLScrollListCtrl::getScrollPos() 2540S32 LLScrollListCtrl::getScrollPos()
2248{ 2541{
2249 return mScrollbar->getDocPos(); 2542 return mScrollbar->getDocPos();
@@ -2431,9 +2724,6 @@ LLView* LLScrollListCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFac
2431 BOOL draw_heading = FALSE; 2724 BOOL draw_heading = FALSE;
2432 node->getAttributeBOOL("draw_heading", draw_heading); 2725 node->getAttributeBOOL("draw_heading", draw_heading);
2433 2726
2434 BOOL collapse_empty_columns = FALSE;
2435 node->getAttributeBOOL("collapse_empty_columns", collapse_empty_columns);
2436
2437 S32 search_column = 0; 2727 S32 search_column = 0;
2438 node->getAttributeS32("search_column", search_column); 2728 node->getAttributeS32("search_column", search_column);
2439 2729
@@ -2454,7 +2744,6 @@ LLView* LLScrollListCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFac
2454 node->getAttributeS32("heading_height", heading_height); 2744 node->getAttributeS32("heading_height", heading_height);
2455 scroll_list->setHeadingHeight(heading_height); 2745 scroll_list->setHeadingHeight(heading_height);
2456 } 2746 }
2457 scroll_list->setCollapseEmptyColumns(collapse_empty_columns);
2458 2747
2459 scroll_list->setScrollListParameters(node); 2748 scroll_list->setScrollListParameters(node);
2460 2749
@@ -2465,7 +2754,7 @@ LLView* LLScrollListCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFac
2465 LLSD columns; 2754 LLSD columns;
2466 S32 index = 0; 2755 S32 index = 0;
2467 LLXMLNodePtr child; 2756 LLXMLNodePtr child;
2468 S32 total_static = 0, num_dynamic = 0; 2757 S32 total_static = 0;
2469 for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) 2758 for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling())
2470 { 2759 {
2471 if (child->hasName("column")) 2760 if (child->hasName("column"))
@@ -2491,8 +2780,10 @@ LLView* LLScrollListCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFac
2491 S32 columnwidth = -1; 2780 S32 columnwidth = -1;
2492 child->getAttributeS32("width", columnwidth); 2781 child->getAttributeS32("width", columnwidth);
2493 2782
2494 if(!columndynamicwidth) total_static += columnwidth; 2783 LLString tooltip;
2495 else ++num_dynamic; 2784 child->getAttributeString("tool_tip", tooltip);
2785
2786 if(!columndynamicwidth) total_static += llmax(0, columnwidth);
2496 2787
2497 F32 columnrelwidth = 0.f; 2788 F32 columnrelwidth = 0.f;
2498 child->getAttributeF32("relwidth", columnrelwidth); 2789 child->getAttributeF32("relwidth", columnrelwidth);
@@ -2509,10 +2800,11 @@ LLView* LLScrollListCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFac
2509 columns[index]["relwidth"] = columnrelwidth; 2800 columns[index]["relwidth"] = columnrelwidth;
2510 columns[index]["dynamicwidth"] = columndynamicwidth; 2801 columns[index]["dynamicwidth"] = columndynamicwidth;
2511 columns[index]["halign"] = (S32)h_align; 2802 columns[index]["halign"] = (S32)h_align;
2803 columns[index]["tool_tip"] = tooltip;
2804
2512 index++; 2805 index++;
2513 } 2806 }
2514 } 2807 }
2515 scroll_list->setNumDynamicColumns(num_dynamic);
2516 scroll_list->setTotalStaticColumnWidth(total_static); 2808 scroll_list->setTotalStaticColumnWidth(total_static);
2517 scroll_list->setColumnHeadings(columns); 2809 scroll_list->setColumnHeadings(columns);
2518 2810
@@ -2566,7 +2858,7 @@ LLView* LLScrollListCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFac
2566 while(token_iter != tokens.end()) 2858 while(token_iter != tokens.end())
2567 { 2859 {
2568 const char* line = token_iter->c_str(); 2860 const char* line = token_iter->c_str();
2569 scroll_list->addSimpleItem(line); 2861 scroll_list->addSimpleElement(line);
2570 ++token_iter; 2862 ++token_iter;
2571 } 2863 }
2572 } 2864 }
@@ -2663,10 +2955,6 @@ BOOL LLScrollListCtrl::canDeselect()
2663void LLScrollListCtrl::addColumn(const LLSD& column, EAddPosition pos) 2955void LLScrollListCtrl::addColumn(const LLSD& column, EAddPosition pos)
2664{ 2956{
2665 LLString name = column["name"].asString(); 2957 LLString name = column["name"].asString();
2666 if (mColumns.empty())
2667 {
2668 mDefaultColumn = 0;
2669 }
2670 // if no column name provided, just use ordinal as name 2958 // if no column name provided, just use ordinal as name
2671 if (name.empty()) 2959 if (name.empty())
2672 { 2960 {
@@ -2691,6 +2979,7 @@ void LLScrollListCtrl::addColumn(const LLSD& column, EAddPosition pos)
2691 } 2979 }
2692 else if(new_column->mDynamicWidth) 2980 else if(new_column->mDynamicWidth)
2693 { 2981 {
2982 mNumDynamicWidthColumns++;
2694 new_column->mWidth = (mItemListRect.getWidth() - mTotalStaticColumnWidth) / mNumDynamicWidthColumns; 2983 new_column->mWidth = (mItemListRect.getWidth() - mTotalStaticColumnWidth) / mNumDynamicWidthColumns;
2695 } 2984 }
2696 S32 top = mItemListRect.mTop; 2985 S32 top = mItemListRect.mTop;
@@ -2724,19 +3013,19 @@ void LLScrollListCtrl::addColumn(const LLSD& column, EAddPosition pos)
2724 new_column->mHeader->setLabel(new_column->mLabel); 3013 new_column->mHeader->setLabel(new_column->mLabel);
2725 //new_column->mHeader->setLabel(new_column->mLabel); 3014 //new_column->mHeader->setLabel(new_column->mLabel);
2726 } 3015 }
3016
3017 new_column->mHeader->setToolTip(column["tool_tip"].asString());
3018
2727 //RN: although it might be useful to change sort order with the keyboard, 3019 //RN: although it might be useful to change sort order with the keyboard,
2728 // mixing tab stops on child items along with the parent item is not supported yet 3020 // mixing tab stops on child items along with the parent item is not supported yet
2729 new_column->mHeader->setTabStop(FALSE); 3021 new_column->mHeader->setTabStop(FALSE);
2730 addChild(new_column->mHeader); 3022 addChild(new_column->mHeader);
2731 new_column->mHeader->setVisible(mDisplayColumnHeaders); 3023 new_column->mHeader->setVisible(mDisplayColumnHeaders);
2732 3024
2733 3025 sendChildToFront(mScrollbar);
2734 // Move scroll to front
2735 removeChild(mScrollbar);
2736 addChild(mScrollbar);
2737
2738 } 3026 }
2739 } 3027 }
3028
2740 updateColumns(); 3029 updateColumns();
2741} 3030}
2742 3031
@@ -2753,18 +3042,18 @@ void LLScrollListCtrl::onClickColumn(void *userdata)
2753 3042
2754 LLScrollListColumn* column = parent->mColumnsIndexed[info->mIndex]; 3043 LLScrollListColumn* column = parent->mColumnsIndexed[info->mIndex];
2755 bool ascending = column->mSortAscending; 3044 bool ascending = column->mSortAscending;
2756 if (column->mSortingColumn != column->mName) 3045 if (column->mSortingColumn != column->mName
3046 && parent->mColumns.find(column->mSortingColumn) != parent->mColumns.end())
2757 { 3047 {
2758 if (parent->mColumns.find(column->mSortingColumn) != parent->mColumns.end()) 3048 LLScrollListColumn& info_redir = parent->mColumns[column->mSortingColumn];
2759 { 3049 column_index = info_redir.mIndex;
2760 LLScrollListColumn& info_redir = parent->mColumns[column->mSortingColumn];
2761 column_index = info_redir.mIndex;
2762 }
2763 } 3050 }
2764 3051
2765 if (column_index == parent->mSortColumn) 3052 // if this column is the primary sort key, reverse the direction
3053 sort_column_t cur_sort_column;
3054 if (!parent->mSortColumns.empty() && parent->mSortColumns.back().first == column_index)
2766 { 3055 {
2767 ascending = !parent->mSortAscending; 3056 ascending = !parent->mSortColumns.back().second;
2768 } 3057 }
2769 3058
2770 parent->sortByColumn(column_index, ascending); 3059 parent->sortByColumn(column_index, ascending);
@@ -2777,12 +3066,17 @@ void LLScrollListCtrl::onClickColumn(void *userdata)
2777 3066
2778std::string LLScrollListCtrl::getSortColumnName() 3067std::string LLScrollListCtrl::getSortColumnName()
2779{ 3068{
2780 LLScrollListColumn* column = mSortColumn >= 0 ? mColumnsIndexed[mSortColumn] : NULL; 3069 LLScrollListColumn* column = mSortColumns.empty() ? NULL : mColumnsIndexed[mSortColumns.back().first];
2781 3070
2782 if (column) return column->mName; 3071 if (column) return column->mName;
2783 else return ""; 3072 else return "";
2784} 3073}
2785 3074
3075BOOL LLScrollListCtrl::needsSorting()
3076{
3077 return !mSortColumns.empty();
3078}
3079
2786void LLScrollListCtrl::clearColumns() 3080void LLScrollListCtrl::clearColumns()
2787{ 3081{
2788 std::map<LLString, LLScrollListColumn>::iterator itor; 3082 std::map<LLString, LLScrollListColumn>::iterator itor;
@@ -2796,6 +3090,7 @@ void LLScrollListCtrl::clearColumns()
2796 } 3090 }
2797 } 3091 }
2798 mColumns.clear(); 3092 mColumns.clear();
3093 mSortColumns.clear();
2799} 3094}
2800 3095
2801void LLScrollListCtrl::setColumnLabel(const LLString& column, const LLString& label) 3096void LLScrollListCtrl::setColumnLabel(const LLString& column, const LLString& label)
@@ -2849,12 +3144,12 @@ LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& value, EAddPosition p
2849 S32 col_index = 0 ; 3144 S32 col_index = 0 ;
2850 for (itor = columns.beginArray(); itor != columns.endArray(); ++itor) 3145 for (itor = columns.beginArray(); itor != columns.endArray(); ++itor)
2851 { 3146 {
2852 LLString column = (*itor)["column"].asString(); 3147 if (itor->isUndefined())
2853
2854 if (mColumns.size() == 0)
2855 { 3148 {
2856 mDefaultColumn = 0; 3149 // skip unused columns in item passed in
3150 continue;
2857 } 3151 }
3152 LLString column = (*itor)["column"].asString();
2858 3153
2859 LLScrollListColumn* columnp = NULL; 3154 LLScrollListColumn* columnp = NULL;
2860 3155
@@ -2879,9 +3174,18 @@ LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& value, EAddPosition p
2879 LLSD new_column; 3174 LLSD new_column;
2880 new_column["name"] = column; 3175 new_column["name"] = column;
2881 new_column["label"] = column; 3176 new_column["label"] = column;
2882 new_column["width"] = (*itor)["width"]; 3177 // if width supplied for column, use it, otherwise
3178 // use adaptive width
3179 if (itor->has("width"))
3180 {
3181 new_column["width"] = (*itor)["width"];
3182 }
3183 else
3184 {
3185 new_column["dynamicwidth"] = true;
3186 }
2883 addColumn(new_column); 3187 addColumn(new_column);
2884 columnp = &mColumns.find(column)->second; 3188 columnp = &mColumns[column];
2885 new_item->setNumColumns(mColumns.size()); 3189 new_item->setNumColumns(mColumns.size());
2886 } 3190 }
2887 3191
@@ -2895,6 +3199,7 @@ LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& value, EAddPosition p
2895 LLString type = (*itor)["type"].asString(); 3199 LLString type = (*itor)["type"].asString();
2896 BOOL has_color = (*itor).has("color"); 3200 BOOL has_color = (*itor).has("color");
2897 LLColor4 color = ((*itor)["color"]); 3201 LLColor4 color = ((*itor)["color"]);
3202 BOOL enabled = !(*itor).has("enabled") || (*itor)["enabled"].asBoolean() == true;
2898 3203
2899 const LLFontGL *font = gResMgr->getRes(fontname); 3204 const LLFontGL *font = gResMgr->getRes(fontname);
2900 if (!font) 3205 if (!font)
@@ -2906,8 +3211,7 @@ LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& value, EAddPosition p
2906 if (type == "icon") 3211 if (type == "icon")
2907 { 3212 {
2908 LLUUID image_id = value.asUUID(); 3213 LLUUID image_id = value.asUUID();
2909 LLImageGL* icon = LLUI::sImageProvider->getUIImageByID(image_id); 3214 LLScrollListIcon* cell = new LLScrollListIcon(value.asUUID(), width);
2910 LLScrollListIcon* cell = new LLScrollListIcon(icon, width, image_id);
2911 if (has_color) 3215 if (has_color)
2912 { 3216 {
2913 cell->setColor(color); 3217 cell->setColor(color);
@@ -2916,8 +3220,10 @@ LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& value, EAddPosition p
2916 } 3220 }
2917 else if (type == "checkbox") 3221 else if (type == "checkbox")
2918 { 3222 {
2919 LLCheckBoxCtrl* ctrl = new LLCheckBoxCtrl(value.asString(), 3223 LLCheckBoxCtrl* ctrl = new LLCheckBoxCtrl("check",
2920 LLRect(0, 0, width, width), "label"); 3224 LLRect(0, width, width, 0), " ");
3225 ctrl->setEnabled(enabled);
3226 ctrl->setValue(value);
2921 LLScrollListCheck* cell = new LLScrollListCheck(ctrl,width); 3227 LLScrollListCheck* cell = new LLScrollListCheck(ctrl,width);
2922 if (has_color) 3228 if (has_color)
2923 { 3229 {
@@ -3070,18 +3376,12 @@ void LLScrollListCtrl::onFocusReceived()
3070{ 3376{
3071 // forget latent selection changes when getting focus 3377 // forget latent selection changes when getting focus
3072 mSelectionChanged = FALSE; 3378 mSelectionChanged = FALSE;
3379 LLUICtrl::onFocusReceived();
3073} 3380}
3074 3381
3075//virtual 3382//virtual
3076void LLScrollListCtrl::onFocusLost() 3383void LLScrollListCtrl::onFocusLost()
3077{ 3384{
3078 if (mIsPopup)
3079 {
3080 if (getParent())
3081 {
3082 getParent()->onFocusLost();
3083 }
3084 }
3085 if (hasMouseCapture()) 3385 if (hasMouseCapture())
3086 { 3386 {
3087 gFocusMgr.setMouseCapture(NULL); 3387 gFocusMgr.setMouseCapture(NULL);
@@ -3133,11 +3433,11 @@ void LLColumnHeader::draw()
3133{ 3433{
3134 if( getVisible() ) 3434 if( getVisible() )
3135 { 3435 {
3136 mDrawArrow = !mColumn->mLabel.empty() && mColumn->mParentCtrl->isSorted() && mColumn->mParentCtrl->getSortColumnName() == mColumn->mSortingColumn; 3436 BOOL draw_arrow = !mColumn->mLabel.empty() && mColumn->mParentCtrl->isSorted() && mColumn->mParentCtrl->getSortColumnName() == mColumn->mSortingColumn;
3137 3437
3138 BOOL is_ascending = mColumn->mParentCtrl->getSortAscending(); 3438 BOOL is_ascending = mColumn->mParentCtrl->getSortAscending();
3139 mArrowImage = is_ascending ? LLUI::sImageProvider->getUIImageByID(LLUUID(LLUI::sAssetsGroup->getString("up_arrow.tga"))) 3439 mButton->setImageOverlay(is_ascending ? "up_arrow.tga" : "down_arrow.tga", LLFontGL::RIGHT, draw_arrow ? LLColor4::white : LLColor4::transparent);
3140 : LLUI::sImageProvider->getUIImageByID(LLUUID(LLUI::sAssetsGroup->getString("down_arrow.tga"))); 3440 mArrowImage = mButton->getImageOverlay()->getImage();
3141 3441
3142 //BOOL clip = mRect.mRight > mColumn->mParentCtrl->getItemListRect().getWidth(); 3442 //BOOL clip = mRect.mRight > mColumn->mParentCtrl->getItemListRect().getWidth();
3143 //LLGLEnable scissor_test(clip ? GL_SCISSOR_TEST : GL_FALSE); 3443 //LLGLEnable scissor_test(clip ? GL_SCISSOR_TEST : GL_FALSE);
@@ -3237,11 +3537,11 @@ void LLColumnHeader::showList()
3237 { 3537 {
3238 if (mColumn->mParentCtrl->getSortAscending()) 3538 if (mColumn->mParentCtrl->getSortAscending())
3239 { 3539 {
3240 low_item_text = cell->getText(); 3540 low_item_text = cell->getValue().asString();
3241 } 3541 }
3242 else 3542 else
3243 { 3543 {
3244 high_item_text = cell->getText(); 3544 high_item_text = cell->getValue().asString();
3245 } 3545 }
3246 } 3546 }
3247 } 3547 }
@@ -3254,11 +3554,11 @@ void LLColumnHeader::showList()
3254 { 3554 {
3255 if (mColumn->mParentCtrl->getSortAscending()) 3555 if (mColumn->mParentCtrl->getSortAscending())
3256 { 3556 {
3257 high_item_text = cell->getText(); 3557 high_item_text = cell->getValue().asString();
3258 } 3558 }
3259 else 3559 else
3260 { 3560 {
3261 low_item_text = cell->getText(); 3561 low_item_text = cell->getValue().asString();
3262 } 3562 }
3263 } 3563 }
3264 } 3564 }