llscrolllistctrl.cpp

Go to the documentation of this file.
00001 
00032 #include <algorithm>
00033 
00034 #include "linden_common.h"
00035 #include "llstl.h"
00036 #include "llboost.h"
00037 
00038 #include "llscrolllistctrl.h"
00039 
00040 #include "indra_constants.h"
00041 
00042 #include "llcheckboxctrl.h"
00043 #include "llclipboard.h"
00044 #include "llfocusmgr.h"
00045 #include "llgl.h"
00046 #include "llglimmediate.h"
00047 #include "llglheaders.h"
00048 #include "llresmgr.h"
00049 #include "llscrollbar.h"
00050 #include "llstring.h"
00051 #include "llui.h"
00052 #include "lluictrlfactory.h"
00053 #include "llwindow.h"
00054 #include "llcontrol.h"
00055 #include "llkeyboard.h"
00056 #include "llresizebar.h"
00057 
00058 const S32 MIN_COLUMN_WIDTH = 20;
00059 const S32 LIST_SNAP_PADDING = 5;
00060 
00061 static LLRegisterWidget<LLScrollListCtrl> r("scroll_list");
00062 
00063 // local structures & classes.
00064 struct SortScrollListItem
00065 {
00066         SortScrollListItem(const std::vector<std::pair<S32, BOOL> >& sort_orders)
00067         :       mSortOrders(sort_orders)
00068         {}
00069 
00070         bool operator()(const LLScrollListItem* i1, const LLScrollListItem* i2)
00071         {
00072                 if ( mSortOrders.empty() )
00073                         return i1 < i2;
00074 
00075                 // sort over all columns in order specified by mSortOrders
00076                 S32 sort_result = 0;
00077                 for (sort_order_t::const_reverse_iterator it = mSortOrders.rbegin();
00078                          it != mSortOrders.rend(); ++it)
00079                 {
00080                         S32 col_idx = it->first;
00081                         BOOL sort_ascending = it->second;
00082 
00083                         const LLScrollListCell *cell1 = i1->getColumn(col_idx);
00084                         const LLScrollListCell *cell2 = i2->getColumn(col_idx);
00085                         S32 order = sort_ascending ? 1 : -1; // ascending or descending sort for this column?
00086                         if (cell1 && cell2)
00087                         {
00088                                 sort_result = order * LLString::compareDict(cell1->getValue().asString(), cell2->getValue().asString());
00089                                 if (sort_result != 0)
00090                                 {
00091                                         break; // we have a sort order!
00092                                 }
00093                         }
00094                 }
00095 
00096                 return sort_result < 0;
00097         }
00098 
00099         typedef std::vector<std::pair<S32, BOOL> > sort_order_t;
00100         const sort_order_t& mSortOrders;
00101 };
00102 
00103 
00104 //
00105 // LLScrollListIcon
00106 //
00107 LLScrollListIcon::LLScrollListIcon(LLUIImagePtr icon, S32 width)
00108         : LLScrollListCell(width),
00109           mIcon(icon),
00110           mColor(LLColor4::white)
00111 {
00112 }
00113 
00114 LLScrollListIcon::LLScrollListIcon(const LLSD& value, S32 width)
00115         : LLScrollListCell(width),
00116         mColor(LLColor4::white)
00117 {
00118         setValue(value);
00119 }
00120 
00121 
00122 LLScrollListIcon::~LLScrollListIcon()
00123 {
00124 }
00125 
00126 void LLScrollListIcon::setValue(const LLSD& value)
00127 {
00128         if (value.isUUID())
00129         {
00130                 // don't use default image specified by LLUUID::null, use no image in that case
00131                 LLUUID image_id = value.asUUID();
00132                 mIcon = image_id.notNull() ? LLUI::sImageProvider->getUIImageByID(image_id) : LLUIImagePtr(NULL);
00133         }
00134         else
00135         {
00136                 LLString value_string = value.asString();
00137                 if (LLUUID::validate(value_string))
00138                 {
00139                         setValue(LLUUID(value_string));
00140                 }
00141                 else if (!value_string.empty())
00142                 {
00143                         mIcon = LLUI::getUIImage(value.asString());
00144                 }
00145                 else
00146                 {
00147                         mIcon = NULL;
00148                 }
00149         }
00150 }
00151 
00152 
00153 void LLScrollListIcon::setColor(const LLColor4& color)
00154 {
00155         mColor = color;
00156 }
00157 
00158 S32     LLScrollListIcon::getWidth() const 
00159 {
00160         // if no specified fix width, use width of icon
00161         if (LLScrollListCell::getWidth() == 0 && mIcon.notNull())
00162         {
00163                 return mIcon->getWidth();
00164         }
00165         return LLScrollListCell::getWidth();
00166 }
00167 
00168 
00169 void LLScrollListIcon::draw(const LLColor4& color, const LLColor4& highlight_color)      const
00170 {
00171         if (mIcon)
00172         {
00173                 mIcon->draw(0, 0, mColor);
00174         }
00175 }
00176 
00177 //
00178 // LLScrollListCheck
00179 //
00180 LLScrollListCheck::LLScrollListCheck(LLCheckBoxCtrl* check_box, S32 width)
00181 {
00182         mCheckBox = check_box;
00183         LLRect rect(mCheckBox->getRect());
00184         if (width)
00185         {
00186                 
00187                 rect.mRight = rect.mLeft + width;
00188                 mCheckBox->setRect(rect);
00189                 setWidth(width);
00190         }
00191         else
00192         {
00193                 setWidth(rect.getWidth()); //check_box->getWidth();
00194         }
00195 }
00196 
00197 LLScrollListCheck::~LLScrollListCheck()
00198 {
00199         delete mCheckBox;
00200 }
00201 
00202 void LLScrollListCheck::draw(const LLColor4& color, const LLColor4& highlight_color) const
00203 {
00204         mCheckBox->draw();
00205 }
00206 
00207 BOOL LLScrollListCheck::handleClick()
00208 { 
00209         if (mCheckBox->getEnabled())
00210         {
00211                 mCheckBox->toggle();
00212         }
00213         // don't change selection when clicking on embedded checkbox
00214         return TRUE; 
00215 }
00216 
00217 //
00218 // LLScrollListSeparator
00219 //
00220 LLScrollListSeparator::LLScrollListSeparator(S32 width) : LLScrollListCell(width)
00221 {
00222 }
00223 
00224 //virtual 
00225 S32 LLScrollListSeparator::getHeight() const
00226 {
00227         return 5;
00228 }
00229 
00230 
00231 void LLScrollListSeparator::draw(const LLColor4& color, const LLColor4& highlight_color) const
00232 {
00233         //*FIXME: use dynamic item heights and make separators narrow, and inactive
00234         gl_line_2d(5, 8, llmax(5, getWidth() - 5), 8, color);
00235 }
00236 
00237 //
00238 // LLScrollListText
00239 //
00240 U32 LLScrollListText::sCount = 0;
00241 
00242 LLScrollListText::LLScrollListText( const LLString& text, const LLFontGL* font, S32 width, U8 font_style, LLFontGL::HAlign font_alignment, LLColor4& color, BOOL use_color, BOOL visible)
00243 :       LLScrollListCell(width),
00244         mText( text ),
00245         mFont( font ),
00246         mColor(color),
00247         mUseColor(use_color),
00248         mFontStyle( font_style ),
00249         mFontAlignment( font_alignment ),
00250         mVisible( visible ),
00251         mHighlightCount( 0 ),
00252         mHighlightOffset( 0 )
00253 {
00254         sCount++;
00255 
00256         // initialize rounded rect image
00257         if (!mRoundedRectImage)
00258         {
00259                 mRoundedRectImage = LLUI::sImageProvider->getUIImage("rounded_square.tga");
00260         }
00261 }
00262 //virtual 
00263 void LLScrollListText::highlightText(S32 offset, S32 num_chars)
00264 {
00265         mHighlightOffset = offset;
00266         mHighlightCount = num_chars;
00267 }
00268 
00269 //virtual 
00270 BOOL LLScrollListText::isText() const
00271 {
00272         return TRUE;
00273 }
00274 
00275 //virtual 
00276 BOOL LLScrollListText::getVisible() const
00277 {
00278         return mVisible;
00279 }
00280 
00281 //virtual 
00282 S32 LLScrollListText::getHeight() const
00283 {
00284         return llround(mFont->getLineHeight());
00285 }
00286 
00287 
00288 LLScrollListText::~LLScrollListText()
00289 {
00290         sCount--;
00291 }
00292 
00293 S32     LLScrollListText::getContentWidth() const
00294 {
00295         return mFont->getWidth(mText.getString());
00296 }
00297 
00298 
00299 void LLScrollListText::setColor(const LLColor4& color)
00300 {
00301         mColor = color;
00302         mUseColor = TRUE;
00303 }
00304 
00305 void LLScrollListText::setText(const LLStringExplicit& text)
00306 {
00307         mText = text;
00308 }
00309 
00310 //virtual
00311 void LLScrollListText::setValue(const LLSD& text)
00312 {
00313         setText(text.asString());
00314 }
00315 
00316 //virtual 
00317 const LLSD LLScrollListText::getValue() const           
00318 { 
00319         return LLSD(mText.getString()); 
00320 }
00321 
00322 
00323 void LLScrollListText::draw(const LLColor4& color, const LLColor4& highlight_color) const
00324 {
00325         LLColor4 display_color;
00326         if (mUseColor)
00327         {
00328                 display_color = mColor;
00329         }
00330         else
00331         {
00332                 display_color = color;
00333         }
00334 
00335         if (mHighlightCount > 0)
00336         {
00337                 S32 left = 0;
00338                 switch(mFontAlignment)
00339                 {
00340                 case LLFontGL::LEFT:
00341                         left = mFont->getWidth(mText.getString(), 0, mHighlightOffset);
00342                         break;
00343                 case LLFontGL::RIGHT:
00344                         left = getWidth() - mFont->getWidth(mText.getString(), mHighlightOffset, S32_MAX);
00345                         break;
00346                 case LLFontGL::HCENTER:
00347                         left = (getWidth() - mFont->getWidth(mText.getString())) / 2;
00348                         break;
00349                 }
00350                 LLRect highlight_rect(left - 2, 
00351                                 llround(mFont->getLineHeight()) + 1, 
00352                                 left + mFont->getWidth(mText.getString(), mHighlightOffset, mHighlightCount) + 1, 
00353                                 1);
00354                 mRoundedRectImage->draw(highlight_rect, highlight_color);
00355         }
00356 
00357         // Try to draw the entire string
00358         F32 right_x;
00359         U32 string_chars = mText.length();
00360         F32 start_x = 0.f;
00361         switch(mFontAlignment)
00362         {
00363         case LLFontGL::LEFT:
00364                 start_x = 0.f;
00365                 break;
00366         case LLFontGL::RIGHT:
00367                 start_x = (F32)getWidth();
00368                 break;
00369         case LLFontGL::HCENTER:
00370                 start_x = (F32)getWidth() * 0.5f;
00371                 break;
00372         }
00373         mFont->render(mText.getWString(), 0, 
00374                                                 start_x, 2.f,
00375                                                 display_color,
00376                                                 mFontAlignment,
00377                                                 LLFontGL::BOTTOM, 
00378                                                 mFontStyle,
00379                                                 string_chars, 
00380                                                 getWidth(),
00381                                                 &right_x, 
00382                                                 FALSE, 
00383                                                 TRUE);
00384 }
00385 
00386 
00387 LLScrollListItem::~LLScrollListItem()
00388 {
00389         std::for_each(mColumns.begin(), mColumns.end(), DeletePointer());
00390 }
00391 
00392 void LLScrollListItem::setNumColumns(S32 columns)
00393 {
00394         S32 prev_columns = mColumns.size();
00395         if (columns < prev_columns)
00396         {
00397                 std::for_each(mColumns.begin()+columns, mColumns.end(), DeletePointer());
00398         }
00399         
00400         mColumns.resize(columns);
00401 
00402         for (S32 col = prev_columns; col < columns; ++col)
00403         {
00404                 mColumns[col] = NULL;
00405         }
00406 }
00407 
00408 void LLScrollListItem::setColumn( S32 column, LLScrollListCell *cell )
00409 {
00410         if (column < (S32)mColumns.size())
00411         {
00412                 delete mColumns[column];
00413                 mColumns[column] = cell;
00414         }
00415         else
00416         {
00417                 llerrs << "LLScrollListItem::setColumn: bad column: " << column << llendl;
00418         }
00419 }
00420 
00421 LLString LLScrollListItem::getContentsCSV() const
00422 {
00423         LLString ret;
00424 
00425         S32 count = getNumColumns();
00426         for (S32 i=0; i<count; ++i)
00427         {
00428                 ret += getColumn(i)->getValue().asString();
00429                 if (i < count-1)
00430                 {
00431                         ret += ", ";
00432                 }
00433         }
00434 
00435         return ret;
00436 }
00437 
00438 void LLScrollListItem::draw(const LLRect& rect, const LLColor4& fg_color, const LLColor4& bg_color, const LLColor4& highlight_color, S32 column_padding)
00439 {
00440         // draw background rect
00441         LLRect bg_rect = rect;
00442         {
00443                 LLGLSNoTexture no_texture;
00444                 gGL.color4fv(bg_color.mV);
00445                 gl_rect_2d( bg_rect );
00446         }
00447 
00448         S32 cur_x = rect.mLeft;
00449         S32 num_cols = getNumColumns();
00450         S32 cur_col = 0;
00451 
00452         for (LLScrollListCell* cell = getColumn(0); cur_col < num_cols; cell = getColumn(++cur_col))
00453         {
00454                 // Two ways a cell could be hidden
00455                 if (cell->getWidth() < 0
00456                         || !cell->getVisible()) continue;
00457 
00458                 LLUI::pushMatrix();
00459                 {
00460                         LLUI::translate((F32) cur_x, (F32) rect.mBottom, 0.0f);
00461 
00462                         cell->draw( fg_color, highlight_color );
00463                 }
00464                 LLUI::popMatrix();
00465                 
00466                 cur_x += cell->getWidth() + column_padding;
00467         }
00468 }
00469 
00470 
00471 void LLScrollListItem::setEnabled(BOOL b)
00472 {
00473         mEnabled = b;
00474 }
00475 
00476 //---------------------------------------------------------------------------
00477 // LLScrollListItemComment
00478 //---------------------------------------------------------------------------
00479 LLScrollListItemComment::LLScrollListItemComment(const LLString& comment_string, const LLColor4& color)
00480 : LLScrollListItem(FALSE),
00481         mColor(color)
00482 {
00483         addColumn( comment_string, LLResMgr::getInstance()->getRes( LLFONT_SANSSERIF_SMALL ) );
00484 }
00485 
00486 void LLScrollListItemComment::draw(const LLRect& rect, const LLColor4& fg_color, const LLColor4& bg_color, const LLColor4& highlight_color, S32 column_padding)
00487 {
00488         LLScrollListCell* cell = getColumn(0);
00489         if (cell)
00490         {
00491                 // Two ways a cell could be hidden
00492                 if (cell->getWidth() < 0
00493                         || !cell->getVisible()) return;
00494 
00495                 LLUI::pushMatrix();
00496                 {
00497                         LLUI::translate((F32)rect.mLeft, (F32)rect.mBottom, 0.0f);
00498 
00499                         // force first cell to be width of entire item
00500                         cell->setWidth(rect.getWidth());
00501                         cell->draw( mColor, highlight_color );
00502                 }
00503                 LLUI::popMatrix();
00504         }
00505 }
00506 
00507 //---------------------------------------------------------------------------
00508 // LLScrollListItemSeparator
00509 //---------------------------------------------------------------------------
00510 LLScrollListItemSeparator::LLScrollListItemSeparator()
00511 : LLScrollListItem(FALSE)
00512 {
00513         LLScrollListSeparator* cell = new LLScrollListSeparator(0);
00514         setNumColumns(1);
00515         setColumn(0, cell);
00516 }
00517 
00518 void LLScrollListItemSeparator::draw(const LLRect& rect, const LLColor4& fg_color, const LLColor4& bg_color, const LLColor4& highlight_color, S32 column_padding)
00519 {
00520         //TODO* move LLScrollListSeparator::draw into here and get rid of it
00521         LLScrollListCell* cell = getColumn(0);
00522         if (cell)
00523         {
00524                 // Two ways a cell could be hidden
00525                 if (cell->getWidth() < 0
00526                         || !cell->getVisible()) return;
00527 
00528                 LLUI::pushMatrix();
00529                 {
00530                         LLUI::translate((F32)rect.mLeft, (F32)rect.mBottom, 0.0f);
00531 
00532                         // force first cell to be width of entire item
00533                         cell->setWidth(rect.getWidth());
00534                         cell->draw( fg_color, highlight_color );
00535                 }
00536                 LLUI::popMatrix();
00537         }
00538 }
00539 
00540 //---------------------------------------------------------------------------
00541 // LLScrollListCtrl
00542 //---------------------------------------------------------------------------
00543 
00544 LLScrollListCtrl::LLScrollListCtrl(const LLString& name, const LLRect& rect,
00545         void (*commit_callback)(LLUICtrl* ctrl, void* userdata),
00546         void* callback_user_data,
00547         BOOL allow_multiple_selection,
00548         BOOL show_border
00549         )
00550  :      LLUICtrl(name, rect, TRUE, commit_callback, callback_user_data),
00551         mLineHeight(0),
00552         mScrollLines(0),
00553         mPageLines(0),
00554         mHeadingHeight(20),
00555         mMaxSelectable(0),
00556         mAllowMultipleSelection( allow_multiple_selection ),
00557         mAllowKeyboardMovement(TRUE),
00558         mCommitOnKeyboardMovement(TRUE),
00559         mCommitOnSelectionChange(FALSE),
00560         mSelectionChanged(FALSE),
00561         mNeedsScroll(FALSE),
00562         mCanSelect(TRUE),
00563         mDisplayColumnHeaders(FALSE),
00564         mColumnsDirty(FALSE),
00565         mMaxItemCount(INT_MAX), 
00566         mMaxContentWidth(0),
00567         mBackgroundVisible( TRUE ),
00568         mDrawStripes(TRUE),
00569         mBgWriteableColor(      LLUI::sColorsGroup->getColor( "ScrollBgWriteableColor" ) ),
00570         mBgReadOnlyColor(       LLUI::sColorsGroup->getColor( "ScrollBgReadOnlyColor" ) ),
00571         mBgSelectedColor( LLUI::sColorsGroup->getColor("ScrollSelectedBGColor") ),
00572         mBgStripeColor( LLUI::sColorsGroup->getColor("ScrollBGStripeColor") ),
00573         mFgSelectedColor( LLUI::sColorsGroup->getColor("ScrollSelectedFGColor") ),
00574         mFgUnselectedColor( LLUI::sColorsGroup->getColor("ScrollUnselectedColor") ),
00575         mFgDisabledColor( LLUI::sColorsGroup->getColor("ScrollDisabledColor") ),
00576         mHighlightedColor( LLUI::sColorsGroup->getColor("ScrollHighlightedColor") ),
00577         mBorderThickness( 2 ),
00578         mOnDoubleClickCallback( NULL ),
00579         mOnMaximumSelectCallback( NULL ),
00580         mOnSortChangedCallback( NULL ),
00581         mHighlightedItem(-1),
00582         mBorder(NULL),
00583         mSearchColumn(0),
00584         mNumDynamicWidthColumns(0),
00585         mTotalStaticColumnWidth(0),
00586         mSorted(TRUE),
00587         mDirty(FALSE),
00588         mOriginalSelection(-1),
00589         mDrewSelected(FALSE)
00590 {
00591         mItemListRect.setOriginAndSize(
00592                 mBorderThickness,
00593                 mBorderThickness,
00594                 getRect().getWidth() - 2 * mBorderThickness,
00595                 getRect().getHeight() - 2 * mBorderThickness );
00596 
00597         updateLineHeight();
00598 
00599         mPageLines = mLineHeight? (mItemListRect.getHeight()) / mLineHeight : 0;
00600 
00601         // Init the scrollbar
00602         LLRect scroll_rect;
00603         scroll_rect.setOriginAndSize( 
00604                 getRect().getWidth() - mBorderThickness - SCROLLBAR_SIZE,
00605                 mItemListRect.mBottom,
00606                 SCROLLBAR_SIZE,
00607                 mItemListRect.getHeight());
00608         mScrollbar = new LLScrollbar( "Scrollbar", scroll_rect,
00609                 LLScrollbar::VERTICAL,
00610                 getItemCount(),
00611                 mScrollLines,
00612                 mPageLines,
00613                 &LLScrollListCtrl::onScrollChange, this );
00614         mScrollbar->setFollowsRight();
00615         mScrollbar->setFollowsTop();
00616         mScrollbar->setFollowsBottom();
00617         mScrollbar->setEnabled( TRUE );
00618         // scrollbar is visible only when needed
00619         mScrollbar->setVisible(FALSE);
00620         addChild(mScrollbar);
00621 
00622         // Border
00623         if (show_border)
00624         {
00625                 LLRect border_rect( 0, getRect().getHeight(), getRect().getWidth(), 0 );
00626                 mBorder = new LLViewBorder( "dlg border", border_rect, LLViewBorder::BEVEL_IN, LLViewBorder::STYLE_LINE, 1 );
00627                 addChild(mBorder);
00628         }
00629 
00630         mColumnPadding = 5;
00631 
00632         mLastSelected = NULL;
00633 }
00634 
00635 LLScrollListCtrl::~LLScrollListCtrl()
00636 {
00637         std::for_each(mItemList.begin(), mItemList.end(), DeletePointer());
00638 
00639         if( gEditMenuHandler == this )
00640         {
00641                 gEditMenuHandler = NULL;
00642         }
00643 }
00644 
00645 
00646 BOOL LLScrollListCtrl::setMaxItemCount(S32 max_count)
00647 {
00648         if (max_count >= getItemCount())
00649         {
00650                 mMaxItemCount = max_count;
00651         }
00652         return (max_count == mMaxItemCount);
00653 }
00654 
00655 S32 LLScrollListCtrl::isEmpty() const
00656 {
00657         return mItemList.empty();
00658 }
00659 
00660 S32 LLScrollListCtrl::getItemCount() const
00661 {
00662         return mItemList.size();
00663 }
00664 
00665 // virtual LLScrolListInterface function (was deleteAllItems)
00666 void LLScrollListCtrl::clearRows()
00667 {
00668         std::for_each(mItemList.begin(), mItemList.end(), DeletePointer());
00669         mItemList.clear();
00670         //mItemCount = 0;
00671 
00672         // Scroll the bar back up to the top.
00673         mScrollbar->setDocParams(0, 0);
00674 
00675         mScrollLines = 0;
00676         mLastSelected = NULL;
00677         updateLayout();
00678         mDirty = FALSE; 
00679 }
00680 
00681 
00682 LLScrollListItem* LLScrollListCtrl::getFirstSelected() const
00683 {
00684         item_list::const_iterator iter;
00685         for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
00686         {
00687                 LLScrollListItem* item  = *iter;
00688                 if (item->getSelected())
00689                 {
00690                         return item;
00691                 }
00692         }
00693         return NULL;
00694 }
00695 
00696 std::vector<LLScrollListItem*> LLScrollListCtrl::getAllSelected() const
00697 {
00698         std::vector<LLScrollListItem*> ret;
00699         item_list::const_iterator iter;
00700         for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
00701         {
00702                 LLScrollListItem* item  = *iter;
00703                 if (item->getSelected())
00704                 {
00705                         ret.push_back(item);
00706                 }
00707         }
00708         return ret;
00709 }
00710 
00711 S32 LLScrollListCtrl::getFirstSelectedIndex() const
00712 {
00713         S32 CurSelectedIndex = 0;
00714         item_list::const_iterator iter;
00715         for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
00716         {
00717                 LLScrollListItem* item  = *iter;
00718                 if (item->getSelected())
00719                 {
00720                         return CurSelectedIndex;
00721                 }
00722                 CurSelectedIndex++;
00723         }
00724 
00725         return -1;
00726 }
00727 
00728 LLScrollListItem* LLScrollListCtrl::getFirstData() const
00729 {
00730         if (mItemList.size() == 0)
00731         {
00732                 return NULL;
00733         }
00734         return mItemList[0];
00735 }
00736 
00737 LLScrollListItem* LLScrollListCtrl::getLastData() const
00738 {
00739         if (mItemList.size() == 0)
00740         {
00741                 return NULL;
00742         }
00743         return mItemList[mItemList.size() - 1];
00744 }
00745 
00746 std::vector<LLScrollListItem*> LLScrollListCtrl::getAllData() const
00747 {
00748         std::vector<LLScrollListItem*> ret;
00749         item_list::const_iterator iter;
00750         for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
00751         {
00752                 LLScrollListItem* item  = *iter;
00753                 ret.push_back(item);
00754         }
00755         return ret;
00756 }
00757 
00758 // returns first matching item
00759 LLScrollListItem* LLScrollListCtrl::getItem(const LLSD& sd) const
00760 {
00761         LLString string_val = sd.asString();
00762 
00763         item_list::const_iterator iter;
00764         for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
00765         {
00766                 LLScrollListItem* item  = *iter;
00767                 // assumes string representation is good enough for comparison
00768                 if (item->getValue().asString() == string_val)
00769                 {
00770                         return item;
00771                 }
00772         }
00773         return NULL;
00774 }
00775 
00776 
00777 void LLScrollListCtrl::reshape( S32 width, S32 height, BOOL called_from_parent )
00778 {
00779         LLUICtrl::reshape( width, height, called_from_parent );
00780 
00781         updateLayout();
00782 }
00783 
00784 void LLScrollListCtrl::updateLayout()
00785 {
00786         // reserve room for column headers, if needed
00787         S32 heading_size = (mDisplayColumnHeaders ? mHeadingHeight : 0);
00788         mItemListRect.setOriginAndSize(
00789                 mBorderThickness,
00790                 mBorderThickness,
00791                 getRect().getWidth() - 2 * mBorderThickness,
00792                 getRect().getHeight() - (2 * mBorderThickness ) - heading_size );
00793 
00794         // how many lines of content in a single "page"
00795         mPageLines = mLineHeight? mItemListRect.getHeight() / mLineHeight : 0;
00796         BOOL scrollbar_visible = getItemCount() > mPageLines;
00797         if (scrollbar_visible)
00798         {
00799                 // provide space on the right for scrollbar
00800                 mItemListRect.mRight = getRect().getWidth() - mBorderThickness - SCROLLBAR_SIZE;
00801         }
00802 
00803         mScrollbar->reshape(SCROLLBAR_SIZE, mItemListRect.getHeight() + (mDisplayColumnHeaders ? mHeadingHeight : 0));
00804         mScrollbar->setPageSize( mPageLines );
00805         mScrollbar->setDocSize( getItemCount() );
00806         mScrollbar->setVisible(scrollbar_visible);
00807 
00808         dirtyColumns();
00809 }
00810 
00811 // Attempt to size the control to show all items.
00812 // Do not make larger than width or height.
00813 void LLScrollListCtrl::fitContents(S32 max_width, S32 max_height)
00814 {
00815         S32 height = llmin( getRequiredRect().getHeight(), max_height );
00816         S32 width = getRect().getWidth();
00817 
00818         reshape( width, height );
00819 }
00820 
00821 
00822 LLRect LLScrollListCtrl::getRequiredRect()
00823 {
00824         S32 heading_size = (mDisplayColumnHeaders ? mHeadingHeight : 0);
00825         S32 height = (mLineHeight * getItemCount()) 
00826                                 + (2 * mBorderThickness ) 
00827                                 + heading_size;
00828         S32 width = getRect().getWidth();
00829 
00830         return LLRect(0, height, width, 0);
00831 }
00832 
00833 
00834 BOOL LLScrollListCtrl::addItem( LLScrollListItem* item, EAddPosition pos, BOOL requires_column )
00835 {
00836         BOOL not_too_big = getItemCount() < mMaxItemCount;
00837         if (not_too_big)
00838         {
00839                 switch( pos )
00840                 {
00841                 case ADD_TOP:
00842                         mItemList.push_front(item);
00843                         setSorted(FALSE);
00844                         break;
00845         
00846                 case ADD_SORTED:
00847                         {
00848                                 // sort by column 0, in ascending order
00849                                 std::vector<sort_column_t> single_sort_column;
00850                                 single_sort_column.push_back(std::make_pair(0, TRUE));
00851 
00852                                 mItemList.push_back(item);
00853                                 std::stable_sort(
00854                                         mItemList.begin(), 
00855                                         mItemList.end(), 
00856                                         SortScrollListItem(single_sort_column));
00857                                 
00858                                 // ADD_SORTED just sorts by first column...
00859                                 // this might not match user sort criteria, so flag list as being in unsorted state
00860                                 setSorted(FALSE);
00861                                 break;
00862                         }       
00863                 case ADD_BOTTOM:
00864                         mItemList.push_back(item);
00865                         setSorted(FALSE);
00866                         break;
00867         
00868                 default:
00869                         llassert(0);
00870                         mItemList.push_back(item);
00871                         setSorted(FALSE);
00872                         break;
00873                 }
00874         
00875                 // create new column on demand
00876                 if (mColumns.empty() && requires_column)
00877                 {
00878                         LLSD new_column;
00879                         new_column["name"] = "default_column";
00880                         new_column["label"] = "";
00881                         new_column["dynamicwidth"] = TRUE;
00882                         addColumn(new_column);
00883                 }
00884 
00885                 updateLineHeightInsert(item);
00886 
00887                 updateLayout();
00888         }
00889 
00890         return not_too_big;
00891 }
00892 
00893 // NOTE: This is *very* expensive for large lists, especially when we are dirtying the list every frame
00894 //  while receiving a long list of names.
00895 // *TODO: Use bookkeeping to make this an incramental cost with item additions
00896 void LLScrollListCtrl::calcColumnWidths()
00897 {
00898         const S32 HEADING_TEXT_PADDING = 30;
00899         const S32 COLUMN_TEXT_PADDING = 20;
00900 
00901         mMaxContentWidth = 0;
00902 
00903         S32 max_item_width = 0;
00904 
00905         ordered_columns_t::iterator column_itor;
00906         for (column_itor = mColumnsIndexed.begin(); column_itor != mColumnsIndexed.end(); ++column_itor)
00907         {
00908                 LLScrollListColumn* column = *column_itor;
00909                 if (!column) continue;
00910 
00911                 // update column width
00912                 S32 new_width = column->mWidth;
00913                 if (column->mRelWidth >= 0)
00914                 {
00915                         new_width = (S32)llround(column->mRelWidth*mItemListRect.getWidth());
00916                 }
00917                 else if (column->mDynamicWidth)
00918                 {
00919                         new_width = (mItemListRect.getWidth() - mTotalStaticColumnWidth) / mNumDynamicWidthColumns;
00920                 }
00921 
00922                 if (new_width != column->mWidth)
00923                 {
00924                         column->mWidth = new_width;
00925                 }
00926 
00927                 // update max content width for this column, by looking at all items
00928                 column->mMaxContentWidth = column->mHeader ? LLFontGL::sSansSerifSmall->getWidth(column->mLabel) + mColumnPadding + HEADING_TEXT_PADDING : 0;
00929                 item_list::iterator iter;
00930                 for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
00931                 {
00932                         LLScrollListCell* cellp = (*iter)->getColumn(column->mIndex);
00933                         if (!cellp) continue;
00934 
00935                         column->mMaxContentWidth = llmax(LLFontGL::sSansSerifSmall->getWidth(cellp->getValue().asString()) + mColumnPadding + COLUMN_TEXT_PADDING, column->mMaxContentWidth);
00936                 }
00937 
00938                 max_item_width += column->mMaxContentWidth;
00939         }
00940 
00941         mMaxContentWidth = max_item_width;
00942 }
00943 
00944 const S32 SCROLL_LIST_ROW_PAD = 2;
00945 
00946 // Line height is the max height of all the cells in all the items.
00947 void LLScrollListCtrl::updateLineHeight()
00948 {
00949         mLineHeight = 0;
00950         item_list::iterator iter;
00951         for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
00952         {
00953                 LLScrollListItem *itemp = *iter;
00954                 S32 num_cols = itemp->getNumColumns();
00955                 S32 i = 0;
00956                 for (const LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i))
00957                 {
00958                         mLineHeight = llmax( mLineHeight, cell->getHeight() + SCROLL_LIST_ROW_PAD );
00959                 }
00960         }
00961 }
00962 
00963 // when the only change to line height is from an insert, we needn't scan the entire list
00964 void LLScrollListCtrl::updateLineHeightInsert(LLScrollListItem* itemp)
00965 {
00966         S32 num_cols = itemp->getNumColumns();
00967         S32 i = 0;
00968         for (const LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i))
00969         {
00970                 mLineHeight = llmax( mLineHeight, cell->getHeight() + SCROLL_LIST_ROW_PAD );
00971         }
00972 }
00973 
00974 
00975 void LLScrollListCtrl::updateColumns()
00976 {
00977         calcColumnWidths();
00978 
00979         // propagate column widths to individual cells
00980         item_list::iterator iter;
00981         for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
00982         {
00983                 LLScrollListItem *itemp = *iter;
00984                 S32 num_cols = itemp->getNumColumns();
00985                 S32 i = 0;
00986                 for (LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i))
00987                 {
00988                         if (i >= (S32)mColumnsIndexed.size()) break;
00989 
00990                         cell->setWidth(mColumnsIndexed[i]->mWidth);
00991                 }
00992         }
00993 
00994         // update column headers
00995         std::vector<LLScrollListColumn*>::iterator column_ordered_it;
00996         S32 left = mItemListRect.mLeft;
00997         LLColumnHeader* last_header = NULL;
00998         for (column_ordered_it = mColumnsIndexed.begin(); column_ordered_it != mColumnsIndexed.end(); ++column_ordered_it)
00999         {
01000                 if ((*column_ordered_it)->mWidth < 0)
01001                 {
01002                         // skip hidden columns
01003                         continue;
01004                 }
01005                 LLScrollListColumn* column = *column_ordered_it;
01006                 
01007                 if (column->mHeader)
01008                 {
01009                         last_header = column->mHeader;
01010                         S32 top = mItemListRect.mTop;
01011                         S32 right = left + column->mWidth;
01012 
01013                         if (column->mIndex != (S32)mColumnsIndexed.size()-1)
01014                         {
01015                                 right += mColumnPadding;
01016                         }
01017                         right = llmax(left, llmin(mItemListRect.getWidth(), right));
01018                         S32 header_width = right - left;
01019 
01020                         last_header->reshape(header_width, mHeadingHeight);
01021                         last_header->translate(
01022                                 left - last_header->getRect().mLeft,
01023                                 top - last_header->getRect().mBottom);
01024                         last_header->setVisible(mDisplayColumnHeaders && header_width > 0);
01025                         left = right;
01026                 }
01027         }
01028 
01029         //FIXME: stretch the entire last column if it is resizable (gestures windows shows truncated text in last column)
01030         // expand last column header we encountered to full list width
01031         if (last_header)
01032         {
01033                 S32 new_width = llmax(0, mItemListRect.mRight - last_header->getRect().mLeft);
01034                 last_header->reshape(new_width, last_header->getRect().getHeight());
01035                 last_header->setVisible(mDisplayColumnHeaders && new_width > 0);
01036         }
01037 }
01038 
01039 void LLScrollListCtrl::setDisplayHeading(BOOL display)
01040 {
01041         mDisplayColumnHeaders = display;
01042 
01043         updateLayout();
01044 }
01045 
01046 void LLScrollListCtrl::setHeadingHeight(S32 heading_height)
01047 {
01048         mHeadingHeight = heading_height;
01049 
01050         updateLayout();
01051 
01052 }
01053 
01054 BOOL LLScrollListCtrl::selectFirstItem()
01055 {
01056         BOOL success = FALSE;
01057 
01058         // our $%&@#$()^%#$()*^ iterators don't let us check against the first item inside out iteration
01059         BOOL first_item = TRUE;
01060 
01061         item_list::iterator iter;
01062         for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
01063         {
01064                 LLScrollListItem *itemp = *iter;
01065                 if( first_item && itemp->getEnabled() )
01066                 {
01067                         if (!itemp->getSelected())
01068                         {
01069                                 selectItem(itemp);
01070                         }
01071                         success = TRUE;
01072                         mOriginalSelection = 0;
01073                 }
01074                 else
01075                 {
01076                         deselectItem(itemp);
01077                 }
01078                 first_item = FALSE;
01079         }
01080         if (mCommitOnSelectionChange)
01081         {
01082                 commitIfChanged();
01083         }
01084         return success;
01085 }
01086 
01087 // Deselects all other items
01088 // virtual
01089 BOOL LLScrollListCtrl::selectNthItem( S32 target_index )
01090 {
01091         return selectItemRange(target_index, target_index);
01092 }
01093 
01094 // virtual
01095 BOOL LLScrollListCtrl::selectItemRange( S32 first_index, S32 last_index )
01096 {
01097         if (mItemList.empty())
01098         {
01099                 return FALSE;
01100         }
01101 
01102         S32 listlen = (S32)mItemList.size();
01103         first_index = llclamp(first_index, 0, listlen-1);
01104         
01105         if (last_index < 0)
01106                 last_index = listlen-1;
01107         else
01108                 last_index = llclamp(last_index, first_index, listlen-1);
01109 
01110         BOOL success = FALSE;
01111         S32 index = 0;
01112         for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); iter++)
01113         {
01114                 LLScrollListItem *itemp = *iter;
01115                 if( index >= first_index && index <= last_index )
01116                 {
01117                         if( itemp->getEnabled() )
01118                         {
01119                                 selectItem(itemp, FALSE);
01120                                 success = TRUE;                         
01121                         }
01122                 }
01123                 else
01124                 {
01125                         deselectItem(itemp);
01126                 }
01127                 index++;
01128         }
01129 
01130         if (mCommitOnSelectionChange)
01131         {
01132                 commitIfChanged();
01133         }
01134 
01135         mSearchString.clear();
01136 
01137         return success;
01138 }
01139 
01140 
01141 void LLScrollListCtrl::swapWithNext(S32 index)
01142 {
01143         if (index >= ((S32)mItemList.size() - 1))
01144         {
01145                 // At end of list, doesn't do anything
01146                 return;
01147         }
01148         LLScrollListItem *cur_itemp = mItemList[index];
01149         mItemList[index] = mItemList[index + 1];
01150         mItemList[index + 1] = cur_itemp;
01151 }
01152 
01153 
01154 void LLScrollListCtrl::swapWithPrevious(S32 index)
01155 {
01156         if (index <= 0)
01157         {
01158                 // At beginning of list, don't do anything
01159         }
01160 
01161         LLScrollListItem *cur_itemp = mItemList[index];
01162         mItemList[index] = mItemList[index - 1];
01163         mItemList[index - 1] = cur_itemp;
01164 }
01165 
01166 
01167 void LLScrollListCtrl::deleteSingleItem(S32 target_index)
01168 {
01169         if (target_index < 0 || target_index >= (S32)mItemList.size())
01170         {
01171                 return;
01172         }
01173 
01174         LLScrollListItem *itemp;
01175         itemp = mItemList[target_index];
01176         if (itemp == mLastSelected)
01177         {
01178                 mLastSelected = NULL;
01179         }
01180         delete itemp;
01181         mItemList.erase(mItemList.begin() + target_index);
01182         dirtyColumns();
01183 }
01184 
01185 //FIXME: refactor item deletion
01186 void LLScrollListCtrl::deleteItems(const LLSD& sd)
01187 {
01188         item_list::iterator iter;
01189         for (iter = mItemList.begin(); iter < mItemList.end(); )
01190         {
01191                 LLScrollListItem* itemp = *iter;
01192                 if (itemp->getValue().asString() == sd.asString())
01193                 {
01194                         if (itemp == mLastSelected)
01195                         {
01196                                 mLastSelected = NULL;
01197                         }
01198                         delete itemp;
01199                         iter = mItemList.erase(iter);
01200                 }
01201                 else
01202                 {
01203                         iter++;
01204                 }
01205         }
01206 
01207         dirtyColumns();
01208 }
01209 
01210 void LLScrollListCtrl::deleteSelectedItems()
01211 {
01212         item_list::iterator iter;
01213         for (iter = mItemList.begin(); iter < mItemList.end(); )
01214         {
01215                 LLScrollListItem* itemp = *iter;
01216                 if (itemp->getSelected())
01217                 {
01218                         delete itemp;
01219                         iter = mItemList.erase(iter);
01220                 }
01221                 else
01222                 {
01223                         iter++;
01224                 }
01225         }
01226         mLastSelected = NULL;
01227         dirtyColumns();
01228 }
01229 
01230 void LLScrollListCtrl::highlightNthItem(S32 target_index)
01231 {
01232         if (mHighlightedItem != target_index)
01233         {
01234                 mHighlightedItem = target_index;
01235         }
01236 }
01237 
01238 S32     LLScrollListCtrl::selectMultiple( LLDynamicArray<LLUUID> ids )
01239 {
01240         item_list::iterator iter;
01241         S32 count = 0;
01242         for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
01243         {
01244                 LLScrollListItem* item = *iter;
01245                 LLDynamicArray<LLUUID>::iterator iditr;
01246                 for(iditr = ids.begin(); iditr != ids.end(); ++iditr)
01247                 {
01248                         if (item->getEnabled() && (item->getUUID() == (*iditr)))
01249                         {
01250                                 selectItem(item,FALSE);
01251                                 ++count;
01252                                 break;
01253                         }
01254                 }
01255                 if(ids.end() != iditr) ids.erase(iditr);
01256         }
01257 
01258         if (mCommitOnSelectionChange)
01259         {
01260                 commitIfChanged();
01261         }
01262         return count;
01263 }
01264 
01265 S32 LLScrollListCtrl::getItemIndex( LLScrollListItem* target_item ) const
01266 {
01267         S32 index = 0;
01268         item_list::const_iterator iter;
01269         for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
01270         {
01271                 LLScrollListItem *itemp = *iter;
01272                 if (target_item == itemp)
01273                 {
01274                         return index;
01275                 }
01276                 index++;
01277         }
01278         return -1;
01279 }
01280 
01281 S32 LLScrollListCtrl::getItemIndex( const LLUUID& target_id ) const
01282 {
01283         S32 index = 0;
01284         item_list::const_iterator iter;
01285         for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
01286         {
01287                 LLScrollListItem *itemp = *iter;
01288                 if (target_id == itemp->getUUID())
01289                 {
01290                         return index;
01291                 }
01292                 index++;
01293         }
01294         return -1;
01295 }
01296 
01297 void LLScrollListCtrl::selectPrevItem( BOOL extend_selection)
01298 {
01299         LLScrollListItem* prev_item = NULL;
01300 
01301         if (!getFirstSelected())
01302         {
01303                 // select last item
01304                 selectNthItem(getItemCount() - 1);
01305         }
01306         else
01307         {
01308                 item_list::iterator iter;
01309                 for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
01310                 {
01311                         LLScrollListItem* cur_item = *iter;
01312 
01313                         if (cur_item->getSelected())
01314                         {
01315                                 if (prev_item)
01316                                 {
01317                                         selectItem(prev_item, !extend_selection);
01318                                 }
01319                                 else
01320                                 {
01321                                         reportInvalidInput();
01322                                 }
01323                                 break;
01324                         }
01325 
01326                         // don't allow navigation to disabled elements
01327                         prev_item = cur_item->getEnabled() ? cur_item : prev_item;
01328                 }
01329         }
01330 
01331         if ((mCommitOnSelectionChange || mCommitOnKeyboardMovement))
01332         {
01333                 commitIfChanged();
01334         }
01335 
01336         mSearchString.clear();
01337 }
01338 
01339 
01340 void LLScrollListCtrl::selectNextItem( BOOL extend_selection)
01341 {
01342         LLScrollListItem* next_item = NULL;
01343 
01344         if (!getFirstSelected())
01345         {
01346                 selectFirstItem();
01347         }
01348         else
01349         {
01350                 item_list::reverse_iterator iter;
01351                 for (iter = mItemList.rbegin(); iter != mItemList.rend(); iter++)
01352                 {
01353                         LLScrollListItem* cur_item = *iter;
01354 
01355                         if (cur_item->getSelected())
01356                         {
01357                                 if (next_item)
01358                                 {
01359                                         selectItem(next_item, !extend_selection);
01360                                 }
01361                                 else
01362                                 {
01363                                         reportInvalidInput();
01364                                 }
01365                                 break;
01366                         }
01367 
01368                         // don't allow navigation to disabled items
01369                         next_item = cur_item->getEnabled() ? cur_item : next_item;
01370                 }
01371         }
01372 
01373         if (mCommitOnKeyboardMovement)
01374         {
01375                 onCommit();
01376         }
01377 
01378         mSearchString.clear();
01379 }
01380 
01381 
01382 
01383 void LLScrollListCtrl::deselectAllItems(BOOL no_commit_on_change)
01384 {
01385         item_list::iterator iter;
01386         for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
01387         {
01388                 LLScrollListItem* item = *iter;
01389                 deselectItem(item);
01390         }
01391 
01392         if (mCommitOnSelectionChange && !no_commit_on_change)
01393         {
01394                 commitIfChanged();
01395         }
01396 }
01397 
01399 // Use this to add comment text such as "Searching", which ignores column settings of list
01400 
01401 LLScrollListItem* LLScrollListCtrl::addCommentText(const LLString& comment_text, EAddPosition pos)
01402 {
01403         LLScrollListItem* item = NULL;
01404         if (getItemCount() < mMaxItemCount)
01405         {
01406                 // always draw comment text with "enabled" color
01407                 item = new LLScrollListItemComment( comment_text, mFgUnselectedColor );
01408                 addItem( item, pos, FALSE );
01409         }
01410         return item;
01411 }
01412 
01413 LLScrollListItem* LLScrollListCtrl::addSeparator(EAddPosition pos)
01414 {
01415         LLScrollListItem* item = new LLScrollListItemSeparator();
01416         addItem(item, pos, FALSE);
01417         return item;
01418 }
01419 
01420 // Selects first enabled item of the given name.
01421 // Returns false if item not found.
01422 BOOL LLScrollListCtrl::selectItemByLabel(const LLString& label, BOOL case_sensitive)
01423 {
01424         // ensure that no stale items are selected, even if we don't find a match
01425         deselectAllItems(TRUE);
01426         //RN: assume no empty items
01427         if (label.empty())
01428         {
01429                 return FALSE;
01430         }
01431 
01432         LLString target_text = label;
01433         if (!case_sensitive)
01434         {
01435                 LLString::toLower(target_text);
01436         }
01437 
01438         BOOL found = FALSE;
01439 
01440         item_list::iterator iter;
01441         S32 index = 0;
01442         for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
01443         {
01444                 LLScrollListItem* item = *iter;
01445                 // Only select enabled items with matching names
01446                 LLString item_text = item->getColumn(0)->getValue().asString();
01447                 if (!case_sensitive)
01448                 {
01449                         LLString::toLower(item_text);
01450                 }
01451                 BOOL select = !found && item->getEnabled() && item_text == target_text;
01452                 if (select)
01453                 {
01454                         selectItem(item);
01455                 }
01456                 found = found || select;
01457                 index++;
01458         }
01459 
01460         if (mCommitOnSelectionChange)
01461         {
01462                 commitIfChanged();
01463         }
01464 
01465         return found;
01466 }
01467 
01468 
01469 BOOL LLScrollListCtrl::selectItemByPrefix(const LLString& target, BOOL case_sensitive)
01470 {
01471         return selectItemByPrefix(utf8str_to_wstring(target), case_sensitive);
01472 }
01473 
01474 // Selects first enabled item that has a name where the name's first part matched the target string.
01475 // Returns false if item not found.
01476 BOOL LLScrollListCtrl::selectItemByPrefix(const LLWString& target, BOOL case_sensitive)
01477 {
01478         BOOL found = FALSE;
01479 
01480         LLWString target_trimmed( target );
01481         S32 target_len = target_trimmed.size();
01482         
01483         if( 0 == target_len )
01484         {
01485                 // Is "" a valid choice?
01486                 item_list::iterator iter;
01487                 for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
01488                 {
01489                         LLScrollListItem* item = *iter;
01490                         // Only select enabled items with matching names
01491                         LLScrollListCell* cellp = item->getColumn(mSearchColumn);
01492                         BOOL select = cellp ? item->getEnabled() && ('\0' == cellp->getValue().asString()[0]) : FALSE;
01493                         if (select)
01494                         {
01495                                 selectItem(item);
01496                                 found = TRUE;
01497                                 break;
01498                         }
01499                 }
01500         }
01501         else
01502         {
01503                 if (!case_sensitive)
01504                 {
01505                         // do comparisons in lower case
01506                         LLWString::toLower(target_trimmed);
01507                 }
01508 
01509                 for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); iter++)
01510                 {
01511                         LLScrollListItem* item = *iter;
01512 
01513                         // Only select enabled items with matching names
01514                         LLScrollListCell* cellp = item->getColumn(mSearchColumn);
01515                         if (!cellp)
01516                         {
01517                                 continue;
01518                         }
01519                         LLWString item_label = utf8str_to_wstring(cellp->getValue().asString());
01520                         if (!case_sensitive)
01521                         {
01522                                 LLWString::toLower(item_label);
01523                         }
01524                         // remove extraneous whitespace from searchable label
01525                         LLWString trimmed_label = item_label;
01526                         LLWString::trim(trimmed_label);
01527                         
01528                         BOOL select = item->getEnabled() && trimmed_label.compare(0, target_trimmed.size(), target_trimmed) == 0;
01529 
01530                         if (select)
01531                         {
01532                                 // find offset of matching text (might have leading whitespace)
01533                                 S32 offset = item_label.find(target_trimmed);
01534                                 cellp->highlightText(offset, target_trimmed.size());
01535                                 selectItem(item);
01536                                 found = TRUE;
01537                                 break;
01538                         }
01539                 }
01540         }
01541 
01542         if (mCommitOnSelectionChange)
01543         {
01544                 commitIfChanged();
01545         }
01546 
01547         return found;
01548 }
01549 
01550 const LLString LLScrollListCtrl::getSelectedItemLabel(S32 column) const
01551 {
01552         LLScrollListItem* item;
01553 
01554         item = getFirstSelected();
01555         if (item)
01556         {
01557                 return item->getColumn(column)->getValue().asString();
01558         }
01559 
01560         return LLString::null;
01561 }
01562 
01564 // "StringUUID" interface: use this when you're creating a list that contains non-unique strings each of which
01565 // has an associated, unique UUID, and only one of which can be selected at a time.
01566 
01567 LLScrollListItem* LLScrollListCtrl::addStringUUIDItem(const LLString& item_text, const LLUUID& id, EAddPosition pos, BOOL enabled, S32 column_width)
01568 {
01569         LLScrollListItem* item = NULL;
01570         if (getItemCount() < mMaxItemCount)
01571         {
01572                 item = new LLScrollListItem( enabled, NULL, id );
01573                 item->addColumn(item_text, LLResMgr::getInstance()->getRes(LLFONT_SANSSERIF_SMALL), column_width);
01574                 addItem( item, pos );
01575         }
01576         return item;
01577 }
01578 
01579 // Select the line or lines that match this UUID
01580 BOOL LLScrollListCtrl::selectByID( const LLUUID& id )
01581 {
01582         return selectByValue( LLSD(id) );
01583 }
01584 
01585 BOOL LLScrollListCtrl::setSelectedByValue(const LLSD& value, BOOL selected)
01586 {
01587         BOOL found = FALSE;
01588 
01589         if (selected && !mAllowMultipleSelection) deselectAllItems(TRUE);
01590 
01591         item_list::iterator iter;
01592         for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
01593         {
01594                 LLScrollListItem* item = *iter;
01595                 if (item->getEnabled() && (item->getValue().asString() == value.asString()))
01596                 {
01597                         if (selected)
01598                         {
01599                                 selectItem(item);
01600                         }
01601                         else
01602                         {
01603                                 deselectItem(item);
01604                         }
01605                         found = TRUE;
01606                         break;
01607                 }
01608         }
01609 
01610         if (mCommitOnSelectionChange)
01611         {
01612                 commitIfChanged();
01613         }
01614 
01615         return found;
01616 }
01617 
01618 BOOL LLScrollListCtrl::isSelected(const LLSD& value) const 
01619 {
01620         item_list::const_iterator iter;
01621         for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
01622         {
01623                 LLScrollListItem* item = *iter;
01624                 if (item->getValue().asString() == value.asString())
01625                 {
01626                         return item->getSelected();
01627                 }
01628         }
01629         return FALSE;
01630 }
01631 
01632 LLUUID LLScrollListCtrl::getStringUUIDSelectedItem() const
01633 {
01634         LLScrollListItem* item = getFirstSelected();
01635 
01636         if (item)
01637         {
01638                 return item->getUUID();
01639         }
01640 
01641         return LLUUID::null;
01642 }
01643 
01644 LLSD LLScrollListCtrl::getSelectedValue()
01645 {
01646         LLScrollListItem* item = getFirstSelected();
01647 
01648         if (item)
01649         {
01650                 return item->getValue();
01651         }
01652         else
01653         {
01654                 return LLSD();
01655         }
01656 }
01657 
01658 void LLScrollListCtrl::drawItems()
01659 {
01660         S32 x = mItemListRect.mLeft;
01661         S32 y = mItemListRect.mTop - mLineHeight;
01662 
01663         // allow for partial line at bottom
01664         S32 num_page_lines = mPageLines + 1;
01665 
01666         LLRect item_rect;
01667 
01668         LLGLSUIDefault gls_ui;
01669         
01670         {
01671                 LLLocalClipRect clip(mItemListRect);
01672 
01673                 S32 cur_y = y;
01674                 
01675                 mDrewSelected = FALSE;
01676 
01677                 S32 line = 0;
01678                 S32 max_columns = 0;
01679 
01680                 LLColor4 highlight_color = LLColor4::white;
01681                 F32 type_ahead_timeout = LLUI::sConfigGroup->getF32("TypeAheadTimeout");
01682                 highlight_color.mV[VALPHA] = clamp_rescale(mSearchTimer.getElapsedTimeF32(), type_ahead_timeout * 0.7f, type_ahead_timeout, 0.4f, 0.f);
01683 
01684                 item_list::iterator iter;
01685                 for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
01686                 {
01687                         LLScrollListItem* item = *iter;
01688                         
01689                         item_rect.setOriginAndSize( 
01690                                 x, 
01691                                 cur_y, 
01692                                 mItemListRect.getWidth(),
01693                                 mLineHeight );
01694 
01695                         //llinfos << item_rect.getWidth() << llendl;
01696 
01697                         if (item->getSelected())
01698                         {
01699                                 mDrewSelected = TRUE;
01700                         }
01701 
01702                         max_columns = llmax(max_columns, item->getNumColumns());
01703 
01704                         LLColor4 fg_color;
01705                         LLColor4 bg_color(LLColor4::transparent);
01706 
01707                         if( mScrollLines <= line && line < mScrollLines + num_page_lines )
01708                         {
01709                                 fg_color = (item->getEnabled() ? mFgUnselectedColor : mFgDisabledColor);
01710                                 if( item->getSelected() && mCanSelect)
01711                                 {
01712                                         bg_color = mBgSelectedColor;
01713                                         fg_color = (item->getEnabled() ? mFgSelectedColor : mFgDisabledColor);
01714                                 }
01715                                 else if (mHighlightedItem == line && mCanSelect)
01716                                 {
01717                                         bg_color = mHighlightedColor;
01718                                 }
01719                                 else 
01720                                 {
01721                                         if (mDrawStripes && (line % 2 == 0) && (max_columns > 1))
01722                                         {
01723                                                 bg_color = mBgStripeColor;
01724                                         }
01725                                 }
01726 
01727                                 if (!item->getEnabled())
01728                                 {
01729                                         bg_color = mBgReadOnlyColor;
01730                                 }
01731 
01732                                 item->draw(item_rect, fg_color, bg_color, highlight_color, mColumnPadding);
01733 
01734                                 cur_y -= mLineHeight;
01735                         }
01736                         line++;
01737                 }
01738         }
01739 }
01740 
01741 
01742 void LLScrollListCtrl::draw()
01743 {
01744         // if user specifies sort, make sure it is maintained
01745         if (needsSorting() && !isSorted())
01746         {
01747                 sortItems();
01748         }
01749 
01750         if (mNeedsScroll)
01751         {
01752                 scrollToShowSelected();
01753                 mNeedsScroll = FALSE;
01754         }
01755         LLRect background(0, getRect().getHeight(), getRect().getWidth(), 0);
01756         // Draw background
01757         if (mBackgroundVisible)
01758         {
01759                 LLGLSNoTexture no_texture;
01760                 gGL.color4fv( getEnabled() ? mBgWriteableColor.mV : mBgReadOnlyColor.mV );
01761                 gl_rect_2d(background);
01762         }
01763 
01764         if (mColumnsDirty)
01765         {
01766                 updateColumns();
01767                 mColumnsDirty = FALSE;
01768         }
01769 
01770         drawItems();
01771 
01772         if (mBorder)
01773         {
01774                 mBorder->setKeyboardFocusHighlight(gFocusMgr.getKeyboardFocus() == this);
01775         }
01776 
01777         LLUICtrl::draw();
01778 }
01779 
01780 void LLScrollListCtrl::setEnabled(BOOL enabled)
01781 {
01782         mCanSelect = enabled;
01783         setTabStop(enabled);
01784         mScrollbar->setTabStop(!enabled && mScrollbar->getPageSize() < mScrollbar->getDocSize());
01785 }
01786 
01787 BOOL LLScrollListCtrl::handleScrollWheel(S32 x, S32 y, S32 clicks)
01788 {
01789         BOOL handled = FALSE;
01790         // Pretend the mouse is over the scrollbar
01791         handled = mScrollbar->handleScrollWheel( 0, 0, clicks );
01792         return handled;
01793 }
01794 
01795 BOOL LLScrollListCtrl::handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect_screen)
01796 {
01797         S32 column_index = getColumnIndexFromOffset(x);
01798         LLScrollListColumn* columnp = getColumn(column_index);
01799 
01800         if (columnp == NULL) return FALSE;
01801 
01802         BOOL handled = FALSE;
01803         // show tooltip for full name of hovered item if it has been truncated
01804         LLScrollListItem* hit_item = hitItem(x, y);
01805         if (hit_item)
01806         {
01807                 LLScrollListCell* hit_cell = hit_item->getColumn(column_index);
01808                 if (!hit_cell) return FALSE;
01809                 //S32 cell_required_width = hit_cell->getContentWidth();
01810                 if (hit_cell 
01811                         && hit_cell->isText())
01812                 {
01813 
01814                         S32 rect_left = getColumnOffsetFromIndex(column_index) + mItemListRect.mLeft;
01815                         S32 rect_bottom = getRowOffsetFromIndex(getItemIndex(hit_item));
01816                         LLRect cell_rect;
01817                         cell_rect.setOriginAndSize(rect_left, rect_bottom, rect_left + columnp->mWidth, mLineHeight);
01818                         // Convert rect local to screen coordinates
01819                         localPointToScreen( 
01820                                 cell_rect.mLeft, cell_rect.mBottom, 
01821                                 &(sticky_rect_screen->mLeft), &(sticky_rect_screen->mBottom) );
01822                         localPointToScreen(
01823                                 cell_rect.mRight, cell_rect.mTop, 
01824                                 &(sticky_rect_screen->mRight), &(sticky_rect_screen->mTop) );
01825 
01826                         msg = hit_cell->getValue().asString();
01827                 }
01828                 handled = TRUE;
01829         }
01830 
01831         // otherwise, look for a tooltip associated with this column
01832         LLColumnHeader* headerp = columnp->mHeader;
01833         if (headerp && !handled)
01834         {
01835                 headerp->handleToolTip(x, y, msg, sticky_rect_screen);
01836                 handled = !msg.empty();
01837         }
01838 
01839         return handled;
01840 }
01841 
01842 BOOL LLScrollListCtrl::selectItemAt(S32 x, S32 y, MASK mask)
01843 {
01844         if (!mCanSelect) return FALSE;
01845 
01846         BOOL selection_changed = FALSE;
01847 
01848         LLScrollListItem* hit_item = hitItem(x, y);
01849 
01850         if( hit_item )
01851         {
01852                 if( mAllowMultipleSelection )
01853                 {
01854                         if (mask & MASK_SHIFT)
01855                         {
01856                                 if (mLastSelected == NULL)
01857                                 {
01858                                         selectItem(hit_item);
01859                                 }
01860                                 else
01861                                 {
01862                                         // Select everthing between mLastSelected and hit_item
01863                                         bool selecting = false;
01864                                         item_list::iterator itor;
01865                                         // If we multiselect backwards, we'll stomp on mLastSelected,
01866                                         // meaning that we never stop selecting until hitting max or
01867                                         // the end of the list.
01868                                         LLScrollListItem* lastSelected = mLastSelected;
01869                                         for (itor = mItemList.begin(); itor != mItemList.end(); ++itor)
01870                                         {
01871                                                 if(mMaxSelectable > 0 && getAllSelected().size() >= mMaxSelectable)
01872                                                 {
01873                                                         if(mOnMaximumSelectCallback)
01874                                                         {
01875                                                                 mOnMaximumSelectCallback(mCallbackUserData);
01876                                                         }
01877                                                         break;
01878                                                 }
01879                                                 LLScrollListItem *item = *itor;
01880                         if (item == hit_item || item == lastSelected)
01881                                                 {
01882                                                         selectItem(item, FALSE);
01883                                                         selecting = !selecting;
01884                                                         if (hit_item == lastSelected)
01885                                                         {
01886                                                                 // stop selecting now, since we just clicked on our last selected item
01887                                                                 selecting = FALSE;
01888                                                         }
01889                                                 }
01890                                                 if (selecting)
01891                                                 {
01892                                                         selectItem(item, FALSE);
01893                                                 }
01894                                         }
01895                                 }
01896                         }
01897                         else if (mask & MASK_CONTROL)
01898                         {
01899                                 if (hit_item->getSelected())
01900                                 {
01901                                         deselectItem(hit_item);
01902                                 }
01903                                 else
01904                                 {
01905                                         if(!(mMaxSelectable > 0 && getAllSelected().size() >= mMaxSelectable))
01906                                         {
01907                                                 selectItem(hit_item, FALSE);
01908                                         }
01909                                         else
01910                                         {
01911                                                 if(mOnMaximumSelectCallback)
01912                                                 {
01913                                                         mOnMaximumSelectCallback(mCallbackUserData);
01914                                                 }
01915                                         }
01916                                 }
01917                         }
01918                         else
01919                         {
01920                                 deselectAllItems(TRUE);
01921                                 selectItem(hit_item);
01922                         }
01923                 }
01924                 else
01925                 {
01926                         selectItem(hit_item);
01927                 }
01928 
01929                 selection_changed = mSelectionChanged;
01930                 if (mCommitOnSelectionChange)
01931                 {
01932                         commitIfChanged();
01933                 }
01934 
01935                 // clear search string on mouse operations
01936                 mSearchString.clear();
01937         }
01938         else
01939         {
01940                 //mLastSelected = NULL;
01941                 //deselectAllItems(TRUE);
01942         }
01943 
01944         return selection_changed;
01945 }
01946 
01947 
01948 BOOL LLScrollListCtrl::handleMouseDown(S32 x, S32 y, MASK mask)
01949 {
01950         BOOL handled = childrenHandleMouseDown(x, y, mask) != NULL;
01951 
01952         if( !handled )
01953         {
01954                 // set keyboard focus first, in case click action wants to move focus elsewhere
01955                 setFocus(TRUE);
01956 
01957                 // clear selection changed flag because user is starting a selection operation
01958                 mSelectionChanged = FALSE;
01959 
01960                 handleClick(x, y, mask);
01961         }
01962 
01963         return TRUE;
01964 }
01965 
01966 BOOL LLScrollListCtrl::handleMouseUp(S32 x, S32 y, MASK mask)
01967 {
01968         if (hasMouseCapture())
01969         {
01970                 // release mouse capture immediately so 
01971                 // scroll to show selected logic will work
01972                 gFocusMgr.setMouseCapture(NULL);
01973                 if(mask == MASK_NONE)
01974                 {
01975                         selectItemAt(x, y, mask);
01976                         mNeedsScroll = TRUE;
01977                 }
01978         }
01979 
01980         // always commit when mouse operation is completed inside list
01981         if (mItemListRect.pointInRect(x,y))
01982         {
01983                 mDirty |= mSelectionChanged;
01984                 mSelectionChanged = FALSE;
01985                 onCommit();
01986         }
01987 
01988         return LLUICtrl::handleMouseUp(x, y, mask);
01989 }
01990 
01991 BOOL LLScrollListCtrl::handleDoubleClick(S32 x, S32 y, MASK mask)
01992 {
01993         //BOOL handled = FALSE;
01994         BOOL handled = handleClick(x, y, mask);
01995 
01996         if (!handled)
01997         {
01998                 // Offer the click to the children, even if we aren't enabled
01999                 // so the scroll bars will work.
02000                 if (NULL == LLView::childrenHandleDoubleClick(x, y, mask))
02001                 {
02002                         if( mCanSelect && mOnDoubleClickCallback )
02003                         {
02004                                 mOnDoubleClickCallback( mCallbackUserData );
02005                         }
02006                 }
02007         }
02008 
02009         return TRUE;
02010 }
02011 
02012 BOOL LLScrollListCtrl::handleClick(S32 x, S32 y, MASK mask)
02013 {
02014         // which row was clicked on?
02015         LLScrollListItem* hit_item = hitItem(x, y);
02016         if (!hit_item) return FALSE;
02017 
02018         // get appropriate cell from that row
02019         S32 column_index = getColumnIndexFromOffset(x);
02020         LLScrollListCell* hit_cell = hit_item->getColumn(column_index);
02021         if (!hit_cell) return FALSE;
02022 
02023         // if cell handled click directly (i.e. clicked on an embedded checkbox)
02024         if (hit_cell->handleClick())
02025         {
02026                 // if item not currently selected, select it
02027                 if (!hit_item->getSelected())
02028                 {
02029                         selectItemAt(x, y, mask);
02030                         gFocusMgr.setMouseCapture(this);
02031                         mNeedsScroll = TRUE;
02032                 }
02033                 
02034                 // propagate state of cell to rest of selected column
02035                 {
02036                         // propagate value of this cell to other selected items
02037                         // and commit the respective widgets
02038                         LLSD item_value = hit_cell->getValue();
02039                         for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); iter++)
02040                         {
02041                                 LLScrollListItem* item = *iter;
02042                                 if (item->getSelected())
02043                                 {
02044                                         LLScrollListCell* cellp = item->getColumn(column_index);
02045                                         cellp->setValue(item_value);
02046                                         cellp->onCommit();
02047                                 }
02048                         }
02049                         //FIXME: find a better way to signal cell changes
02050                         onCommit();
02051                 }
02052                 // eat click (e.g. do not trigger double click callback)
02053                 return TRUE;
02054         }
02055         else
02056         {
02057                 // treat this as a normal single item selection
02058                 selectItemAt(x, y, mask);
02059                 gFocusMgr.setMouseCapture(this);
02060                 mNeedsScroll = TRUE;
02061                 // do not eat click (allow double click callback)
02062                 return FALSE;
02063         }
02064 }
02065 
02066 LLScrollListItem* LLScrollListCtrl::hitItem( S32 x, S32 y )
02067 {
02068         // Excludes disabled items.
02069         LLScrollListItem* hit_item = NULL;
02070 
02071         LLRect item_rect;
02072         item_rect.setLeftTopAndSize( 
02073                 mItemListRect.mLeft,
02074                 mItemListRect.mTop,
02075                 mItemListRect.getWidth(),
02076                 mLineHeight );
02077 
02078         // allow for partial line at bottom
02079         S32 num_page_lines = mPageLines + 1;
02080 
02081         S32 line = 0;
02082         item_list::iterator iter;
02083         for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
02084         {
02085                 LLScrollListItem* item  = *iter;
02086                 if( mScrollLines <= line && line < mScrollLines + num_page_lines )
02087                 {
02088                         if( item->getEnabled() && item_rect.pointInRect( x, y ) )
02089                         {
02090                                 hit_item = item;
02091                                 break;
02092                         }
02093 
02094                         item_rect.translate(0, -mLineHeight);
02095                 }
02096                 line++;
02097         }
02098 
02099         return hit_item;
02100 }
02101 
02102 S32 LLScrollListCtrl::getColumnIndexFromOffset(S32 x)
02103 {
02104         // which column did we hit?
02105         S32 left = 0;
02106         S32 right = 0;
02107         S32 width = 0;
02108         S32 column_index = 0;
02109 
02110         ordered_columns_t::const_iterator iter = mColumnsIndexed.begin();
02111         ordered_columns_t::const_iterator end = mColumnsIndexed.end();
02112         for ( ; iter != end; ++iter)
02113         {
02114                 width = (*iter)->mWidth + mColumnPadding;
02115                 right += width;
02116                 if (left <= x && x < right )
02117                 {
02118                         break;
02119                 }
02120                 
02121                 // set left for next column as right of current column
02122                 left = right;
02123                 column_index++;
02124         }
02125 
02126         return llclamp(column_index, 0, getNumColumns() - 1);
02127 }
02128 
02129 
02130 S32 LLScrollListCtrl::getColumnOffsetFromIndex(S32 index)
02131 {
02132         S32 column_offset = 0;
02133         ordered_columns_t::const_iterator iter = mColumnsIndexed.begin();
02134         ordered_columns_t::const_iterator end = mColumnsIndexed.end();
02135         for ( ; iter != end; ++iter)
02136         {
02137                 if (index-- <= 0)
02138                 {
02139                         return column_offset;
02140                 }
02141                 column_offset += (*iter)->mWidth + mColumnPadding;
02142         }
02143 
02144         // when running off the end, return the rightmost pixel
02145         return mItemListRect.mRight;
02146 }
02147 
02148 S32 LLScrollListCtrl::getRowOffsetFromIndex(S32 index)
02149 {
02150         S32 row_bottom = ((mItemListRect.mTop - (index - mScrollLines)) * mLineHeight) 
02151                                                 - mLineHeight;
02152         return row_bottom;
02153 }
02154 
02155 
02156 BOOL LLScrollListCtrl::handleHover(S32 x,S32 y,MASK mask)
02157 {
02158         BOOL    handled = FALSE;
02159 
02160         if (hasMouseCapture())
02161         {
02162                 if(mask == MASK_NONE)
02163                 {
02164                         selectItemAt(x, y, mask);
02165                         mNeedsScroll = TRUE;
02166                 }
02167         }
02168         else 
02169         if (mCanSelect)
02170         {
02171                 LLScrollListItem* item = hitItem(x, y);
02172                 if (item)
02173                 {
02174                         highlightNthItem(getItemIndex(item));
02175                 }
02176                 else
02177                 {
02178                         highlightNthItem(-1);
02179                 }
02180         }
02181 
02182         handled = LLUICtrl::handleHover( x, y, mask );
02183 
02184         return handled;
02185 }
02186 
02187 
02188 BOOL LLScrollListCtrl::handleKeyHere(KEY key,MASK mask )
02189 {
02190         BOOL handled = FALSE;
02191 
02192         // not called from parent means we have keyboard focus or a child does
02193         if (mCanSelect) 
02194         {
02195                 // Ignore capslock
02196                 mask = mask;
02197 
02198                 if (mask == MASK_NONE)
02199                 {
02200                         switch(key)
02201                         {
02202                         case KEY_UP:
02203                                 if (mAllowKeyboardMovement || hasFocus())
02204                                 {
02205                                         // commit implicit in call
02206                                         selectPrevItem(FALSE);
02207                                         mNeedsScroll = TRUE;
02208                                         handled = TRUE;
02209                                 }
02210                                 break;
02211                         case KEY_DOWN:
02212                                 if (mAllowKeyboardMovement || hasFocus())
02213                                 {
02214                                         // commit implicit in call
02215                                         selectNextItem(FALSE);
02216                                         mNeedsScroll = TRUE;
02217                                         handled = TRUE;
02218                                 }
02219                                 break;
02220                         case KEY_PAGE_UP:
02221                                 if (mAllowKeyboardMovement || hasFocus())
02222                                 {
02223                                         selectNthItem(getFirstSelectedIndex() - (mScrollbar->getPageSize() - 1));
02224                                         mNeedsScroll = TRUE;
02225                                         if (mCommitOnKeyboardMovement
02226                                                 && !mCommitOnSelectionChange) 
02227                                         {
02228                                                 onCommit();
02229                                         }
02230                                         handled = TRUE;
02231                                 }
02232                                 break;
02233                         case KEY_PAGE_DOWN:
02234                                 if (mAllowKeyboardMovement || hasFocus())
02235                                 {
02236                                         selectNthItem(getFirstSelectedIndex() + (mScrollbar->getPageSize() - 1));
02237                                         mNeedsScroll = TRUE;
02238                                         if (mCommitOnKeyboardMovement
02239                                                 && !mCommitOnSelectionChange) 
02240                                         {
02241                                                 onCommit();
02242                                         }
02243                                         handled = TRUE;
02244                                 }
02245                                 break;
02246                         case KEY_HOME:
02247                                 if (mAllowKeyboardMovement || hasFocus())
02248                                 {
02249                                         selectFirstItem();
02250                                         mNeedsScroll = TRUE;
02251                                         if (mCommitOnKeyboardMovement
02252                                                 && !mCommitOnSelectionChange) 
02253                                         {
02254                                                 onCommit();
02255                                         }
02256                                         handled = TRUE;
02257                                 }
02258                                 break;
02259                         case KEY_END:
02260                                 if (mAllowKeyboardMovement || hasFocus())
02261                                 {
02262                                         selectNthItem(getItemCount() - 1);
02263                                         mNeedsScroll = TRUE;
02264                                         if (mCommitOnKeyboardMovement
02265                                                 && !mCommitOnSelectionChange) 
02266                                         {
02267                                                 onCommit();
02268                                         }
02269                                         handled = TRUE;
02270                                 }
02271                                 break;
02272                         case KEY_RETURN:
02273                                 // JC - Special case: Only claim to have handled it
02274                                 // if we're the special non-commit-on-move
02275                                 // type. AND we are visible
02276                                 if (!mCommitOnKeyboardMovement && mask == MASK_NONE)
02277                                 {
02278                                         onCommit();
02279                                         mSearchString.clear();
02280                                         handled = TRUE;
02281                                 }
02282                                 break;
02283                         case KEY_BACKSPACE:
02284                                 mSearchTimer.reset();
02285                                 if (mSearchString.size())
02286                                 {
02287                                         mSearchString.erase(mSearchString.size() - 1, 1);
02288                                 }
02289                                 if (mSearchString.empty())
02290                                 {
02291                                         if (getFirstSelected())
02292                                         {
02293                                                 LLScrollListCell* cellp = getFirstSelected()->getColumn(mSearchColumn);
02294                                                 if (cellp)
02295                                                 {
02296                                                         cellp->highlightText(0, 0);
02297                                                 }
02298                                         }
02299                                 }
02300                                 else if (selectItemByPrefix(wstring_to_utf8str(mSearchString), FALSE))
02301                                 {
02302                                         mNeedsScroll = TRUE;
02303                                         // update search string only on successful match
02304                                         mSearchTimer.reset();
02305 
02306                                         if (mCommitOnKeyboardMovement
02307                                                 && !mCommitOnSelectionChange) 
02308                                         {
02309                                                 onCommit();
02310                                         }
02311                                 }
02312                                 break;
02313                         default:
02314                                 break;
02315                         }
02316                 }
02317                 // TODO: multiple: shift-up, shift-down, shift-home, shift-end, select all
02318         }
02319 
02320         return handled;
02321 }
02322 
02323 BOOL LLScrollListCtrl::handleUnicodeCharHere(llwchar uni_char)
02324 {
02325         if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL
02326         {
02327                 return FALSE;
02328         }
02329 
02330         // perform incremental search based on keyboard input
02331         if (mSearchTimer.getElapsedTimeF32() > LLUI::sConfigGroup->getF32("TypeAheadTimeout"))
02332         {
02333                 mSearchString.clear();
02334         }
02335 
02336         // type ahead search is case insensitive
02337         uni_char = LLStringOps::toLower((llwchar)uni_char);
02338 
02339         if (selectItemByPrefix(wstring_to_utf8str(mSearchString + (llwchar)uni_char), FALSE))
02340         {
02341                 // update search string only on successful match
02342                 mNeedsScroll = TRUE;
02343                 mSearchString += uni_char;
02344                 mSearchTimer.reset();
02345 
02346                 if (mCommitOnKeyboardMovement
02347                         && !mCommitOnSelectionChange) 
02348                 {
02349                         onCommit();
02350                 }
02351         }
02352         // handle iterating over same starting character
02353         else if (isRepeatedChars(mSearchString + (llwchar)uni_char) && !mItemList.empty())
02354         {
02355                 // start from last selected item, in case we previously had a successful match against
02356                 // duplicated characters ('AA' matches 'Aaron')
02357                 item_list::iterator start_iter = mItemList.begin();
02358                 S32 first_selected = getFirstSelectedIndex();
02359 
02360                 // if we have a selection (> -1) then point iterator at the selected item
02361                 if (first_selected > 0)
02362                 {
02363                         // point iterator to first selected item
02364                         start_iter += first_selected;
02365                 }
02366 
02367                 // start search at first item after current selection
02368                 item_list::iterator iter = start_iter;
02369                 ++iter;
02370                 if (iter == mItemList.end())
02371                 {
02372                         iter = mItemList.begin();
02373                 }
02374 
02375                 // loop around once, back to previous selection
02376                 while(iter != start_iter)
02377                 {
02378                         LLScrollListItem* item = *iter;
02379 
02380                         LLScrollListCell* cellp = item->getColumn(mSearchColumn);
02381                         if (cellp)
02382                         {
02383                                 // Only select enabled items with matching first characters
02384                                 LLWString item_label = utf8str_to_wstring(cellp->getValue().asString());
02385                                 if (item->getEnabled() && LLStringOps::toLower(item_label[0]) == uni_char)
02386                                 {
02387                                         selectItem(item);
02388                                         mNeedsScroll = TRUE;
02389                                         cellp->highlightText(0, 1);
02390                                         mSearchTimer.reset();
02391 
02392                                         if (mCommitOnKeyboardMovement
02393                                                 && !mCommitOnSelectionChange) 
02394                                         {
02395                                                 onCommit();
02396                                         }
02397 
02398                                         break;
02399                                 }
02400                         }
02401 
02402                         ++iter;
02403                         if (iter == mItemList.end())
02404                         {
02405                                 iter = mItemList.begin();
02406                         }
02407                 }
02408         }
02409 
02410         return TRUE;
02411 }
02412 
02413 
02414 void LLScrollListCtrl::reportInvalidInput()
02415 {
02416         make_ui_sound("UISndBadKeystroke");
02417 }
02418 
02419 BOOL LLScrollListCtrl::isRepeatedChars(const LLWString& string) const
02420 {
02421         if (string.empty())
02422         {
02423                 return FALSE;
02424         }
02425 
02426         llwchar first_char = string[0];
02427 
02428         for (U32 i = 0; i < string.size(); i++)
02429         {
02430                 if (string[i] != first_char)
02431                 {
02432                         return FALSE;
02433                 }
02434         }
02435 
02436         return TRUE;
02437 }
02438 
02439 void LLScrollListCtrl::selectItem(LLScrollListItem* itemp, BOOL select_single_item)
02440 {
02441         if (!itemp) return;
02442 
02443         if (!itemp->getSelected())
02444         {
02445                 if (mLastSelected)
02446                 {
02447                         LLScrollListCell* cellp = mLastSelected->getColumn(mSearchColumn);
02448                         if (cellp)
02449                         {
02450                                 cellp->highlightText(0, 0);
02451                         }
02452                 }
02453                 if (select_single_item)
02454                 {
02455                         deselectAllItems(TRUE);
02456                 }
02457                 itemp->setSelected(TRUE);
02458                 mLastSelected = itemp;
02459                 mSelectionChanged = TRUE;
02460         }
02461 }
02462 
02463 void LLScrollListCtrl::deselectItem(LLScrollListItem* itemp)
02464 {
02465         if (!itemp) return;
02466 
02467         if (itemp->getSelected())
02468         {
02469                 if (mLastSelected == itemp)
02470                 {
02471                         mLastSelected = NULL;
02472                 }
02473 
02474                 itemp->setSelected(FALSE);
02475                 LLScrollListCell* cellp = itemp->getColumn(mSearchColumn);
02476                 if (cellp)
02477                 {
02478                         cellp->highlightText(0, 0);     
02479                 }
02480                 mSelectionChanged = TRUE;
02481         }
02482 }
02483 
02484 void LLScrollListCtrl::commitIfChanged()
02485 {
02486         if (mSelectionChanged)
02487         {
02488                 mDirty = TRUE;
02489                 mSelectionChanged = FALSE;
02490                 onCommit();
02491         }
02492 }
02493 
02494 struct SameSortColumn
02495 {
02496         SameSortColumn(S32 column) : mColumn(column) {}
02497         S32 mColumn;
02498 
02499         bool operator()(std::pair<S32, BOOL> sort_column) { return sort_column.first == mColumn; }
02500 };
02501 
02502 BOOL LLScrollListCtrl::setSort(S32 column, BOOL ascending)
02503 {
02504         sort_column_t new_sort_column(column, ascending);
02505 
02506         if (mSortColumns.empty())
02507         {
02508                 mSortColumns.push_back(new_sort_column);
02509                 return TRUE;
02510         }
02511         else
02512         {       
02513                 // grab current sort column
02514                 sort_column_t cur_sort_column = mSortColumns.back();
02515                 
02516                 // remove any existing sort criterion referencing this column
02517                 // and add the new one
02518                 mSortColumns.erase(remove_if(mSortColumns.begin(), mSortColumns.end(), SameSortColumn(column)), mSortColumns.end()); 
02519                 mSortColumns.push_back(new_sort_column);
02520 
02521                 // did the sort criteria change?
02522                 return (cur_sort_column != new_sort_column);
02523         }
02524 }
02525 
02526 // Called by scrollbar
02527 //static
02528 void LLScrollListCtrl::onScrollChange( S32 new_pos, LLScrollbar* scrollbar, void* userdata )
02529 {
02530         LLScrollListCtrl* self = (LLScrollListCtrl*) userdata;
02531         self->mScrollLines = new_pos;
02532 }
02533 
02534 
02535 void LLScrollListCtrl::sortByColumn(LLString name, BOOL ascending)
02536 {
02537         if (name.empty())
02538         {
02539                 sortItems();
02540                 return;
02541         }
02542 
02543         std::map<LLString, LLScrollListColumn>::iterator itor = mColumns.find(name);
02544         if (itor != mColumns.end())
02545         {
02546                 sortByColumn((*itor).second.mIndex, ascending);
02547         }
02548 }
02549 
02550 // First column is column 0
02551 void  LLScrollListCtrl::sortByColumn(U32 column, BOOL ascending)
02552 {
02553         if (setSort(column, ascending))
02554         {
02555                 sortItems();
02556         }
02557 }
02558 
02559 void LLScrollListCtrl::sortItems()
02560 {
02561         // do stable sort to preserve any previous sorts
02562         std::stable_sort(
02563                 mItemList.begin(), 
02564                 mItemList.end(), 
02565                 SortScrollListItem(mSortColumns));
02566 
02567         setSorted(TRUE);
02568 }
02569 
02570 void LLScrollListCtrl::dirtyColumns() 
02571 { 
02572         mColumnsDirty = TRUE; 
02573 
02574         // need to keep mColumnsIndexed up to date
02575         // just in case someone indexes into it immediately
02576         mColumnsIndexed.resize(mColumns.size());
02577 
02578         std::map<LLString, LLScrollListColumn>::iterator column_itor;
02579         for (column_itor = mColumns.begin(); column_itor != mColumns.end(); ++column_itor)
02580         {
02581                 LLScrollListColumn *column = &column_itor->second;
02582                 mColumnsIndexed[column_itor->second.mIndex] = column;
02583         }
02584 }
02585 
02586 
02587 S32 LLScrollListCtrl::getScrollPos() const
02588 {
02589         return mScrollbar->getDocPos();
02590 }
02591 
02592 
02593 void LLScrollListCtrl::setScrollPos( S32 pos )
02594 {
02595         mScrollbar->setDocPos( pos );
02596 
02597         onScrollChange(mScrollbar->getDocPos(), mScrollbar, this);
02598 }
02599 
02600 
02601 void LLScrollListCtrl::scrollToShowSelected()
02602 {
02603         // don't scroll automatically when capturing mouse input
02604         // as that will change what is currently under the mouse cursor
02605         if (hasMouseCapture())
02606         {
02607                 return;
02608         }
02609 
02610         S32 index = getFirstSelectedIndex();
02611         if (index < 0)
02612         {
02613                 return;
02614         }
02615 
02616         LLScrollListItem* item = mItemList[index];
02617         if (!item)
02618         {
02619                 // I don't THINK this should ever happen.
02620                 return;
02621         }
02622 
02623         S32 lowest = mScrollLines;
02624         S32 highest = mScrollLines + mPageLines;
02625 
02626         if (index < lowest)
02627         {
02628                 // need to scroll to show item
02629                 setScrollPos(index);
02630         }
02631         else if (highest <= index)
02632         {
02633                 setScrollPos(index - mPageLines + 1);
02634         }
02635 }
02636 
02637 // virtual
02638 LLXMLNodePtr LLScrollListCtrl::getXML(bool save_children) const
02639 {
02640         LLXMLNodePtr node = LLUICtrl::getXML();
02641 
02642         // Attributes
02643 
02644         node->createChild("multi_select", TRUE)->setBoolValue(mAllowMultipleSelection);
02645 
02646         node->createChild("draw_border", TRUE)->setBoolValue((mBorder != NULL));
02647 
02648         node->createChild("draw_heading", TRUE)->setBoolValue(mDisplayColumnHeaders);
02649 
02650         node->createChild("background_visible", TRUE)->setBoolValue(mBackgroundVisible);
02651 
02652         node->createChild("draw_stripes", TRUE)->setBoolValue(mDrawStripes);
02653 
02654         node->createChild("column_padding", TRUE)->setIntValue(mColumnPadding);
02655 
02656         addColorXML(node, mBgWriteableColor, "bg_writeable_color", "ScrollBgWriteableColor");
02657         addColorXML(node, mBgReadOnlyColor, "bg_read_only_color", "ScrollBgReadOnlyColor");
02658         addColorXML(node, mBgSelectedColor, "bg_selected_color", "ScrollSelectedBGColor");
02659         addColorXML(node, mBgStripeColor, "bg_stripe_color", "ScrollBGStripeColor");
02660         addColorXML(node, mFgSelectedColor, "fg_selected_color", "ScrollSelectedFGColor");
02661         addColorXML(node, mFgUnselectedColor, "fg_unselected_color", "ScrollUnselectedColor");
02662         addColorXML(node, mFgDisabledColor, "fg_disable_color", "ScrollDisabledColor");
02663         addColorXML(node, mHighlightedColor, "highlighted_color", "ScrollHighlightedColor");
02664 
02665         // Contents
02666 
02667         std::map<LLString, LLScrollListColumn>::const_iterator itor;
02668         std::vector<const LLScrollListColumn*> sorted_list;
02669         sorted_list.resize(mColumns.size());
02670         for (itor = mColumns.begin(); itor != mColumns.end(); ++itor)
02671         {
02672                 sorted_list[itor->second.mIndex] = &itor->second;
02673         }
02674 
02675         std::vector<const LLScrollListColumn*>::iterator itor2;
02676         for (itor2 = sorted_list.begin(); itor2 != sorted_list.end(); ++itor2)
02677         {
02678                 LLXMLNodePtr child_node = node->createChild("column", FALSE);
02679                 const LLScrollListColumn *column = *itor2;
02680 
02681                 child_node->createChild("name", TRUE)->setStringValue(column->mName);
02682                 child_node->createChild("label", TRUE)->setStringValue(column->mLabel);
02683                 child_node->createChild("width", TRUE)->setIntValue(column->mWidth);
02684         }
02685 
02686         return node;
02687 }
02688 
02689 void LLScrollListCtrl::setScrollListParameters(LLXMLNodePtr node)
02690 {
02691         // James: This is not a good way to do colors. We need a central "UI style"
02692         // manager that sets the colors for ALL scroll lists, buttons, etc.
02693 
02694         LLColor4 color;
02695         if(node->hasAttribute("fg_unselected_color"))
02696         {
02697                 LLUICtrlFactory::getAttributeColor(node,"fg_unselected_color", color);
02698                 setFgUnselectedColor(color);
02699         }
02700         if(node->hasAttribute("fg_selected_color"))
02701         {
02702                 LLUICtrlFactory::getAttributeColor(node,"fg_selected_color", color);
02703                 setFgSelectedColor(color);
02704         }
02705         if(node->hasAttribute("bg_selected_color"))
02706         {
02707                 LLUICtrlFactory::getAttributeColor(node,"bg_selected_color", color);
02708                 setBgSelectedColor(color);
02709         }
02710         if(node->hasAttribute("fg_disable_color"))
02711         {
02712                 LLUICtrlFactory::getAttributeColor(node,"fg_disable_color", color);
02713                 setFgDisableColor(color);
02714         }
02715         if(node->hasAttribute("bg_writeable_color"))
02716         {
02717                 LLUICtrlFactory::getAttributeColor(node,"bg_writeable_color", color);
02718                 setBgWriteableColor(color);
02719         }
02720         if(node->hasAttribute("bg_read_only_color"))
02721         {
02722                 LLUICtrlFactory::getAttributeColor(node,"bg_read_only_color", color);
02723                 setReadOnlyBgColor(color);
02724         }
02725         if (LLUICtrlFactory::getAttributeColor(node,"bg_stripe_color", color))
02726         {
02727                 setBgStripeColor(color);
02728         }
02729         if (LLUICtrlFactory::getAttributeColor(node,"highlighted_color", color))
02730         {
02731                 setHighlightedColor(color);
02732         }
02733 
02734         if(node->hasAttribute("background_visible"))
02735         {
02736                 BOOL background_visible;
02737                 node->getAttributeBOOL("background_visible", background_visible);
02738                 setBackgroundVisible(background_visible);
02739         }
02740 
02741         if(node->hasAttribute("draw_stripes"))
02742         {
02743                 BOOL draw_stripes;
02744                 node->getAttributeBOOL("draw_stripes", draw_stripes);
02745                 setDrawStripes(draw_stripes);
02746         }
02747         
02748         if(node->hasAttribute("column_padding"))
02749         {
02750                 S32 column_padding;
02751                 node->getAttributeS32("column_padding", column_padding);
02752                 setColumnPadding(column_padding);
02753         }
02754 }
02755 
02756 // static
02757 LLView* LLScrollListCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory)
02758 {
02759         LLString name("scroll_list");
02760         node->getAttributeString("name", name);
02761 
02762         LLRect rect;
02763         createRect(node, rect, parent, LLRect());
02764 
02765         BOOL multi_select = FALSE;
02766         node->getAttributeBOOL("multi_select", multi_select);
02767 
02768         BOOL draw_border = TRUE;
02769         node->getAttributeBOOL("draw_border", draw_border);
02770 
02771         BOOL draw_heading = FALSE;
02772         node->getAttributeBOOL("draw_heading", draw_heading);
02773 
02774         S32 search_column = 0;
02775         node->getAttributeS32("search_column", search_column);
02776 
02777         S32 sort_column = -1;
02778         node->getAttributeS32("sort_column", sort_column);
02779 
02780         BOOL sort_ascending = TRUE;
02781         node->getAttributeBOOL("sort_ascending", sort_ascending);
02782 
02783         LLUICtrlCallback callback = NULL;
02784 
02785         LLScrollListCtrl* scroll_list = new LLScrollListCtrl(
02786                 name,
02787                 rect,
02788                 callback,
02789                 NULL,
02790                 multi_select,
02791                 draw_border);
02792 
02793         scroll_list->setDisplayHeading(draw_heading);
02794         if (node->hasAttribute("heading_height"))
02795         {
02796                 S32 heading_height;
02797                 node->getAttributeS32("heading_height", heading_height);
02798                 scroll_list->setHeadingHeight(heading_height);
02799         }
02800 
02801         scroll_list->setScrollListParameters(node);
02802 
02803         scroll_list->initFromXML(node, parent);
02804 
02805         scroll_list->setSearchColumn(search_column);
02806 
02807         if (sort_column >= 0)
02808         {
02809                 scroll_list->sortByColumn(sort_column, sort_ascending);
02810         }
02811 
02812         LLSD columns;
02813         S32 index = 0;
02814         LLXMLNodePtr child;
02815         S32 total_static = 0;
02816         for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling())
02817         {
02818                 if (child->hasName("column"))
02819                 {
02820                         LLString labelname("");
02821                         child->getAttributeString("label", labelname);
02822 
02823                         LLString columnname(labelname);
02824                         child->getAttributeString("name", columnname);
02825 
02826                         LLString sortname(columnname);
02827                         child->getAttributeString("sort", sortname);
02828                 
02829                         BOOL sort_ascending = TRUE;
02830                         child->getAttributeBOOL("sort_ascending", sort_ascending);
02831 
02832                         LLString imagename;
02833                         child->getAttributeString("image", imagename);
02834 
02835                         BOOL columndynamicwidth = FALSE;
02836                         child->getAttributeBOOL("dynamicwidth", columndynamicwidth);
02837 
02838                         S32 columnwidth = -1;
02839                         child->getAttributeS32("width", columnwidth);   
02840 
02841                         LLString tooltip;
02842                         child->getAttributeString("tool_tip", tooltip);
02843 
02844                         if(!columndynamicwidth) total_static += llmax(0, columnwidth);
02845 
02846                         F32 columnrelwidth = 0.f;
02847                         child->getAttributeF32("relwidth", columnrelwidth);
02848 
02849                         LLFontGL::HAlign h_align = LLFontGL::LEFT;
02850                         h_align = LLView::selectFontHAlign(child);
02851 
02852                         columns[index]["name"] = columnname;
02853                         columns[index]["sort"] = sortname;
02854                         columns[index]["sort_ascending"] = sort_ascending;
02855                         columns[index]["image"] = imagename;
02856                         columns[index]["label"] = labelname;
02857                         columns[index]["width"] = columnwidth;
02858                         columns[index]["relwidth"] = columnrelwidth;
02859                         columns[index]["dynamicwidth"] = columndynamicwidth;
02860                         columns[index]["halign"] = (S32)h_align;
02861                         columns[index]["tool_tip"] = tooltip;
02862                         
02863                         index++;
02864                 }
02865         }
02866         scroll_list->setTotalStaticColumnWidth(total_static);
02867         scroll_list->setColumnHeadings(columns);
02868 
02869         for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling())
02870         {
02871                 if (child->hasName("row"))
02872                 {
02873                         LLUUID id;
02874                         child->getAttributeUUID("id", id);
02875 
02876                         LLSD row;
02877 
02878                         row["id"] = id;
02879 
02880                         S32 column_idx = 0;
02881                         LLXMLNodePtr row_child;
02882                         for (row_child = child->getFirstChild(); row_child.notNull(); row_child = row_child->getNextSibling())
02883                         {
02884                                 if (row_child->hasName("column"))
02885                                 {
02886                                         LLString value = row_child->getTextContents();
02887 
02888                                         LLString columnname("");
02889                                         row_child->getAttributeString("name", columnname);
02890 
02891                                         LLString font("");
02892                                         row_child->getAttributeString("font", font);
02893 
02894                                         LLString font_style("");
02895                                         row_child->getAttributeString("font-style", font_style);
02896 
02897                                         row["columns"][column_idx]["column"] = columnname;
02898                                         row["columns"][column_idx]["value"] = value;
02899                                         row["columns"][column_idx]["font"] = font;
02900                                         row["columns"][column_idx]["font-style"] = font_style;
02901                                         column_idx++;
02902                                 }
02903                         }
02904                         scroll_list->addElement(row);
02905                 }
02906         }
02907 
02908         LLString contents = node->getTextContents();
02909         if (!contents.empty())
02910         {
02911                 typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
02912                 boost::char_separator<char> sep("\t\n");
02913                 tokenizer tokens(contents, sep);
02914                 tokenizer::iterator token_iter = tokens.begin();
02915 
02916                 while(token_iter != tokens.end())
02917                 {
02918                         const char* line = token_iter->c_str();
02919                         scroll_list->addSimpleElement(line);
02920                         ++token_iter;
02921                 }
02922         }
02923         
02924         return scroll_list;
02925 }
02926 
02927 // LLEditMenuHandler functions
02928 
02929 // virtual
02930 void    LLScrollListCtrl::copy()
02931 {
02932         LLString buffer;
02933 
02934         std::vector<LLScrollListItem*> items = getAllSelected();
02935         std::vector<LLScrollListItem*>::iterator itor;
02936         for (itor = items.begin(); itor != items.end(); ++itor)
02937         {
02938                 buffer += (*itor)->getContentsCSV() + "\n";
02939         }
02940         gClipboard.copyFromSubstring(utf8str_to_wstring(buffer), 0, buffer.length());
02941 }
02942 
02943 // virtual
02944 BOOL    LLScrollListCtrl::canCopy() const
02945 {
02946         return (getFirstSelected() != NULL);
02947 }
02948 
02949 // virtual
02950 void    LLScrollListCtrl::cut()
02951 {
02952         copy();
02953         doDelete();
02954 }
02955 
02956 // virtual
02957 BOOL    LLScrollListCtrl::canCut() const
02958 {
02959         return canCopy() && canDoDelete();
02960 }
02961 
02962 // virtual
02963 void    LLScrollListCtrl::selectAll()
02964 {
02965         // Deselects all other items
02966         item_list::iterator iter;
02967         for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
02968         {
02969                 LLScrollListItem *itemp = *iter;
02970                 if( itemp->getEnabled() )
02971                 {
02972                         selectItem(itemp, FALSE);
02973                 }
02974         }
02975 
02976         if (mCommitOnSelectionChange)
02977         {
02978                 commitIfChanged();
02979         }
02980 }
02981 
02982 // virtual
02983 BOOL    LLScrollListCtrl::canSelectAll() const
02984 {
02985         return getCanSelect() && mAllowMultipleSelection && !(mMaxSelectable > 0 && mItemList.size() > mMaxSelectable);
02986 }
02987 
02988 // virtual
02989 void    LLScrollListCtrl::deselect()
02990 {
02991         deselectAllItems();
02992 }
02993 
02994 // virtual
02995 BOOL    LLScrollListCtrl::canDeselect() const
02996 {
02997         return getCanSelect();
02998 }
02999 
03000 void LLScrollListCtrl::addColumn(const LLSD& column, EAddPosition pos)
03001 {
03002         LLString name = column["name"].asString();
03003         // if no column name provided, just use ordinal as name
03004         if (name.empty())
03005         {
03006                 std::ostringstream new_name;
03007                 new_name << mColumnsIndexed.size();
03008                 name = new_name.str();
03009         }
03010         if (mColumns.find(name) == mColumns.end())
03011         {
03012                 // Add column
03013                 mColumns[name] = LLScrollListColumn(column);
03014                 LLScrollListColumn* new_column = &mColumns[name];
03015                 new_column->mParentCtrl = this;
03016                 new_column->mIndex = mColumns.size()-1;
03017 
03018                 // Add button
03019                 if (new_column->mWidth > 0 || new_column->mRelWidth > 0 || new_column->mDynamicWidth)
03020                 {
03021                         if (new_column->mRelWidth >= 0)
03022                         {
03023                                 new_column->mWidth = (S32)llround(new_column->mRelWidth*mItemListRect.getWidth());
03024                         }
03025                         else if(new_column->mDynamicWidth)
03026                         {
03027                                 mNumDynamicWidthColumns++;
03028                                 new_column->mWidth = (mItemListRect.getWidth() - mTotalStaticColumnWidth) / mNumDynamicWidthColumns;
03029                         }
03030                         S32 top = mItemListRect.mTop;
03031                         S32 left = mItemListRect.mLeft;
03032                         {
03033                                 std::map<LLString, LLScrollListColumn>::iterator itor;
03034                                 for (itor = mColumns.begin(); itor != mColumns.end(); ++itor)
03035                                 {
03036                                         if (itor->second.mIndex < new_column->mIndex &&
03037                                                 itor->second.mWidth > 0)
03038                                         {
03039                                                 left += itor->second.mWidth + mColumnPadding;
03040                                         }
03041                                 }
03042                         }
03043                         LLString button_name = "btn_" + name;
03044                         S32 right = left+new_column->mWidth;
03045                         if (new_column->mIndex != (S32)mColumns.size()-1)
03046                         {
03047                                 right += mColumnPadding;
03048                         }
03049                         LLRect temp_rect = LLRect(left,top+mHeadingHeight,right,top);
03050                         new_column->mHeader = new LLColumnHeader(button_name, temp_rect, new_column); 
03051                         if(column["image"].asString() != "")
03052                         {
03053                                 //new_column->mHeader->setScaleImage(false);
03054                                 new_column->mHeader->setImage(column["image"].asString());                              
03055                         }
03056                         else
03057                         {
03058                                 new_column->mHeader->setLabel(new_column->mLabel);
03059                                 //new_column->mHeader->setLabel(new_column->mLabel);
03060                         }
03061 
03062                         new_column->mHeader->setToolTip(column["tool_tip"].asString());
03063 
03064                         //RN: although it might be useful to change sort order with the keyboard,
03065                         // mixing tab stops on child items along with the parent item is not supported yet
03066                         new_column->mHeader->setTabStop(FALSE);
03067                         addChild(new_column->mHeader);
03068                         new_column->mHeader->setVisible(mDisplayColumnHeaders);
03069 
03070                         sendChildToFront(mScrollbar);
03071                 }
03072         }
03073 
03074         dirtyColumns();
03075 }
03076 
03077 // static
03078 void LLScrollListCtrl::onClickColumn(void *userdata)
03079 {
03080         LLScrollListColumn *info = (LLScrollListColumn*)userdata;
03081         if (!info) return;
03082 
03083         LLScrollListCtrl *parent = info->mParentCtrl;
03084         if (!parent) return;
03085 
03086         S32 column_index = info->mIndex;
03087 
03088         LLScrollListColumn* column = parent->mColumnsIndexed[info->mIndex];
03089         bool ascending = column->mSortAscending;
03090         if (column->mSortingColumn != column->mName
03091                 && parent->mColumns.find(column->mSortingColumn) != parent->mColumns.end())
03092         {
03093                 LLScrollListColumn& info_redir = parent->mColumns[column->mSortingColumn];
03094                 column_index = info_redir.mIndex;
03095         }
03096 
03097         // if this column is the primary sort key, reverse the direction
03098         sort_column_t cur_sort_column;
03099         if (!parent->mSortColumns.empty() && parent->mSortColumns.back().first == column_index)
03100         {
03101                 ascending = !parent->mSortColumns.back().second;
03102         }
03103 
03104         parent->sortByColumn(column_index, ascending);
03105 
03106         if (parent->mOnSortChangedCallback)
03107         {
03108                 parent->mOnSortChangedCallback(parent->getCallbackUserData());
03109         }
03110 }
03111 
03112 std::string LLScrollListCtrl::getSortColumnName()
03113 {
03114         LLScrollListColumn* column = mSortColumns.empty() ? NULL : mColumnsIndexed[mSortColumns.back().first];
03115 
03116         if (column) return column->mName;
03117         else return "";
03118 }
03119 
03120 BOOL LLScrollListCtrl::needsSorting()
03121 {
03122         return !mSortColumns.empty();
03123 }
03124 
03125 void LLScrollListCtrl::clearColumns()
03126 {
03127         std::map<LLString, LLScrollListColumn>::iterator itor;
03128         for (itor = mColumns.begin(); itor != mColumns.end(); ++itor)
03129         {
03130                 LLColumnHeader *header = itor->second.mHeader;
03131                 if (header)
03132                 {
03133                         removeChild(header);
03134                         delete header;
03135                 }
03136         }
03137         mColumns.clear();
03138         mSortColumns.clear();
03139 }
03140 
03141 void LLScrollListCtrl::setColumnLabel(const LLString& column, const LLString& label)
03142 {
03143         std::map<LLString, LLScrollListColumn>::iterator itor = mColumns.find(column);
03144         if (itor != mColumns.end())
03145         {
03146                 itor->second.mLabel = label;
03147                 if (itor->second.mHeader)
03148                 {
03149                         itor->second.mHeader->setLabel(label);
03150                 }
03151         }
03152 }
03153 
03154 LLScrollListColumn* LLScrollListCtrl::getColumn(S32 index)
03155 {
03156         if (index < 0 || index >= (S32)mColumnsIndexed.size())
03157         {
03158                 return NULL;
03159         }
03160         return mColumnsIndexed[index];
03161 }
03162 
03163 void LLScrollListCtrl::setColumnHeadings(LLSD headings)
03164 {
03165         mColumns.clear();
03166         LLSD::array_const_iterator itor;
03167         for (itor = headings.beginArray(); itor != headings.endArray(); ++itor)
03168         {
03169                 addColumn(*itor);
03170         }
03171 }
03172 
03173 LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& value, EAddPosition pos, void* userdata)
03174 {
03175         // ID
03176         LLSD id = value["id"];
03177 
03178         LLScrollListItem *new_item = new LLScrollListItem(id, userdata);
03179         if (value.has("enabled"))
03180         {
03181                 new_item->setEnabled( value["enabled"].asBoolean() );
03182         }
03183 
03184         new_item->setNumColumns(mColumns.size());
03185 
03186         // Add any columns we don't already have
03187         LLSD columns = value["columns"];
03188         LLSD::array_const_iterator itor;
03189         S32 col_index = 0 ;
03190         for (itor = columns.beginArray(); itor != columns.endArray(); ++itor)
03191         {
03192                 if (itor->isUndefined())
03193                 {
03194                         // skip unused columns in item passed in
03195                         continue;
03196                 }
03197                 LLString column = (*itor)["column"].asString();
03198 
03199                 LLScrollListColumn* columnp = NULL;
03200 
03201                 // empty columns strings index by ordinal
03202                 if (column.empty())
03203                 {
03204                         std::ostringstream new_name;
03205                         new_name << col_index;
03206                         column = new_name.str();
03207                 }
03208 
03209                 std::map<LLString, LLScrollListColumn>::iterator column_itor;
03210                 column_itor = mColumns.find(column);
03211                 if (column_itor != mColumns.end()) 
03212                 {
03213                         columnp = &column_itor->second;
03214                 }
03215 
03216                 // create new column on demand
03217                 if (!columnp)
03218                 {
03219                         LLSD new_column;
03220                         new_column["name"] = column;
03221                         new_column["label"] = column;
03222                         // if width supplied for column, use it, otherwise 
03223                         // use adaptive width
03224                         if (itor->has("width"))
03225                         {
03226                                 new_column["width"] = (*itor)["width"];
03227                         }
03228                         else
03229                         {
03230                                 new_column["dynamicwidth"] = true;
03231                         }
03232                         addColumn(new_column);
03233                         columnp = &mColumns[column];
03234                         new_item->setNumColumns(mColumns.size());
03235                 }
03236 
03237                 S32 index = columnp->mIndex;
03238                 S32 width = columnp->mWidth;
03239                 LLFontGL::HAlign font_alignment = columnp->mFontAlignment;
03240 
03241                 LLSD value = (*itor)["value"];
03242                 LLString fontname = (*itor)["font"].asString();
03243                 LLString fontstyle = (*itor)["font-style"].asString();
03244                 LLString type = (*itor)["type"].asString();
03245                 BOOL has_color = (*itor).has("color");
03246                 LLColor4 color = ((*itor)["color"]);
03247                 BOOL enabled = !(*itor).has("enabled") || (*itor)["enabled"].asBoolean() == true;
03248 
03249                 const LLFontGL *font = LLResMgr::getInstance()->getRes(fontname);
03250                 if (!font)
03251                 {
03252                         font = LLResMgr::getInstance()->getRes( LLFONT_SANSSERIF_SMALL );
03253                 }
03254                 U8 font_style = LLFontGL::getStyleFromString(fontstyle);
03255 
03256                 if (type == "icon")
03257                 {
03258                         LLScrollListIcon* cell = new LLScrollListIcon(value, width);
03259                         if (has_color)
03260                         {
03261                                 cell->setColor(color);
03262                         }
03263                         new_item->setColumn(index, cell);
03264                 }
03265                 else if (type == "checkbox")
03266                 {
03267                         LLCheckBoxCtrl* ctrl = new LLCheckBoxCtrl("check",
03268                                                                                                                 LLRect(0, width, width, 0), " ");
03269                         ctrl->setEnabled(enabled);
03270                         ctrl->setValue(value);
03271                         LLScrollListCheck* cell = new LLScrollListCheck(ctrl,width);
03272                         if (has_color)
03273                         {
03274                                 cell->setColor(color);
03275                         }
03276                         new_item->setColumn(index, cell);
03277                 }
03278                 else if (type == "separator")
03279                 {
03280                         LLScrollListSeparator* cell = new LLScrollListSeparator(width);
03281                         if (has_color)
03282                         {
03283                                 cell->setColor(color);
03284                         }
03285                         new_item->setColumn(index, cell);
03286                 }
03287                 else
03288                 {
03289                         LLScrollListText* cell = new LLScrollListText(value.asString(), font, width, font_style, font_alignment);
03290                         if (has_color)
03291                         {
03292                                 cell->setColor(color);
03293                         }
03294                         new_item->setColumn(index, cell);
03295                         if (columnp->mHeader && !value.asString().empty())
03296                         {
03297                                 columnp->mHeader->setHasResizableElement(TRUE);
03298                         }
03299                 }
03300 
03301                 col_index++;
03302         }
03303 
03304         // add dummy cells for missing columns
03305         for (column_map_t::iterator column_it = mColumns.begin(); column_it != mColumns.end(); ++column_it)
03306         {
03307                 S32 column_idx = column_it->second.mIndex;
03308                 if (new_item->getColumn(column_idx) == NULL)
03309                 {
03310                         LLScrollListColumn* column_ptr = &column_it->second;
03311                         new_item->setColumn(column_idx, new LLScrollListText("", LLResMgr::getInstance()->getRes( LLFONT_SANSSERIF_SMALL ), column_ptr->mWidth, LLFontGL::NORMAL));
03312                 }
03313         }
03314 
03315         addItem(new_item, pos);
03316 
03317         return new_item;
03318 }
03319 
03320 LLScrollListItem* LLScrollListCtrl::addSimpleElement(const LLString& value, EAddPosition pos, const LLSD& id)
03321 {
03322         LLSD entry_id = id;
03323 
03324         if (id.isUndefined())
03325         {
03326                 entry_id = value;
03327         }
03328 
03329         LLScrollListItem *new_item = new LLScrollListItem(entry_id);
03330 
03331         const LLFontGL *font = LLResMgr::getInstance()->getRes( LLFONT_SANSSERIF_SMALL );
03332 
03333         new_item->addColumn(value, font, getRect().getWidth());
03334 
03335         addItem(new_item, pos);
03336         return new_item;
03337 }
03338 
03339 void LLScrollListCtrl::setValue(const LLSD& value )
03340 {
03341         LLSD::array_const_iterator itor;
03342         for (itor = value.beginArray(); itor != value.endArray(); ++itor)
03343         {
03344                 addElement(*itor);
03345         }
03346 }
03347 
03348 LLSD LLScrollListCtrl::getValue() const
03349 {
03350         LLScrollListItem *item = getFirstSelected();
03351         if (!item) return LLSD();
03352         return item->getValue();
03353 }
03354 
03355 BOOL LLScrollListCtrl::operateOnSelection(EOperation op)
03356 {
03357         if (op == OP_DELETE)
03358         {
03359                 deleteSelectedItems();
03360                 return TRUE;
03361         }
03362         else if (op == OP_DESELECT)
03363         {
03364                 deselectAllItems();
03365         }
03366         return FALSE;
03367 }
03368 
03369 BOOL LLScrollListCtrl::operateOnAll(EOperation op)
03370 {
03371         if (op == OP_DELETE)
03372         {
03373                 clearRows();
03374                 return TRUE;
03375         }
03376         else if (op == OP_DESELECT)
03377         {
03378                 deselectAllItems();
03379         }
03380         else if (op == OP_SELECT)
03381         {
03382                 selectAll();
03383         }
03384         return FALSE;
03385 }
03386 //virtual 
03387 void LLScrollListCtrl::setFocus(BOOL b)
03388 {
03389         mSearchString.clear();
03390         // for tabbing into pristine scroll lists (Finder)
03391         if (!getFirstSelected())
03392         {
03393                 selectFirstItem();
03394                 //onCommit(); // SJB: selectFirstItem() will call onCommit() if appropriate
03395         }
03396         LLUICtrl::setFocus(b);
03397 }
03398 
03399 
03400 // virtual 
03401 BOOL    LLScrollListCtrl::isDirty() const               
03402 {
03403         BOOL grubby = mDirty;
03404         if ( !mAllowMultipleSelection )
03405         {
03406                 grubby = (mOriginalSelection != getFirstSelectedIndex());
03407         }
03408         return grubby;
03409 }
03410 
03411 // Clear dirty state
03412 void LLScrollListCtrl::resetDirty()
03413 {
03414         mDirty = FALSE;
03415         mOriginalSelection = getFirstSelectedIndex();
03416 }
03417 
03418 
03419 //virtual
03420 void LLScrollListCtrl::onFocusReceived()
03421 {
03422         // forget latent selection changes when getting focus
03423         mSelectionChanged = FALSE;
03424         LLUICtrl::onFocusReceived();
03425 }
03426 
03427 //virtual 
03428 void LLScrollListCtrl::onFocusLost()
03429 {
03430         if (hasMouseCapture())
03431         {
03432                 gFocusMgr.setMouseCapture(NULL);
03433         }
03434         LLUICtrl::onFocusLost();
03435 }
03436 
03437 LLColumnHeader::LLColumnHeader(const LLString& label, const LLRect &rect, LLScrollListColumn* column, const LLFontGL* fontp) : 
03438         LLComboBox(label, rect, label, NULL, NULL), 
03439         mColumn(column),
03440         mOrigLabel(label),
03441         mShowSortOptions(FALSE),
03442         mHasResizableElement(FALSE)
03443 {
03444         mListPosition = LLComboBox::ABOVE;
03445         setCommitCallback(onSelectSort);
03446         setCallbackUserData(this);
03447         mButton->setTabStop(FALSE);
03448         // require at least two frames between mouse down and mouse up event to capture intentional "hold" not just bad framerate
03449         mButton->setHeldDownDelay(LLUI::sConfigGroup->getF32("ColumnHeaderDropDownDelay"), 2);
03450         mButton->setHeldDownCallback(onHeldDown);
03451         mButton->setClickedCallback(onClick);
03452         mButton->setMouseDownCallback(onMouseDown);
03453 
03454         mButton->setCallbackUserData(this);
03455 
03456         mAscendingText = "[LOW]...[HIGH](Ascending)";
03457         mDescendingText = "[HIGH]...[LOW](Descending)";
03458 
03459         mList->reshape(llmax(mList->getRect().getWidth(), 110, getRect().getWidth()), mList->getRect().getHeight());
03460 
03461         // resize handles on left and right
03462         const S32 RESIZE_BAR_THICKNESS = 3;
03463         mResizeBar = new LLResizeBar( 
03464                 "resizebar",
03465                 this,
03466                 LLRect( getRect().getWidth() - RESIZE_BAR_THICKNESS, getRect().getHeight(), getRect().getWidth(), 0), 
03467                 MIN_COLUMN_WIDTH, S32_MAX, LLResizeBar::RIGHT );
03468         addChild(mResizeBar);
03469 
03470         mResizeBar->setEnabled(FALSE);
03471 }
03472 
03473 LLColumnHeader::~LLColumnHeader()
03474 {
03475 }
03476 
03477 void LLColumnHeader::draw()
03478 {
03479         BOOL draw_arrow = !mColumn->mLabel.empty() && mColumn->mParentCtrl->isSorted() && mColumn->mParentCtrl->getSortColumnName() == mColumn->mSortingColumn;
03480 
03481         BOOL is_ascending = mColumn->mParentCtrl->getSortAscending();
03482         mButton->setImageOverlay(is_ascending ? "up_arrow.tga" : "down_arrow.tga", LLFontGL::RIGHT, draw_arrow ? LLColor4::white : LLColor4::transparent);
03483         mArrowImage = mButton->getImageOverlay();
03484 
03485         //BOOL clip = getRect().mRight > mColumn->mParentCtrl->getItemListRect().getWidth();
03486         //LLGLEnable scissor_test(clip ? GL_SCISSOR_TEST : GL_FALSE);
03487 
03488         //LLRect column_header_local_rect(-getRect().mLeft, getRect().getHeight(), mColumn->mParentCtrl->getItemListRect().getWidth() - getRect().mLeft, 0);
03489         //LLUI::setScissorRegionLocal(column_header_local_rect);
03490 
03491         // Draw children
03492         LLComboBox::draw();
03493 
03494         if (mList->getVisible())
03495         {
03496                 // sync sort order with list selection every frame
03497                 mColumn->mParentCtrl->sortByColumn(mColumn->mSortingColumn, getCurrentIndex() == 0);
03498         }
03499 }
03500 
03501 BOOL LLColumnHeader::handleDoubleClick(S32 x, S32 y, MASK mask)
03502 {
03503         if (canResize() && mResizeBar->getRect().pointInRect(x, y))
03504         {
03505                 // reshape column to max content width
03506                 LLRect column_rect = getRect();
03507                 column_rect.mRight = column_rect.mLeft + mColumn->mMaxContentWidth;
03508                 userSetShape(column_rect);
03509         }
03510         else
03511         {
03512                 onClick(this);
03513         }
03514         return TRUE;
03515 }
03516 
03517 void LLColumnHeader::setImage(const LLString &image_name)
03518 {
03519         if (mButton)
03520         {
03521                 mButton->setImageSelected(image_name);
03522                 mButton->setImageUnselected(image_name);
03523         }
03524 }
03525 
03526 //static
03527 void LLColumnHeader::onClick(void* user_data)
03528 {
03529         LLColumnHeader* headerp = (LLColumnHeader*)user_data;
03530         if (!headerp) return;
03531 
03532         LLScrollListColumn* column = headerp->mColumn;
03533         if (!column) return;
03534 
03535         if (headerp->mList->getVisible())
03536         {
03537                 headerp->hideList();
03538         }
03539 
03540         LLScrollListCtrl::onClickColumn(column);
03541 
03542         // propage new sort order to sort order list
03543         headerp->mList->selectNthItem(column->mParentCtrl->getSortAscending() ? 0 : 1);
03544 }
03545 
03546 //static
03547 void LLColumnHeader::onMouseDown(void* user_data)
03548 {
03549         // for now, do nothing but block the normal showList() behavior
03550         return;
03551 }
03552 
03553 //static
03554 void LLColumnHeader::onHeldDown(void* user_data)
03555 {
03556         LLColumnHeader* headerp = (LLColumnHeader*)user_data;
03557         headerp->showList();
03558 }
03559 
03560 void LLColumnHeader::showList()
03561 {
03562         if (mShowSortOptions)
03563         {
03564                 //LLSD item_val = mColumn->mParentCtrl->getFirstData()->getValue();
03565                 mOrigLabel = mButton->getLabelSelected();
03566 
03567                 // move sort column over to this column and do initial sort
03568                 mColumn->mParentCtrl->sortByColumn(mColumn->mSortingColumn, mColumn->mParentCtrl->getSortAscending());
03569 
03570                 LLString low_item_text;
03571                 LLString high_item_text;
03572 
03573                 LLScrollListItem* itemp = mColumn->mParentCtrl->getFirstData();
03574                 if (itemp)
03575                 {
03576                         LLScrollListCell* cell = itemp->getColumn(mColumn->mIndex);
03577                         if (cell && cell->isText())
03578                         {
03579                                 if (mColumn->mParentCtrl->getSortAscending())
03580                                 {
03581                                         low_item_text = cell->getValue().asString();
03582                                 }
03583                                 else
03584                                 {
03585                                         high_item_text = cell->getValue().asString();
03586                                 }
03587                         }
03588                 }
03589 
03590                 itemp = mColumn->mParentCtrl->getLastData();
03591                 if (itemp)
03592                 {
03593                         LLScrollListCell* cell = itemp->getColumn(mColumn->mIndex);
03594                         if (cell && cell->isText())
03595                         {
03596                                 if (mColumn->mParentCtrl->getSortAscending())
03597                                 {
03598                                         high_item_text = cell->getValue().asString();
03599                                 }
03600                                 else
03601                                 {
03602                                         low_item_text = cell->getValue().asString();
03603                                 }
03604                         }
03605                 }
03606 
03607                 LLString::truncate(low_item_text, 3);
03608                 LLString::truncate(high_item_text, 3);
03609 
03610                 LLString ascending_string;
03611                 LLString descending_string;
03612 
03613                 if (low_item_text.empty() || high_item_text.empty())
03614                 {
03615                         ascending_string = "Ascending";
03616                         descending_string = "Descending";
03617                 }
03618                 else
03619                 {
03620                         mAscendingText.setArg("[LOW]", low_item_text);
03621                         mAscendingText.setArg("[HIGH]", high_item_text);
03622                         mDescendingText.setArg("[LOW]", low_item_text);
03623                         mDescendingText.setArg("[HIGH]", high_item_text);
03624                         ascending_string = mAscendingText.getString();
03625                         descending_string = mDescendingText.getString();
03626                 }
03627 
03628                 S32 text_width = LLFontGL::sSansSerifSmall->getWidth(ascending_string);
03629                 text_width = llmax(text_width, LLFontGL::sSansSerifSmall->getWidth(descending_string)) + 10;
03630                 text_width = llmax(text_width, getRect().getWidth() - 30);
03631 
03632                 mList->getColumn(0)->mWidth = text_width;
03633                 ((LLScrollListText*)mList->getFirstData()->getColumn(0))->setText(ascending_string);
03634                 ((LLScrollListText*)mList->getLastData()->getColumn(0))->setText(descending_string);
03635 
03636                 mList->reshape(llmax(text_width + 30, 110, getRect().getWidth()), mList->getRect().getHeight());
03637 
03638                 LLComboBox::showList();
03639         }
03640 }
03641 
03642 //static 
03643 void LLColumnHeader::onSelectSort(LLUICtrl* ctrl, void* user_data)
03644 {
03645         LLColumnHeader* headerp = (LLColumnHeader*)user_data;
03646         if (!headerp) return;
03647 
03648         LLScrollListColumn* column = headerp->mColumn;
03649         if (!column) return;
03650         LLScrollListCtrl *parent = column->mParentCtrl;
03651         if (!parent) return;
03652 
03653         if (headerp->getCurrentIndex() == 0)
03654         {
03655                 // ascending
03656                 parent->sortByColumn(column->mSortingColumn, TRUE);
03657         }
03658         else
03659         {
03660                 // descending
03661                 parent->sortByColumn(column->mSortingColumn, FALSE);
03662         }
03663 
03664         // restore original column header
03665         headerp->setLabel(headerp->mOrigLabel);
03666 }
03667 
03668 LLView* LLColumnHeader::findSnapEdge(S32& new_edge_val, const LLCoordGL& mouse_dir, ESnapEdge snap_edge, ESnapType snap_type, S32 threshold, S32 padding)
03669 {
03670         // this logic assumes dragging on right
03671         llassert(snap_edge == SNAP_RIGHT);
03672 
03673         // use higher snap threshold for column headers
03674         threshold = llmin(threshold, 15);
03675 
03676         LLRect snap_rect = getSnapRect();
03677 
03678         S32 snap_delta = mColumn->mMaxContentWidth - snap_rect.getWidth();
03679 
03680         // x coord growing means column growing, so same signs mean we're going in right direction
03681         if (llabs(snap_delta) <= threshold && mouse_dir.mX * snap_delta > 0 ) 
03682         {
03683                 new_edge_val = snap_rect.mRight + snap_delta;
03684         }
03685         else 
03686         {
03687                 LLScrollListColumn* next_column = mColumn->mParentCtrl->getColumn(mColumn->mIndex + 1);
03688                 while (next_column)
03689                 {
03690                         if (next_column->mHeader)
03691                         {
03692                                 snap_delta = (next_column->mHeader->getSnapRect().mRight - next_column->mMaxContentWidth) - snap_rect.mRight;
03693                                 if (llabs(snap_delta) <= threshold && mouse_dir.mX * snap_delta > 0 ) 
03694                                 {
03695                                         new_edge_val = snap_rect.mRight + snap_delta;
03696                                 }
03697                                 break;
03698                         }
03699                         next_column = mColumn->mParentCtrl->getColumn(next_column->mIndex + 1);
03700                 }
03701         }
03702 
03703         return this;
03704 }
03705 
03706 void LLColumnHeader::userSetShape(const LLRect& new_rect)
03707 {
03708         S32 new_width = new_rect.getWidth();
03709         S32 delta_width = new_width - (getRect().getWidth() /*+ mColumn->mParentCtrl->getColumnPadding()*/);
03710 
03711         if (delta_width != 0)
03712         {
03713                 S32 remaining_width = delta_width;
03714                 S32 col;
03715                 for (col = mColumn->mIndex + 1; col < mColumn->mParentCtrl->getNumColumns(); col++)
03716                 {
03717                         LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col);
03718                         if (!columnp) break;
03719 
03720                         if (columnp->mHeader && columnp->mHeader->canResize())
03721                         {
03722                                 // how many pixels in width can this column afford to give up?
03723                                 S32 resize_buffer_amt = llmax(0, columnp->mWidth - MIN_COLUMN_WIDTH);
03724                                 
03725                                 // user shrinking column, need to add width to other columns
03726                                 if (delta_width < 0)
03727                                 {
03728                                         if (!columnp->mDynamicWidth && columnp->mWidth > 0)
03729                                         {
03730                                                 // statically sized column, give all remaining width to this column
03731                                                 columnp->mWidth -= remaining_width;
03732                                                 if (columnp->mRelWidth > 0.f)
03733                                                 {
03734                                                         columnp->mRelWidth = (F32)columnp->mWidth / (F32)mColumn->mParentCtrl->getItemListRect().getWidth();
03735                                                 }
03736                                         }
03737                                         break;
03738                                 }
03739                                 else
03740                                 {
03741                                         // user growing column, need to take width from other columns
03742                                         remaining_width -= resize_buffer_amt;
03743 
03744                                         if (!columnp->mDynamicWidth && columnp->mWidth > 0)
03745                                         {
03746                                                 columnp->mWidth -= llmin(columnp->mWidth - MIN_COLUMN_WIDTH, delta_width);
03747                                                 if (columnp->mRelWidth > 0.f)
03748                                                 {
03749                                                         columnp->mRelWidth = (F32)columnp->mWidth / (F32)mColumn->mParentCtrl->getItemListRect().getWidth();
03750                                                 }
03751                                         }
03752 
03753                                         if (remaining_width <= 0)
03754                                         {
03755                                                 // width sucked up from neighboring columns, done
03756                                                 break;
03757                                         }
03758                                 }
03759                         }
03760                 }
03761 
03762                 // clamp resize amount to maximum that can be absorbed by other columns
03763                 if (delta_width > 0)
03764                 {
03765                         delta_width -= llmax(remaining_width, 0);
03766                 }
03767 
03768                 // propagate constrained delta_width to new width for this column
03769                 new_width = getRect().getWidth() + delta_width - mColumn->mParentCtrl->getColumnPadding();
03770 
03771                 // use requested width
03772                 mColumn->mWidth = new_width;
03773 
03774                 // update proportional spacing
03775                 if (mColumn->mRelWidth > 0.f)
03776                 {
03777                         mColumn->mRelWidth = (F32)new_width / (F32)mColumn->mParentCtrl->getItemListRect().getWidth();
03778                 }
03779 
03780                 // tell scroll list to layout columns again
03781                 // do immediate update to get proper feedback to resize handle
03782                 // which needs to know how far the resize actually went
03783                 mColumn->mParentCtrl->updateColumns();
03784         }
03785 }
03786 
03787 void LLColumnHeader::setHasResizableElement(BOOL resizable)
03788 {
03789         // for now, dynamically spaced columns can't be resized
03790         if (mColumn->mDynamicWidth) return;
03791 
03792         if (resizable != mHasResizableElement)
03793         {
03794                 mHasResizableElement = resizable;
03795 
03796                 S32 num_resizable_columns = 0;
03797                 S32 col;
03798                 for (col = 0; col < mColumn->mParentCtrl->getNumColumns(); col++)
03799                 {
03800                         LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col);
03801                         if (columnp->mHeader && columnp->mHeader->canResize())
03802                         {
03803                                 num_resizable_columns++;
03804                         }
03805                 }
03806 
03807                 S32 num_resizers_enabled = 0;
03808 
03809                 // now enable/disable resize handles on resizable columns if we have at least two
03810                 for (col = 0; col < mColumn->mParentCtrl->getNumColumns(); col++)
03811                 {
03812                         LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col);
03813                         if (!columnp->mHeader) continue;
03814                         BOOL enable = num_resizable_columns >= 2 && num_resizers_enabled < (num_resizable_columns - 1) && columnp->mHeader->canResize();
03815                         columnp->mHeader->enableResizeBar(enable);
03816                         if (enable)
03817                         {
03818                                 num_resizers_enabled++;
03819                         }
03820                 }
03821         }
03822 }
03823 
03824 void LLColumnHeader::enableResizeBar(BOOL enable)
03825 {
03826         // for now, dynamically spaced columns can't be resized
03827         if (!mColumn->mDynamicWidth)
03828         {
03829                 mResizeBar->setEnabled(enable);
03830         }
03831 }
03832 
03833 BOOL LLColumnHeader::canResize()
03834 {
03835         return getVisible() && (mHasResizableElement || mColumn->mDynamicWidth);
03836 }

Generated on Fri May 16 08:32:57 2008 for SecondLife by  doxygen 1.5.5