Refactor LLScrollList* and relatives
Breaks out llscrolllistcolumn, llscrolllistcell, and llscrolllistitem from llscrolllistctrl and llflyoutbutton from llcombobox
This commit is contained in:
484
indra/llui/llscrolllistcolumn.cpp
Normal file
484
indra/llui/llscrolllistcolumn.cpp
Normal file
@@ -0,0 +1,484 @@
|
||||
/**
|
||||
* @file llscrollcolumnheader.cpp
|
||||
* @brief Scroll lists are composed of rows (items), each of which
|
||||
* contains columns (cells).
|
||||
*
|
||||
* $LicenseInfo:firstyear=2007&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2010, Linden Research, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation;
|
||||
* version 2.1 of the License only.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#include "linden_common.h"
|
||||
|
||||
#include "llscrolllistcolumn.h"
|
||||
|
||||
#include "llbutton.h"
|
||||
#include "llkeyboard.h" // For gKeyboard
|
||||
#include "llresizebar.h"
|
||||
#include "lluictrlfactory.h"
|
||||
|
||||
const S32 MIN_COLUMN_WIDTH = 20;
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// LLScrollColumnHeader
|
||||
//---------------------------------------------------------------------------
|
||||
LLScrollColumnHeader::LLScrollColumnHeader(const std::string& label, const LLRect& rect, LLScrollListColumn* column, const LLFontGL* fontp)
|
||||
: LLComboBox(label, rect, label),
|
||||
mColumn(column),
|
||||
mOrigLabel(label),
|
||||
mHasResizableElement(FALSE)
|
||||
{
|
||||
mListPosition = LLComboBox::ABOVE;
|
||||
setCommitCallback(boost::bind(&LLScrollColumnHeader::onSelectSort, this));
|
||||
mButton->setTabStop(FALSE);
|
||||
mButton->setClickedCallback(boost::bind(&LLScrollColumnHeader::onClick, this, _2));
|
||||
|
||||
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;
|
||||
LLResizeBar::Params resize_bar_p;
|
||||
resize_bar_p.resizing_view(this);
|
||||
resize_bar_p.rect(LLRect(getRect().getWidth() - RESIZE_BAR_THICKNESS, getRect().getHeight(), getRect().getWidth(), 0));
|
||||
resize_bar_p.min_size(MIN_COLUMN_WIDTH);
|
||||
resize_bar_p.side(LLResizeBar::RIGHT);
|
||||
resize_bar_p.enabled(false);
|
||||
mResizeBar = LLUICtrlFactory::create<LLResizeBar>(resize_bar_p);
|
||||
addChild(mResizeBar);
|
||||
|
||||
mImageOverlayAlignment = LLFontGL::HCENTER;
|
||||
mImageOverlayColor = LLColor4::white;
|
||||
}
|
||||
|
||||
LLScrollColumnHeader::~LLScrollColumnHeader()
|
||||
{}
|
||||
|
||||
void LLScrollColumnHeader::draw()
|
||||
{
|
||||
std::string sort_column = mColumn->mParentCtrl->getSortColumnName();
|
||||
BOOL draw_arrow = !mColumn->mLabel.empty()
|
||||
&& mColumn->mParentCtrl->isSorted()
|
||||
// check for indirect sorting column as well as column's sorting name
|
||||
&& (sort_column == mColumn->mSortingColumn || sort_column == mColumn->mName);
|
||||
|
||||
BOOL is_ascending = mColumn->mParentCtrl->getSortAscending();
|
||||
if (draw_arrow)
|
||||
{
|
||||
mButton->setImageOverlay(is_ascending ? "up_arrow.tga" : "down_arrow.tga", LLFontGL::RIGHT, LLColor4::white);
|
||||
}
|
||||
else
|
||||
{
|
||||
mButton->setImageOverlay(LLUUID::null);
|
||||
}
|
||||
mArrowImage = mButton->getImageOverlay();
|
||||
|
||||
// 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 LLScrollColumnHeader::handleDoubleClick(S32 x, S32 y, MASK mask)
|
||||
{
|
||||
if (canResize() && mResizeBar->getRect().pointInRect(x, y))
|
||||
{
|
||||
// reshape column to max content width
|
||||
mColumn->mParentCtrl->calcMaxContentWidth();
|
||||
LLRect column_rect = getRect();
|
||||
column_rect.mRight = column_rect.mLeft + mColumn->mMaxContentWidth;
|
||||
setShape(column_rect, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
onClick(LLSD());
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void LLScrollColumnHeader::setImage(const std::string& image_name)
|
||||
{
|
||||
if (mButton)
|
||||
{
|
||||
mButton->setImageSelected(LLUI::getUIImage(image_name));
|
||||
mButton->setImageUnselected(LLUI::getUIImage(image_name));
|
||||
}
|
||||
}
|
||||
|
||||
void LLScrollColumnHeader::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;
|
||||
}
|
||||
}
|
||||
|
||||
void LLScrollColumnHeader::onClick(const LLSD& data)
|
||||
{
|
||||
if (mColumn)
|
||||
{
|
||||
if (mList->getVisible()) hideList();
|
||||
LLScrollListCtrl::onClickColumn(mColumn);
|
||||
|
||||
// propagate new sort order to sort order list
|
||||
mList->selectNthItem(mColumn->mParentCtrl->getSortAscending() ? 0 : 1);
|
||||
mList->setFocus(true);
|
||||
}
|
||||
}
|
||||
|
||||
void LLScrollColumnHeader::onSelectSort()
|
||||
{
|
||||
if (!mColumn) return;
|
||||
LLScrollListCtrl* parent = mColumn->mParentCtrl;
|
||||
if (!parent) return;
|
||||
parent->sortByColumn(mColumn->mSortingColumn, getCurrentIndex() == 0);
|
||||
|
||||
// restore original column header
|
||||
setLabel(mOrigLabel);
|
||||
}
|
||||
|
||||
LLView* LLScrollColumnHeader::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 LLScrollColumnHeader::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->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->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->dirtyColumns(); //Must flag as dirty, else updateColumns will probably be a noop.
|
||||
mColumn->mParentCtrl->updateColumns();
|
||||
}
|
||||
}
|
||||
|
||||
void LLScrollColumnHeader::setHasResizableElement(BOOL resizable)
|
||||
{
|
||||
if (mHasResizableElement != resizable)
|
||||
{
|
||||
mColumn->mParentCtrl->dirtyColumns();
|
||||
mHasResizableElement = resizable;
|
||||
}
|
||||
}
|
||||
|
||||
void LLScrollColumnHeader::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 LLScrollColumnHeader::enableResizeBar(BOOL enable)
|
||||
{
|
||||
mResizeBar->setEnabled(enable);
|
||||
}
|
||||
|
||||
BOOL LLScrollColumnHeader::canResize()
|
||||
{
|
||||
return getVisible() && (mHasResizableElement || mColumn->mDynamicWidth);
|
||||
}
|
||||
|
||||
//
|
||||
// LLScrollListColumn
|
||||
//
|
||||
// Default constructor
|
||||
LLScrollListColumn::LLScrollListColumn() : mName(), mSortingColumn(), mSortDirection(ASCENDING), 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),
|
||||
mName(sd.get("name").asString()),
|
||||
mLabel(sd.get("label").asString()),
|
||||
mHeader(NULL),
|
||||
mMaxContentWidth(0),
|
||||
mDynamicWidth(sd.has("dynamicwidth") && sd.get("dynamicwidth").asBoolean()),
|
||||
mRelWidth(-1.f),
|
||||
mFontAlignment(LLFontGL::LEFT),
|
||||
mSortingColumn(sd.has("sort") ? sd.get("sort").asString() : mName)
|
||||
{
|
||||
if (sd.has("sort_ascending"))
|
||||
{
|
||||
mSortDirection = sd.get("sort_ascending").asBoolean() ? ASCENDING : DESCENDING;
|
||||
}
|
||||
else
|
||||
{
|
||||
mSortDirection = ASCENDING;
|
||||
}
|
||||
|
||||
if (sd.has("relwidth") && sd.get("relwidth").asFloat() > 0)
|
||||
{
|
||||
mRelWidth = sd.get("relwidth").asFloat();
|
||||
if (mRelWidth > 1) mRelWidth = 1;
|
||||
mDynamicWidth = false;
|
||||
}
|
||||
else if (!mDynamicWidth)
|
||||
{
|
||||
setWidth(sd.get("width").asInteger());
|
||||
}
|
||||
|
||||
if (sd.has("halign"))
|
||||
{
|
||||
mFontAlignment = (LLFontGL::HAlign)llclamp(sd.get("halign").asInteger(), (S32)LLFontGL::LEFT, (S32)LLFontGL::HCENTER);
|
||||
}
|
||||
}
|
||||
|
||||
void LLScrollListColumn::setWidth(S32 width)
|
||||
{
|
||||
if (!mDynamicWidth && mRelWidth <= 0.f)
|
||||
{
|
||||
mParentCtrl->updateStaticColumnWidth(this, width);
|
||||
}
|
||||
mWidth = width;
|
||||
}
|
||||
Reference in New Issue
Block a user