-Color Fixies- Fixed Gemini skin not looking as it originally did. We now use DefaultListText to color all otherwise uncolored text in scroll list cells. All of our dark skins have been updated to use white here, as dark skins are intended to have white text... This includes the Dark we ship with. LLFloaterActiveSpeakers no longer uses hard coded font colors, it uses SpeakersInactive, DefaultListText, and SpeakersGhost. LLFloaterAvatarList no longer uses hard coded font colors, it uses DefaultListText, RadarTextChatRange, RadarTextShoutRange, and RadarTextDrawDist, in place of previously hard coded font colors Since the tooltip defines color meaning, these new colors should only be skinned to change shade. DefaultListText defaults to black; SpeaksInactive, gray(grey4); SpeakersGhost, Red; RadarTextChatRange, Red; RadarTextShoutRange, Yellow(yellow1); RadarTextDrawDist, Green(green2). -Translation update and fixies- Partial credit to viewer-development, thanks for hanging onto old strings! Also, updated to look more like v-d in translated areas. Punctuation strings can be used quite a lot. Brought them all in, just in case. AscentPrefs*: Drag and Drop points now use CurrentlySetTo, CurrentlyNotSet, AnItemNotOnThisAccount, and NotLoggedIn. (applies, as well, to FloaterAO) Power User message is now built from PowerUser1, PowerUser2, Unlocked:, PowerUser3, RightClick, PowerUser4 and PowerUser5; this should give translators enough space to explain any tough to translate, and make the message easier to alter in the future, if necessary. LLCompileQueue: Now uses translation strings from upstream Starting, Done, Resetting, Running, and NotRunning in its xml. CompileQueueTitle, CompileQueueStart, CompileQueueDownloadedCompiling, CompileQueueScriptNotFound, CompileQueueProblemDownloading, CompileQueueInsufficientPermDownload, CompileQueueInsufficientPermFor, CompileQueueUnknownFailure, ResetQueueTitle, ResetQueueStart, NotRunQueueTitle, and NotRunQueueStart from strings. LLFloaterAvatarList(floater_radar) now uses has_entered, has_left, the_sim, draw_distance, shout_range, and chat_range in its xml for translatable alerts. LLFloaterLand(floater_about_land) now uses minutes, 1_minute, 1_second, seconds, and remaining from its xml. LLFloaterLand, LLFolderView, LLPanelDirBrowser now make use of InventoryNoMatchingItems, NoneFound, and Searching in their searches. LLFolderView was brought closer to v-d in translation. LLGroupNotify now uses GroupNotifyGroupNotice, GroupNotifySentBy, GroupNotifyAttached, next (also now used by LLNotify), ok, GroupNotifyGroupNotices, GroupNotifyViewPastNotices, GroupNotifyOpenAttachment, and GroupNotifySaveAttachment. LLInventoryFilter synced with V-D for translation: Now uses Animations, Calling Cards, Clothing, Gestures, Landmarks, Notecards, Objects, Scripts, Sounds, Textures, Snapshots, No Filters, Since Logoff, and Worn. LLManipRotate now uses Direction_Forward, Direction_Left, Direction_Right, Direction_Back, Direction_North, Direction_South, Direction_West, Direction_East, Direction_Up, and Direction_Down, like upstream v-d. LLPanelAvatar(panel_avatar) now uses None string in its xml for when there are no groups, also removed cruft. Though the None has not been showing up for quite some time, anyway... LLPanelObjectInventory now uses Buy, LoadingContents and NoContents, however the last two strings did not seem to show up anyway... thanks to Latif Khalifa, Jean Horten, theGenius Indigo and Cubbi Bearcat for confirming this happens across many different versions and on both Windows and linux(32 and 64). LLPreviewScript now uses CompileSuccessful, SaveComplete, ObjectOutOfRange, and CompileSuccessfulSaving. LLScrollingPanelParam now translates Less and More. Avatar Shape Information strings now used for customize appearance panels. LLTextureCtrl now uses multiple_textures. LLToolpie has been updated to use v-d include order and now uses UnmuteAvatar, MuteAvatar, UnmuteObject, and MuteObject2 strings. LLVOAvatarSelf now uses BodyParts* strings for translation of toolpie entries pertaining to bodyparts. LLViewerMenuFile now uses UnknownFileExtension, and UploadingCosts. LLViewerMessage now uses Cancel(also used by LLViewerDisplay), AcquiredItems, Saved_message, IM_autoresponse_sent_item, IM_autoresponded_to, IM_announce_incoming, InvOfferDecline, InvOfferGaveYou, InvOfferOwnedByUnknownUser, InvOfferAnObjectNamed, InvOfferOwnedBy, InvOfferOwnedByUnknownGroup, and InvOfferOwnedByGroup. -AvatarRadar Update- AvatarRadar enhanced with code from both Cool VL Viewer and some avian corpse. e_radar_alert_type has been reworked to allow bitwise usage in the future. Cool stuff: gSavedSettings for radar are now CachedControls, entering boolean is now checked before going into the switchcase, Style changes, yay C++ style. Avian Flu: Distance for shout range corrected to be 96. handleKeyHere: If the user hits enter with an avatar on the radar selected, focus camera on selected avatar; ctrl-enter, teleport to selected avatar. Otherwise: Tiny spelling fixies, and a suggestive comment.
4283 lines
100 KiB
C++
4283 lines
100 KiB
C++
/**
|
|
* @file llscrolllistctrl.cpp
|
|
* @brief LLScrollListCtrl base class
|
|
*
|
|
* $LicenseInfo:firstyear=2001&license=viewergpl$
|
|
*
|
|
* Copyright (c) 2001-2009, Linden Research, Inc.
|
|
*
|
|
* Second Life Viewer Source Code
|
|
* The source code in this file ("Source Code") is provided by Linden Lab
|
|
* to you under the terms of the GNU General Public License, version 2.0
|
|
* ("GPL"), unless you have obtained a separate licensing agreement
|
|
* ("Other License"), formally executed by you and Linden Lab. Terms of
|
|
* the GPL can be found in doc/GPL-license.txt in this distribution, or
|
|
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
|
|
*
|
|
* There are special exceptions to the terms and conditions of the GPL as
|
|
* it is applied to this Source Code. View the full text of the exception
|
|
* in the file doc/FLOSS-exception.txt in this software distribution, or
|
|
* online at
|
|
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
|
|
*
|
|
* By copying, modifying or distributing this software, you acknowledge
|
|
* that you have read and understood your obligations described above,
|
|
* and agree to abide by those obligations.
|
|
*
|
|
* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
|
|
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
|
|
* COMPLETENESS OR PERFORMANCE.
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#include <algorithm>
|
|
|
|
#include "linden_common.h"
|
|
#include "llstl.h"
|
|
#include "llboost.h"
|
|
|
|
#include "llscrolllistctrl.h"
|
|
|
|
#include "indra_constants.h"
|
|
|
|
#include "llcheckboxctrl.h"
|
|
#include "llclipboard.h"
|
|
#include "llfocusmgr.h"
|
|
#include "lllocalcliprect.h"
|
|
#include "llrender.h"
|
|
#include "llresmgr.h"
|
|
#include "llscrollbar.h"
|
|
#include "llstring.h"
|
|
#include "llui.h"
|
|
#include "lluictrlfactory.h"
|
|
#include "llwindow.h"
|
|
#include "llcontrol.h"
|
|
#include "llkeyboard.h"
|
|
#include "llresizebar.h"
|
|
// <edit>
|
|
#include "lllineeditor.h"
|
|
// </edit>
|
|
|
|
const S32 MIN_COLUMN_WIDTH = 20;
|
|
const S32 LIST_SNAP_PADDING = 5;
|
|
|
|
static LLRegisterWidget<LLScrollListCtrl> r("scroll_list");
|
|
|
|
// local structures & classes.
|
|
struct SortScrollListItem
|
|
{
|
|
SortScrollListItem(const std::vector<std::pair<S32, BOOL> >& sort_orders)
|
|
: mSortOrders(sort_orders)
|
|
{}
|
|
|
|
bool operator()(const LLScrollListItem* i1, const LLScrollListItem* i2)
|
|
{
|
|
// sort over all columns in order specified by mSortOrders
|
|
S32 sort_result = 0;
|
|
for (sort_order_t::const_reverse_iterator it = mSortOrders.rbegin();
|
|
it != mSortOrders.rend(); ++it)
|
|
{
|
|
S32 col_idx = it->first;
|
|
BOOL sort_ascending = it->second;
|
|
|
|
const LLScrollListCell *cell1 = i1->getColumn(col_idx);
|
|
const LLScrollListCell *cell2 = i2->getColumn(col_idx);
|
|
S32 order = sort_ascending ? 1 : -1; // ascending or descending sort for this column?
|
|
if (cell1 && cell2)
|
|
{
|
|
sort_result = order * LLStringUtil::compareDict(cell1->getValue().asString(), cell2->getValue().asString());
|
|
if (sort_result != 0)
|
|
{
|
|
break; // we have a sort order!
|
|
}
|
|
}
|
|
}
|
|
|
|
return sort_result < 0;
|
|
}
|
|
|
|
typedef std::vector<std::pair<S32, BOOL> > sort_order_t;
|
|
const sort_order_t& mSortOrders;
|
|
};
|
|
|
|
|
|
//
|
|
// LLScrollListIcon
|
|
//
|
|
LLScrollListIcon::LLScrollListIcon(LLUIImagePtr icon, S32 width)
|
|
: LLScrollListCell(width),
|
|
mIcon(icon),
|
|
// <edit>
|
|
mCallback(NULL),
|
|
mUserData(NULL),
|
|
// </edit>
|
|
mColor(LLColor4::white)
|
|
{
|
|
}
|
|
|
|
LLScrollListIcon::LLScrollListIcon(const LLSD& value, S32 width)
|
|
: LLScrollListCell(width),
|
|
// <edit>
|
|
mCallback(NULL),
|
|
mUserData(NULL),
|
|
// </edit>
|
|
mColor(LLColor4::white)
|
|
{
|
|
setValue(value);
|
|
}
|
|
|
|
|
|
LLScrollListIcon::~LLScrollListIcon()
|
|
{
|
|
}
|
|
|
|
void LLScrollListIcon::setValue(const LLSD& value)
|
|
{
|
|
if (value.isUUID())
|
|
{
|
|
// don't use default image specified by LLUUID::null, use no image in that case
|
|
LLUUID image_id = value.asUUID();
|
|
mIcon = image_id.notNull() ? LLUI::getUIImageByID(image_id) : LLUIImagePtr(NULL);
|
|
}
|
|
else
|
|
{
|
|
std::string value_string = value.asString();
|
|
if (LLUUID::validate(value_string))
|
|
{
|
|
setValue(LLUUID(value_string));
|
|
}
|
|
else if (!value_string.empty())
|
|
{
|
|
mIcon = LLUI::getUIImage(value.asString());
|
|
}
|
|
else
|
|
{
|
|
mIcon = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void LLScrollListIcon::setColor(const LLColor4& color)
|
|
{
|
|
mColor = color;
|
|
}
|
|
|
|
S32 LLScrollListIcon::getWidth() const
|
|
{
|
|
// if no specified fix width, use width of icon
|
|
if (LLScrollListCell::getWidth() == 0 && mIcon.notNull())
|
|
{
|
|
return mIcon->getWidth();
|
|
}
|
|
return LLScrollListCell::getWidth();
|
|
}
|
|
|
|
// <edit>
|
|
void LLScrollListIcon::setClickCallback(BOOL (*callback)(void*), void* user_data)
|
|
{
|
|
mCallback = callback;
|
|
mUserData = user_data;
|
|
}
|
|
|
|
BOOL LLScrollListIcon::handleClick()
|
|
{
|
|
if(mCallback) return mCallback(mUserData);
|
|
return FALSE;
|
|
}
|
|
// </edit>
|
|
|
|
void LLScrollListIcon::draw(const LLColor4& color, const LLColor4& highlight_color) const
|
|
{
|
|
if (mIcon)
|
|
{
|
|
mIcon->draw(0, 0, mColor);
|
|
}
|
|
}
|
|
|
|
//
|
|
// LLScrollListCheck
|
|
//
|
|
LLScrollListCheck::LLScrollListCheck(LLCheckBoxCtrl* check_box, S32 width)
|
|
{
|
|
mCheckBox = check_box;
|
|
LLRect rect(mCheckBox->getRect());
|
|
if (width)
|
|
{
|
|
|
|
rect.mRight = rect.mLeft + width;
|
|
mCheckBox->setRect(rect);
|
|
setWidth(width);
|
|
}
|
|
else
|
|
{
|
|
setWidth(rect.getWidth()); //check_box->getWidth();
|
|
}
|
|
}
|
|
|
|
LLScrollListCheck::~LLScrollListCheck()
|
|
{
|
|
delete mCheckBox;
|
|
}
|
|
|
|
void LLScrollListCheck::draw(const LLColor4& color, const LLColor4& highlight_color) const
|
|
{
|
|
mCheckBox->draw();
|
|
}
|
|
|
|
BOOL LLScrollListCheck::handleClick()
|
|
{
|
|
if (mCheckBox->getEnabled())
|
|
{
|
|
mCheckBox->toggle();
|
|
}
|
|
// don't change selection when clicking on embedded checkbox
|
|
return TRUE;
|
|
}
|
|
|
|
// <edit>
|
|
//
|
|
// LLScrollListLineEditor
|
|
//
|
|
LLScrollListLineEditor::LLScrollListLineEditor(LLLineEditor* line_editor, S32 width)
|
|
{
|
|
mLineEditor = line_editor;
|
|
LLRect rect(mLineEditor->getRect());
|
|
if (width)
|
|
{
|
|
|
|
rect.mRight = rect.mLeft + width;
|
|
mLineEditor->setRect(rect);
|
|
setWidth(width);
|
|
}
|
|
else
|
|
{
|
|
setWidth(rect.getWidth()); //line_editor->getWidth();
|
|
}
|
|
}
|
|
|
|
LLScrollListLineEditor::~LLScrollListLineEditor()
|
|
{
|
|
delete mLineEditor;
|
|
}
|
|
|
|
void LLScrollListLineEditor::draw(const LLColor4& color, const LLColor4& highlight_color) const
|
|
{
|
|
mLineEditor->draw();
|
|
}
|
|
|
|
BOOL LLScrollListLineEditor::handleClick()
|
|
{
|
|
if (mLineEditor->getEnabled())
|
|
{
|
|
mLineEditor->setFocus(TRUE);
|
|
mLineEditor->selectAll();
|
|
}
|
|
// return value changes selection?
|
|
return FALSE; //TRUE;
|
|
}
|
|
|
|
BOOL LLScrollListLineEditor::handleUnicodeChar(llwchar uni_char, BOOL called_from_parent)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL LLScrollListLineEditor::handleUnicodeCharHere(llwchar uni_char )
|
|
{
|
|
return TRUE;
|
|
}
|
|
// </edit>
|
|
|
|
//
|
|
// LLScrollListSeparator
|
|
//
|
|
LLScrollListSeparator::LLScrollListSeparator(S32 width) : LLScrollListCell(width)
|
|
{
|
|
}
|
|
|
|
//virtual
|
|
S32 LLScrollListSeparator::getHeight() const
|
|
{
|
|
return 5;
|
|
}
|
|
|
|
|
|
void LLScrollListSeparator::draw(const LLColor4& color, const LLColor4& highlight_color) const
|
|
{
|
|
// *FIXME: use dynamic item heights and make separators narrow, and inactive
|
|
gl_line_2d(5, 8, llmax(5, getWidth() - 5), 8, color);
|
|
}
|
|
|
|
//
|
|
// LLScrollListText
|
|
//
|
|
U32 LLScrollListText::sCount = 0;
|
|
|
|
LLScrollListText::LLScrollListText( const std::string& text, const LLFontGL* font, S32 width, U8 font_style, LLFontGL::HAlign font_alignment, LLColor4& color, BOOL use_color, BOOL visible)
|
|
: LLScrollListCell(width),
|
|
mText( text ),
|
|
mFont( font ),
|
|
mColor(color),
|
|
mUseColor(use_color),
|
|
mFontStyle( font_style ),
|
|
mFontAlignment( font_alignment ),
|
|
mVisible( visible ),
|
|
mHighlightCount( 0 ),
|
|
mHighlightOffset( 0 )
|
|
{
|
|
sCount++;
|
|
|
|
// initialize rounded rect image
|
|
if (!mRoundedRectImage)
|
|
{
|
|
mRoundedRectImage = LLUI::getUIImage("rounded_square.tga");
|
|
}
|
|
}
|
|
//virtual
|
|
void LLScrollListText::highlightText(S32 offset, S32 num_chars)
|
|
{
|
|
mHighlightOffset = offset;
|
|
mHighlightCount = num_chars;
|
|
}
|
|
|
|
//virtual
|
|
BOOL LLScrollListText::isText() const
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
//virtual
|
|
BOOL LLScrollListText::getVisible() const
|
|
{
|
|
return mVisible;
|
|
}
|
|
|
|
//virtual
|
|
S32 LLScrollListText::getHeight() const
|
|
{
|
|
return llround(mFont->getLineHeight());
|
|
}
|
|
|
|
|
|
LLScrollListText::~LLScrollListText()
|
|
{
|
|
sCount--;
|
|
}
|
|
|
|
S32 LLScrollListText::getContentWidth() const
|
|
{
|
|
return mFont->getWidth(mText.getString());
|
|
}
|
|
|
|
|
|
void LLScrollListText::setColor(const LLColor4& color)
|
|
{
|
|
mColor = color;
|
|
mUseColor = TRUE;
|
|
}
|
|
|
|
void LLScrollListText::setText(const LLStringExplicit& text)
|
|
{
|
|
mText = text;
|
|
}
|
|
|
|
//virtual
|
|
void LLScrollListText::setValue(const LLSD& text)
|
|
{
|
|
setText(text.asString());
|
|
}
|
|
|
|
//virtual
|
|
const LLSD LLScrollListText::getValue() const
|
|
{
|
|
return LLSD(mText.getString());
|
|
}
|
|
|
|
|
|
void LLScrollListText::draw(const LLColor4& color, const LLColor4& highlight_color) const
|
|
{
|
|
LLColor4 display_color;
|
|
if (mUseColor)
|
|
{
|
|
display_color = mColor;
|
|
}
|
|
else
|
|
{
|
|
display_color = color;
|
|
}
|
|
|
|
if (mHighlightCount > 0)
|
|
{
|
|
S32 left = 0;
|
|
switch(mFontAlignment)
|
|
{
|
|
case LLFontGL::LEFT:
|
|
left = mFont->getWidth(mText.getString(), 0, mHighlightOffset);
|
|
break;
|
|
case LLFontGL::RIGHT:
|
|
left = getWidth() - mFont->getWidth(mText.getString(), mHighlightOffset, S32_MAX);
|
|
break;
|
|
case LLFontGL::HCENTER:
|
|
left = (getWidth() - mFont->getWidth(mText.getString())) / 2;
|
|
break;
|
|
}
|
|
LLRect highlight_rect(left - 2,
|
|
llround(mFont->getLineHeight()) + 1,
|
|
left + mFont->getWidth(mText.getString(), mHighlightOffset, mHighlightCount) + 1,
|
|
1);
|
|
mRoundedRectImage->draw(highlight_rect, highlight_color);
|
|
}
|
|
|
|
// Try to draw the entire string
|
|
F32 right_x;
|
|
U32 string_chars = mText.length();
|
|
F32 start_x = 0.f;
|
|
switch(mFontAlignment)
|
|
{
|
|
case LLFontGL::LEFT:
|
|
start_x = (mFontStyle & LLFontGL::ITALIC) ? 2.f : 0.f; //Italic text seems need a little padding.
|
|
break;
|
|
case LLFontGL::RIGHT:
|
|
start_x = (F32)getWidth();
|
|
break;
|
|
case LLFontGL::HCENTER:
|
|
start_x = (F32)getWidth() * 0.5f;
|
|
break;
|
|
}
|
|
mFont->render(mText.getWString(), 0,
|
|
start_x, 2.f,
|
|
display_color,
|
|
mFontAlignment,
|
|
LLFontGL::BOTTOM,
|
|
mFontStyle,
|
|
LLFontGL::NO_SHADOW,
|
|
string_chars,
|
|
getWidth(),
|
|
&right_x,
|
|
FALSE,
|
|
TRUE);
|
|
}
|
|
|
|
LLScrollListDate::LLScrollListDate( const LLDate& date, const LLFontGL* font, S32 width, U8 font_style, LLFontGL::HAlign font_alignment, LLColor4& color, BOOL use_color, BOOL visible)
|
|
: LLScrollListText(date.asRFC1123(), font, width, font_style, font_alignment, color, use_color, visible),
|
|
mDate(date)
|
|
{
|
|
}
|
|
|
|
void LLScrollListDate::setValue(const LLSD& value)
|
|
{
|
|
mDate = value.asDate();
|
|
LLScrollListText::setValue(mDate.asRFC1123());
|
|
}
|
|
|
|
const LLSD LLScrollListDate::getValue() const
|
|
{
|
|
return mDate;
|
|
}
|
|
|
|
LLScrollListItem::~LLScrollListItem()
|
|
{
|
|
std::for_each(mColumns.begin(), mColumns.end(), DeletePointer());
|
|
}
|
|
|
|
void LLScrollListItem::setNumColumns(S32 columns)
|
|
{
|
|
S32 prev_columns = mColumns.size();
|
|
if (columns < prev_columns)
|
|
{
|
|
std::for_each(mColumns.begin()+columns, mColumns.end(), DeletePointer());
|
|
}
|
|
|
|
mColumns.resize(columns);
|
|
|
|
for (S32 col = prev_columns; col < columns; ++col)
|
|
{
|
|
mColumns[col] = NULL;
|
|
}
|
|
}
|
|
|
|
void LLScrollListItem::setColumn( S32 column, LLScrollListCell *cell )
|
|
{
|
|
if (column < (S32)mColumns.size())
|
|
{
|
|
delete mColumns[column];
|
|
mColumns[column] = cell;
|
|
}
|
|
else
|
|
{
|
|
llerrs << "LLScrollListItem::setColumn: bad column: " << column << llendl;
|
|
}
|
|
}
|
|
|
|
std::string LLScrollListItem::getContentsCSV() const
|
|
{
|
|
std::string ret;
|
|
|
|
S32 count = getNumColumns();
|
|
for (S32 i=0; i<count; ++i)
|
|
{
|
|
ret += getColumn(i)->getValue().asString();
|
|
if (i < count-1)
|
|
{
|
|
ret += ", ";
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void LLScrollListItem::draw(const LLRect& rect, const LLColor4& fg_color, const LLColor4& bg_color, const LLColor4& highlight_color, S32 column_padding)
|
|
{
|
|
// draw background rect
|
|
LLRect bg_rect = rect;
|
|
{
|
|
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
gGL.color4fv(bg_color.mV);
|
|
gl_rect_2d( bg_rect );
|
|
}
|
|
|
|
S32 cur_x = rect.mLeft;
|
|
S32 num_cols = getNumColumns();
|
|
S32 cur_col = 0;
|
|
|
|
for (LLScrollListCell* cell = getColumn(0); cur_col < num_cols; cell = getColumn(++cur_col))
|
|
{
|
|
// Two ways a cell could be hidden
|
|
if (cell->getWidth() < 0
|
|
|| !cell->getVisible()) continue;
|
|
|
|
LLUI::pushMatrix();
|
|
{
|
|
LLUI::translate((F32) cur_x, (F32) rect.mBottom, 0.0f);
|
|
|
|
cell->draw( fg_color, highlight_color );
|
|
}
|
|
LLUI::popMatrix();
|
|
|
|
cur_x += cell->getWidth() + column_padding;
|
|
}
|
|
}
|
|
|
|
|
|
void LLScrollListItem::setEnabled(BOOL b)
|
|
{
|
|
mEnabled = b;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// LLScrollListItemComment
|
|
//---------------------------------------------------------------------------
|
|
LLScrollListItemComment::LLScrollListItemComment(const std::string& comment_string, const LLColor4& color)
|
|
: LLScrollListItem(FALSE),
|
|
mColor(color)
|
|
{
|
|
addColumn( comment_string, LLResMgr::getInstance()->getRes( LLFONT_SANSSERIF_SMALL ) );
|
|
}
|
|
|
|
void LLScrollListItemComment::draw(const LLRect& rect, const LLColor4& fg_color, const LLColor4& bg_color, const LLColor4& highlight_color, S32 column_padding)
|
|
{
|
|
LLScrollListCell* cell = getColumn(0);
|
|
if (cell)
|
|
{
|
|
// Two ways a cell could be hidden
|
|
if (cell->getWidth() < 0
|
|
|| !cell->getVisible()) return;
|
|
|
|
LLUI::pushMatrix();
|
|
{
|
|
LLUI::translate((F32)rect.mLeft, (F32)rect.mBottom, 0.0f);
|
|
|
|
// force first cell to be width of entire item
|
|
cell->setWidth(rect.getWidth());
|
|
cell->draw( mColor, highlight_color );
|
|
}
|
|
LLUI::popMatrix();
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// LLScrollListItemSeparator
|
|
//---------------------------------------------------------------------------
|
|
LLScrollListItemSeparator::LLScrollListItemSeparator()
|
|
: LLScrollListItem(FALSE)
|
|
{
|
|
LLScrollListSeparator* cell = new LLScrollListSeparator(0);
|
|
setNumColumns(1);
|
|
setColumn(0, cell);
|
|
}
|
|
|
|
void LLScrollListItemSeparator::draw(const LLRect& rect, const LLColor4& fg_color, const LLColor4& bg_color, const LLColor4& highlight_color, S32 column_padding)
|
|
{
|
|
//TODO* move LLScrollListSeparator::draw into here and get rid of it
|
|
LLScrollListCell* cell = getColumn(0);
|
|
if (cell)
|
|
{
|
|
// Two ways a cell could be hidden
|
|
if (cell->getWidth() < 0
|
|
|| !cell->getVisible()) return;
|
|
|
|
LLUI::pushMatrix();
|
|
{
|
|
LLUI::translate((F32)rect.mLeft, (F32)rect.mBottom, 0.0f);
|
|
|
|
// force first cell to be width of entire item
|
|
cell->setWidth(rect.getWidth());
|
|
cell->draw( fg_color, highlight_color );
|
|
}
|
|
LLUI::popMatrix();
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// LLScrollListCtrl
|
|
//---------------------------------------------------------------------------
|
|
|
|
LLScrollListCtrl::LLScrollListCtrl(const std::string& name, const LLRect& rect,
|
|
void (*commit_callback)(LLUICtrl* ctrl, void* userdata),
|
|
void* callback_user_data,
|
|
BOOL allow_multiple_selection,
|
|
BOOL show_border
|
|
)
|
|
: LLUICtrl(name, rect, TRUE, commit_callback, callback_user_data),
|
|
mLineHeight(0),
|
|
mScrollLines(0),
|
|
mMouseWheelOpaque(true),
|
|
mPageLines(0),
|
|
mHeadingHeight(20),
|
|
mMaxSelectable(0),
|
|
mAllowMultipleSelection( allow_multiple_selection ),
|
|
mAllowKeyboardMovement(TRUE),
|
|
mCommitOnKeyboardMovement(TRUE),
|
|
mCommitOnSelectionChange(FALSE),
|
|
mSelectionChanged(FALSE),
|
|
mNeedsScroll(FALSE),
|
|
mCanSelect(TRUE),
|
|
mDisplayColumnHeaders(FALSE),
|
|
mColumnsDirty(FALSE),
|
|
mMaxItemCount(INT_MAX),
|
|
mMaxContentWidth(0),
|
|
mBackgroundVisible( TRUE ),
|
|
mDrawStripes(TRUE),
|
|
mBgWriteableColor( LLUI::sColorsGroup->getColor( "ScrollBgWriteableColor" ) ),
|
|
mBgReadOnlyColor( LLUI::sColorsGroup->getColor( "ScrollBgReadOnlyColor" ) ),
|
|
mBgSelectedColor( LLUI::sColorsGroup->getColor("ScrollSelectedBGColor") ),
|
|
mBgStripeColor( LLUI::sColorsGroup->getColor("ScrollBGStripeColor") ),
|
|
mFgSelectedColor( LLUI::sColorsGroup->getColor("ScrollSelectedFGColor") ),
|
|
mFgUnselectedColor( LLUI::sColorsGroup->getColor("ScrollUnselectedColor") ),
|
|
mFgDisabledColor( LLUI::sColorsGroup->getColor("ScrollDisabledColor") ),
|
|
mHighlightedColor( LLUI::sColorsGroup->getColor("ScrollHighlightedColor") ),
|
|
mDefaultListTextColor( LLUI::sColorsGroup->getColor("DefaultListText") ),
|
|
mBorderThickness( 2 ),
|
|
mOnDoubleClickCallback( NULL ),
|
|
mOnMaximumSelectCallback( NULL ),
|
|
mOnSortChangedCallback( NULL ),
|
|
mHighlightedItem(-1),
|
|
mBorder(NULL),
|
|
mSearchColumn(0),
|
|
mNumDynamicWidthColumns(0),
|
|
mTotalStaticColumnWidth(0),
|
|
mTotalColumnPadding(0),
|
|
mSorted(TRUE),
|
|
mDirty(FALSE),
|
|
mOriginalSelection(-1),
|
|
mDrewSelected(FALSE)
|
|
{
|
|
mItemListRect.setOriginAndSize(
|
|
mBorderThickness,
|
|
mBorderThickness,
|
|
getRect().getWidth() - 2 * mBorderThickness,
|
|
getRect().getHeight() - 2 * mBorderThickness );
|
|
|
|
updateLineHeight();
|
|
|
|
mPageLines = mLineHeight? (mItemListRect.getHeight()) / mLineHeight : 0;
|
|
|
|
// Init the scrollbar
|
|
LLRect scroll_rect;
|
|
scroll_rect.setOriginAndSize(
|
|
getRect().getWidth() - mBorderThickness - SCROLLBAR_SIZE,
|
|
mItemListRect.mBottom,
|
|
SCROLLBAR_SIZE,
|
|
mItemListRect.getHeight());
|
|
mScrollbar = new LLScrollbar( std::string("Scrollbar"), scroll_rect,
|
|
LLScrollbar::VERTICAL,
|
|
getItemCount(),
|
|
mScrollLines,
|
|
mPageLines,
|
|
&LLScrollListCtrl::onScrollChange, this );
|
|
mScrollbar->setFollowsRight();
|
|
mScrollbar->setFollowsTop();
|
|
mScrollbar->setFollowsBottom();
|
|
mScrollbar->setEnabled( TRUE );
|
|
// scrollbar is visible only when needed
|
|
mScrollbar->setVisible(FALSE);
|
|
addChild(mScrollbar);
|
|
|
|
// Border
|
|
if (show_border)
|
|
{
|
|
LLRect border_rect( 0, getRect().getHeight(), getRect().getWidth(), 0 );
|
|
mBorder = new LLViewBorder( std::string("dlg border"), border_rect, LLViewBorder::BEVEL_IN, LLViewBorder::STYLE_LINE, 1 );
|
|
addChild(mBorder);
|
|
}
|
|
|
|
mColumnPadding = 5;
|
|
|
|
mLastSelected = NULL;
|
|
}
|
|
|
|
S32 LLScrollListCtrl::getSearchColumn()
|
|
{
|
|
// search for proper search column
|
|
if (mSearchColumn < 0)
|
|
{
|
|
LLScrollListItem* itemp = getFirstData();
|
|
if (itemp)
|
|
{
|
|
for(S32 column = 0; column < getNumColumns(); column++)
|
|
{
|
|
LLScrollListCell* cell = itemp->getColumn(column);
|
|
if (cell && cell->isText())
|
|
{
|
|
mSearchColumn = column;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return llclamp(mSearchColumn, 0, getNumColumns());
|
|
}
|
|
|
|
LLScrollListCtrl::~LLScrollListCtrl()
|
|
{
|
|
std::for_each(mItemList.begin(), mItemList.end(), DeletePointer());
|
|
}
|
|
|
|
|
|
BOOL LLScrollListCtrl::setMaxItemCount(S32 max_count)
|
|
{
|
|
if (max_count >= getItemCount())
|
|
{
|
|
mMaxItemCount = max_count;
|
|
}
|
|
return (max_count == mMaxItemCount);
|
|
}
|
|
|
|
S32 LLScrollListCtrl::isEmpty() const
|
|
{
|
|
return mItemList.empty();
|
|
}
|
|
|
|
S32 LLScrollListCtrl::getItemCount() const
|
|
{
|
|
return mItemList.size();
|
|
}
|
|
|
|
// virtual LLScrolListInterface function (was deleteAllItems)
|
|
void LLScrollListCtrl::clearRows()
|
|
{
|
|
std::for_each(mItemList.begin(), mItemList.end(), DeletePointer());
|
|
mItemList.clear();
|
|
//mItemCount = 0;
|
|
|
|
// Scroll the bar back up to the top.
|
|
mScrollbar->setDocParams(0, 0);
|
|
|
|
mScrollLines = 0;
|
|
mLastSelected = NULL;
|
|
updateLayout();
|
|
mDirty = FALSE;
|
|
}
|
|
|
|
|
|
LLScrollListItem* LLScrollListCtrl::getFirstSelected() const
|
|
{
|
|
if (!getCanSelect())
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
item_list::const_iterator iter;
|
|
for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
|
|
{
|
|
LLScrollListItem* item = *iter;
|
|
if (item->getSelected())
|
|
{
|
|
return item;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
std::vector<LLScrollListItem*> LLScrollListCtrl::getAllSelected() const
|
|
{
|
|
std::vector<LLScrollListItem*> ret;
|
|
|
|
if (!getCanSelect())
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
|
|
item_list::const_iterator iter;
|
|
for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
|
|
{
|
|
LLScrollListItem* item = *iter;
|
|
if (item->getSelected())
|
|
{
|
|
ret.push_back(item);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
LLDynamicArray<LLUUID> LLScrollListCtrl::getSelectedIDs()
|
|
{
|
|
LLUUID selected_id;
|
|
LLDynamicArray<LLUUID> ids;
|
|
std::vector<LLScrollListItem*> selected = this->getAllSelected();
|
|
for(std::vector<LLScrollListItem*>::iterator itr = selected.begin(); itr != selected.end(); ++itr)
|
|
{
|
|
ids.push_back((*itr)->getUUID());
|
|
}
|
|
return ids;
|
|
}
|
|
|
|
S32 LLScrollListCtrl::getFirstSelectedIndex() const
|
|
{
|
|
if (!getCanSelect())
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
|
|
S32 CurSelectedIndex = 0;
|
|
item_list::const_iterator iter;
|
|
for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
|
|
{
|
|
LLScrollListItem* item = *iter;
|
|
if (item->getSelected())
|
|
{
|
|
return CurSelectedIndex;
|
|
}
|
|
CurSelectedIndex++;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
LLScrollListItem* LLScrollListCtrl::getFirstData() const
|
|
{
|
|
if (mItemList.size() == 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
return mItemList[0];
|
|
}
|
|
|
|
LLScrollListItem* LLScrollListCtrl::getLastData() const
|
|
{
|
|
if (mItemList.size() == 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
return mItemList[mItemList.size() - 1];
|
|
}
|
|
|
|
std::vector<LLScrollListItem*> LLScrollListCtrl::getAllData() const
|
|
{
|
|
std::vector<LLScrollListItem*> ret;
|
|
item_list::const_iterator iter;
|
|
for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
|
|
{
|
|
LLScrollListItem* item = *iter;
|
|
ret.push_back(item);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// returns first matching item
|
|
LLScrollListItem* LLScrollListCtrl::getItem(const LLSD& sd) const
|
|
{
|
|
std::string string_val = sd.asString();
|
|
|
|
item_list::const_iterator iter;
|
|
for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
|
|
{
|
|
LLScrollListItem* item = *iter;
|
|
// assumes string representation is good enough for comparison
|
|
if (item->getValue().asString() == string_val)
|
|
{
|
|
return item;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void LLScrollListCtrl::reshape( S32 width, S32 height, BOOL called_from_parent )
|
|
{
|
|
LLUICtrl::reshape( width, height, called_from_parent );
|
|
|
|
updateLayout();
|
|
}
|
|
|
|
void LLScrollListCtrl::updateLayout()
|
|
{
|
|
// reserve room for column headers, if needed
|
|
S32 heading_size = (mDisplayColumnHeaders ? mHeadingHeight : 0);
|
|
mItemListRect.setOriginAndSize(
|
|
mBorderThickness,
|
|
mBorderThickness,
|
|
getRect().getWidth() - 2 * mBorderThickness,
|
|
getRect().getHeight() - (2 * mBorderThickness ) - heading_size );
|
|
|
|
// how many lines of content in a single "page"
|
|
mPageLines = mLineHeight? mItemListRect.getHeight() / mLineHeight : 0;
|
|
BOOL scrollbar_visible = getItemCount() > mPageLines;
|
|
if (scrollbar_visible)
|
|
{
|
|
// provide space on the right for scrollbar
|
|
mItemListRect.mRight = getRect().getWidth() - mBorderThickness - SCROLLBAR_SIZE;
|
|
}
|
|
|
|
mScrollbar->reshape(SCROLLBAR_SIZE, mItemListRect.getHeight() + (mDisplayColumnHeaders ? mHeadingHeight : 0));
|
|
mScrollbar->setPageSize( mPageLines );
|
|
mScrollbar->setDocSize( getItemCount() );
|
|
mScrollbar->setVisible(scrollbar_visible);
|
|
|
|
dirtyColumns();
|
|
}
|
|
|
|
// Attempt to size the control to show all items.
|
|
// Do not make larger than width or height.
|
|
void LLScrollListCtrl::fitContents(S32 max_width, S32 max_height)
|
|
{
|
|
S32 height = llmin( getRequiredRect().getHeight(), max_height );
|
|
S32 width = getRect().getWidth();
|
|
|
|
reshape( width, height );
|
|
}
|
|
|
|
|
|
LLRect LLScrollListCtrl::getRequiredRect()
|
|
{
|
|
S32 heading_size = (mDisplayColumnHeaders ? mHeadingHeight : 0);
|
|
S32 height = (mLineHeight * getItemCount())
|
|
+ (2 * mBorderThickness )
|
|
+ heading_size;
|
|
S32 width = getRect().getWidth();
|
|
|
|
return LLRect(0, height, width, 0);
|
|
}
|
|
|
|
|
|
BOOL LLScrollListCtrl::addItem( LLScrollListItem* item, EAddPosition pos, BOOL requires_column )
|
|
{
|
|
BOOL not_too_big = getItemCount() < mMaxItemCount;
|
|
if (not_too_big)
|
|
{
|
|
switch( pos )
|
|
{
|
|
case ADD_TOP:
|
|
mItemList.push_front(item);
|
|
setSorted(FALSE);
|
|
break;
|
|
|
|
case ADD_SORTED:
|
|
{
|
|
// sort by column 0, in ascending order
|
|
std::vector<sort_column_t> single_sort_column;
|
|
single_sort_column.push_back(std::make_pair(0, TRUE));
|
|
|
|
mItemList.push_back(item);
|
|
std::stable_sort(
|
|
mItemList.begin(),
|
|
mItemList.end(),
|
|
SortScrollListItem(single_sort_column));
|
|
|
|
// ADD_SORTED just sorts by first column...
|
|
// this might not match user sort criteria, so flag list as being in unsorted state
|
|
setSorted(FALSE);
|
|
break;
|
|
}
|
|
case ADD_BOTTOM:
|
|
mItemList.push_back(item);
|
|
setSorted(FALSE);
|
|
break;
|
|
|
|
default:
|
|
llassert(0);
|
|
mItemList.push_back(item);
|
|
setSorted(FALSE);
|
|
break;
|
|
}
|
|
|
|
// create new column on demand
|
|
if (mColumns.empty() && requires_column)
|
|
{
|
|
LLSD new_column;
|
|
new_column["name"] = "default_column";
|
|
new_column["label"] = "";
|
|
new_column["dynamicwidth"] = TRUE;
|
|
addColumn(new_column);
|
|
}
|
|
|
|
updateLineHeightInsert(item);
|
|
|
|
updateLayout();
|
|
}
|
|
|
|
return not_too_big;
|
|
}
|
|
|
|
// NOTE: This is *very* expensive for large lists, especially when we are dirtying the list every frame
|
|
// while receiving a long list of names.
|
|
// *TODO: Use bookkeeping to make this an incramental cost with item additions
|
|
void LLScrollListCtrl::calcColumnWidths()
|
|
{
|
|
const S32 HEADING_TEXT_PADDING = 25;
|
|
const S32 COLUMN_TEXT_PADDING = 10;
|
|
|
|
mMaxContentWidth = 0;
|
|
|
|
S32 max_item_width = 0;
|
|
|
|
ordered_columns_t::iterator column_itor;
|
|
for (column_itor = mColumnsIndexed.begin(); column_itor != mColumnsIndexed.end(); ++column_itor)
|
|
{
|
|
LLScrollListColumn* column = *column_itor;
|
|
if (!column) continue;
|
|
|
|
// update column width
|
|
S32 new_width = column->getWidth();
|
|
if (column->mRelWidth >= 0)
|
|
{
|
|
new_width = (S32)llround(column->mRelWidth*mItemListRect.getWidth());
|
|
}
|
|
else if (column->mDynamicWidth)
|
|
{
|
|
new_width = (mItemListRect.getWidth() - mTotalStaticColumnWidth - mTotalColumnPadding) / mNumDynamicWidthColumns;
|
|
}
|
|
|
|
column->setWidth(new_width);
|
|
|
|
// update max content width for this column, by looking at all items
|
|
column->mMaxContentWidth = column->mHeader ? LLFontGL::getFontSansSerifSmall()->getWidth(column->mLabel) + mColumnPadding + HEADING_TEXT_PADDING : 0;
|
|
item_list::iterator iter;
|
|
for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
|
|
{
|
|
LLScrollListCell* cellp = (*iter)->getColumn(column->mIndex);
|
|
if (!cellp) continue;
|
|
|
|
column->mMaxContentWidth = llmax(LLFontGL::getFontSansSerifSmall()->getWidth(cellp->getValue().asString()) + mColumnPadding + COLUMN_TEXT_PADDING, column->mMaxContentWidth);
|
|
}
|
|
|
|
max_item_width += column->mMaxContentWidth;
|
|
}
|
|
|
|
mMaxContentWidth = max_item_width;
|
|
}
|
|
|
|
const S32 SCROLL_LIST_ROW_PAD = 2;
|
|
|
|
// Line height is the max height of all the cells in all the items.
|
|
void LLScrollListCtrl::updateLineHeight()
|
|
{
|
|
mLineHeight = 0;
|
|
item_list::iterator iter;
|
|
for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
|
|
{
|
|
LLScrollListItem *itemp = *iter;
|
|
S32 num_cols = itemp->getNumColumns();
|
|
S32 i = 0;
|
|
for (const LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i))
|
|
{
|
|
mLineHeight = llmax( mLineHeight, cell->getHeight() + SCROLL_LIST_ROW_PAD );
|
|
}
|
|
}
|
|
}
|
|
|
|
// when the only change to line height is from an insert, we needn't scan the entire list
|
|
void LLScrollListCtrl::updateLineHeightInsert(LLScrollListItem* itemp)
|
|
{
|
|
S32 num_cols = itemp->getNumColumns();
|
|
S32 i = 0;
|
|
for (const LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i))
|
|
{
|
|
mLineHeight = llmax( mLineHeight, cell->getHeight() + SCROLL_LIST_ROW_PAD );
|
|
}
|
|
}
|
|
|
|
|
|
void LLScrollListCtrl::updateColumns()
|
|
{
|
|
calcColumnWidths();
|
|
|
|
// update column headers
|
|
std::vector<LLScrollListColumn*>::iterator column_ordered_it;
|
|
S32 left = mItemListRect.mLeft;
|
|
LLColumnHeader* last_header = NULL;
|
|
for (column_ordered_it = mColumnsIndexed.begin(); column_ordered_it != mColumnsIndexed.end(); ++column_ordered_it)
|
|
{
|
|
if ((*column_ordered_it)->getWidth() < 0)
|
|
{
|
|
// skip hidden columns
|
|
continue;
|
|
}
|
|
LLScrollListColumn* column = *column_ordered_it;
|
|
|
|
if (column->mHeader)
|
|
{
|
|
column->mHeader->updateResizeBars();
|
|
|
|
last_header = column->mHeader;
|
|
S32 top = mItemListRect.mTop;
|
|
S32 right = left + column->getWidth();
|
|
|
|
if (column->mIndex != (S32)mColumnsIndexed.size()-1)
|
|
{
|
|
right += mColumnPadding;
|
|
}
|
|
right = llmax(left, llmin(mItemListRect.getWidth(), right));
|
|
S32 header_width = right - left;
|
|
|
|
last_header->reshape(header_width, mHeadingHeight);
|
|
last_header->translate(
|
|
left - last_header->getRect().mLeft,
|
|
top - last_header->getRect().mBottom);
|
|
last_header->setVisible(mDisplayColumnHeaders && header_width > 0);
|
|
left = right;
|
|
}
|
|
}
|
|
|
|
// expand last column header we encountered to full list width
|
|
if (last_header && last_header->canResize())
|
|
{
|
|
S32 new_width = llmax(0, mItemListRect.mRight - last_header->getRect().mLeft);
|
|
last_header->reshape(new_width, last_header->getRect().getHeight());
|
|
last_header->setVisible(mDisplayColumnHeaders && new_width > 0);
|
|
last_header->getColumn()->setWidth(new_width);
|
|
}
|
|
|
|
// propagate column widths to individual cells
|
|
item_list::iterator iter;
|
|
for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
|
|
{
|
|
LLScrollListItem *itemp = *iter;
|
|
S32 num_cols = itemp->getNumColumns();
|
|
S32 i = 0;
|
|
for (LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i))
|
|
{
|
|
if (i >= (S32)mColumnsIndexed.size()) break;
|
|
|
|
cell->setWidth(mColumnsIndexed[i]->getWidth());
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void LLScrollListCtrl::setDisplayHeading(BOOL display)
|
|
{
|
|
mDisplayColumnHeaders = display;
|
|
|
|
updateLayout();
|
|
}
|
|
|
|
void LLScrollListCtrl::setHeadingHeight(S32 heading_height)
|
|
{
|
|
mHeadingHeight = heading_height;
|
|
|
|
updateLayout();
|
|
|
|
}
|
|
|
|
BOOL LLScrollListCtrl::selectFirstItem()
|
|
{
|
|
BOOL success = FALSE;
|
|
|
|
// our $%&@#$()^%#$()*^ iterators don't let us check against the first item inside out iteration
|
|
BOOL first_item = TRUE;
|
|
|
|
item_list::iterator iter;
|
|
for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
|
|
{
|
|
LLScrollListItem *itemp = *iter;
|
|
if( first_item && itemp->getEnabled() )
|
|
{
|
|
if (!itemp->getSelected())
|
|
{
|
|
selectItem(itemp);
|
|
}
|
|
success = TRUE;
|
|
mOriginalSelection = 0;
|
|
}
|
|
else
|
|
{
|
|
deselectItem(itemp);
|
|
}
|
|
first_item = FALSE;
|
|
}
|
|
if (mCommitOnSelectionChange)
|
|
{
|
|
commitIfChanged();
|
|
}
|
|
return success;
|
|
}
|
|
|
|
// Deselects all other items
|
|
// virtual
|
|
BOOL LLScrollListCtrl::selectNthItem( S32 target_index )
|
|
{
|
|
return selectItemRange(target_index, target_index);
|
|
}
|
|
|
|
// virtual
|
|
BOOL LLScrollListCtrl::selectItemRange( S32 first_index, S32 last_index )
|
|
{
|
|
if (mItemList.empty())
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
S32 listlen = (S32)mItemList.size();
|
|
first_index = llclamp(first_index, 0, listlen-1);
|
|
|
|
if (last_index < 0)
|
|
last_index = listlen-1;
|
|
else
|
|
last_index = llclamp(last_index, first_index, listlen-1);
|
|
|
|
BOOL success = FALSE;
|
|
S32 index = 0;
|
|
for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); )
|
|
{
|
|
LLScrollListItem *itemp = *iter;
|
|
if(!itemp)
|
|
{
|
|
iter = mItemList.erase(iter);
|
|
continue ;
|
|
}
|
|
|
|
if( index >= first_index && index <= last_index )
|
|
{
|
|
if( itemp->getEnabled() )
|
|
{
|
|
selectItem(itemp, FALSE);
|
|
success = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
deselectItem(itemp);
|
|
}
|
|
index++;
|
|
iter++ ;
|
|
}
|
|
|
|
if (mCommitOnSelectionChange)
|
|
{
|
|
commitIfChanged();
|
|
}
|
|
|
|
mSearchString.clear();
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
void LLScrollListCtrl::swapWithNext(S32 index)
|
|
{
|
|
if (index >= ((S32)mItemList.size() - 1))
|
|
{
|
|
// At end of list, doesn't do anything
|
|
return;
|
|
}
|
|
LLScrollListItem *cur_itemp = mItemList[index];
|
|
mItemList[index] = mItemList[index + 1];
|
|
mItemList[index + 1] = cur_itemp;
|
|
}
|
|
|
|
|
|
void LLScrollListCtrl::swapWithPrevious(S32 index)
|
|
{
|
|
if (index <= 0)
|
|
{
|
|
// At beginning of list, don't do anything
|
|
return;
|
|
}
|
|
|
|
LLScrollListItem *cur_itemp = mItemList[index];
|
|
mItemList[index] = mItemList[index - 1];
|
|
mItemList[index - 1] = cur_itemp;
|
|
}
|
|
|
|
void LLScrollListCtrl::moveToFront(S32 index)
|
|
{
|
|
if(index == 0 || index >= (S32)mItemList.size())
|
|
{
|
|
return;
|
|
}
|
|
|
|
LLScrollListCtrl::item_list::iterator it = mItemList.begin();
|
|
std::advance(it,index);
|
|
mItemList.push_front(*it);
|
|
mItemList.erase(it);
|
|
}
|
|
|
|
void LLScrollListCtrl::deleteSingleItem(S32 target_index)
|
|
{
|
|
if (target_index < 0 || target_index >= (S32)mItemList.size())
|
|
{
|
|
return;
|
|
}
|
|
|
|
LLScrollListItem *itemp;
|
|
itemp = mItemList[target_index];
|
|
if (itemp == mLastSelected)
|
|
{
|
|
mLastSelected = NULL;
|
|
}
|
|
delete itemp;
|
|
mItemList.erase(mItemList.begin() + target_index);
|
|
dirtyColumns();
|
|
}
|
|
|
|
//FIXME: refactor item deletion
|
|
void LLScrollListCtrl::deleteItems(const LLSD& sd)
|
|
{
|
|
item_list::iterator iter;
|
|
for (iter = mItemList.begin(); iter < mItemList.end(); )
|
|
{
|
|
LLScrollListItem* itemp = *iter;
|
|
if (itemp->getValue().asString() == sd.asString())
|
|
{
|
|
if (itemp == mLastSelected)
|
|
{
|
|
mLastSelected = NULL;
|
|
}
|
|
delete itemp;
|
|
iter = mItemList.erase(iter);
|
|
}
|
|
else
|
|
{
|
|
iter++;
|
|
}
|
|
}
|
|
|
|
dirtyColumns();
|
|
}
|
|
|
|
void LLScrollListCtrl::deleteSelectedItems()
|
|
{
|
|
item_list::iterator iter;
|
|
for (iter = mItemList.begin(); iter < mItemList.end(); )
|
|
{
|
|
LLScrollListItem* itemp = *iter;
|
|
if (itemp->getSelected())
|
|
{
|
|
delete itemp;
|
|
iter = mItemList.erase(iter);
|
|
}
|
|
else
|
|
{
|
|
iter++;
|
|
}
|
|
}
|
|
mLastSelected = NULL;
|
|
dirtyColumns();
|
|
}
|
|
|
|
void LLScrollListCtrl::highlightNthItem(S32 target_index)
|
|
{
|
|
if (mHighlightedItem != target_index)
|
|
{
|
|
mHighlightedItem = target_index;
|
|
}
|
|
}
|
|
|
|
S32 LLScrollListCtrl::selectMultiple( LLDynamicArray<LLUUID> ids )
|
|
{
|
|
item_list::iterator iter;
|
|
S32 count = 0;
|
|
for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
|
|
{
|
|
LLScrollListItem* item = *iter;
|
|
LLDynamicArray<LLUUID>::iterator iditr;
|
|
for(iditr = ids.begin(); iditr != ids.end(); ++iditr)
|
|
{
|
|
if (item->getEnabled() && (item->getUUID() == (*iditr)))
|
|
{
|
|
selectItem(item,FALSE);
|
|
++count;
|
|
break;
|
|
}
|
|
}
|
|
if(ids.end() != iditr) ids.erase(iditr);
|
|
}
|
|
|
|
if (mCommitOnSelectionChange)
|
|
{
|
|
commitIfChanged();
|
|
}
|
|
return count;
|
|
}
|
|
|
|
S32 LLScrollListCtrl::getItemIndex( LLScrollListItem* target_item ) const
|
|
{
|
|
S32 index = 0;
|
|
item_list::const_iterator iter;
|
|
for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
|
|
{
|
|
LLScrollListItem *itemp = *iter;
|
|
if (target_item == itemp)
|
|
{
|
|
return index;
|
|
}
|
|
index++;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
S32 LLScrollListCtrl::getItemIndex( const LLUUID& target_id ) const
|
|
{
|
|
S32 index = 0;
|
|
item_list::const_iterator iter;
|
|
for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
|
|
{
|
|
LLScrollListItem *itemp = *iter;
|
|
if (target_id == itemp->getUUID())
|
|
{
|
|
return index;
|
|
}
|
|
index++;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void LLScrollListCtrl::selectPrevItem( BOOL extend_selection)
|
|
{
|
|
LLScrollListItem* prev_item = NULL;
|
|
|
|
if (!getFirstSelected())
|
|
{
|
|
// select last item
|
|
selectNthItem(getItemCount() - 1);
|
|
}
|
|
else
|
|
{
|
|
item_list::iterator iter;
|
|
for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
|
|
{
|
|
LLScrollListItem* cur_item = *iter;
|
|
|
|
if (cur_item->getSelected())
|
|
{
|
|
if (prev_item)
|
|
{
|
|
selectItem(prev_item, !extend_selection);
|
|
}
|
|
else
|
|
{
|
|
reportInvalidInput();
|
|
}
|
|
break;
|
|
}
|
|
|
|
// don't allow navigation to disabled elements
|
|
prev_item = cur_item->getEnabled() ? cur_item : prev_item;
|
|
}
|
|
}
|
|
|
|
if ((mCommitOnSelectionChange || mCommitOnKeyboardMovement))
|
|
{
|
|
commitIfChanged();
|
|
}
|
|
|
|
mSearchString.clear();
|
|
}
|
|
|
|
|
|
void LLScrollListCtrl::selectNextItem( BOOL extend_selection)
|
|
{
|
|
LLScrollListItem* next_item = NULL;
|
|
|
|
if (!getFirstSelected())
|
|
{
|
|
selectFirstItem();
|
|
}
|
|
else
|
|
{
|
|
item_list::reverse_iterator iter;
|
|
for (iter = mItemList.rbegin(); iter != mItemList.rend(); iter++)
|
|
{
|
|
LLScrollListItem* cur_item = *iter;
|
|
|
|
if (cur_item->getSelected())
|
|
{
|
|
if (next_item)
|
|
{
|
|
selectItem(next_item, !extend_selection);
|
|
}
|
|
else
|
|
{
|
|
reportInvalidInput();
|
|
}
|
|
break;
|
|
}
|
|
|
|
// don't allow navigation to disabled items
|
|
next_item = cur_item->getEnabled() ? cur_item : next_item;
|
|
}
|
|
}
|
|
|
|
if (mCommitOnKeyboardMovement)
|
|
{
|
|
onCommit();
|
|
}
|
|
|
|
mSearchString.clear();
|
|
}
|
|
|
|
|
|
|
|
void LLScrollListCtrl::deselectAllItems(BOOL no_commit_on_change)
|
|
{
|
|
item_list::iterator iter;
|
|
for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
|
|
{
|
|
LLScrollListItem* item = *iter;
|
|
deselectItem(item);
|
|
}
|
|
|
|
if (mCommitOnSelectionChange && !no_commit_on_change)
|
|
{
|
|
commitIfChanged();
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Use this to add comment text such as "Searching", which ignores column settings of list
|
|
|
|
LLScrollListItem* LLScrollListCtrl::addCommentText(const std::string& comment_text, EAddPosition pos)
|
|
{
|
|
LLScrollListItem* item = NULL;
|
|
if (getItemCount() < mMaxItemCount)
|
|
{
|
|
// always draw comment text with "enabled" color
|
|
item = new LLScrollListItemComment( comment_text, mFgUnselectedColor );
|
|
addItem( item, pos, FALSE );
|
|
}
|
|
return item;
|
|
}
|
|
|
|
LLScrollListItem* LLScrollListCtrl::addSeparator(EAddPosition pos)
|
|
{
|
|
LLScrollListItem* item = new LLScrollListItemSeparator();
|
|
addItem(item, pos, FALSE);
|
|
return item;
|
|
}
|
|
|
|
// Selects first enabled item of the given name.
|
|
// Returns false if item not found.
|
|
BOOL LLScrollListCtrl::selectItemByLabel(const std::string& label, BOOL case_sensitive)
|
|
{
|
|
// ensure that no stale items are selected, even if we don't find a match
|
|
deselectAllItems(TRUE);
|
|
//RN: assume no empty items
|
|
if (label.empty())
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
std::string target_text = label;
|
|
if (!case_sensitive)
|
|
{
|
|
LLStringUtil::toLower(target_text);
|
|
}
|
|
|
|
BOOL found = FALSE;
|
|
|
|
item_list::iterator iter;
|
|
S32 index = 0;
|
|
for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
|
|
{
|
|
LLScrollListItem* item = *iter;
|
|
// Only select enabled items with matching names
|
|
std::string item_text = item->getColumn(0)->getValue().asString();
|
|
if (!case_sensitive)
|
|
{
|
|
LLStringUtil::toLower(item_text);
|
|
}
|
|
BOOL select = !found && item->getEnabled() && item_text == target_text;
|
|
if (select)
|
|
{
|
|
selectItem(item);
|
|
}
|
|
found = found || select;
|
|
index++;
|
|
}
|
|
|
|
if (mCommitOnSelectionChange)
|
|
{
|
|
commitIfChanged();
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
|
|
BOOL LLScrollListCtrl::selectItemByPrefix(const std::string& target, BOOL case_sensitive)
|
|
{
|
|
return selectItemByPrefix(utf8str_to_wstring(target), case_sensitive);
|
|
}
|
|
|
|
// Selects first enabled item that has a name where the name's first part matched the target string.
|
|
// Returns false if item not found.
|
|
BOOL LLScrollListCtrl::selectItemByPrefix(const LLWString& target, BOOL case_sensitive)
|
|
{
|
|
BOOL found = FALSE;
|
|
|
|
LLWString target_trimmed( target );
|
|
S32 target_len = target_trimmed.size();
|
|
|
|
if( 0 == target_len )
|
|
{
|
|
// Is "" a valid choice?
|
|
item_list::iterator iter;
|
|
for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
|
|
{
|
|
LLScrollListItem* item = *iter;
|
|
// Only select enabled items with matching names
|
|
LLScrollListCell* cellp = item->getColumn(getSearchColumn());
|
|
BOOL select = cellp ? item->getEnabled() && ('\0' == cellp->getValue().asString()[0]) : FALSE;
|
|
if (select)
|
|
{
|
|
selectItem(item);
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!case_sensitive)
|
|
{
|
|
// do comparisons in lower case
|
|
LLWStringUtil::toLower(target_trimmed);
|
|
}
|
|
|
|
for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); iter++)
|
|
{
|
|
LLScrollListItem* item = *iter;
|
|
|
|
// Only select enabled items with matching names
|
|
LLScrollListCell* cellp = item->getColumn(getSearchColumn());
|
|
if (!cellp)
|
|
{
|
|
continue;
|
|
}
|
|
LLWString item_label = utf8str_to_wstring(cellp->getValue().asString());
|
|
if (!case_sensitive)
|
|
{
|
|
LLWStringUtil::toLower(item_label);
|
|
}
|
|
// remove extraneous whitespace from searchable label
|
|
LLWString trimmed_label = item_label;
|
|
LLWStringUtil::trim(trimmed_label);
|
|
|
|
BOOL select = item->getEnabled() && trimmed_label.compare(0, target_trimmed.size(), target_trimmed) == 0;
|
|
|
|
if (select)
|
|
{
|
|
// find offset of matching text (might have leading whitespace)
|
|
S32 offset = item_label.find(target_trimmed);
|
|
cellp->highlightText(offset, target_trimmed.size());
|
|
selectItem(item);
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mCommitOnSelectionChange)
|
|
{
|
|
commitIfChanged();
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
const std::string LLScrollListCtrl::getSelectedItemLabel(S32 column) const
|
|
{
|
|
LLScrollListItem* item;
|
|
|
|
item = getFirstSelected();
|
|
if (item)
|
|
{
|
|
return item->getColumn(column)->getValue().asString();
|
|
}
|
|
|
|
return LLStringUtil::null;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// "StringUUID" interface: use this when you're creating a list that contains non-unique strings each of which
|
|
// has an associated, unique UUID, and only one of which can be selected at a time.
|
|
|
|
LLScrollListItem* LLScrollListCtrl::addStringUUIDItem(const std::string& item_text, const LLUUID& id, EAddPosition pos, BOOL enabled, S32 column_width)
|
|
{
|
|
LLScrollListItem* item = NULL;
|
|
if (getItemCount() < mMaxItemCount)
|
|
{
|
|
item = new LLScrollListItem( enabled, NULL, id );
|
|
item->addColumn(item_text, LLResMgr::getInstance()->getRes(LLFONT_SANSSERIF_SMALL), column_width);
|
|
addItem( item, pos );
|
|
}
|
|
return item;
|
|
}
|
|
|
|
// Select the line or lines that match this UUID
|
|
BOOL LLScrollListCtrl::selectByID( const LLUUID& id )
|
|
{
|
|
return selectByValue( LLSD(id) );
|
|
}
|
|
|
|
BOOL LLScrollListCtrl::setSelectedByValue(const LLSD& value, BOOL selected)
|
|
{
|
|
BOOL found = FALSE;
|
|
|
|
if (selected && !mAllowMultipleSelection) deselectAllItems(TRUE);
|
|
|
|
item_list::iterator iter;
|
|
for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
|
|
{
|
|
LLScrollListItem* item = *iter;
|
|
if (item->getEnabled() && (item->getValue().asString() == value.asString()))
|
|
{
|
|
if (selected)
|
|
{
|
|
selectItem(item);
|
|
}
|
|
else
|
|
{
|
|
deselectItem(item);
|
|
}
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (mCommitOnSelectionChange)
|
|
{
|
|
commitIfChanged();
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
BOOL LLScrollListCtrl::isSelected(const LLSD& value) const
|
|
{
|
|
item_list::const_iterator iter;
|
|
for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
|
|
{
|
|
LLScrollListItem* item = *iter;
|
|
if (item->getValue().asString() == value.asString())
|
|
{
|
|
return item->getSelected();
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
LLUUID LLScrollListCtrl::getStringUUIDSelectedItem() const
|
|
{
|
|
LLScrollListItem* item = getFirstSelected();
|
|
|
|
if (item)
|
|
{
|
|
return item->getUUID();
|
|
}
|
|
|
|
return LLUUID::null;
|
|
}
|
|
|
|
LLSD LLScrollListCtrl::getSelectedValue()
|
|
{
|
|
LLScrollListItem* item = getFirstSelected();
|
|
|
|
if (item)
|
|
{
|
|
return item->getValue();
|
|
}
|
|
else
|
|
{
|
|
return LLSD();
|
|
}
|
|
}
|
|
|
|
void LLScrollListCtrl::drawItems()
|
|
{
|
|
S32 x = mItemListRect.mLeft;
|
|
S32 y = mItemListRect.mTop - mLineHeight;
|
|
|
|
// allow for partial line at bottom
|
|
S32 num_page_lines = mPageLines + 1;
|
|
|
|
LLRect item_rect;
|
|
|
|
LLGLSUIDefault gls_ui;
|
|
|
|
{
|
|
LLLocalClipRect clip(mItemListRect);
|
|
|
|
S32 cur_y = y;
|
|
|
|
mDrewSelected = FALSE;
|
|
|
|
S32 line = 0;
|
|
S32 max_columns = 0;
|
|
|
|
LLColor4 highlight_color = LLColor4::white;
|
|
F32 type_ahead_timeout = LLUI::sConfigGroup->getF32("TypeAheadTimeout");
|
|
highlight_color.mV[VALPHA] = clamp_rescale(mSearchTimer.getElapsedTimeF32(), type_ahead_timeout * 0.7f, type_ahead_timeout, 0.4f, 0.f);
|
|
|
|
item_list::iterator iter;
|
|
for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
|
|
{
|
|
LLScrollListItem* item = *iter;
|
|
|
|
item_rect.setOriginAndSize(
|
|
x,
|
|
cur_y,
|
|
mItemListRect.getWidth(),
|
|
mLineHeight );
|
|
|
|
//llinfos << item_rect.getWidth() << llendl;
|
|
|
|
if (item->getSelected())
|
|
{
|
|
mDrewSelected = TRUE;
|
|
}
|
|
|
|
max_columns = llmax(max_columns, item->getNumColumns());
|
|
|
|
LLColor4 fg_color;
|
|
LLColor4 bg_color(LLColor4::transparent);
|
|
|
|
if( mScrollLines <= line && line < mScrollLines + num_page_lines )
|
|
{
|
|
fg_color = (item->getEnabled() ? mFgUnselectedColor : mFgDisabledColor);
|
|
if( item->getSelected() && mCanSelect)
|
|
{
|
|
bg_color = mBgSelectedColor;
|
|
fg_color = (item->getEnabled() ? mFgSelectedColor : mFgDisabledColor);
|
|
}
|
|
else if (mHighlightedItem == line && mCanSelect)
|
|
{
|
|
bg_color = mHighlightedColor;
|
|
}
|
|
else
|
|
{
|
|
if (mDrawStripes && (line % 2 == 0) && (max_columns > 1))
|
|
{
|
|
bg_color = mBgStripeColor;
|
|
}
|
|
}
|
|
|
|
if (!item->getEnabled())
|
|
{
|
|
bg_color = mBgReadOnlyColor;
|
|
}
|
|
|
|
item->draw(item_rect, fg_color, bg_color, highlight_color, mColumnPadding);
|
|
|
|
cur_y -= mLineHeight;
|
|
}
|
|
line++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void LLScrollListCtrl::draw()
|
|
{
|
|
LLLocalClipRect clip(getLocalRect());
|
|
|
|
// if user specifies sort, make sure it is maintained
|
|
if (needsSorting() && !isSorted())
|
|
{
|
|
sortItems();
|
|
}
|
|
|
|
if (mNeedsScroll)
|
|
{
|
|
scrollToShowSelected();
|
|
mNeedsScroll = FALSE;
|
|
}
|
|
LLRect background(0, getRect().getHeight(), getRect().getWidth(), 0);
|
|
// Draw background
|
|
if (mBackgroundVisible)
|
|
{
|
|
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
gGL.color4fv( getEnabled() ? mBgWriteableColor.mV : mBgReadOnlyColor.mV );
|
|
gl_rect_2d(background);
|
|
}
|
|
|
|
if (mColumnsDirty)
|
|
{
|
|
updateColumns();
|
|
mColumnsDirty = FALSE;
|
|
}
|
|
|
|
drawItems();
|
|
|
|
if (mBorder)
|
|
{
|
|
mBorder->setKeyboardFocusHighlight(gFocusMgr.getKeyboardFocus() == this);
|
|
}
|
|
|
|
LLUICtrl::draw();
|
|
}
|
|
|
|
void LLScrollListCtrl::setEnabled(BOOL enabled)
|
|
{
|
|
mCanSelect = enabled;
|
|
setTabStop(enabled);
|
|
mScrollbar->setTabStop(!enabled && mScrollbar->getPageSize() < mScrollbar->getDocSize());
|
|
}
|
|
|
|
BOOL LLScrollListCtrl::handleScrollWheel(S32 x, S32 y, S32 clicks)
|
|
{
|
|
BOOL handled = FALSE;
|
|
// Pretend the mouse is over the scrollbar
|
|
handled = mScrollbar->handleScrollWheel( 0, 0, clicks );
|
|
|
|
if (mMouseWheelOpaque)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
return handled;
|
|
}
|
|
|
|
BOOL LLScrollListCtrl::handleToolTip(S32 x, S32 y, std::string& msg, LLRect* sticky_rect_screen)
|
|
{
|
|
S32 column_index = getColumnIndexFromOffset(x);
|
|
LLScrollListColumn* columnp = getColumn(column_index);
|
|
|
|
if (columnp == NULL) return FALSE;
|
|
|
|
BOOL handled = FALSE;
|
|
// show tooltip for full name of hovered item if it has been truncated
|
|
LLScrollListItem* hit_item = hitItem(x, y);
|
|
|
|
if (hit_item)
|
|
{
|
|
// If the item has a specific tool tip set by XUI use that first
|
|
std::string tooltip=hit_item->getToolTip();
|
|
if(!tooltip.empty())
|
|
{
|
|
msg=tooltip;
|
|
return TRUE;
|
|
}
|
|
|
|
LLScrollListCell* hit_cell = hit_item->getColumn(column_index);
|
|
if (!hit_cell) return FALSE;
|
|
//S32 cell_required_width = hit_cell->getContentWidth();
|
|
if (hit_cell
|
|
&& hit_cell->isText())
|
|
{
|
|
|
|
S32 rect_left = getColumnOffsetFromIndex(column_index) + mItemListRect.mLeft;
|
|
S32 rect_bottom = getRowOffsetFromIndex(getItemIndex(hit_item));
|
|
LLRect cell_rect;
|
|
cell_rect.setOriginAndSize(rect_left, rect_bottom, rect_left + columnp->getWidth(), mLineHeight);
|
|
// Convert rect local to screen coordinates
|
|
localPointToScreen(
|
|
cell_rect.mLeft, cell_rect.mBottom,
|
|
&(sticky_rect_screen->mLeft), &(sticky_rect_screen->mBottom) );
|
|
localPointToScreen(
|
|
cell_rect.mRight, cell_rect.mTop,
|
|
&(sticky_rect_screen->mRight), &(sticky_rect_screen->mTop) );
|
|
|
|
msg = hit_cell->getValue().asString();
|
|
}
|
|
handled = TRUE;
|
|
}
|
|
|
|
// otherwise, look for a tooltip associated with this column
|
|
LLColumnHeader* headerp = columnp->mHeader;
|
|
if (headerp && !handled)
|
|
{
|
|
headerp->handleToolTip(x, y, msg, sticky_rect_screen);
|
|
handled = !msg.empty();
|
|
}
|
|
|
|
return handled;
|
|
}
|
|
|
|
BOOL LLScrollListCtrl::selectItemAt(S32 x, S32 y, MASK mask)
|
|
{
|
|
if (!mCanSelect) return FALSE;
|
|
|
|
BOOL selection_changed = FALSE;
|
|
|
|
LLScrollListItem* hit_item = hitItem(x, y);
|
|
|
|
if( hit_item )
|
|
{
|
|
if( mAllowMultipleSelection )
|
|
{
|
|
if (mask & MASK_SHIFT)
|
|
{
|
|
if (mLastSelected == NULL)
|
|
{
|
|
selectItem(hit_item);
|
|
}
|
|
else
|
|
{
|
|
// Select everthing between mLastSelected and hit_item
|
|
bool selecting = false;
|
|
item_list::iterator itor;
|
|
// If we multiselect backwards, we'll stomp on mLastSelected,
|
|
// meaning that we never stop selecting until hitting max or
|
|
// the end of the list.
|
|
LLScrollListItem* lastSelected = mLastSelected;
|
|
for (itor = mItemList.begin(); itor != mItemList.end(); ++itor)
|
|
{
|
|
if(mMaxSelectable > 0 && getAllSelected().size() >= mMaxSelectable)
|
|
{
|
|
if(mOnMaximumSelectCallback)
|
|
{
|
|
mOnMaximumSelectCallback(mCallbackUserData);
|
|
}
|
|
break;
|
|
}
|
|
LLScrollListItem *item = *itor;
|
|
if (item == hit_item || item == lastSelected)
|
|
{
|
|
selectItem(item, FALSE);
|
|
selecting = !selecting;
|
|
if (hit_item == lastSelected)
|
|
{
|
|
// stop selecting now, since we just clicked on our last selected item
|
|
selecting = FALSE;
|
|
}
|
|
}
|
|
if (selecting)
|
|
{
|
|
selectItem(item, FALSE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (mask & MASK_CONTROL)
|
|
{
|
|
if (hit_item->getSelected())
|
|
{
|
|
deselectItem(hit_item);
|
|
}
|
|
else
|
|
{
|
|
if(!(mMaxSelectable > 0 && getAllSelected().size() >= mMaxSelectable))
|
|
{
|
|
selectItem(hit_item, FALSE);
|
|
}
|
|
else
|
|
{
|
|
if(mOnMaximumSelectCallback)
|
|
{
|
|
mOnMaximumSelectCallback(mCallbackUserData);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
deselectAllItems(TRUE);
|
|
selectItem(hit_item);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
selectItem(hit_item);
|
|
}
|
|
|
|
selection_changed = mSelectionChanged;
|
|
if (mCommitOnSelectionChange)
|
|
{
|
|
commitIfChanged();
|
|
}
|
|
|
|
// clear search string on mouse operations
|
|
mSearchString.clear();
|
|
}
|
|
else
|
|
{
|
|
//mLastSelected = NULL;
|
|
//deselectAllItems(TRUE);
|
|
}
|
|
|
|
return selection_changed;
|
|
}
|
|
|
|
|
|
BOOL LLScrollListCtrl::handleMouseDown(S32 x, S32 y, MASK mask)
|
|
{
|
|
BOOL handled = childrenHandleMouseDown(x, y, mask) != NULL;
|
|
|
|
if( !handled )
|
|
{
|
|
// set keyboard focus first, in case click action wants to move focus elsewhere
|
|
setFocus(TRUE);
|
|
|
|
// clear selection changed flag because user is starting a selection operation
|
|
mSelectionChanged = FALSE;
|
|
|
|
handleClick(x, y, mask);
|
|
|
|
setFocus(TRUE);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL LLScrollListCtrl::handleMouseUp(S32 x, S32 y, MASK mask)
|
|
{
|
|
if (hasMouseCapture())
|
|
{
|
|
// release mouse capture immediately so
|
|
// scroll to show selected logic will work
|
|
gFocusMgr.setMouseCapture(NULL);
|
|
if(mask == MASK_NONE)
|
|
{
|
|
selectItemAt(x, y, mask);
|
|
mNeedsScroll = TRUE;
|
|
}
|
|
}
|
|
|
|
// always commit when mouse operation is completed inside list
|
|
if (mItemListRect.pointInRect(x,y))
|
|
{
|
|
mDirty |= mSelectionChanged;
|
|
mSelectionChanged = FALSE;
|
|
onCommit();
|
|
}
|
|
|
|
return LLUICtrl::handleMouseUp(x, y, mask);
|
|
}
|
|
|
|
BOOL LLScrollListCtrl::handleDoubleClick(S32 x, S32 y, MASK mask)
|
|
{
|
|
//BOOL handled = FALSE;
|
|
BOOL handled = handleClick(x, y, mask);
|
|
|
|
if (!handled)
|
|
{
|
|
// Offer the click to the children, even if we aren't enabled
|
|
// so the scroll bars will work.
|
|
if (NULL == LLView::childrenHandleDoubleClick(x, y, mask))
|
|
{
|
|
if( mCanSelect && mOnDoubleClickCallback )
|
|
{
|
|
mOnDoubleClickCallback( mCallbackUserData );
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL LLScrollListCtrl::handleClick(S32 x, S32 y, MASK mask)
|
|
{
|
|
// which row was clicked on?
|
|
LLScrollListItem* hit_item = hitItem(x, y);
|
|
if (!hit_item) return FALSE;
|
|
|
|
// get appropriate cell from that row
|
|
S32 column_index = getColumnIndexFromOffset(x);
|
|
LLScrollListCell* hit_cell = hit_item->getColumn(column_index);
|
|
if (!hit_cell) return FALSE;
|
|
|
|
// if cell handled click directly (i.e. clicked on an embedded checkbox)
|
|
if (hit_cell->handleClick())
|
|
{
|
|
// if item not currently selected, select it
|
|
if (!hit_item->getSelected())
|
|
{
|
|
selectItemAt(x, y, mask);
|
|
gFocusMgr.setMouseCapture(this);
|
|
mNeedsScroll = TRUE;
|
|
}
|
|
|
|
// propagate state of cell to rest of selected column
|
|
{
|
|
// propagate value of this cell to other selected items
|
|
// and commit the respective widgets
|
|
LLSD item_value = hit_cell->getValue();
|
|
for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); iter++)
|
|
{
|
|
LLScrollListItem* item = *iter;
|
|
if (item->getSelected())
|
|
{
|
|
LLScrollListCell* cellp = item->getColumn(column_index);
|
|
cellp->setValue(item_value);
|
|
cellp->onCommit();
|
|
}
|
|
}
|
|
//FIXME: find a better way to signal cell changes
|
|
onCommit();
|
|
}
|
|
// eat click (e.g. do not trigger double click callback)
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
// treat this as a normal single item selection
|
|
selectItemAt(x, y, mask);
|
|
gFocusMgr.setMouseCapture(this);
|
|
mNeedsScroll = TRUE;
|
|
// do not eat click (allow double click callback)
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
LLScrollListItem* LLScrollListCtrl::hitItem( S32 x, S32 y )
|
|
{
|
|
// Excludes disabled items.
|
|
LLScrollListItem* hit_item = NULL;
|
|
|
|
LLRect item_rect;
|
|
item_rect.setLeftTopAndSize(
|
|
mItemListRect.mLeft,
|
|
mItemListRect.mTop,
|
|
mItemListRect.getWidth(),
|
|
mLineHeight );
|
|
|
|
// allow for partial line at bottom
|
|
S32 num_page_lines = mPageLines + 1;
|
|
|
|
S32 line = 0;
|
|
item_list::iterator iter;
|
|
for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
|
|
{
|
|
LLScrollListItem* item = *iter;
|
|
if( mScrollLines <= line && line < mScrollLines + num_page_lines )
|
|
{
|
|
if( item->getEnabled() && item_rect.pointInRect( x, y ) )
|
|
{
|
|
hit_item = item;
|
|
break;
|
|
}
|
|
|
|
item_rect.translate(0, -mLineHeight);
|
|
}
|
|
line++;
|
|
}
|
|
|
|
return hit_item;
|
|
}
|
|
|
|
S32 LLScrollListCtrl::getColumnIndexFromOffset(S32 x)
|
|
{
|
|
// which column did we hit?
|
|
S32 left = 0;
|
|
S32 right = 0;
|
|
S32 width = 0;
|
|
S32 column_index = 0;
|
|
|
|
ordered_columns_t::const_iterator iter = mColumnsIndexed.begin();
|
|
ordered_columns_t::const_iterator end = mColumnsIndexed.end();
|
|
for ( ; iter != end; ++iter)
|
|
{
|
|
width = (*iter)->getWidth() + mColumnPadding;
|
|
right += width;
|
|
if (left <= x && x < right )
|
|
{
|
|
break;
|
|
}
|
|
|
|
// set left for next column as right of current column
|
|
left = right;
|
|
column_index++;
|
|
}
|
|
|
|
return llclamp(column_index, 0, getNumColumns() - 1);
|
|
}
|
|
|
|
|
|
S32 LLScrollListCtrl::getColumnOffsetFromIndex(S32 index)
|
|
{
|
|
S32 column_offset = 0;
|
|
ordered_columns_t::const_iterator iter = mColumnsIndexed.begin();
|
|
ordered_columns_t::const_iterator end = mColumnsIndexed.end();
|
|
for ( ; iter != end; ++iter)
|
|
{
|
|
if (index-- <= 0)
|
|
{
|
|
return column_offset;
|
|
}
|
|
column_offset += (*iter)->getWidth() + mColumnPadding;
|
|
}
|
|
|
|
// when running off the end, return the rightmost pixel
|
|
return mItemListRect.mRight;
|
|
}
|
|
|
|
S32 LLScrollListCtrl::getRowOffsetFromIndex(S32 index)
|
|
{
|
|
S32 row_bottom = ((mItemListRect.mTop - (index - mScrollLines)) * mLineHeight)
|
|
- mLineHeight;
|
|
return row_bottom;
|
|
}
|
|
|
|
|
|
BOOL LLScrollListCtrl::handleHover(S32 x,S32 y,MASK mask)
|
|
{
|
|
BOOL handled = FALSE;
|
|
|
|
if (hasMouseCapture())
|
|
{
|
|
if(mask == MASK_NONE)
|
|
{
|
|
selectItemAt(x, y, mask);
|
|
mNeedsScroll = TRUE;
|
|
}
|
|
}
|
|
else
|
|
if (mCanSelect)
|
|
{
|
|
LLScrollListItem* item = hitItem(x, y);
|
|
if (item)
|
|
{
|
|
highlightNthItem(getItemIndex(item));
|
|
}
|
|
else
|
|
{
|
|
highlightNthItem(-1);
|
|
}
|
|
}
|
|
|
|
handled = LLUICtrl::handleHover( x, y, mask );
|
|
|
|
return handled;
|
|
}
|
|
|
|
|
|
BOOL LLScrollListCtrl::handleKeyHere(KEY key,MASK mask )
|
|
{
|
|
BOOL handled = FALSE;
|
|
|
|
// not called from parent means we have keyboard focus or a child does
|
|
if (mCanSelect)
|
|
{
|
|
// Ignore capslock
|
|
//mask = mask; //Why was this here?
|
|
|
|
if (mask == MASK_NONE)
|
|
{
|
|
switch(key)
|
|
{
|
|
case KEY_UP:
|
|
if (mAllowKeyboardMovement || hasFocus())
|
|
{
|
|
// commit implicit in call
|
|
selectPrevItem(FALSE);
|
|
mNeedsScroll = TRUE;
|
|
handled = TRUE;
|
|
}
|
|
break;
|
|
case KEY_DOWN:
|
|
if (mAllowKeyboardMovement || hasFocus())
|
|
{
|
|
// commit implicit in call
|
|
selectNextItem(FALSE);
|
|
mNeedsScroll = TRUE;
|
|
handled = TRUE;
|
|
}
|
|
break;
|
|
case KEY_PAGE_UP:
|
|
if (mAllowKeyboardMovement || hasFocus())
|
|
{
|
|
selectNthItem(getFirstSelectedIndex() - (mScrollbar->getPageSize() - 1));
|
|
mNeedsScroll = TRUE;
|
|
if (mCommitOnKeyboardMovement
|
|
&& !mCommitOnSelectionChange)
|
|
{
|
|
onCommit();
|
|
}
|
|
handled = TRUE;
|
|
}
|
|
break;
|
|
case KEY_PAGE_DOWN:
|
|
if (mAllowKeyboardMovement || hasFocus())
|
|
{
|
|
selectNthItem(getFirstSelectedIndex() + (mScrollbar->getPageSize() - 1));
|
|
mNeedsScroll = TRUE;
|
|
if (mCommitOnKeyboardMovement
|
|
&& !mCommitOnSelectionChange)
|
|
{
|
|
onCommit();
|
|
}
|
|
handled = TRUE;
|
|
}
|
|
break;
|
|
case KEY_HOME:
|
|
if (mAllowKeyboardMovement || hasFocus())
|
|
{
|
|
selectFirstItem();
|
|
mNeedsScroll = TRUE;
|
|
if (mCommitOnKeyboardMovement
|
|
&& !mCommitOnSelectionChange)
|
|
{
|
|
onCommit();
|
|
}
|
|
handled = TRUE;
|
|
}
|
|
break;
|
|
case KEY_END:
|
|
if (mAllowKeyboardMovement || hasFocus())
|
|
{
|
|
selectNthItem(getItemCount() - 1);
|
|
mNeedsScroll = TRUE;
|
|
if (mCommitOnKeyboardMovement
|
|
&& !mCommitOnSelectionChange)
|
|
{
|
|
onCommit();
|
|
}
|
|
handled = TRUE;
|
|
}
|
|
break;
|
|
case KEY_RETURN:
|
|
// JC - Special case: Only claim to have handled it
|
|
// if we're the special non-commit-on-move
|
|
// type. AND we are visible
|
|
if (!mCommitOnKeyboardMovement && mask == MASK_NONE)
|
|
{
|
|
onCommit();
|
|
mSearchString.clear();
|
|
handled = TRUE;
|
|
}
|
|
break;
|
|
case KEY_BACKSPACE:
|
|
mSearchTimer.reset();
|
|
if (mSearchString.size())
|
|
{
|
|
mSearchString.erase(mSearchString.size() - 1, 1);
|
|
}
|
|
if (mSearchString.empty())
|
|
{
|
|
if (getFirstSelected())
|
|
{
|
|
LLScrollListCell* cellp = getFirstSelected()->getColumn(getSearchColumn());
|
|
if (cellp)
|
|
{
|
|
cellp->highlightText(0, 0);
|
|
}
|
|
}
|
|
}
|
|
else if (selectItemByPrefix(wstring_to_utf8str(mSearchString), FALSE))
|
|
{
|
|
mNeedsScroll = TRUE;
|
|
// update search string only on successful match
|
|
mSearchTimer.reset();
|
|
|
|
if (mCommitOnKeyboardMovement
|
|
&& !mCommitOnSelectionChange)
|
|
{
|
|
onCommit();
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
// TODO: multiple: shift-up, shift-down, shift-home, shift-end, select all
|
|
}
|
|
|
|
return handled;
|
|
}
|
|
|
|
BOOL LLScrollListCtrl::handleUnicodeCharHere(llwchar uni_char)
|
|
{
|
|
if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
// perform incremental search based on keyboard input
|
|
if (mSearchTimer.getElapsedTimeF32() > LLUI::sConfigGroup->getF32("TypeAheadTimeout"))
|
|
{
|
|
mSearchString.clear();
|
|
}
|
|
|
|
// type ahead search is case insensitive
|
|
uni_char = LLStringOps::toLower((llwchar)uni_char);
|
|
|
|
if (selectItemByPrefix(wstring_to_utf8str(mSearchString + (llwchar)uni_char), FALSE))
|
|
{
|
|
// update search string only on successful match
|
|
mNeedsScroll = TRUE;
|
|
mSearchString += uni_char;
|
|
mSearchTimer.reset();
|
|
|
|
if (mCommitOnKeyboardMovement
|
|
&& !mCommitOnSelectionChange)
|
|
{
|
|
onCommit();
|
|
}
|
|
}
|
|
// handle iterating over same starting character
|
|
else if (isRepeatedChars(mSearchString + (llwchar)uni_char) && !mItemList.empty())
|
|
{
|
|
// start from last selected item, in case we previously had a successful match against
|
|
// duplicated characters ('AA' matches 'Aaron')
|
|
item_list::iterator start_iter = mItemList.begin();
|
|
S32 first_selected = getFirstSelectedIndex();
|
|
|
|
// if we have a selection (> -1) then point iterator at the selected item
|
|
if (first_selected > 0)
|
|
{
|
|
// point iterator to first selected item
|
|
start_iter += first_selected;
|
|
}
|
|
|
|
// start search at first item after current selection
|
|
item_list::iterator iter = start_iter;
|
|
++iter;
|
|
if (iter == mItemList.end())
|
|
{
|
|
iter = mItemList.begin();
|
|
}
|
|
|
|
// loop around once, back to previous selection
|
|
while(iter != start_iter)
|
|
{
|
|
LLScrollListItem* item = *iter;
|
|
|
|
LLScrollListCell* cellp = item->getColumn(getSearchColumn());
|
|
if (cellp)
|
|
{
|
|
// Only select enabled items with matching first characters
|
|
LLWString item_label = utf8str_to_wstring(cellp->getValue().asString());
|
|
if (item->getEnabled() && LLStringOps::toLower(item_label[0]) == uni_char)
|
|
{
|
|
selectItem(item);
|
|
mNeedsScroll = TRUE;
|
|
cellp->highlightText(0, 1);
|
|
mSearchTimer.reset();
|
|
|
|
if (mCommitOnKeyboardMovement
|
|
&& !mCommitOnSelectionChange)
|
|
{
|
|
onCommit();
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
++iter;
|
|
if (iter == mItemList.end())
|
|
{
|
|
iter = mItemList.begin();
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
void LLScrollListCtrl::reportInvalidInput()
|
|
{
|
|
make_ui_sound("UISndBadKeystroke");
|
|
}
|
|
|
|
BOOL LLScrollListCtrl::isRepeatedChars(const LLWString& string) const
|
|
{
|
|
if (string.empty())
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
llwchar first_char = string[0];
|
|
|
|
for (U32 i = 0; i < string.size(); i++)
|
|
{
|
|
if (string[i] != first_char)
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void LLScrollListCtrl::selectItem(LLScrollListItem* itemp, BOOL select_single_item)
|
|
{
|
|
if (!itemp) return;
|
|
|
|
if (!itemp->getSelected())
|
|
{
|
|
if (mLastSelected)
|
|
{
|
|
LLScrollListCell* cellp = mLastSelected->getColumn(getSearchColumn());
|
|
if (cellp)
|
|
{
|
|
cellp->highlightText(0, 0);
|
|
}
|
|
}
|
|
if (select_single_item)
|
|
{
|
|
deselectAllItems(TRUE);
|
|
}
|
|
itemp->setSelected(TRUE);
|
|
mLastSelected = itemp;
|
|
mSelectionChanged = TRUE;
|
|
}
|
|
}
|
|
|
|
void LLScrollListCtrl::deselectItem(LLScrollListItem* itemp)
|
|
{
|
|
if (!itemp) return;
|
|
|
|
if (itemp->getSelected())
|
|
{
|
|
if (mLastSelected == itemp)
|
|
{
|
|
mLastSelected = NULL;
|
|
}
|
|
|
|
itemp->setSelected(FALSE);
|
|
LLScrollListCell* cellp = itemp->getColumn(getSearchColumn());
|
|
if (cellp)
|
|
{
|
|
cellp->highlightText(0, 0);
|
|
}
|
|
mSelectionChanged = TRUE;
|
|
}
|
|
}
|
|
|
|
void LLScrollListCtrl::commitIfChanged()
|
|
{
|
|
if (mSelectionChanged)
|
|
{
|
|
mDirty = TRUE;
|
|
mSelectionChanged = FALSE;
|
|
onCommit();
|
|
}
|
|
}
|
|
|
|
struct SameSortColumn
|
|
{
|
|
SameSortColumn(S32 column) : mColumn(column) {}
|
|
S32 mColumn;
|
|
|
|
bool operator()(std::pair<S32, BOOL> sort_column) { return sort_column.first == mColumn; }
|
|
};
|
|
|
|
BOOL LLScrollListCtrl::setSort(S32 column_idx, BOOL ascending)
|
|
{
|
|
LLScrollListColumn* sort_column = getColumn(column_idx);
|
|
if (!sort_column) return FALSE;
|
|
|
|
sort_column->mSortAscending = ascending;
|
|
|
|
sort_column_t new_sort_column(column_idx, ascending);
|
|
|
|
if (mSortColumns.empty())
|
|
{
|
|
mSortColumns.push_back(new_sort_column);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
// grab current sort column
|
|
sort_column_t cur_sort_column = mSortColumns.back();
|
|
|
|
// remove any existing sort criterion referencing this column
|
|
// and add the new one
|
|
mSortColumns.erase(remove_if(mSortColumns.begin(), mSortColumns.end(), SameSortColumn(column_idx)), mSortColumns.end());
|
|
mSortColumns.push_back(new_sort_column);
|
|
|
|
// did the sort criteria change?
|
|
return (cur_sort_column != new_sort_column);
|
|
}
|
|
}
|
|
|
|
// Called by scrollbar
|
|
//static
|
|
void LLScrollListCtrl::onScrollChange( S32 new_pos, LLScrollbar* scrollbar, void* userdata )
|
|
{
|
|
LLScrollListCtrl* self = (LLScrollListCtrl*) userdata;
|
|
self->mScrollLines = new_pos;
|
|
}
|
|
|
|
|
|
void LLScrollListCtrl::sortByColumn(const std::string& name, BOOL ascending)
|
|
{
|
|
std::map<std::string, LLScrollListColumn>::iterator itor = mColumns.find(name);
|
|
if (itor != mColumns.end())
|
|
{
|
|
sortByColumnIndex((*itor).second.mIndex, ascending);
|
|
}
|
|
}
|
|
|
|
// First column is column 0
|
|
void LLScrollListCtrl::sortByColumnIndex(U32 column, BOOL ascending)
|
|
{
|
|
if (setSort(column, ascending))
|
|
{
|
|
sortItems();
|
|
}
|
|
}
|
|
|
|
void LLScrollListCtrl::sortItems()
|
|
{
|
|
// do stable sort to preserve any previous sorts
|
|
std::stable_sort(
|
|
mItemList.begin(),
|
|
mItemList.end(),
|
|
SortScrollListItem(mSortColumns));
|
|
|
|
setSorted(TRUE);
|
|
}
|
|
|
|
// for one-shot sorts, does not save sort column/order
|
|
void LLScrollListCtrl::sortOnce(S32 column, BOOL ascending)
|
|
{
|
|
std::vector<std::pair<S32, BOOL> > sort_column;
|
|
sort_column.push_back(std::make_pair(column, ascending));
|
|
|
|
// do stable sort to preserve any previous sorts
|
|
std::stable_sort(
|
|
mItemList.begin(),
|
|
mItemList.end(),
|
|
SortScrollListItem(sort_column));
|
|
}
|
|
|
|
void LLScrollListCtrl::dirtyColumns()
|
|
{
|
|
mColumnsDirty = TRUE;
|
|
|
|
// need to keep mColumnsIndexed up to date
|
|
// just in case someone indexes into it immediately
|
|
mColumnsIndexed.resize(mColumns.size());
|
|
|
|
std::map<std::string, LLScrollListColumn>::iterator column_itor;
|
|
for (column_itor = mColumns.begin(); column_itor != mColumns.end(); ++column_itor)
|
|
{
|
|
LLScrollListColumn *column = &column_itor->second;
|
|
mColumnsIndexed[column_itor->second.mIndex] = column;
|
|
}
|
|
}
|
|
|
|
|
|
S32 LLScrollListCtrl::getScrollPos() const
|
|
{
|
|
return mScrollbar->getDocPos();
|
|
}
|
|
|
|
|
|
void LLScrollListCtrl::setScrollPos( S32 pos )
|
|
{
|
|
mScrollbar->setDocPos( pos );
|
|
|
|
onScrollChange(mScrollbar->getDocPos(), mScrollbar, this);
|
|
}
|
|
|
|
|
|
void LLScrollListCtrl::scrollToShowSelected()
|
|
{
|
|
// don't scroll automatically when capturing mouse input
|
|
// as that will change what is currently under the mouse cursor
|
|
if (hasMouseCapture())
|
|
{
|
|
return;
|
|
}
|
|
|
|
S32 index = getFirstSelectedIndex();
|
|
if (index < 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
LLScrollListItem* item = mItemList[index];
|
|
if (!item)
|
|
{
|
|
// I don't THINK this should ever happen.
|
|
return;
|
|
}
|
|
|
|
S32 lowest = mScrollLines;
|
|
S32 highest = mScrollLines + mPageLines;
|
|
|
|
if (index < lowest)
|
|
{
|
|
// need to scroll to show item
|
|
setScrollPos(index);
|
|
}
|
|
else if (highest <= index)
|
|
{
|
|
setScrollPos(index - mPageLines + 1);
|
|
}
|
|
}
|
|
|
|
void LLScrollListCtrl::updateStaticColumnWidth(LLScrollListColumn* col, S32 new_width)
|
|
{
|
|
mTotalStaticColumnWidth += llmax(0, new_width) - llmax(0, col->getWidth());
|
|
}
|
|
|
|
|
|
// virtual
|
|
LLXMLNodePtr LLScrollListCtrl::getXML(bool save_children) const
|
|
{
|
|
LLXMLNodePtr node = LLUICtrl::getXML();
|
|
|
|
node->setName(LL_SCROLL_LIST_CTRL_TAG);
|
|
|
|
// Attributes
|
|
|
|
node->createChild("multi_select", TRUE)->setBoolValue(mAllowMultipleSelection);
|
|
|
|
node->createChild("draw_border", TRUE)->setBoolValue((mBorder != NULL));
|
|
|
|
node->createChild("draw_heading", TRUE)->setBoolValue(mDisplayColumnHeaders);
|
|
|
|
node->createChild("background_visible", TRUE)->setBoolValue(mBackgroundVisible);
|
|
|
|
node->createChild("draw_stripes", TRUE)->setBoolValue(mDrawStripes);
|
|
|
|
node->createChild("column_padding", TRUE)->setIntValue(mColumnPadding);
|
|
|
|
node->createChild("mouse_wheel_opaque", TRUE)->setBoolValue(mMouseWheelOpaque);
|
|
|
|
addColorXML(node, mBgWriteableColor, "bg_writeable_color", "ScrollBgWriteableColor");
|
|
addColorXML(node, mBgReadOnlyColor, "bg_read_only_color", "ScrollBgReadOnlyColor");
|
|
addColorXML(node, mBgSelectedColor, "bg_selected_color", "ScrollSelectedBGColor");
|
|
addColorXML(node, mBgStripeColor, "bg_stripe_color", "ScrollBGStripeColor");
|
|
addColorXML(node, mFgSelectedColor, "fg_selected_color", "ScrollSelectedFGColor");
|
|
addColorXML(node, mFgUnselectedColor, "fg_unselected_color", "ScrollUnselectedColor");
|
|
addColorXML(node, mFgDisabledColor, "fg_disable_color", "ScrollDisabledColor");
|
|
addColorXML(node, mHighlightedColor, "highlighted_color", "ScrollHighlightedColor");
|
|
|
|
// Contents
|
|
|
|
std::map<std::string, LLScrollListColumn>::const_iterator itor;
|
|
std::vector<const LLScrollListColumn*> sorted_list;
|
|
sorted_list.resize(mColumns.size());
|
|
for (itor = mColumns.begin(); itor != mColumns.end(); ++itor)
|
|
{
|
|
sorted_list[itor->second.mIndex] = &itor->second;
|
|
}
|
|
|
|
std::vector<const LLScrollListColumn*>::iterator itor2;
|
|
for (itor2 = sorted_list.begin(); itor2 != sorted_list.end(); ++itor2)
|
|
{
|
|
LLXMLNodePtr child_node = node->createChild("column", FALSE);
|
|
const LLScrollListColumn *column = *itor2;
|
|
|
|
child_node->createChild("name", TRUE)->setStringValue(column->mName);
|
|
child_node->createChild("label", TRUE)->setStringValue(column->mLabel);
|
|
child_node->createChild("width", TRUE)->setIntValue(column->getWidth());
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
void LLScrollListCtrl::setScrollListParameters(LLXMLNodePtr node)
|
|
{
|
|
// James: This is not a good way to do colors. We need a central "UI style"
|
|
// manager that sets the colors for ALL scroll lists, buttons, etc.
|
|
|
|
LLColor4 color;
|
|
if(node->hasAttribute("fg_unselected_color"))
|
|
{
|
|
LLUICtrlFactory::getAttributeColor(node,"fg_unselected_color", color);
|
|
setFgUnselectedColor(color);
|
|
}
|
|
if(node->hasAttribute("fg_selected_color"))
|
|
{
|
|
LLUICtrlFactory::getAttributeColor(node,"fg_selected_color", color);
|
|
setFgSelectedColor(color);
|
|
}
|
|
if(node->hasAttribute("bg_selected_color"))
|
|
{
|
|
LLUICtrlFactory::getAttributeColor(node,"bg_selected_color", color);
|
|
setBgSelectedColor(color);
|
|
}
|
|
if(node->hasAttribute("fg_disable_color"))
|
|
{
|
|
LLUICtrlFactory::getAttributeColor(node,"fg_disable_color", color);
|
|
setFgDisableColor(color);
|
|
}
|
|
if(node->hasAttribute("bg_writeable_color"))
|
|
{
|
|
LLUICtrlFactory::getAttributeColor(node,"bg_writeable_color", color);
|
|
setBgWriteableColor(color);
|
|
}
|
|
if(node->hasAttribute("bg_read_only_color"))
|
|
{
|
|
LLUICtrlFactory::getAttributeColor(node,"bg_read_only_color", color);
|
|
setReadOnlyBgColor(color);
|
|
}
|
|
if (LLUICtrlFactory::getAttributeColor(node,"bg_stripe_color", color))
|
|
{
|
|
setBgStripeColor(color);
|
|
}
|
|
if (LLUICtrlFactory::getAttributeColor(node,"highlighted_color", color))
|
|
{
|
|
setHighlightedColor(color);
|
|
}
|
|
|
|
if(node->hasAttribute("background_visible"))
|
|
{
|
|
BOOL background_visible;
|
|
node->getAttributeBOOL("background_visible", background_visible);
|
|
setBackgroundVisible(background_visible);
|
|
}
|
|
|
|
if(node->hasAttribute("draw_stripes"))
|
|
{
|
|
BOOL draw_stripes;
|
|
node->getAttributeBOOL("draw_stripes", draw_stripes);
|
|
setDrawStripes(draw_stripes);
|
|
}
|
|
|
|
if(node->hasAttribute("column_padding"))
|
|
{
|
|
S32 column_padding;
|
|
node->getAttributeS32("column_padding", column_padding);
|
|
setColumnPadding(column_padding);
|
|
}
|
|
}
|
|
|
|
// static
|
|
LLView* LLScrollListCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory)
|
|
{
|
|
std::string name("scroll_list");
|
|
node->getAttributeString("name", name);
|
|
|
|
LLRect rect;
|
|
createRect(node, rect, parent, LLRect());
|
|
|
|
BOOL multi_select = FALSE;
|
|
node->getAttributeBOOL("multi_select", multi_select);
|
|
|
|
BOOL draw_border = TRUE;
|
|
node->getAttributeBOOL("draw_border", draw_border);
|
|
|
|
BOOL draw_heading = FALSE;
|
|
node->getAttributeBOOL("draw_heading", draw_heading);
|
|
|
|
S32 search_column = 0;
|
|
node->getAttributeS32("search_column", search_column);
|
|
|
|
S32 sort_column = -1;
|
|
node->getAttributeS32("sort_column", sort_column);
|
|
|
|
BOOL sort_ascending = TRUE;
|
|
node->getAttributeBOOL("sort_ascending", sort_ascending);
|
|
|
|
BOOL mouse_wheel_opaque = TRUE;
|
|
node->getAttributeBOOL("mouse_wheel_opaque", mouse_wheel_opaque);
|
|
|
|
LLUICtrlCallback callback = NULL;
|
|
|
|
LLScrollListCtrl* scroll_list = new LLScrollListCtrl(
|
|
name,
|
|
rect,
|
|
callback,
|
|
NULL,
|
|
multi_select,
|
|
draw_border);
|
|
|
|
scroll_list->setDisplayHeading(draw_heading);
|
|
if (node->hasAttribute("heading_height"))
|
|
{
|
|
S32 heading_height;
|
|
node->getAttributeS32("heading_height", heading_height);
|
|
scroll_list->setHeadingHeight(heading_height);
|
|
}
|
|
|
|
scroll_list->setScrollListParameters(node);
|
|
|
|
scroll_list->initFromXML(node, parent);
|
|
|
|
scroll_list->setSearchColumn(search_column);
|
|
|
|
scroll_list->mMouseWheelOpaque = mouse_wheel_opaque;
|
|
|
|
LLSD columns;
|
|
S32 index = 0;
|
|
LLXMLNodePtr child;
|
|
for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling())
|
|
{
|
|
if (child->hasName("column"))
|
|
{
|
|
std::string labelname("");
|
|
child->getAttributeString("label", labelname);
|
|
|
|
std::string columnname(labelname);
|
|
child->getAttributeString("name", columnname);
|
|
|
|
std::string sortname(columnname);
|
|
child->getAttributeString("sort", sortname);
|
|
|
|
BOOL sort_ascending = TRUE;
|
|
child->getAttributeBOOL("sort_ascending", sort_ascending);
|
|
|
|
std::string imagename;
|
|
child->getAttributeString("image", imagename);
|
|
|
|
std::string imageoverlay;
|
|
child->getAttributeString("image_overlay", imageoverlay);
|
|
|
|
BOOL columndynamicwidth = FALSE;
|
|
child->getAttributeBOOL("dynamicwidth", columndynamicwidth);
|
|
|
|
S32 columnwidth = -1;
|
|
child->getAttributeS32("width", columnwidth);
|
|
|
|
std::string tooltip;
|
|
child->getAttributeString("tool_tip", tooltip);
|
|
|
|
F32 columnrelwidth = 0.f;
|
|
child->getAttributeF32("relwidth", columnrelwidth);
|
|
|
|
LLFontGL::HAlign h_align = LLFontGL::LEFT;
|
|
h_align = LLView::selectFontHAlign(child);
|
|
|
|
columns[index]["name"] = columnname;
|
|
columns[index]["sort"] = sortname;
|
|
columns[index]["sort_ascending"] = sort_ascending;
|
|
columns[index]["image"] = imagename;
|
|
columns[index]["image_overlay"] = imageoverlay;
|
|
columns[index]["label"] = labelname;
|
|
columns[index]["width"] = columnwidth;
|
|
columns[index]["relwidth"] = columnrelwidth;
|
|
columns[index]["dynamicwidth"] = columndynamicwidth;
|
|
columns[index]["halign"] = (S32)h_align;
|
|
columns[index]["tool_tip"] = tooltip;
|
|
|
|
index++;
|
|
}
|
|
}
|
|
scroll_list->setColumnHeadings(columns);
|
|
|
|
if (sort_column >= 0)
|
|
{
|
|
scroll_list->sortByColumnIndex(sort_column, sort_ascending);
|
|
}
|
|
|
|
for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling())
|
|
{
|
|
if (child->hasName("row"))
|
|
{
|
|
LLUUID id;
|
|
child->getAttributeUUID("id", id);
|
|
|
|
LLSD row;
|
|
|
|
row["id"] = id;
|
|
|
|
S32 column_idx = 0;
|
|
LLXMLNodePtr row_child;
|
|
for (row_child = child->getFirstChild(); row_child.notNull(); row_child = row_child->getNextSibling())
|
|
{
|
|
if (row_child->hasName("column"))
|
|
{
|
|
std::string value = row_child->getTextContents();
|
|
|
|
std::string columnname("");
|
|
row_child->getAttributeString("name", columnname);
|
|
|
|
std::string font("");
|
|
row_child->getAttributeString("font", font);
|
|
|
|
std::string font_style("");
|
|
row_child->getAttributeString("font-style", font_style);
|
|
|
|
row["columns"][column_idx]["column"] = columnname;
|
|
row["columns"][column_idx]["value"] = value;
|
|
row["columns"][column_idx]["font"] = font;
|
|
row["columns"][column_idx]["font-style"] = font_style;
|
|
column_idx++;
|
|
}
|
|
}
|
|
scroll_list->addElement(row);
|
|
}
|
|
}
|
|
|
|
std::string contents = node->getTextContents();
|
|
if (!contents.empty())
|
|
{
|
|
typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
|
|
boost::char_separator<char> sep("\t\n");
|
|
tokenizer tokens(contents, sep);
|
|
tokenizer::iterator token_iter = tokens.begin();
|
|
|
|
while(token_iter != tokens.end())
|
|
{
|
|
const std::string& line = *token_iter;
|
|
scroll_list->addSimpleElement(line);
|
|
++token_iter;
|
|
}
|
|
}
|
|
|
|
return scroll_list;
|
|
}
|
|
|
|
// LLEditMenuHandler functions
|
|
|
|
// virtual
|
|
void LLScrollListCtrl::copy()
|
|
{
|
|
std::string buffer;
|
|
|
|
std::vector<LLScrollListItem*> items = getAllSelected();
|
|
std::vector<LLScrollListItem*>::iterator itor;
|
|
for (itor = items.begin(); itor != items.end(); ++itor)
|
|
{
|
|
buffer += (*itor)->getContentsCSV() + "\n";
|
|
}
|
|
gClipboard.copyFromSubstring(utf8str_to_wstring(buffer), 0, buffer.length());
|
|
}
|
|
|
|
// virtual
|
|
BOOL LLScrollListCtrl::canCopy() const
|
|
{
|
|
return (getFirstSelected() != NULL);
|
|
}
|
|
|
|
// virtual
|
|
void LLScrollListCtrl::cut()
|
|
{
|
|
copy();
|
|
doDelete();
|
|
}
|
|
|
|
// virtual
|
|
BOOL LLScrollListCtrl::canCut() const
|
|
{
|
|
return canCopy() && canDoDelete();
|
|
}
|
|
|
|
// virtual
|
|
void LLScrollListCtrl::selectAll()
|
|
{
|
|
// Deselects all other items
|
|
item_list::iterator iter;
|
|
for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
|
|
{
|
|
LLScrollListItem *itemp = *iter;
|
|
if( itemp->getEnabled() )
|
|
{
|
|
selectItem(itemp, FALSE);
|
|
}
|
|
}
|
|
|
|
if (mCommitOnSelectionChange)
|
|
{
|
|
commitIfChanged();
|
|
}
|
|
}
|
|
|
|
// virtual
|
|
BOOL LLScrollListCtrl::canSelectAll() const
|
|
{
|
|
return getCanSelect() && mAllowMultipleSelection && !(mMaxSelectable > 0 && mItemList.size() > mMaxSelectable);
|
|
}
|
|
|
|
// virtual
|
|
void LLScrollListCtrl::deselect()
|
|
{
|
|
deselectAllItems();
|
|
}
|
|
|
|
// virtual
|
|
BOOL LLScrollListCtrl::canDeselect() const
|
|
{
|
|
return getCanSelect();
|
|
}
|
|
|
|
void LLScrollListCtrl::addColumn(const LLSD& column, EAddPosition pos)
|
|
{
|
|
std::string name = column["name"].asString();
|
|
// if no column name provided, just use ordinal as name
|
|
if (name.empty())
|
|
{
|
|
std::ostringstream new_name;
|
|
new_name << mColumnsIndexed.size();
|
|
name = new_name.str();
|
|
}
|
|
if (mColumns.find(name) == mColumns.end())
|
|
{
|
|
// Add column
|
|
mColumns[name] = LLScrollListColumn(column, this);
|
|
LLScrollListColumn* new_column = &mColumns[name];
|
|
new_column->mParentCtrl = this;
|
|
new_column->mIndex = mColumns.size()-1;
|
|
|
|
// Add button
|
|
if (new_column->getWidth() > 0 || new_column->mRelWidth > 0 || new_column->mDynamicWidth)
|
|
{
|
|
if (getNumColumns() > 0)
|
|
{
|
|
mTotalColumnPadding += mColumnPadding;
|
|
}
|
|
if (new_column->mRelWidth >= 0)
|
|
{
|
|
new_column->setWidth((S32)llround(new_column->mRelWidth*mItemListRect.getWidth()));
|
|
}
|
|
else if(new_column->mDynamicWidth)
|
|
{
|
|
mNumDynamicWidthColumns++;
|
|
new_column->setWidth((mItemListRect.getWidth() - mTotalStaticColumnWidth - mTotalColumnPadding) / mNumDynamicWidthColumns);
|
|
}
|
|
S32 top = mItemListRect.mTop;
|
|
S32 left = mItemListRect.mLeft;
|
|
{
|
|
std::map<std::string, LLScrollListColumn>::iterator itor;
|
|
for (itor = mColumns.begin(); itor != mColumns.end(); ++itor)
|
|
{
|
|
if (itor->second.mIndex < new_column->mIndex &&
|
|
itor->second.getWidth() > 0)
|
|
{
|
|
left += itor->second.getWidth() + mColumnPadding;
|
|
}
|
|
}
|
|
}
|
|
std::string button_name = "btn_" + name;
|
|
S32 right = left+new_column->getWidth();
|
|
if (new_column->mIndex != (S32)mColumns.size()-1)
|
|
{
|
|
right += mColumnPadding;
|
|
}
|
|
LLRect temp_rect = LLRect(left,top+mHeadingHeight,right,top);
|
|
new_column->mHeader = new LLColumnHeader(button_name, temp_rect, new_column);
|
|
if(column["image"].asString() != "")
|
|
{
|
|
//new_column->mHeader->setScaleImage(false);
|
|
new_column->mHeader->setImage(column["image"].asString());
|
|
}
|
|
else if(column["image_overlay"].asString() != "")
|
|
{
|
|
new_column->mHeader->setImageOverlay(column["image_overlay"].asString());
|
|
}
|
|
else
|
|
{
|
|
new_column->mHeader->setLabel(new_column->mLabel);
|
|
//new_column->mHeader->setLabel(new_column->mLabel);
|
|
}
|
|
|
|
new_column->mHeader->setToolTip(column["tool_tip"].asString());
|
|
|
|
//RN: although it might be useful to change sort order with the keyboard,
|
|
// mixing tab stops on child items along with the parent item is not supported yet
|
|
new_column->mHeader->setTabStop(FALSE);
|
|
addChild(new_column->mHeader);
|
|
new_column->mHeader->setVisible(mDisplayColumnHeaders);
|
|
|
|
sendChildToFront(mScrollbar);
|
|
}
|
|
}
|
|
|
|
dirtyColumns();
|
|
}
|
|
|
|
// static
|
|
void LLScrollListCtrl::onClickColumn(void *userdata)
|
|
{
|
|
LLScrollListColumn *info = (LLScrollListColumn*)userdata;
|
|
if (!info) return;
|
|
|
|
LLScrollListCtrl *parent = info->mParentCtrl;
|
|
if (!parent) return;
|
|
|
|
S32 column_index = info->mIndex;
|
|
|
|
LLScrollListColumn* column = parent->mColumnsIndexed[info->mIndex];
|
|
bool ascending = column->mSortAscending;
|
|
if (column->mSortingColumn != column->mName
|
|
&& parent->mColumns.find(column->mSortingColumn) != parent->mColumns.end())
|
|
{
|
|
LLScrollListColumn& info_redir = parent->mColumns[column->mSortingColumn];
|
|
column_index = info_redir.mIndex;
|
|
}
|
|
|
|
// if this column is the primary sort key, reverse the direction
|
|
sort_column_t cur_sort_column;
|
|
if (!parent->mSortColumns.empty() && parent->mSortColumns.back().first == column_index)
|
|
{
|
|
ascending = !parent->mSortColumns.back().second;
|
|
}
|
|
|
|
parent->sortByColumnIndex(column_index, ascending);
|
|
|
|
if (parent->mOnSortChangedCallback)
|
|
{
|
|
parent->mOnSortChangedCallback(parent->getCallbackUserData());
|
|
}
|
|
}
|
|
|
|
std::string LLScrollListCtrl::getSortColumnName()
|
|
{
|
|
LLScrollListColumn* column = mSortColumns.empty() ? NULL : mColumnsIndexed[mSortColumns.back().first];
|
|
|
|
if (column) return column->mName;
|
|
else return "";
|
|
}
|
|
|
|
BOOL LLScrollListCtrl::needsSorting()
|
|
{
|
|
return !mSortColumns.empty();
|
|
}
|
|
|
|
void LLScrollListCtrl::clearColumns()
|
|
{
|
|
std::map<std::string, LLScrollListColumn>::iterator itor;
|
|
for (itor = mColumns.begin(); itor != mColumns.end(); ++itor)
|
|
{
|
|
LLColumnHeader *header = itor->second.mHeader;
|
|
if (header)
|
|
{
|
|
removeChild(header);
|
|
delete header;
|
|
}
|
|
}
|
|
mColumns.clear();
|
|
mSortColumns.clear();
|
|
mTotalStaticColumnWidth = 0;
|
|
mTotalColumnPadding = 0;
|
|
}
|
|
|
|
void LLScrollListCtrl::setColumnLabel(const std::string& column, const std::string& label)
|
|
{
|
|
std::map<std::string, LLScrollListColumn>::iterator itor = mColumns.find(column);
|
|
if (itor != mColumns.end())
|
|
{
|
|
itor->second.mLabel = label;
|
|
if (itor->second.mHeader)
|
|
{
|
|
itor->second.mHeader->setLabel(label);
|
|
}
|
|
}
|
|
}
|
|
|
|
LLScrollListColumn* LLScrollListCtrl::getColumn(S32 index)
|
|
{
|
|
if (index < 0 || index >= (S32)mColumnsIndexed.size())
|
|
{
|
|
return NULL;
|
|
}
|
|
return mColumnsIndexed[index];
|
|
}
|
|
|
|
void LLScrollListCtrl::setColumnHeadings(LLSD headings)
|
|
{
|
|
mColumns.clear();
|
|
LLSD::array_const_iterator itor;
|
|
for (itor = headings.beginArray(); itor != headings.endArray(); ++itor)
|
|
{
|
|
addColumn(*itor);
|
|
}
|
|
}
|
|
|
|
LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& value, EAddPosition pos, void* userdata)
|
|
{
|
|
// ID
|
|
LLSD id = value["id"];
|
|
|
|
LLScrollListItem *new_item = new LLScrollListItem(id, userdata);
|
|
if (value.has("enabled"))
|
|
{
|
|
new_item->setEnabled( value["enabled"].asBoolean() );
|
|
}
|
|
|
|
new_item->setNumColumns(mColumns.size());
|
|
|
|
// Add any columns we don't already have
|
|
LLSD columns = value["columns"];
|
|
LLSD::array_const_iterator itor;
|
|
S32 col_index = 0 ;
|
|
for (itor = columns.beginArray(); itor != columns.endArray(); ++itor)
|
|
{
|
|
if (itor->isUndefined())
|
|
{
|
|
// skip unused columns in item passed in
|
|
continue;
|
|
}
|
|
std::string column = (*itor)["column"].asString();
|
|
|
|
LLScrollListColumn* columnp = NULL;
|
|
|
|
// empty columns strings index by ordinal
|
|
if (column.empty())
|
|
{
|
|
std::ostringstream new_name;
|
|
new_name << col_index;
|
|
column = new_name.str();
|
|
}
|
|
|
|
std::map<std::string, LLScrollListColumn>::iterator column_itor;
|
|
column_itor = mColumns.find(column);
|
|
if (column_itor != mColumns.end())
|
|
{
|
|
columnp = &column_itor->second;
|
|
}
|
|
|
|
// create new column on demand
|
|
if (!columnp)
|
|
{
|
|
LLSD new_column;
|
|
new_column["name"] = column;
|
|
new_column["label"] = column;
|
|
// if width supplied for column, use it, otherwise
|
|
// use adaptive width
|
|
if (itor->has("width"))
|
|
{
|
|
new_column["width"] = (*itor)["width"];
|
|
}
|
|
else
|
|
{
|
|
new_column["dynamicwidth"] = true;
|
|
}
|
|
addColumn(new_column);
|
|
columnp = &mColumns[column];
|
|
new_item->setNumColumns(mColumns.size());
|
|
}
|
|
|
|
S32 index = columnp->mIndex;
|
|
S32 width = columnp->getWidth();
|
|
LLFontGL::HAlign font_alignment = columnp->mFontAlignment;
|
|
LLColor4 fcolor = LLColor4::black;
|
|
|
|
LLSD value = (*itor)["value"];
|
|
std::string fontname = (*itor)["font"].asString();
|
|
std::string fontstyle = (*itor)["font-style"].asString();
|
|
std::string type = (*itor)["type"].asString();
|
|
|
|
if ((*itor).has("font-color"))
|
|
{
|
|
LLSD sd_color = (*itor)["font-color"];
|
|
fcolor.setValue(sd_color);
|
|
}
|
|
|
|
BOOL has_color = (*itor).has("color");
|
|
LLColor4 color = ((*itor)["color"]);
|
|
BOOL enabled = !(*itor).has("enabled") || (*itor)["enabled"].asBoolean() == true;
|
|
|
|
const LLFontGL *font = LLResMgr::getInstance()->getRes(fontname);
|
|
if (!font)
|
|
{
|
|
font = LLResMgr::getInstance()->getRes( LLFONT_SANSSERIF_SMALL );
|
|
}
|
|
U8 font_style = LLFontGL::getStyleFromString(fontstyle);
|
|
|
|
if (type == "icon")
|
|
{
|
|
LLScrollListIcon* cell = new LLScrollListIcon(value, width);
|
|
if (has_color)
|
|
{
|
|
cell->setColor(color);
|
|
}
|
|
new_item->setColumn(index, cell);
|
|
}
|
|
else if (type == "checkbox")
|
|
{
|
|
LLCheckBoxCtrl* ctrl = new LLCheckBoxCtrl(std::string("check"),
|
|
LLRect(0, width, width, 0), std::string(" "));
|
|
ctrl->setEnabled(enabled);
|
|
ctrl->setValue(value);
|
|
LLScrollListCheck* cell = new LLScrollListCheck(ctrl,width);
|
|
if (has_color)
|
|
{
|
|
cell->setColor(color);
|
|
}
|
|
new_item->setColumn(index, cell);
|
|
}
|
|
else if (type == "separator")
|
|
{
|
|
LLScrollListSeparator* cell = new LLScrollListSeparator(width);
|
|
if (has_color)
|
|
{
|
|
cell->setColor(color);
|
|
}
|
|
new_item->setColumn(index, cell);
|
|
}
|
|
else if (type == "date")
|
|
{
|
|
LLScrollListDate* cell = new LLScrollListDate(value.asDate(), font, width, font_style, font_alignment);
|
|
if (has_color)
|
|
{
|
|
cell->setColor(color);
|
|
}
|
|
new_item->setColumn(index, cell);
|
|
if (columnp->mHeader && !value.asString().empty())
|
|
{
|
|
columnp->mHeader->setHasResizableElement(TRUE);
|
|
}
|
|
}
|
|
// <edit>
|
|
else if(type == "line_editor")
|
|
{
|
|
LLLineEditor* ctrl = new LLLineEditor(std::string("line_editor"),
|
|
LLRect(0, width, width, 0), std::string(""));
|
|
ctrl->setEnabled(enabled);
|
|
ctrl->setValue(value);
|
|
LLScrollListLineEditor* cell = new LLScrollListLineEditor(ctrl,width);
|
|
if (has_color)
|
|
{
|
|
cell->setColor(color);
|
|
}
|
|
new_item->setColumn(index, cell);
|
|
}
|
|
// </edit>
|
|
else
|
|
{
|
|
LLScrollListText* cell = new LLScrollListText(value.asString(), font, width, font_style, font_alignment, fcolor, TRUE);
|
|
if (has_color)
|
|
{
|
|
cell->setColor(color);
|
|
}
|
|
else
|
|
{
|
|
cell->setColor(mDefaultListTextColor);
|
|
}
|
|
new_item->setColumn(index, cell);
|
|
if (columnp->mHeader && !value.asString().empty())
|
|
{
|
|
columnp->mHeader->setHasResizableElement(TRUE);
|
|
}
|
|
}
|
|
|
|
col_index++;
|
|
}
|
|
|
|
// add dummy cells for missing columns
|
|
for (column_map_t::iterator column_it = mColumns.begin(); column_it != mColumns.end(); ++column_it)
|
|
{
|
|
S32 column_idx = column_it->second.mIndex;
|
|
if (new_item->getColumn(column_idx) == NULL)
|
|
{
|
|
LLScrollListColumn* column_ptr = &column_it->second;
|
|
new_item->setColumn(column_idx, new LLScrollListText(LLStringUtil::null, LLResMgr::getInstance()->getRes( LLFONT_SANSSERIF_SMALL ), column_ptr->getWidth(), LLFontGL::NORMAL));
|
|
}
|
|
}
|
|
|
|
addItem(new_item, pos);
|
|
|
|
return new_item;
|
|
}
|
|
|
|
LLScrollListItem* LLScrollListCtrl::addSimpleElement(const std::string& value, EAddPosition pos, const LLSD& id)
|
|
{
|
|
LLSD entry_id = id;
|
|
|
|
if (id.isUndefined())
|
|
{
|
|
entry_id = value;
|
|
}
|
|
|
|
LLScrollListItem *new_item = new LLScrollListItem(entry_id);
|
|
|
|
const LLFontGL *font = LLResMgr::getInstance()->getRes( LLFONT_SANSSERIF_SMALL );
|
|
|
|
new_item->addColumn(value, font, getRect().getWidth());
|
|
|
|
addItem(new_item, pos);
|
|
return new_item;
|
|
}
|
|
|
|
void LLScrollListCtrl::setValue(const LLSD& value )
|
|
{
|
|
LLSD::array_const_iterator itor;
|
|
for (itor = value.beginArray(); itor != value.endArray(); ++itor)
|
|
{
|
|
addElement(*itor);
|
|
}
|
|
}
|
|
|
|
LLSD LLScrollListCtrl::getValue() const
|
|
{
|
|
LLScrollListItem *item = getFirstSelected();
|
|
if (!item) return LLSD();
|
|
return item->getValue();
|
|
}
|
|
|
|
BOOL LLScrollListCtrl::operateOnSelection(EOperation op)
|
|
{
|
|
if (op == OP_DELETE)
|
|
{
|
|
deleteSelectedItems();
|
|
return TRUE;
|
|
}
|
|
else if (op == OP_DESELECT)
|
|
{
|
|
deselectAllItems();
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL LLScrollListCtrl::operateOnAll(EOperation op)
|
|
{
|
|
if (op == OP_DELETE)
|
|
{
|
|
clearRows();
|
|
return TRUE;
|
|
}
|
|
else if (op == OP_DESELECT)
|
|
{
|
|
deselectAllItems();
|
|
}
|
|
else if (op == OP_SELECT)
|
|
{
|
|
selectAll();
|
|
}
|
|
return FALSE;
|
|
}
|
|
//virtual
|
|
void LLScrollListCtrl::setFocus(BOOL b)
|
|
{
|
|
mSearchString.clear();
|
|
// for tabbing into pristine scroll lists (Finder)
|
|
if (!getFirstSelected())
|
|
{
|
|
selectFirstItem();
|
|
//onCommit(); // SJB: selectFirstItem() will call onCommit() if appropriate
|
|
}
|
|
LLUICtrl::setFocus(b);
|
|
}
|
|
|
|
|
|
// virtual
|
|
BOOL LLScrollListCtrl::isDirty() const
|
|
{
|
|
BOOL grubby = mDirty;
|
|
if ( !mAllowMultipleSelection )
|
|
{
|
|
grubby = (mOriginalSelection != getFirstSelectedIndex());
|
|
}
|
|
return grubby;
|
|
}
|
|
|
|
// Clear dirty state
|
|
void LLScrollListCtrl::resetDirty()
|
|
{
|
|
mDirty = FALSE;
|
|
mOriginalSelection = getFirstSelectedIndex();
|
|
}
|
|
|
|
|
|
//virtual
|
|
void LLScrollListCtrl::onFocusReceived()
|
|
{
|
|
// forget latent selection changes when getting focus
|
|
mSelectionChanged = FALSE;
|
|
LLUICtrl::onFocusReceived();
|
|
}
|
|
|
|
//virtual
|
|
void LLScrollListCtrl::onFocusLost()
|
|
{
|
|
if (hasMouseCapture())
|
|
{
|
|
gFocusMgr.setMouseCapture(NULL);
|
|
}
|
|
LLUICtrl::onFocusLost();
|
|
}
|
|
|
|
LLColumnHeader::LLColumnHeader(const std::string& label, const LLRect &rect, LLScrollListColumn* column, const LLFontGL* fontp) :
|
|
LLComboBox(label, rect, label, NULL, NULL),
|
|
mColumn(column),
|
|
mOrigLabel(label),
|
|
mShowSortOptions(FALSE),
|
|
mHasResizableElement(FALSE)
|
|
{
|
|
mListPosition = LLComboBox::ABOVE;
|
|
setCommitCallback(onSelectSort);
|
|
setCallbackUserData(this);
|
|
mButton->setTabStop(FALSE);
|
|
// require at least two frames between mouse down and mouse up event to capture intentional "hold" not just bad framerate
|
|
mButton->setHeldDownDelay(LLUI::sConfigGroup->getF32("ColumnHeaderDropDownDelay"), 2);
|
|
mButton->setHeldDownCallback(boost::bind(&LLColumnHeader::onHeldDown, this));
|
|
mButton->setClickedCallback(boost::bind(&LLColumnHeader::onClick, this));
|
|
mButton->setMouseDownCallback(boost::bind(&LLColumnHeader::onMouseDown, this));
|
|
|
|
mButton->setCallbackUserData(this);
|
|
mButton->setToolTip(label);
|
|
|
|
mAscendingText = std::string("[LOW]...[HIGH](Ascending)"); // *TODO: Translate
|
|
mDescendingText = std::string("[HIGH]...[LOW](Descending)"); // *TODO: Translate
|
|
|
|
mList->reshape(llmax(mList->getRect().getWidth(), 110, getRect().getWidth()), mList->getRect().getHeight());
|
|
|
|
// resize handles on left and right
|
|
const S32 RESIZE_BAR_THICKNESS = 3;
|
|
mResizeBar = new LLResizeBar(
|
|
std::string("resizebar"),
|
|
this,
|
|
LLRect( getRect().getWidth() - RESIZE_BAR_THICKNESS, getRect().getHeight(), getRect().getWidth(), 0),
|
|
MIN_COLUMN_WIDTH, S32_MAX, LLResizeBar::RIGHT );
|
|
addChild(mResizeBar);
|
|
|
|
mResizeBar->setEnabled(FALSE);
|
|
|
|
mImageOverlayAlignment = LLFontGL::HCENTER;
|
|
mImageOverlayColor = LLColor4::white;
|
|
}
|
|
|
|
LLColumnHeader::~LLColumnHeader()
|
|
{
|
|
}
|
|
|
|
void LLColumnHeader::draw()
|
|
{
|
|
BOOL draw_arrow = !mColumn->mLabel.empty() && mColumn->mParentCtrl->isSorted() && mColumn->mParentCtrl->getSortColumnName() == mColumn->mSortingColumn;
|
|
|
|
BOOL is_ascending = mColumn->mParentCtrl->getSortAscending();
|
|
mButton->setImageOverlay(is_ascending ? "up_arrow.tga" : "down_arrow.tga", LLFontGL::RIGHT, draw_arrow ? LLColor4::white : LLColor4::transparent);
|
|
mArrowImage = mButton->getImageOverlay();
|
|
|
|
//BOOL clip = getRect().mRight > mColumn->mParentCtrl->getItemListRect().getWidth();
|
|
//LLGLEnable scissor_test(clip ? GL_SCISSOR_TEST : GL_FALSE);
|
|
|
|
//LLRect column_header_local_rect(-getRect().mLeft, getRect().getHeight(), mColumn->mParentCtrl->getItemListRect().getWidth() - getRect().mLeft, 0);
|
|
//LLUI::setScissorRegionLocal(column_header_local_rect);
|
|
|
|
// Draw children
|
|
LLComboBox::draw();
|
|
|
|
if (mImageOverlay.notNull()) //Ugly dupe code from llbutton...
|
|
{
|
|
BOOL pressed_by_keyboard = FALSE;
|
|
if (mButton->hasFocus())
|
|
{
|
|
pressed_by_keyboard = gKeyboard->getKeyDown(' ') || (mButton->getCommitOnReturn() && gKeyboard->getKeyDown(KEY_RETURN));
|
|
}
|
|
|
|
// Unselected image assignments
|
|
S32 local_mouse_x;
|
|
S32 local_mouse_y;
|
|
LLUI::getMousePositionLocal(mButton, &local_mouse_x, &local_mouse_y);
|
|
|
|
BOOL pressed = pressed_by_keyboard
|
|
|| (mButton->hasMouseCapture() && mButton->pointInView(local_mouse_x, local_mouse_y))
|
|
|| mButton->getToggleState();
|
|
|
|
// Now draw special overlay..
|
|
// let overlay image and text play well together
|
|
S32 button_width = mButton->getRect().getWidth();
|
|
S32 button_height = mButton->getRect().getHeight();
|
|
S32 text_left = mButton->getLeftHPad();
|
|
S32 text_right = button_width - mButton->getRightHPad();
|
|
S32 text_width = text_right - text_left;
|
|
|
|
// draw overlay image
|
|
|
|
// get max width and height (discard level 0)
|
|
S32 overlay_width = mImageOverlay->getWidth();
|
|
S32 overlay_height = mImageOverlay->getHeight();
|
|
|
|
F32 scale_factor = llmin((F32)button_width / (F32)overlay_width, (F32)button_height / (F32)overlay_height, 1.f);
|
|
overlay_width = llround((F32)overlay_width * scale_factor);
|
|
overlay_height = llround((F32)overlay_height * scale_factor);
|
|
|
|
S32 center_x = mButton->getLocalRect().getCenterX();
|
|
S32 center_y = mButton->getLocalRect().getCenterY();
|
|
|
|
//FUGLY HACK FOR "DEPRESSED" BUTTONS
|
|
if (pressed)
|
|
{
|
|
center_y--;
|
|
center_x++;
|
|
}
|
|
|
|
// fade out overlay images on disabled buttons
|
|
LLColor4 overlay_color = mImageOverlayColor;
|
|
if (!mButton->getEnabled())
|
|
{
|
|
overlay_color.mV[VALPHA] = 0.5f;
|
|
}
|
|
|
|
switch(mImageOverlayAlignment)
|
|
{
|
|
case LLFontGL::LEFT:
|
|
text_left += overlay_width + 1;
|
|
text_width -= overlay_width + 1;
|
|
mImageOverlay->draw(
|
|
text_left,
|
|
center_y - (overlay_height / 2),
|
|
overlay_width,
|
|
overlay_height,
|
|
overlay_color);
|
|
break;
|
|
case LLFontGL::HCENTER:
|
|
mImageOverlay->draw(
|
|
center_x - (overlay_width / 2),
|
|
center_y - (overlay_height / 2),
|
|
overlay_width,
|
|
overlay_height,
|
|
overlay_color);
|
|
break;
|
|
case LLFontGL::RIGHT:
|
|
text_right -= overlay_width + 1;
|
|
text_width -= overlay_width + 1;
|
|
mImageOverlay->draw(
|
|
text_right - overlay_width,
|
|
center_y - (overlay_height / 2),
|
|
overlay_width,
|
|
overlay_height,
|
|
overlay_color);
|
|
break;
|
|
default:
|
|
// draw nothing
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
if (mList->getVisible())
|
|
{
|
|
// sync sort order with list selection every frame
|
|
mColumn->mParentCtrl->sortByColumn(mColumn->mSortingColumn, getCurrentIndex() == 0);
|
|
}
|
|
}
|
|
|
|
BOOL LLColumnHeader::handleDoubleClick(S32 x, S32 y, MASK mask)
|
|
{
|
|
if (canResize() && mResizeBar->getRect().pointInRect(x, y))
|
|
{
|
|
// reshape column to max content width
|
|
LLRect column_rect = getRect();
|
|
column_rect.mRight = column_rect.mLeft + mColumn->mMaxContentWidth;
|
|
setShape(column_rect,true);
|
|
}
|
|
else
|
|
{
|
|
onClick(this);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
void LLColumnHeader::setImage(const std::string &image_name)
|
|
{
|
|
if (mButton)
|
|
{
|
|
mButton->setImageSelected(LLUI::getUIImage(image_name));
|
|
mButton->setImageUnselected(LLUI::getUIImage(image_name));
|
|
}
|
|
}
|
|
|
|
void LLColumnHeader::setImageOverlay(const std::string &image_name, LLFontGL::HAlign alignment, const LLColor4& color)
|
|
{
|
|
if (image_name.empty())
|
|
{
|
|
mImageOverlay = NULL;
|
|
}
|
|
else
|
|
{
|
|
mImageOverlay = LLUI::getUIImage(image_name);
|
|
mImageOverlayAlignment = alignment;
|
|
mImageOverlayColor = color;
|
|
}
|
|
}
|
|
|
|
//static
|
|
void LLColumnHeader::onClick(void* user_data)
|
|
{
|
|
LLColumnHeader* headerp = (LLColumnHeader*)user_data;
|
|
if (!headerp) return;
|
|
|
|
LLScrollListColumn* column = headerp->mColumn;
|
|
if (!column) return;
|
|
|
|
if (headerp->mList->getVisible())
|
|
{
|
|
headerp->hideList();
|
|
}
|
|
|
|
LLScrollListCtrl::onClickColumn(column);
|
|
|
|
// propagate new sort order to sort order list
|
|
headerp->mList->selectNthItem(column->mParentCtrl->getSortAscending() ? 0 : 1);
|
|
|
|
headerp->mList->setFocus(TRUE);
|
|
}
|
|
|
|
//static
|
|
void LLColumnHeader::onMouseDown(void* user_data)
|
|
{
|
|
// for now, do nothing but block the normal showList() behavior
|
|
return;
|
|
}
|
|
|
|
//static
|
|
void LLColumnHeader::onHeldDown(void* user_data)
|
|
{
|
|
LLColumnHeader* headerp = (LLColumnHeader*)user_data;
|
|
headerp->showList();
|
|
}
|
|
|
|
void LLColumnHeader::showList()
|
|
{
|
|
if (mShowSortOptions)
|
|
{
|
|
//LLSD item_val = mColumn->mParentCtrl->getFirstData()->getValue();
|
|
mOrigLabel = mButton->getLabelSelected();
|
|
|
|
// move sort column over to this column and do initial sort
|
|
mColumn->mParentCtrl->sortByColumn(mColumn->mSortingColumn, mColumn->mParentCtrl->getSortAscending());
|
|
|
|
std::string low_item_text;
|
|
std::string high_item_text;
|
|
|
|
LLScrollListItem* itemp = mColumn->mParentCtrl->getFirstData();
|
|
if (itemp)
|
|
{
|
|
LLScrollListCell* cell = itemp->getColumn(mColumn->mIndex);
|
|
if (cell && cell->isText())
|
|
{
|
|
if (mColumn->mParentCtrl->getSortAscending())
|
|
{
|
|
low_item_text = cell->getValue().asString();
|
|
}
|
|
else
|
|
{
|
|
high_item_text = cell->getValue().asString();
|
|
}
|
|
}
|
|
}
|
|
|
|
itemp = mColumn->mParentCtrl->getLastData();
|
|
if (itemp)
|
|
{
|
|
LLScrollListCell* cell = itemp->getColumn(mColumn->mIndex);
|
|
if (cell && cell->isText())
|
|
{
|
|
if (mColumn->mParentCtrl->getSortAscending())
|
|
{
|
|
high_item_text = cell->getValue().asString();
|
|
}
|
|
else
|
|
{
|
|
low_item_text = cell->getValue().asString();
|
|
}
|
|
}
|
|
}
|
|
|
|
LLStringUtil::truncate(low_item_text, 3);
|
|
LLStringUtil::truncate(high_item_text, 3);
|
|
|
|
std::string ascending_string;
|
|
std::string descending_string;
|
|
|
|
if (low_item_text.empty() || high_item_text.empty())
|
|
{
|
|
ascending_string = "Ascending";
|
|
descending_string = "Descending";
|
|
}
|
|
else
|
|
{
|
|
mAscendingText.setArg("[LOW]", low_item_text);
|
|
mAscendingText.setArg("[HIGH]", high_item_text);
|
|
mDescendingText.setArg("[LOW]", low_item_text);
|
|
mDescendingText.setArg("[HIGH]", high_item_text);
|
|
ascending_string = mAscendingText.getString();
|
|
descending_string = mDescendingText.getString();
|
|
}
|
|
|
|
S32 text_width = LLFontGL::getFontSansSerifSmall()->getWidth(ascending_string);
|
|
text_width = llmax(text_width, LLFontGL::getFontSansSerifSmall()->getWidth(descending_string)) + 10;
|
|
text_width = llmax(text_width, getRect().getWidth() - 30);
|
|
|
|
mList->getColumn(0)->setWidth(text_width);
|
|
((LLScrollListText*)mList->getFirstData()->getColumn(0))->setText(ascending_string);
|
|
((LLScrollListText*)mList->getLastData()->getColumn(0))->setText(descending_string);
|
|
|
|
mList->reshape(llmax(text_width + 30, 110, getRect().getWidth()), mList->getRect().getHeight());
|
|
|
|
LLComboBox::showList();
|
|
}
|
|
}
|
|
|
|
//static
|
|
void LLColumnHeader::onSelectSort(LLUICtrl* ctrl, void* user_data)
|
|
{
|
|
LLColumnHeader* headerp = (LLColumnHeader*)user_data;
|
|
if (!headerp) return;
|
|
|
|
LLScrollListColumn* column = headerp->mColumn;
|
|
if (!column) return;
|
|
LLScrollListCtrl *parent = column->mParentCtrl;
|
|
if (!parent) return;
|
|
|
|
if (headerp->getCurrentIndex() == 0)
|
|
{
|
|
// ascending
|
|
parent->sortByColumn(column->mSortingColumn, TRUE);
|
|
}
|
|
else
|
|
{
|
|
// descending
|
|
parent->sortByColumn(column->mSortingColumn, FALSE);
|
|
}
|
|
|
|
// restore original column header
|
|
headerp->setLabel(headerp->mOrigLabel);
|
|
}
|
|
|
|
LLView* LLColumnHeader::findSnapEdge(S32& new_edge_val, const LLCoordGL& mouse_dir, ESnapEdge snap_edge, ESnapType snap_type, S32 threshold, S32 padding)
|
|
{
|
|
// this logic assumes dragging on right
|
|
llassert(snap_edge == SNAP_RIGHT);
|
|
|
|
// use higher snap threshold for column headers
|
|
threshold = llmin(threshold, 10);
|
|
|
|
LLRect snap_rect = getSnapRect();
|
|
|
|
S32 snap_delta = mColumn->mMaxContentWidth - snap_rect.getWidth();
|
|
|
|
// x coord growing means column growing, so same signs mean we're going in right direction
|
|
if (llabs(snap_delta) <= threshold && mouse_dir.mX * snap_delta > 0 )
|
|
{
|
|
new_edge_val = snap_rect.mRight + snap_delta;
|
|
}
|
|
else
|
|
{
|
|
LLScrollListColumn* next_column = mColumn->mParentCtrl->getColumn(mColumn->mIndex + 1);
|
|
while (next_column)
|
|
{
|
|
if (next_column->mHeader)
|
|
{
|
|
snap_delta = (next_column->mHeader->getSnapRect().mRight - next_column->mMaxContentWidth) - snap_rect.mRight;
|
|
if (llabs(snap_delta) <= threshold && mouse_dir.mX * snap_delta > 0 )
|
|
{
|
|
new_edge_val = snap_rect.mRight + snap_delta;
|
|
}
|
|
break;
|
|
}
|
|
next_column = mColumn->mParentCtrl->getColumn(next_column->mIndex + 1);
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
void LLColumnHeader::handleReshape(const LLRect& new_rect, bool by_user)
|
|
{
|
|
S32 new_width = new_rect.getWidth();
|
|
S32 delta_width = new_width - (getRect().getWidth() /*+ mColumn->mParentCtrl->getColumnPadding()*/);
|
|
|
|
if (delta_width != 0)
|
|
{
|
|
S32 remaining_width = -delta_width;
|
|
S32 col;
|
|
for (col = mColumn->mIndex + 1; col < mColumn->mParentCtrl->getNumColumns(); col++)
|
|
{
|
|
LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col);
|
|
if (!columnp) continue;
|
|
|
|
if (columnp->mHeader && columnp->mHeader->canResize())
|
|
{
|
|
// how many pixels in width can this column afford to give up?
|
|
S32 resize_buffer_amt = llmax(0, columnp->getWidth() - MIN_COLUMN_WIDTH);
|
|
|
|
// user shrinking column, need to add width to other columns
|
|
if (delta_width < 0)
|
|
{
|
|
if (/*!columnp->mDynamicWidth && */columnp->getWidth() > 0)
|
|
{
|
|
// statically sized column, give all remaining width to this column
|
|
columnp->setWidth(columnp->getWidth() + remaining_width);
|
|
if (columnp->mRelWidth > 0.f)
|
|
{
|
|
columnp->mRelWidth = (F32)columnp->getWidth() / (F32)mColumn->mParentCtrl->getItemListRect().getWidth();
|
|
}
|
|
// all padding went to this widget, we're done
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// user growing column, need to take width from other columns
|
|
remaining_width += resize_buffer_amt;
|
|
|
|
if (/*!columnp->mDynamicWidth && */columnp->getWidth() > 0)
|
|
{
|
|
columnp->setWidth(columnp->getWidth() - llmin(columnp->getWidth() - MIN_COLUMN_WIDTH, delta_width));
|
|
if (columnp->mRelWidth > 0.f)
|
|
{
|
|
columnp->mRelWidth = (F32)columnp->getWidth() / (F32)mColumn->mParentCtrl->getItemListRect().getWidth();
|
|
}
|
|
}
|
|
|
|
if (remaining_width >= 0)
|
|
{
|
|
// width sucked up from neighboring columns, done
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// clamp resize amount to maximum that can be absorbed by other columns
|
|
if (delta_width > 0)
|
|
{
|
|
delta_width += llmin(remaining_width, 0);
|
|
}
|
|
|
|
// propagate constrained delta_width to new width for this column
|
|
new_width = getRect().getWidth() + delta_width - mColumn->mParentCtrl->getColumnPadding();
|
|
|
|
// use requested width
|
|
mColumn->setWidth(new_width);
|
|
|
|
// update proportional spacing
|
|
if (mColumn->mRelWidth > 0.f)
|
|
{
|
|
mColumn->mRelWidth = (F32)new_width / (F32)mColumn->mParentCtrl->getItemListRect().getWidth();
|
|
}
|
|
|
|
// tell scroll list to layout columns again
|
|
// do immediate update to get proper feedback to resize handle
|
|
// which needs to know how far the resize actually went
|
|
mColumn->mParentCtrl->updateColumns();
|
|
}
|
|
}
|
|
|
|
void LLColumnHeader::setHasResizableElement(BOOL resizable)
|
|
{
|
|
// for now, dynamically spaced columns can't be resized
|
|
// if (mColumn->mDynamicWidth) return;
|
|
|
|
if (mHasResizableElement != resizable)
|
|
{
|
|
mColumn->mParentCtrl->dirtyColumns();
|
|
mHasResizableElement = resizable;
|
|
}
|
|
}
|
|
|
|
void LLColumnHeader::updateResizeBars()
|
|
{
|
|
S32 num_resizable_columns = 0;
|
|
S32 col;
|
|
for (col = 0; col < mColumn->mParentCtrl->getNumColumns(); col++)
|
|
{
|
|
LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col);
|
|
if (columnp->mHeader && columnp->mHeader->canResize())
|
|
{
|
|
num_resizable_columns++;
|
|
}
|
|
}
|
|
|
|
S32 num_resizers_enabled = 0;
|
|
|
|
// now enable/disable resize handles on resizable columns if we have at least two
|
|
for (col = 0; col < mColumn->mParentCtrl->getNumColumns(); col++)
|
|
{
|
|
LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col);
|
|
if (!columnp->mHeader) continue;
|
|
BOOL enable = num_resizable_columns >= 2 && num_resizers_enabled < (num_resizable_columns - 1) && columnp->mHeader->canResize();
|
|
columnp->mHeader->enableResizeBar(enable);
|
|
if (enable)
|
|
{
|
|
num_resizers_enabled++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLColumnHeader::enableResizeBar(BOOL enable)
|
|
{
|
|
// for now, dynamically spaced columns can't be resized
|
|
//if (!mColumn->mDynamicWidth)
|
|
{
|
|
mResizeBar->setEnabled(enable);
|
|
}
|
|
}
|
|
|
|
BOOL LLColumnHeader::canResize()
|
|
{
|
|
return getVisible() && (mHasResizableElement || mColumn->mDynamicWidth);
|
|
}
|
|
|
|
void LLScrollListColumn::setWidth(S32 width)
|
|
{
|
|
if (!mDynamicWidth && mRelWidth <= 0.f)
|
|
{
|
|
mParentCtrl->updateStaticColumnWidth(this, width);
|
|
}
|
|
mWidth = width;
|
|
}
|
|
|
|
// Default constructor
|
|
LLScrollListColumn::LLScrollListColumn() :
|
|
mName(),
|
|
mSortingColumn(),
|
|
mSortAscending(TRUE),
|
|
mLabel(),
|
|
mWidth(-1),
|
|
mRelWidth(-1.0),
|
|
mDynamicWidth(FALSE),
|
|
mMaxContentWidth(0),
|
|
mIndex(-1),
|
|
mParentCtrl(NULL),
|
|
mHeader(NULL),
|
|
mFontAlignment(LLFontGL::LEFT)
|
|
{ }
|
|
|
|
LLScrollListColumn::LLScrollListColumn(const LLSD &sd, LLScrollListCtrl* parent) :
|
|
mWidth(0),
|
|
mIndex (-1),
|
|
mParentCtrl(parent),
|
|
mHeader(NULL),
|
|
mMaxContentWidth(0),
|
|
mDynamicWidth(FALSE),
|
|
mRelWidth(-1.f)
|
|
{
|
|
mName = sd.get("name").asString();
|
|
mSortingColumn = mName;
|
|
if (sd.has("sort"))
|
|
{
|
|
mSortingColumn = sd.get("sort").asString();
|
|
}
|
|
mSortAscending = TRUE;
|
|
if (sd.has("sort_ascending"))
|
|
{
|
|
mSortAscending = sd.get("sort_ascending").asBoolean();
|
|
}
|
|
mLabel = sd.get("label").asString();
|
|
if (sd.has("relwidth") && (F32)sd.get("relwidth").asReal() > 0)
|
|
{
|
|
mRelWidth = (F32)sd.get("relwidth").asReal();
|
|
if (mRelWidth < 0) mRelWidth = 0;
|
|
if (mRelWidth > 1) mRelWidth = 1;
|
|
mDynamicWidth = FALSE;
|
|
}
|
|
else if(sd.has("dynamicwidth") && (BOOL)sd.get("dynamicwidth").asBoolean() == TRUE)
|
|
{
|
|
mDynamicWidth = TRUE;
|
|
mRelWidth = -1;
|
|
}
|
|
else
|
|
{
|
|
|
|
setWidth(sd.get("width").asInteger());
|
|
}
|
|
|
|
if (sd.has("halign"))
|
|
{
|
|
mFontAlignment = (LLFontGL::HAlign)llclamp(sd.get("halign").asInteger(), (S32)LLFontGL::LEFT, (S32)LLFontGL::HCENTER);
|
|
}
|
|
else
|
|
{
|
|
mFontAlignment = LLFontGL::LEFT;
|
|
}
|
|
|
|
}
|