phoenix-firestorm/indra/llui/llmenugl.cpp

4487 lines
113 KiB
C++

/**
* @file llmenugl.cpp
* @brief LLMenuItemGL base class
*
* $LicenseInfo:firstyear=2001&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$
*/
//*****************************************************************************
//
// This file contains the opengl based menu implementation.
//
// NOTES: A menu label is split into 4 columns. The left column, the
// label colum, the accelerator column, and the right column. The left
// column is used for displaying boolean values for toggle and check
// controls. The right column is used for submenus.
//
//*****************************************************************************
//#include "llviewerprecompiledheaders.h"
#include "linden_common.h"
#include "llmenugl.h"
#include "llgl.h"
#include "llmath.h"
#include "llrender.h"
#include "llfocusmgr.h"
#include "llcoord.h"
#include "llwindow.h"
#include "llcriticaldamp.h"
#include "lluictrlfactory.h"
#include "llbutton.h"
#include "llfontgl.h"
#include "llresmgr.h"
#include "lltrans.h"
#include "llui.h"
#include "llstl.h"
#include "v2math.h"
#include <set>
#include <boost/tokenizer.hpp>
#include "llclipboard.h" // <FS:ND/ To let someone copy a menus text + accelerator to clipboard
// static
LLMenuHolderGL *LLMenuGL::sMenuContainer = NULL;
view_listener_t::listener_map_t view_listener_t::sListeners;
S32 MENU_BAR_HEIGHT = 0;
S32 MENU_BAR_WIDTH = 0;
///============================================================================
/// Local function declarations, constants, enums, and typedefs
///============================================================================
const S32 LABEL_BOTTOM_PAD_PIXELS = 2;
const U32 LEFT_PAD_PIXELS = 3;
const U32 LEFT_WIDTH_PIXELS = 15;
const U32 LEFT_PLAIN_PIXELS = LEFT_PAD_PIXELS + LEFT_WIDTH_PIXELS;
const U32 RIGHT_PAD_PIXELS = 7;
const U32 RIGHT_WIDTH_PIXELS = 15;
const U32 RIGHT_PLAIN_PIXELS = RIGHT_PAD_PIXELS + RIGHT_WIDTH_PIXELS;
const U32 PLAIN_PAD_PIXELS = LEFT_PAD_PIXELS + LEFT_WIDTH_PIXELS + RIGHT_PAD_PIXELS + RIGHT_WIDTH_PIXELS;
const U32 BRIEF_PAD_PIXELS = 2;
const U32 SEPARATOR_HEIGHT_PIXELS = 8;
const S32 TEAROFF_SEPARATOR_HEIGHT_PIXELS = 10;
const S32 MENU_ITEM_PADDING = 4;
const std::string SEPARATOR_NAME("separator");
const std::string VERTICAL_SEPARATOR_LABEL( "|" );
const std::string LLMenuGL::BOOLEAN_TRUE_PREFIX( "\xE2\x9C\x94" ); // U+2714 HEAVY CHECK MARK
const std::string LLMenuGL::BRANCH_SUFFIX( "\xe2\x96\xb8" ); // U+25B6 BLACK RIGHT-POINTING TRIANGLE
const std::string LLMenuGL::ARROW_UP ("^^^^^^^");
const std::string LLMenuGL::ARROW_DOWN("vvvvvvv");
const F32 MAX_MOUSE_SLOPE_SUB_MENU = 0.9f;
BOOL LLMenuGL::sKeyboardMode = FALSE;
LLHandle<LLView> LLMenuHolderGL::sItemLastSelectedHandle;
LLFrameTimer LLMenuHolderGL::sItemActivationTimer;
const F32 ACTIVATE_HIGHLIGHT_TIME = 0.3f;
static MenuRegistry::Register<LLMenuItemGL> register_menu_item("menu_item");
static MenuRegistry::Register<LLMenuItemSeparatorGL> register_separator("menu_item_separator");
static MenuRegistry::Register<LLMenuItemCallGL> register_menu_item_call("menu_item_call");
static MenuRegistry::Register<LLMenuItemCheckGL> register_menu_item_check("menu_item_check");
// Created programmatically but we need to specify custom colors in xml
static MenuRegistry::Register<LLMenuItemTearOffGL> register_menu_item_tear_off("menu_item_tear_off");
static MenuRegistry::Register<LLMenuGL> register_menu("menu");
static LLDefaultChildRegistry::Register<LLMenuGL> register_menu_default("menu");
///============================================================================
/// Class LLMenuItemGL
///============================================================================
LLMenuItemGL::Params::Params()
: shortcut("shortcut"),
shortcut_linux("shortcut_linux"), // <FS> Remap shortcuts on Linux
jump_key("jump_key", KEY_NONE),
use_mac_ctrl("use_mac_ctrl", false),
allow_key_repeat("allow_key_repeat", false),
rect("rect"),
left("left"),
top("top"),
right("right"),
bottom("bottom"),
width("width"),
height("height"),
bottom_delta("bottom_delta"),
left_delta("left_delta"),
enabled_color("enabled_color"),
disabled_color("disabled_color"),
highlight_bg_color("highlight_bg_color"),
highlight_fg_color("highlight_fg_color")
{
changeDefault(mouse_opaque, true);
}
// Default constructor
LLMenuItemGL::LLMenuItemGL(const LLMenuItemGL::Params& p)
: LLUICtrl(p),
mJumpKey(p.jump_key),
mAllowKeyRepeat(p.allow_key_repeat),
mHighlight( FALSE ),
mGotHover( FALSE ),
mBriefItem( FALSE ),
mDrawTextDisabled( FALSE ),
mFont(p.font),
mAcceleratorKey(KEY_NONE),
mAcceleratorMask(MASK_NONE),
mLabel(p.label.isProvided() ? p.label() : p.name()),
mEnabledColor(p.enabled_color()),
mDisabledColor(p.disabled_color()),
mHighlightBackground(p.highlight_bg_color()),
mHighlightForeground(p.highlight_fg_color())
{
#ifdef LL_DARWIN
// See if this Mac accelerator should really use the ctrl key and not get mapped to cmd
BOOL useMacCtrl = p.use_mac_ctrl;
#endif // LL_DARWIN
std::string shortcut = p.shortcut;
// <FS> Remap shortcuts on Linux
#ifdef LL_LINUX
static LLUICachedControl<bool> fsRemapLinuxShortcuts("FSRemapLinuxShortcuts", false);
if (fsRemapLinuxShortcuts && p.shortcut_linux.isProvided())
{
shortcut = p.shortcut_linux;
}
#endif // LL_LINUX
// </FS>
if (shortcut.find("control") != shortcut.npos)
{
#ifdef LL_DARWIN
if ( useMacCtrl )
{
mAcceleratorMask |= MASK_MAC_CONTROL;
}
#endif // LL_DARWIN
mAcceleratorMask |= MASK_CONTROL;
}
if (shortcut.find("alt") != shortcut.npos)
{
mAcceleratorMask |= MASK_ALT;
}
if (shortcut.find("shift") != shortcut.npos)
{
mAcceleratorMask |= MASK_SHIFT;
}
S32 pipe_pos = shortcut.rfind("|");
std::string key_str = shortcut.substr(pipe_pos+1);
LLKeyboard::keyFromString(key_str, &mAcceleratorKey);
LL_DEBUGS("HotKeys") << "Process short cut key: shortcut: " << shortcut
<< ", key str: " << key_str
<< ", accelerator mask: " << mAcceleratorMask
<< ", accelerator key: " << mAcceleratorKey
<< LL_ENDL;
}
//virtual
void LLMenuItemGL::setValue(const LLSD& value)
{
setLabel(value.asString());
}
//virtual
LLSD LLMenuItemGL::getValue() const
{
return getLabel();
}
//virtual
bool LLMenuItemGL::hasAccelerator(const KEY &key, const MASK &mask) const
{
return (mAcceleratorKey == key) && (mAcceleratorMask == mask);
}
//virtual
BOOL LLMenuItemGL::handleAcceleratorKey(KEY key, MASK mask)
{
if( getEnabled() && (!gKeyboard->getKeyRepeated(key) || mAllowKeyRepeat) && (key == mAcceleratorKey) && (mask == (mAcceleratorMask & MASK_NORMALKEYS)) )
{
onCommit();
return TRUE;
}
return FALSE;
}
BOOL LLMenuItemGL::handleHover(S32 x, S32 y, MASK mask)
{
getWindow()->setCursor(UI_CURSOR_ARROW);
return TRUE;
}
//virtual
BOOL LLMenuItemGL::handleRightMouseDown(S32 x, S32 y, MASK mask)
{
// <FS:ND> Holding CTRL & ALT while right clicking a menu entry will copy the menu
// text and shortcut (accelerator) to the clipboard. The menu items action will not be called.
if( (MASK_CONTROL|MASK_ALT) == ((MASK_CONTROL|MASK_ALT) & mask) )
{
LLWString label = utf8string_to_wstring( getLabel() + ": " + mDrawAccelLabel.getString() );
LLClipboard::instance().copyToClipboard( label, 0, label.size() );
return FALSE;
}
// </FS:ND>
return LLUICtrl::handleRightMouseDown(x,y,mask);
}
void LLMenuItemGL::onMouseEnter(S32 x, S32 y, MASK mask)
{
setHover(TRUE);
LLUICtrl::onMouseEnter(x,y,mask);
}
void LLMenuItemGL::onMouseLeave(S32 x, S32 y, MASK mask)
{
setHover(FALSE);
LLUICtrl::onMouseLeave(x,y,mask);
}
//virtual
BOOL LLMenuItemGL::handleRightMouseUp(S32 x, S32 y, MASK mask)
{
// If this event came from a right-click context menu spawn,
// process as a left-click to allow menu items to be hit
if (LLMenuHolderGL::sContextMenuSpawnPos.mX != S32_MAX
|| LLMenuHolderGL::sContextMenuSpawnPos.mY != S32_MAX)
{
BOOL handled = handleMouseUp(x, y, mask);
return handled;
}
return LLUICtrl::handleRightMouseUp(x,y,mask);
}
// This function checks to see if the accelerator key is already in use;
// if not, it will be added to the list
BOOL LLMenuItemGL::addToAcceleratorList(std::list <LLMenuKeyboardBinding*> *listp)
{
LLMenuKeyboardBinding *accelerator = NULL;
if (mAcceleratorKey != KEY_NONE)
{
std::list<LLMenuKeyboardBinding*>::iterator list_it;
for (list_it = listp->begin(); list_it != listp->end(); ++list_it)
{
accelerator = *list_it;
if ((accelerator->mKey == mAcceleratorKey) && (accelerator->mMask == (mAcceleratorMask & MASK_NORMALKEYS)))
{
// *NOTE: get calling code to throw up warning or route
// warning messages back to app-provided output
// std::string warning;
// warning.append("Duplicate key binding <");
// appendAcceleratorString( warning );
// warning.append("> for menu items:\n ");
// warning.append(accelerator->mName);
// warning.append("\n ");
// warning.append(mLabel);
// LL_WARNS() << warning << LL_ENDL;
// LLAlertDialog::modalAlert(warning);
return FALSE;
}
}
if (!accelerator)
{
accelerator = new LLMenuKeyboardBinding;
if (accelerator)
{
accelerator->mKey = mAcceleratorKey;
accelerator->mMask = (mAcceleratorMask & MASK_NORMALKEYS);
// accelerator->mName = mLabel;
}
listp->push_back(accelerator);//addData(accelerator);
}
}
return TRUE;
}
// This function appends the character string representation of
// the current accelerator key and mask to the provided string.
void LLMenuItemGL::appendAcceleratorString( std::string& st ) const
{
st = LLKeyboard::stringFromAccelerator( mAcceleratorMask, mAcceleratorKey );
LL_DEBUGS("HotKeys") << "appendAcceleratorString: " << st << LL_ENDL;
}
void LLMenuItemGL::setJumpKey(KEY key)
{
mJumpKey = LLStringOps::toUpper((char)key);
}
// virtual
U32 LLMenuItemGL::getNominalHeight( void ) const
{
return mFont->getLineHeight() + MENU_ITEM_PADDING;
}
//virtual
void LLMenuItemGL::setBriefItem(BOOL brief)
{
mBriefItem = brief;
}
//virtual
BOOL LLMenuItemGL::isBriefItem() const
{
return mBriefItem;
}
// Get the parent menu for this item
LLMenuGL* LLMenuItemGL::getMenu() const
{
return (LLMenuGL*) getParent();
}
// getNominalWidth() - returns the normal width of this control in
// pixels - this is used for calculating the widest item, as well as
// for horizontal arrangement.
U32 LLMenuItemGL::getNominalWidth( void ) const
{
U32 width;
if (mBriefItem)
{
width = BRIEF_PAD_PIXELS;
}
else
{
width = PLAIN_PAD_PIXELS;
}
if( KEY_NONE != mAcceleratorKey )
{
width += getMenu()->getShortcutPad();
std::string temp;
appendAcceleratorString( temp );
width += mFont->getWidth( temp );
}
width += mFont->getWidth( mLabel.getWString().c_str() );
return width;
}
// called to rebuild the draw label
void LLMenuItemGL::buildDrawLabel( void )
{
mDrawAccelLabel.clear();
std::string st = mDrawAccelLabel.getString();
appendAcceleratorString( st );
mDrawAccelLabel = st;
}
void LLMenuItemGL::onCommit( void )
{
// Check torn-off status to allow left-arrow keyboard navigation back
// to parent menu.
// Also, don't hide if item triggered by keyboard shortcut (and hence
// parent not visible).
if (!getMenu()->getTornOff()
&& getMenu()->getVisible())
{
LLMenuGL::sMenuContainer->hideMenus();
}
LLUICtrl::onCommit();
}
// set the hover status (called by it's menu)
void LLMenuItemGL::setHighlight( BOOL highlight )
{
if (highlight)
{
getMenu()->clearHoverItem();
}
if (mHighlight != highlight)
{
dirtyRect();
}
mHighlight = highlight;
}
BOOL LLMenuItemGL::handleKeyHere( KEY key, MASK mask )
{
if (getHighlight() &&
getMenu()->isOpen())
{
if (key == KEY_UP)
{
// switch to keyboard navigation mode
LLMenuGL::setKeyboardMode(TRUE);
getMenu()->highlightPrevItem(this);
return TRUE;
}
else if (key == KEY_DOWN)
{
// switch to keyboard navigation mode
LLMenuGL::setKeyboardMode(TRUE);
getMenu()->highlightNextItem(this);
return TRUE;
}
else if (key == KEY_RETURN && mask == MASK_NONE)
{
// switch to keyboard navigation mode
LLMenuGL::setKeyboardMode(TRUE);
onCommit();
return TRUE;
}
}
return FALSE;
}
BOOL LLMenuItemGL::handleMouseUp( S32 x, S32 y, MASK mask)
{
// switch to mouse navigation mode
LLMenuGL::setKeyboardMode(FALSE);
onCommit();
make_ui_sound("UISndClickRelease");
return LLView::handleMouseUp(x, y, mask);
}
BOOL LLMenuItemGL::handleMouseDown( S32 x, S32 y, MASK mask)
{
// switch to mouse navigation mode
LLMenuGL::setKeyboardMode(FALSE);
setHighlight(TRUE);
return LLView::handleMouseDown(x, y, mask);
}
BOOL LLMenuItemGL::handleScrollWheel( S32 x, S32 y, S32 clicks )
{
// If the menu is scrollable let it handle the wheel event.
return !getMenu()->isScrollable();
}
void LLMenuItemGL::draw( void )
{
// *FIX: This can be optimized by using switches. Want to avoid
// that until the functionality is finalized.
// HACK: Brief items don't highlight. Pie menu takes care of it. JC
// let disabled items be highlighted, just don't draw them as such
if( getEnabled() && getHighlight() && !mBriefItem)
{
gGL.color4fv( mHighlightBackground.get().mV );
gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth(), 0 );
}
LLColor4 color;
if ( getEnabled() && getHighlight() )
{
color = mHighlightForeground.get();
}
else if( getEnabled() && !mDrawTextDisabled )
{
color = mEnabledColor.get();
}
else
{
color = mDisabledColor.get();
}
// Highlight if needed
if( ll::ui::SearchableControl::getHighlighted() )
color = ll::ui::SearchableControl::getHighlightColor();
// Draw the text on top.
if (mBriefItem)
{
mFont->render( mLabel, 0, BRIEF_PAD_PIXELS / 2, 0, color,
LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL);
}
else
{
if( !mDrawBoolLabel.empty() )
{
mFont->render( mDrawBoolLabel.getWString(), 0, (F32)LEFT_PAD_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f), color,
LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, FALSE );
}
mFont->render( mLabel.getWString(), 0, (F32)LEFT_PLAIN_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f), color,
LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, FALSE );
if( !mDrawAccelLabel.empty() )
{
mFont->render( mDrawAccelLabel.getWString(), 0, (F32)getRect().mRight - (F32)RIGHT_PLAIN_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f), color,
LLFontGL::RIGHT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, FALSE );
}
if( !mDrawBranchLabel.empty() )
{
mFont->render( mDrawBranchLabel.getWString(), 0, (F32)getRect().mRight - (F32)RIGHT_PAD_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f), color,
LLFontGL::RIGHT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, FALSE );
}
}
// underline "jump" key only when keyboard navigation has been initiated
if (getMenu()->jumpKeysActive() && LLMenuGL::getKeyboardMode())
{
std::string upper_case_label = mLabel.getString();
LLStringUtil::toUpper(upper_case_label);
std::string::size_type offset = upper_case_label.find(mJumpKey);
if (offset != std::string::npos)
{
S32 x_begin = LEFT_PLAIN_PIXELS + mFont->getWidth(mLabel, 0, offset);
S32 x_end = LEFT_PLAIN_PIXELS + mFont->getWidth(mLabel, 0, offset + 1);
gl_line_2d(x_begin, (MENU_ITEM_PADDING / 2) + 1, x_end, (MENU_ITEM_PADDING / 2) + 1);
}
}
}
BOOL LLMenuItemGL::setLabelArg( const std::string& key, const LLStringExplicit& text )
{
mLabel.setArg(key, text);
return TRUE;
}
void LLMenuItemGL::onVisibilityChange(BOOL new_visibility)
{
if (getMenu())
{
getMenu()->needsArrange();
}
LLView::onVisibilityChange(new_visibility);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Class LLMenuItemSeparatorGL
//
// This class represents a separator.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
LLMenuItemSeparatorGL::Params::Params()
: on_visible("on_visible")
{
}
LLMenuItemSeparatorGL::LLMenuItemSeparatorGL(const LLMenuItemSeparatorGL::Params& p) :
LLMenuItemGL( p )
{
if (p.on_visible.isProvided())
{
mVisibleSignal.connect(initEnableCallback(p.on_visible));
}
}
//virtual
U32 LLMenuItemSeparatorGL::getNominalHeight( void ) const
{
return SEPARATOR_HEIGHT_PIXELS;
}
void LLMenuItemSeparatorGL::draw( void )
{
gGL.color4fv( mDisabledColor.get().mV );
const S32 y = getRect().getHeight() / 2;
const S32 PAD = 6;
gl_line_2d( PAD, y, getRect().getWidth() - PAD, y );
}
void LLMenuItemSeparatorGL::buildDrawLabel( void )
{
if (mVisibleSignal.num_slots() > 0)
{
bool visible = mVisibleSignal(this, LLSD());
setVisible(visible);
}
}
BOOL LLMenuItemSeparatorGL::handleMouseDown(S32 x, S32 y, MASK mask)
{
LLMenuGL* parent_menu = getMenu();
if (y > getRect().getHeight() / 2)
{
// the menu items are in the child list in bottom up order
LLView* prev_menu_item = parent_menu->findNextSibling(this);
return (prev_menu_item && prev_menu_item->getVisible() && prev_menu_item->getEnabled()) ? prev_menu_item->handleMouseDown(x, prev_menu_item->getRect().getHeight(), mask) : FALSE;
}
else
{
LLView* next_menu_item = parent_menu->findPrevSibling(this);
return (next_menu_item && next_menu_item->getVisible() && next_menu_item->getEnabled()) ? next_menu_item->handleMouseDown(x, 0, mask) : FALSE;
}
}
BOOL LLMenuItemSeparatorGL::handleMouseUp(S32 x, S32 y, MASK mask)
{
LLMenuGL* parent_menu = getMenu();
if (y > getRect().getHeight() / 2)
{
LLView* prev_menu_item = parent_menu->findNextSibling(this);
return (prev_menu_item && prev_menu_item->getVisible() && prev_menu_item->getEnabled()) ? prev_menu_item->handleMouseUp(x, prev_menu_item->getRect().getHeight(), mask) : FALSE;
}
else
{
LLView* next_menu_item = parent_menu->findPrevSibling(this);
return (next_menu_item && next_menu_item->getVisible() && next_menu_item->getEnabled()) ? next_menu_item->handleMouseUp(x, 0, mask) : FALSE;
}
}
BOOL LLMenuItemSeparatorGL::handleHover(S32 x, S32 y, MASK mask)
{
LLMenuGL* parent_menu = getMenu();
if (y > getRect().getHeight() / 2)
{
parent_menu->highlightPrevItem(this, FALSE);
return FALSE;
}
else
{
parent_menu->highlightNextItem(this, FALSE);
return FALSE;
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Class LLMenuItemVerticalSeparatorGL
//
// This class represents a vertical separator.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class LLMenuItemVerticalSeparatorGL
: public LLMenuItemSeparatorGL
{
public:
LLMenuItemVerticalSeparatorGL( void );
virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask) { return FALSE; }
};
LLMenuItemVerticalSeparatorGL::LLMenuItemVerticalSeparatorGL( void )
{
setLabel( VERTICAL_SEPARATOR_LABEL );
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Class LLMenuItemTearOffGL
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
LLMenuItemTearOffGL::LLMenuItemTearOffGL(const LLMenuItemTearOffGL::Params& p)
: LLMenuItemGL(p)
{
}
// Returns the first floater ancestor if there is one
LLFloater* LLMenuItemTearOffGL::getParentFloater()
{
LLView* parent_view = getMenu();
while (parent_view)
{
if (dynamic_cast<LLFloater*>(parent_view))
{
return dynamic_cast<LLFloater*>(parent_view);
}
bool parent_is_menu = dynamic_cast<LLMenuGL*>(parent_view) && !dynamic_cast<LLMenuBarGL*>(parent_view);
if (parent_is_menu)
{
// use menu parent
parent_view = dynamic_cast<LLMenuGL*>(parent_view)->getParentMenuItem();
}
else
{
// just use regular view parent
parent_view = parent_view->getParent();
}
}
return NULL;
}
void LLMenuItemTearOffGL::onCommit()
{
if (getMenu()->getTornOff())
{
LLTearOffMenu * torn_off_menu = dynamic_cast<LLTearOffMenu*>(getMenu()->getParent());
if (torn_off_menu)
{
torn_off_menu->closeFloater();
}
}
else
{
// transfer keyboard focus and highlight to first real item in list
if (getHighlight())
{
getMenu()->highlightNextItem(this);
}
getMenu()->needsArrange();
LLFloater* parent_floater = getParentFloater();
LLFloater* tear_off_menu = LLTearOffMenu::create(getMenu());
if (tear_off_menu)
{
if (parent_floater)
{
parent_floater->addDependentFloater(tear_off_menu, FALSE);
}
// give focus to torn off menu because it will have
// been taken away when parent menu closes
tear_off_menu->setFocus(TRUE);
}
}
LLMenuItemGL::onCommit();
}
void LLMenuItemTearOffGL::draw()
{
// disabled items can be highlighted, but shouldn't render as such
if( getEnabled() && getHighlight() && !isBriefItem())
{
gGL.color4fv( mHighlightBackground.get().mV );
gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth(), 0 );
}
if (getEnabled())
{
gGL.color4fv( mEnabledColor.get().mV );
}
else
{
gGL.color4fv( mDisabledColor.get().mV );
}
const S32 y = getRect().getHeight() / 3;
const S32 PAD = 6;
gl_line_2d( PAD, y, getRect().getWidth() - PAD, y );
gl_line_2d( PAD, y * 2, getRect().getWidth() - PAD, y * 2 );
}
U32 LLMenuItemTearOffGL::getNominalHeight( void ) const
{
return TEAROFF_SEPARATOR_HEIGHT_PIXELS;
}
///============================================================================
/// Class LLMenuItemCallGL
///============================================================================
LLMenuItemCallGL::LLMenuItemCallGL(const LLMenuItemCallGL::Params& p)
: LLMenuItemGL(p)
{
}
void LLMenuItemCallGL::initFromParams(const Params& p)
{
if (p.on_visible.isProvided())
{
mVisibleSignal.connect(initEnableCallback(p.on_visible));
}
if (p.on_enable.isProvided())
{
setEnableCallback(initEnableCallback(p.on_enable));
// Set the enabled control variable (for backwards compatability)
if (p.on_enable.control_name.isProvided() && !p.on_enable.control_name().empty())
{
LLControlVariable* control = findControl(p.on_enable.control_name());
if (control)
{
setEnabledControlVariable(control);
}
else
{
LL_WARNS() << "Failed to assign 'enabled' control variable to menu " << getName()
<< ": control " << p.on_enable.control_name()
<< " does not exist." << LL_ENDL;
}
}
}
if (p.on_click.isProvided())
{
setCommitCallback(initCommitCallback(p.on_click));
}
LLUICtrl::initFromParams(p);
}
void LLMenuItemCallGL::onCommit( void )
{
// RN: menu item can be deleted in callback, so beware
getMenu()->setItemLastSelected( this );
LLMenuItemGL::onCommit();
}
void LLMenuItemCallGL::updateEnabled( void )
{
if (mEnableSignal.num_slots() > 0)
{
bool enabled = mEnableSignal(this, LLSD());
if (mEnabledControlVariable)
{
if (!enabled)
{
// callback overrides control variable; this will call setEnabled()
mEnabledControlVariable->set(false);
}
}
else
{
setEnabled(enabled);
}
}
}
void LLMenuItemCallGL::updateVisible( void )
{
if (mVisibleSignal.num_slots() > 0)
{
bool visible = mVisibleSignal(this, LLSD());
setVisible(visible);
}
}
void LLMenuItemCallGL::buildDrawLabel( void )
{
updateEnabled();
updateVisible();
LLMenuItemGL::buildDrawLabel();
}
BOOL LLMenuItemCallGL::handleKeyHere( KEY key, MASK mask )
{
return LLMenuItemGL::handleKeyHere(key, mask);
}
BOOL LLMenuItemCallGL::handleAcceleratorKey( KEY key, MASK mask )
{
if( (!gKeyboard->getKeyRepeated(key) || getAllowKeyRepeat()) && (key == mAcceleratorKey) && (mask == (mAcceleratorMask & MASK_NORMALKEYS)) )
{
updateEnabled();
if (getEnabled())
{
onCommit();
return TRUE;
}
}
return FALSE;
}
// handleRightMouseUp moved into base class LLMenuItemGL so clicks are
// handled for all menu item types
///============================================================================
/// Class LLMenuItemCheckGL
///============================================================================
LLMenuItemCheckGL::LLMenuItemCheckGL (const LLMenuItemCheckGL::Params& p)
: LLMenuItemCallGL(p)
{
}
void LLMenuItemCheckGL::initFromParams(const Params& p)
{
if (p.on_check.isProvided())
{
setCheckCallback(initEnableCallback(p.on_check));
// Set the control name (for backwards compatability)
if (p.on_check.control_name.isProvided() && !p.on_check.control_name().empty())
{
setControlName(p.on_check.control_name());
}
}
LLMenuItemCallGL::initFromParams(p);
}
void LLMenuItemCheckGL::onCommit( void )
{
LLMenuItemCallGL::onCommit();
}
//virtual
void LLMenuItemCheckGL::setValue(const LLSD& value)
{
LLUICtrl::setValue(value);
if(value.asBoolean())
{
mDrawBoolLabel = LLMenuGL::BOOLEAN_TRUE_PREFIX;
}
else
{
mDrawBoolLabel.clear();
}
}
//virtual
LLSD LLMenuItemCheckGL::getValue() const
{
// Get our boolean value from the view model.
// If we don't override this method then the implementation from
// LLMenuItemGL will return a string. (EXT-8501)
return LLUICtrl::getValue();
}
// called to rebuild the draw label
void LLMenuItemCheckGL::buildDrawLabel( void )
{
// Note: mCheckSignal() returns true if no callbacks are set
bool checked = mCheckSignal(this, LLSD());
if (mControlVariable)
{
if (!checked)
setControlValue(false); // callback overrides control variable; this will call setValue()
}
else
{
setValue(checked);
}
if(getValue().asBoolean())
{
mDrawBoolLabel = LLMenuGL::BOOLEAN_TRUE_PREFIX;
}
else
{
mDrawBoolLabel.clear();
}
LLMenuItemCallGL::buildDrawLabel();
}
///============================================================================
/// Class LLMenuItemBranchGL
///============================================================================
LLMenuItemBranchGL::LLMenuItemBranchGL(const LLMenuItemBranchGL::Params& p)
: LLMenuItemGL(p)
{
LLMenuGL* branch = p.branch;
if (branch)
{
mBranchHandle = branch->getHandle();
branch->setVisible(FALSE);
branch->setParentMenuItem(this);
}
}
LLMenuItemBranchGL::~LLMenuItemBranchGL()
{
if (mBranchHandle.get())
{
mBranchHandle.get()->die();
}
}
// virtual
LLView* LLMenuItemBranchGL::getChildView(const std::string& name, BOOL recurse) const
{
LLMenuGL* branch = getBranch();
if (branch)
{
if (branch->getName() == name)
{
return branch;
}
// Always recurse on branches
return branch->getChildView(name, recurse);
}
return LLView::getChildView(name, recurse);
}
LLView* LLMenuItemBranchGL::findChildView(const std::string& name, BOOL recurse) const
{
LLMenuGL* branch = getBranch();
if (branch)
{
if (branch->getName() == name)
{
return branch;
}
// Always recurse on branches
return branch->findChildView(name, recurse);
}
return LLView::findChildView(name, recurse);
}
// virtual
BOOL LLMenuItemBranchGL::handleMouseUp(S32 x, S32 y, MASK mask)
{
// switch to mouse navigation mode
LLMenuGL::setKeyboardMode(FALSE);
onCommit();
make_ui_sound("UISndClickRelease");
return TRUE;
}
bool LLMenuItemBranchGL::hasAccelerator(const KEY &key, const MASK &mask) const
{
return getBranch() && getBranch()->hasAccelerator(key, mask);
}
BOOL LLMenuItemBranchGL::handleAcceleratorKey(KEY key, MASK mask)
{
return getBranch() && getBranch()->handleAcceleratorKey(key, mask);
}
// This function checks to see if the accelerator key is already in use;
// if not, it will be added to the list
BOOL LLMenuItemBranchGL::addToAcceleratorList(std::list<LLMenuKeyboardBinding*> *listp)
{
LLMenuGL* branch = getBranch();
if (!branch)
return FALSE;
U32 item_count = branch->getItemCount();
LLMenuItemGL *item;
while (item_count--)
{
if ((item = branch->getItem(item_count)))
{
return item->addToAcceleratorList(listp);
}
}
return FALSE;
}
// called to rebuild the draw label
void LLMenuItemBranchGL::buildDrawLabel( void )
{
mDrawAccelLabel.clear();
std::string st = mDrawAccelLabel;
appendAcceleratorString( st );
mDrawAccelLabel = st;
mDrawBranchLabel = LLMenuGL::BRANCH_SUFFIX;
}
void LLMenuItemBranchGL::onCommit( void )
{
openMenu();
// keyboard navigation automatically propagates highlight to sub-menu
// to facilitate fast menu control via jump keys
if (LLMenuGL::getKeyboardMode() && getBranch() && !getBranch()->getHighlightedItem())
{
getBranch()->highlightNextItem(NULL);
}
LLUICtrl::onCommit();
}
BOOL LLMenuItemBranchGL::handleKey(KEY key, MASK mask, BOOL called_from_parent)
{
BOOL handled = FALSE;
if (getBranch() && called_from_parent)
{
handled = getBranch()->handleKey(key, mask, called_from_parent);
}
if (!handled)
{
handled = LLMenuItemGL::handleKey(key, mask, called_from_parent);
}
return handled;
}
BOOL LLMenuItemBranchGL::handleUnicodeChar(llwchar uni_char, BOOL called_from_parent)
{
BOOL handled = FALSE;
if (getBranch() && called_from_parent)
{
handled = getBranch()->handleUnicodeChar(uni_char, TRUE);
}
if (!handled)
{
handled = LLMenuItemGL::handleUnicodeChar(uni_char, called_from_parent);
}
return handled;
}
void LLMenuItemBranchGL::setHighlight( BOOL highlight )
{
if (highlight == getHighlight())
return;
LLMenuGL* branch = getBranch();
if (!branch)
return;
BOOL auto_open = getEnabled() && (!branch->getVisible() || branch->getTornOff());
// torn off menus don't open sub menus on hover unless they have focus
LLFloater * menu_parent = dynamic_cast<LLFloater *>(getMenu()->getParent());
if (getMenu()->getTornOff() && menu_parent && !menu_parent->hasFocus())
{
auto_open = FALSE;
}
// don't auto open torn off sub-menus (need to explicitly active menu item to give them focus)
if (branch->getTornOff())
{
auto_open = FALSE;
}
LLMenuItemGL::setHighlight(highlight);
if( highlight )
{
if(auto_open)
{
openMenu();
}
}
else
{
if (branch->getTornOff())
{
LLFloater * branch_parent = dynamic_cast<LLFloater *>(branch->getParent());
if (branch_parent)
{
branch_parent->setFocus(FALSE);
}
branch->clearHoverItem();
}
else
{
branch->setVisible( FALSE );
}
}
}
void LLMenuItemBranchGL::draw()
{
LLMenuItemGL::draw();
if (getBranch() && getBranch()->getVisible() && !getBranch()->getTornOff())
{
setHighlight(TRUE);
}
}
void LLMenuItemBranchGL::updateBranchParent(LLView* parentp)
{
if (getBranch() && getBranch()->getParent() == NULL)
{
// make the branch menu a sibling of my parent menu
getBranch()->updateParent(parentp);
}
}
void LLMenuItemBranchGL::onVisibilityChange( BOOL new_visibility )
{
if (new_visibility == FALSE && getBranch() && !getBranch()->getTornOff())
{
getBranch()->setVisible(FALSE);
}
LLMenuItemGL::onVisibilityChange(new_visibility);
}
BOOL LLMenuItemBranchGL::handleKeyHere( KEY key, MASK mask )
{
LLMenuGL* branch = getBranch();
if (!branch)
return LLMenuItemGL::handleKeyHere(key, mask);
// an item is highlighted, my menu is open, and I have an active sub menu or we are in
// keyboard navigation mode
if (getHighlight()
&& getMenu()->isOpen()
&& (isActive() || LLMenuGL::getKeyboardMode()))
{
if (branch->getVisible() && key == KEY_LEFT)
{
// switch to keyboard navigation mode
LLMenuGL::setKeyboardMode(TRUE);
BOOL handled = branch->clearHoverItem();
if (branch->getTornOff())
{
LLFloater * branch_parent = dynamic_cast<LLFloater *>(branch->getParent());
if (branch_parent)
{
branch_parent->setFocus(FALSE);
}
}
if (handled && getMenu()->getTornOff())
{
LLFloater * menu_parent = dynamic_cast<LLFloater *>(getMenu()->getParent());
if (menu_parent)
{
menu_parent->setFocus(TRUE);
}
}
return handled;
}
if (key == KEY_RIGHT && !branch->getHighlightedItem())
{
// switch to keyboard navigation mode
LLMenuGL::setKeyboardMode(TRUE);
LLMenuItemGL* itemp = branch->highlightNextItem(NULL);
if (itemp)
{
return TRUE;
}
}
}
return LLMenuItemGL::handleKeyHere(key, mask);
}
//virtual
BOOL LLMenuItemBranchGL::isActive() const
{
return isOpen() && getBranch() && getBranch()->getHighlightedItem();
}
//virtual
BOOL LLMenuItemBranchGL::isOpen() const
{
return getBranch() && getBranch()->isOpen();
}
void LLMenuItemBranchGL::openMenu()
{
LLMenuGL* branch = getBranch();
if (!branch)
return;
if (branch->getTornOff())
{
LLFloater * branch_parent = dynamic_cast<LLFloater *>(branch->getParent());
if (branch_parent)
{
gFloaterView->bringToFront(branch_parent);
// this might not be necessary, as torn off branches don't get focus and hence no highligth
branch->highlightNextItem(NULL);
}
}
else if( !branch->getVisible() )
{
// get valid rectangle for menus
const LLRect menu_region_rect = LLMenuGL::sMenuContainer->getMenuRect();
branch->arrange();
LLRect branch_rect = branch->getRect();
// calculate root-view relative position for branch menu
S32 left = getRect().mRight;
S32 top = getRect().mTop - getRect().mBottom;
localPointToOtherView(left, top, &left, &top, branch->getParent());
branch_rect.setLeftTopAndSize( left, top,
branch_rect.getWidth(), branch_rect.getHeight() );
if (branch->getCanTearOff())
{
branch_rect.translate(0, TEAROFF_SEPARATOR_HEIGHT_PIXELS);
}
branch->setRect( branch_rect );
// if branch extends outside of menu region change the direction it opens in
S32 x, y;
S32 delta_x = 0;
S32 delta_y = 0;
branch->localPointToOtherView( 0, 0, &x, &y, branch->getParent() );
if( y < menu_region_rect.mBottom )
{
// open upwards if menu extends past bottom
// adjust by the height of the menu item branch since it is a submenu
if (y + 2 * branch_rect.getHeight() - getRect().getHeight() > menu_region_rect.mTop)
{
// overlaps with top border, align with top
delta_y = menu_region_rect.mTop - y - branch_rect.getHeight();
}
else
{
delta_y = branch_rect.getHeight() - getRect().getHeight();
}
}
if( x + branch_rect.getWidth() > menu_region_rect.mRight )
{
// move sub-menu over to left side
delta_x = llmax(-x, ( -(branch_rect.getWidth() + getRect().getWidth())));
}
branch->translate( delta_x, delta_y );
branch->setVisible( TRUE );
branch->getParent()->sendChildToFront(branch);
dirtyRect();
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Class LLMenuItemBranchDownGL
//
// The LLMenuItemBranchDownGL represents a menu item that has a
// sub-menu. This is used to make menu bar menus.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class LLMenuItemBranchDownGL : public LLMenuItemBranchGL
{
protected:
public:
LLMenuItemBranchDownGL( const Params& );
// returns the normal width of this control in pixels - this is
// used for calculating the widest item, as well as for horizontal
// arrangement.
virtual U32 getNominalWidth( void ) const;
// called to rebuild the draw label
virtual void buildDrawLabel( void );
// handles opening, positioning, and arranging the menu branch associated with this item
virtual void openMenu( void );
// set the hover status (called by it's menu) and if the object is
// active. This is used for behavior transfer.
virtual void setHighlight( BOOL highlight );
virtual BOOL isActive( void ) const;
// LLView functionality
virtual BOOL handleMouseDown( S32 x, S32 y, MASK mask );
virtual BOOL handleMouseUp( S32 x, S32 y, MASK mask );
virtual void draw( void );
virtual BOOL handleKeyHere(KEY key, MASK mask);
virtual BOOL handleAcceleratorKey(KEY key, MASK mask);
virtual void onFocusLost();
virtual void setFocus(BOOL b);
};
LLMenuItemBranchDownGL::LLMenuItemBranchDownGL( const Params& p) :
LLMenuItemBranchGL(p)
{
}
// returns the normal width of this control in pixels - this is used
// for calculating the widest item, as well as for horizontal
// arrangement.
U32 LLMenuItemBranchDownGL::getNominalWidth( void ) const
{
U32 width = LEFT_PAD_PIXELS + LEFT_WIDTH_PIXELS + RIGHT_PAD_PIXELS;
width += getFont()->getWidth( mLabel.getWString().c_str() );
return width;
}
// called to rebuild the draw label
void LLMenuItemBranchDownGL::buildDrawLabel( void )
{
mDrawAccelLabel.clear();
std::string st = mDrawAccelLabel;
appendAcceleratorString( st );
mDrawAccelLabel = st;
}
void LLMenuItemBranchDownGL::openMenu( void )
{
LLMenuGL* branch = getBranch();
if( branch->getVisible() && !branch->getTornOff() )
{
branch->setVisible( FALSE );
}
else
{
if (branch->getTornOff())
{
LLFloater * branch_parent = dynamic_cast<LLFloater *>(branch->getParent());
if (branch_parent)
{
gFloaterView->bringToFront(branch_parent);
}
}
else
{
// We're showing the drop-down menu, so patch up its labels/rects
branch->arrange();
LLRect rect = branch->getRect();
S32 left = 0;
S32 top = getRect().mBottom;
localPointToOtherView(left, top, &left, &top, branch->getParent());
rect.setLeftTopAndSize( left, top,
rect.getWidth(), rect.getHeight() );
branch->setRect( rect );
S32 x = 0;
S32 y = 0;
branch->localPointToScreen( 0, 0, &x, &y );
S32 delta_x = 0;
LLCoordScreen window_size;
LLWindow* windowp = getWindow();
windowp->getSize(&window_size);
S32 window_width = window_size.mX;
if( x > window_width - rect.getWidth() )
{
delta_x = (window_width - rect.getWidth()) - x;
}
branch->translate( delta_x, 0 );
setHighlight(TRUE);
branch->setVisible( TRUE );
branch->getParent()->sendChildToFront(branch);
}
}
}
// set the hover status (called by it's menu)
void LLMenuItemBranchDownGL::setHighlight( BOOL highlight )
{
if (highlight == getHighlight())
return;
//NOTE: Purposely calling all the way to the base to bypass auto-open.
LLMenuItemGL::setHighlight(highlight);
LLMenuGL* branch = getBranch();
if (!branch)
return;
if( !highlight)
{
if (branch->getTornOff())
{
LLFloater * branch_parent = dynamic_cast<LLFloater *>(branch->getParent());
if (branch_parent)
{
branch_parent->setFocus(FALSE);
}
branch->clearHoverItem();
}
else
{
branch->setVisible( FALSE );
}
}
}
BOOL LLMenuItemBranchDownGL::isActive() const
{
// for top level menus, being open is sufficient to be considered
// active, because clicking on them with the mouse will open
// them, without moving keyboard focus to them
return isOpen();
}
BOOL LLMenuItemBranchDownGL::handleMouseDown( S32 x, S32 y, MASK mask )
{
// switch to mouse control mode
LLMenuGL::setKeyboardMode(FALSE);
if (getVisible() && isOpen())
{
LLMenuGL::sMenuContainer->hideMenus();
}
else
{
onCommit();
}
make_ui_sound("UISndClick");
return TRUE;
}
BOOL LLMenuItemBranchDownGL::handleMouseUp( S32 x, S32 y, MASK mask )
{
return TRUE;
}
BOOL LLMenuItemBranchDownGL::handleAcceleratorKey(KEY key, MASK mask)
{
BOOL branch_visible = getBranch()->getVisible();
BOOL handled = getBranch()->handleAcceleratorKey(key, mask);
if (handled && !branch_visible && isInVisibleChain())
{
// flash this menu entry because we triggered an invisible menu item
LLMenuHolderGL::setActivatedItem(this);
}
return handled;
}
void LLMenuItemBranchDownGL::onFocusLost()
{
// needed for tab-based selection
LLMenuItemBranchGL::onFocusLost();
// <FS:Ansariel> FIRE-31882 / FIRE-31896 / FIRE-31913 / FIRE-31920: Fix all kind of weird menu focus issues
//LLMenuGL::setKeyboardMode(FALSE);
//setHighlight(FALSE);
}
void LLMenuItemBranchDownGL::setFocus(BOOL b)
{
// needed for tab-based selection
LLMenuItemBranchGL::setFocus(b);
// <FS:Ansariel> FIRE-31882 / FIRE-31896 / FIRE-31913 / FIRE-31920: Fix all kind of weird menu focus issues
//LLMenuGL::setKeyboardMode(b);
//setHighlight(b);
}
BOOL LLMenuItemBranchDownGL::handleKeyHere(KEY key, MASK mask)
{
BOOL menu_open = getBranch()->getVisible();
// don't do keyboard navigation of top-level menus unless in keyboard mode, or menu expanded
if (getHighlight() && getMenu()->isOpen() && (isActive() || LLMenuGL::getKeyboardMode()))
{
if (key == KEY_LEFT)
{
// switch to keyboard navigation mode
LLMenuGL::setKeyboardMode(TRUE);
LLMenuItemGL* itemp = getMenu()->highlightPrevItem(this);
// open new menu only if previous menu was open
if (itemp && itemp->getEnabled() && menu_open)
{
itemp->onCommit();
}
return TRUE;
}
else if (key == KEY_RIGHT)
{
// switch to keyboard navigation mode
LLMenuGL::setKeyboardMode(TRUE);
LLMenuItemGL* itemp = getMenu()->highlightNextItem(this);
// open new menu only if previous menu was open
if (itemp && itemp->getEnabled() && menu_open)
{
itemp->onCommit();
}
return TRUE;
}
else if (key == KEY_DOWN)
{
// switch to keyboard navigation mode
LLMenuGL::setKeyboardMode(TRUE);
if (!isActive())
{
onCommit();
}
getBranch()->highlightNextItem(NULL);
return TRUE;
}
else if (key == KEY_UP)
{
// switch to keyboard navigation mode
LLMenuGL::setKeyboardMode(TRUE);
if (!isActive())
{
onCommit();
}
getBranch()->highlightPrevItem(NULL);
return TRUE;
}
}
return FALSE;
}
void LLMenuItemBranchDownGL::draw( void )
{
//FIXME: try removing this
if (getBranch()->getVisible() && !getBranch()->getTornOff())
{
setHighlight(TRUE);
}
if( getHighlight() )
{
gGL.color4fv( mHighlightBackground.get().mV );
gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth(), 0 );
}
LLColor4 color;
if (getHighlight())
{
color = mHighlightForeground.get();
}
else if( getEnabled() )
{
color = mEnabledColor.get();
}
else
{
color = mDisabledColor.get();
}
getFont()->render( mLabel.getWString(), 0, (F32)getRect().getWidth() / 2.f, (F32)LABEL_BOTTOM_PAD_PIXELS, color,
LLFontGL::HCENTER, LLFontGL::BOTTOM, LLFontGL::NORMAL);
// underline navigation key only when keyboard navigation has been initiated
if (getMenu()->jumpKeysActive() && LLMenuGL::getKeyboardMode())
{
std::string upper_case_label = mLabel.getString();
LLStringUtil::toUpper(upper_case_label);
std::string::size_type offset = upper_case_label.find(getJumpKey());
if (offset != std::string::npos)
{
S32 x_offset = ll_round((F32)getRect().getWidth() / 2.f - getFont()->getWidthF32(mLabel.getString(), 0, S32_MAX) / 2.f);
S32 x_begin = x_offset + getFont()->getWidth(mLabel, 0, offset);
S32 x_end = x_offset + getFont()->getWidth(mLabel, 0, offset + 1);
gl_line_2d(x_begin, LABEL_BOTTOM_PAD_PIXELS, x_end, LABEL_BOTTOM_PAD_PIXELS);
}
}
}
class LLMenuScrollItem : public LLMenuItemCallGL
{
public:
enum EArrowType
{
ARROW_DOWN,
ARROW_UP
};
struct ArrowTypes : public LLInitParam::TypeValuesHelper<EArrowType, ArrowTypes>
{
static void declareValues()
{
declare("up", ARROW_UP);
declare("down", ARROW_DOWN);
}
};
struct Params : public LLInitParam::Block<Params, LLMenuItemCallGL::Params>
{
Optional<EArrowType, ArrowTypes> arrow_type;
Optional<CommitCallbackParam> scroll_callback;
};
protected:
LLMenuScrollItem(const Params&);
friend class LLUICtrlFactory;
public:
/*virtual*/ void draw();
/*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent);
/*virtual*/ void setEnabled(BOOL enabled);
virtual void onCommit( void );
private:
LLButton* mArrowBtn;
};
LLMenuScrollItem::LLMenuScrollItem(const Params& p)
: LLMenuItemCallGL(p)
{
std::string icon;
if (p.arrow_type.isProvided() && p.arrow_type == ARROW_UP)
{
icon = "arrow_up.tga";
}
else
{
icon = "arrow_down.tga";
}
LLButton::Params bparams;
// Disabled the Return key handling by LLMenuScrollItem instead of
// passing the key press to the currently selected menu item. See STORM-385.
bparams.commit_on_return(false);
bparams.mouse_opaque(true);
bparams.scale_image(false);
bparams.click_callback(p.scroll_callback);
bparams.mouse_held_callback(p.scroll_callback);
bparams.follows.flags(FOLLOWS_ALL);
std::string background = "transparent.j2c";
bparams.image_unselected.name(background);
bparams.image_disabled.name(background);
bparams.image_selected.name(background);
bparams.image_hover_selected.name(background);
bparams.image_disabled_selected.name(background);
bparams.image_hover_unselected.name(background);
bparams.image_overlay.name(icon);
mArrowBtn = LLUICtrlFactory::create<LLButton>(bparams);
addChild(mArrowBtn);
}
/*virtual*/
void LLMenuScrollItem::draw()
{
LLUICtrl::draw();
}
/*virtual*/
void LLMenuScrollItem::reshape(S32 width, S32 height, BOOL called_from_parent)
{
mArrowBtn->reshape(width, height, called_from_parent);
LLView::reshape(width, height, called_from_parent);
}
/*virtual*/
void LLMenuScrollItem::setEnabled(BOOL enabled)
{
mArrowBtn->setEnabled(enabled);
LLView::setEnabled(enabled);
}
void LLMenuScrollItem::onCommit( void )
{
LLUICtrl::onCommit();
}
///============================================================================
/// Class LLMenuGL
///============================================================================
LLMenuGL::LLMenuGL(const LLMenuGL::Params& p)
: LLUICtrl(p),
mBackgroundColor( p.bg_color() ),
mBgVisible( p.bg_visible ),
mDropShadowed( p.drop_shadow ),
mHasSelection(false),
mHorizontalLayout( p.horizontal_layout ),
mScrollable(mHorizontalLayout ? FALSE : p.scrollable), // Scrolling is supported only for vertical layout
mMaxScrollableItems(p.max_scrollable_items),
mPreferredWidth(p.preferred_width),
mKeepFixedSize( p.keep_fixed_size ),
mLabel (p.label),
mLastMouseX(0),
mLastMouseY(0),
mMouseVelX(0),
mMouseVelY(0),
mTornOff(FALSE),
mTearOffItem(NULL),
mSpilloverBranch(NULL),
mFirstVisibleItem(NULL),
mArrowUpItem(NULL),
mArrowDownItem(NULL),
mSpilloverMenu(NULL),
mJumpKey(p.jump_key),
mCreateJumpKeys(p.create_jump_keys),
mNeedsArrange(FALSE),
mAlwaysShowMenu(FALSE),
mResetScrollPositionOnShow(true),
mShortcutPad(p.shortcut_pad)
{
typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
boost::char_separator<char> sep("_");
tokenizer tokens(p.label(), sep);
tokenizer::iterator token_iter;
S32 token_count = 0;
std::string new_menu_label;
for( token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter)
{
new_menu_label += (*token_iter);
if (token_count > 0)
{
setJumpKey((*token_iter).c_str()[0]);
}
++token_count;
}
setLabel(new_menu_label);
mFadeTimer.stop();
}
void LLMenuGL::initFromParams(const LLMenuGL::Params& p)
{
LLUICtrl::initFromParams(p);
setCanTearOff(p.can_tear_off);
}
// Destroys the object
LLMenuGL::~LLMenuGL( void )
{
// delete the branch, as it might not be in view hierarchy
// leave the menu, because it is always in view hierarchy
delete mSpilloverBranch;
mJumpKeys.clear();
}
void LLMenuGL::setCanTearOff(BOOL tear_off)
{
if (tear_off && mTearOffItem == NULL)
{
LLMenuItemTearOffGL::Params p;
mTearOffItem = LLUICtrlFactory::create<LLMenuItemTearOffGL>(p);
addChild(mTearOffItem);
}
else if (!tear_off && mTearOffItem != NULL)
{
mItems.remove(mTearOffItem);
removeChild(mTearOffItem);
delete mTearOffItem;
mTearOffItem = NULL;
needsArrange();
}
}
bool LLMenuGL::addChild(LLView* view, S32 tab_group)
{
LLMenuGL* menup = dynamic_cast<LLMenuGL*>(view);
if (menup)
{
return appendMenu(menup);
}
LLMenuItemGL* itemp = dynamic_cast<LLMenuItemGL*>(view);
if (itemp)
{
return append(itemp);
}
return false;
}
// Used in LLContextMenu and in LLTogleableMenu
// Add an item to the context menu branch
bool LLMenuGL::addContextChild(LLView* view, S32 tab_group)
{
LLContextMenu* context = dynamic_cast<LLContextMenu*>(view);
if (context)
{
return appendContextSubMenu(context);
}
LLMenuItemSeparatorGL* separator = dynamic_cast<LLMenuItemSeparatorGL*>(view);
if (separator)
{
return append(separator);
}
LLMenuItemGL* item = dynamic_cast<LLMenuItemGL*>(view);
if (item)
{
return append(item);
}
LLMenuGL* menup = dynamic_cast<LLMenuGL*>(view);
if (menup)
{
return appendMenu(menup);
}
return false;
}
void LLMenuGL::deleteAllChildren()
{
mItems.clear();
LLUICtrl::deleteAllChildren();
}
void LLMenuGL::removeChild( LLView* ctrl)
{
// previously a dynamic_cast with if statement to check validity
// unfortunately removeChild is called by ~LLView, and at that point the
// object being deleted is no longer a LLMenuItemGL so a dynamic_cast will fail
LLMenuItemGL* itemp = static_cast<LLMenuItemGL*>(ctrl);
item_list_t::iterator found_it = std::find(mItems.begin(), mItems.end(), (itemp));
if (found_it != mItems.end())
{
mItems.erase(found_it);
}
return LLUICtrl::removeChild(ctrl);
}
BOOL LLMenuGL::postBuild()
{
createJumpKeys();
return LLUICtrl::postBuild();
}
// are we the childmost active menu and hence our jump keys should be enabled?
// or are we a free-standing torn-off menu (which uses jump keys too)
BOOL LLMenuGL::jumpKeysActive()
{
LLMenuItemGL* highlighted_item = getHighlightedItem();
BOOL active = getVisible() && getEnabled();
if (active)
{
if (getTornOff())
{
// activation of jump keys on torn off menus controlled by keyboard focus
LLFloater * parent = dynamic_cast<LLFloater *>(getParent());
if (parent)
{
active = parent->hasFocus();
}
}
else
{
// Are we the terminal active menu?
// Yes, if parent menu item deems us to be active (just being visible is sufficient for top-level menus)
// and we don't have a highlighted menu item pointing to an active sub-menu
active = (!getParentMenuItem() || getParentMenuItem()->isActive()) // I have a parent that is active...
&& (!highlighted_item || !highlighted_item->isActive()); //... but no child that is active
}
}
return active;
}
BOOL LLMenuGL::isOpen()
{
if (getTornOff())
{
LLMenuItemGL* itemp = getHighlightedItem();
// if we have an open sub-menu, then we are considered part of
// the open menu chain even if we don't have focus
if (itemp && itemp->isOpen())
{
return TRUE;
}
// otherwise we are only active if we have keyboard focus
LLFloater * parent = dynamic_cast<LLFloater *>(getParent());
if (parent)
{
return parent->hasFocus();
}
return FALSE;
}
else
{
// normally, menus are hidden as soon as the user focuses
// on another menu, so just use the visibility criterion
return getVisible();
}
}
bool LLMenuGL::scrollItems(EScrollingDirection direction)
{
// Slowing down items scrolling when arrow button is held
if (mScrollItemsTimer.hasExpired() && NULL != mFirstVisibleItem)
{
mScrollItemsTimer.setTimerExpirySec(.033f);
}
else
{
return false;
}
switch (direction)
{
case SD_UP:
{
item_list_t::iterator cur_item_iter;
item_list_t::iterator prev_item_iter;
for (cur_item_iter = mItems.begin(), prev_item_iter = mItems.begin(); cur_item_iter != mItems.end(); cur_item_iter++)
{
if( (*cur_item_iter) == mFirstVisibleItem)
{
break;
}
if ((*cur_item_iter)->getVisible())
{
prev_item_iter = cur_item_iter;
}
}
if ((*prev_item_iter)->getVisible())
{
mFirstVisibleItem = *prev_item_iter;
}
break;
}
case SD_DOWN:
{
if (NULL == mFirstVisibleItem)
{
mFirstVisibleItem = *mItems.begin();
}
item_list_t::iterator cur_item_iter;
for (cur_item_iter = mItems.begin(); cur_item_iter != mItems.end(); cur_item_iter++)
{
if( (*cur_item_iter) == mFirstVisibleItem)
{
break;
}
}
item_list_t::iterator next_item_iter;
if (cur_item_iter != mItems.end())
{
for (next_item_iter = ++cur_item_iter; next_item_iter != mItems.end(); next_item_iter++)
{
if( (*next_item_iter)->getVisible())
{
break;
}
}
if (next_item_iter != mItems.end() &&
(*next_item_iter)->getVisible())
{
mFirstVisibleItem = *next_item_iter;
}
}
break;
}
case SD_BEGIN:
{
mFirstVisibleItem = *mItems.begin();
break;
}
case SD_END:
{
item_list_t::reverse_iterator first_visible_item_iter = mItems.rend();
// Need to scroll through number of actual existing items in menu.
// Otherwise viewer will hang for a time needed to scroll U32_MAX
// times in std::advance(). STORM-659.
size_t nitems = mItems.size();
U32 scrollable_items = nitems < mMaxScrollableItems ? nitems : mMaxScrollableItems;
// Advance by mMaxScrollableItems back from the end of the list
// to make the last item visible.
std::advance(first_visible_item_iter, scrollable_items);
mFirstVisibleItem = *first_visible_item_iter;
break;
}
default:
LL_WARNS() << "Unknown scrolling direction: " << direction << LL_ENDL;
}
mNeedsArrange = TRUE;
arrangeAndClear();
return true;
}
// rearrange the child rects so they fit the shape of the menu.
void LLMenuGL::arrange( void )
{
// calculate the height & width, and set our rect based on that
// information.
const LLRect& initial_rect = getRect();
U32 width = 0, height = MENU_ITEM_PADDING;
cleanupSpilloverBranch();
if( mItems.size() )
{
const LLRect menu_region_rect = LLMenuGL::sMenuContainer ? LLMenuGL::sMenuContainer->getMenuRect() : LLRect(0, S32_MAX, S32_MAX, 0);
// torn off menus are not constrained to the size of the screen
U32 max_width = getTornOff() ? U32_MAX : menu_region_rect.getWidth();
U32 max_height = getTornOff() ? U32_MAX: menu_region_rect.getHeight();
// *FIX: create the item first and then ask for its dimensions?
// <FS:Ansariel> Calculate proper width for localized string
//S32 spillover_item_width = PLAIN_PAD_PIXELS + LLFontGL::getFontSansSerif()->getWidth( std::string("More") ); // *TODO: Translate
S32 spillover_item_width = PLAIN_PAD_PIXELS + LLFontGL::getFontSansSerif()->getWidth( LLTrans::getString("More") );
// </FS:Ansariel>
S32 spillover_item_height = LLFontGL::getFontSansSerif()->getLineHeight() + MENU_ITEM_PADDING;
// Scrolling support
item_list_t::iterator first_visible_item_iter;
item_list_t::iterator first_hidden_item_iter = mItems.end();
S32 height_before_first_visible_item = -1;
S32 visible_items_height = 0;
U32 scrollable_items_cnt = 0;
if (mHorizontalLayout)
{
item_list_t::iterator item_iter;
for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter)
{
// do first so LLMenuGLItemCall can call on_visible to determine if visible
(*item_iter)->buildDrawLabel();
if ((*item_iter)->getVisible())
{
if (!getTornOff()
&& *item_iter != mSpilloverBranch
&& width + (*item_iter)->getNominalWidth() > max_width - spillover_item_width)
{
// no room for any more items
createSpilloverBranch();
std::vector<LLMenuItemGL*> items_to_remove;
std::copy(item_iter, mItems.end(), std::back_inserter(items_to_remove));
std::vector<LLMenuItemGL*>::iterator spillover_iter;
for (spillover_iter= items_to_remove.begin(); spillover_iter != items_to_remove.end(); ++spillover_iter)
{
LLMenuItemGL* itemp = (*spillover_iter);
removeChild(itemp);
mSpilloverMenu->addChild(itemp);
}
addChild(mSpilloverBranch);
height = llmax(height, mSpilloverBranch->getNominalHeight());
width += mSpilloverBranch->getNominalWidth();
break;
}
else
{
// track our rect
height = llmax(height, (*item_iter)->getNominalHeight());
width += (*item_iter)->getNominalWidth();
}
}
}
}
else
{
for (LLMenuItemGL* itemp : mItems)
{
// do first so LLMenuGLItemCall can call on_visible to determine if visible
itemp->buildDrawLabel();
}
item_list_t::iterator item_iter;
for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter)
{
if ((*item_iter)->getVisible())
{
if (!getTornOff()
&& !mScrollable
&& *item_iter != mSpilloverBranch
&& height + (*item_iter)->getNominalHeight() > max_height - spillover_item_height)
{
// don't show only one item
int visible_items = 0;
item_list_t::iterator count_iter;
for (count_iter = item_iter; count_iter != mItems.end(); ++count_iter)
{
if((*count_iter)->getVisible())
visible_items++;
}
if (visible_items>1)
{
// no room for any more items
createSpilloverBranch();
std::vector<LLMenuItemGL*> items_to_remove;
std::copy(item_iter, mItems.end(), std::back_inserter(items_to_remove));
std::vector<LLMenuItemGL*>::iterator spillover_iter;
for (spillover_iter= items_to_remove.begin(); spillover_iter != items_to_remove.end(); ++spillover_iter)
{
LLMenuItemGL* itemp = (*spillover_iter);
removeChild(itemp);
mSpilloverMenu->addChild(itemp);
}
addChild(mSpilloverBranch);
height += mSpilloverBranch->getNominalHeight();
width = llmax( width, mSpilloverBranch->getNominalWidth() );
break;
}
}
// track our rect
height += (*item_iter)->getNominalHeight();
width = llmax( width, (*item_iter)->getNominalWidth() );
if (mScrollable)
{
// Determining visible items boundaries
if (NULL == mFirstVisibleItem)
{
mFirstVisibleItem = *item_iter;
}
if (*item_iter == mFirstVisibleItem)
{
height_before_first_visible_item = height - (*item_iter)->getNominalHeight();
first_visible_item_iter = item_iter;
scrollable_items_cnt = 0;
}
if (-1 != height_before_first_visible_item && 0 == visible_items_height &&
(++scrollable_items_cnt > mMaxScrollableItems ||
height - height_before_first_visible_item > max_height - spillover_item_height * 2 ))
{
first_hidden_item_iter = item_iter;
visible_items_height = height - height_before_first_visible_item - (*item_iter)->getNominalHeight();
scrollable_items_cnt--;
}
}
}
}
if (mPreferredWidth < U32_MAX)
width = llmin(mPreferredWidth, max_width);
if (mScrollable)
{
S32 max_items_height = max_height - spillover_item_height * 2;
if (visible_items_height == 0)
visible_items_height = height - height_before_first_visible_item;
// Fix mFirstVisibleItem value, if it doesn't allow to display all items, that can fit
if (visible_items_height < max_items_height && scrollable_items_cnt < mMaxScrollableItems)
{
item_list_t::iterator tmp_iter(first_visible_item_iter);
while (visible_items_height < max_items_height &&
scrollable_items_cnt < mMaxScrollableItems &&
first_visible_item_iter != mItems.begin())
{
if ((*first_visible_item_iter)->getVisible())
{
// It keeps visible item, after first_visible_item_iter
tmp_iter = first_visible_item_iter;
}
first_visible_item_iter--;
if ((*first_visible_item_iter)->getVisible())
{
visible_items_height += (*first_visible_item_iter)->getNominalHeight();
height_before_first_visible_item -= (*first_visible_item_iter)->getNominalHeight();
scrollable_items_cnt++;
}
}
// Roll back one item, that doesn't fit
if (visible_items_height > max_items_height)
{
visible_items_height -= (*first_visible_item_iter)->getNominalHeight();
height_before_first_visible_item += (*first_visible_item_iter)->getNominalHeight();
scrollable_items_cnt--;
first_visible_item_iter = tmp_iter;
}
if (!(*first_visible_item_iter)->getVisible())
{
first_visible_item_iter = tmp_iter;
}
mFirstVisibleItem = *first_visible_item_iter;
}
}
}
S32 cur_height = (S32)llmin(max_height, height);
if (mScrollable &&
(height_before_first_visible_item > MENU_ITEM_PADDING ||
height_before_first_visible_item + visible_items_height < (S32)height))
{
// Reserving 2 extra slots for arrow items
cur_height = visible_items_height + spillover_item_height * 2;
}
setRect(LLRect(getRect().mLeft, getRect().mTop, getRect().mLeft + width, getRect().mTop - cur_height));
S32 cur_width = 0;
S32 offset = 0;
if (mScrollable)
{
// No space for all items, creating arrow items
if (height_before_first_visible_item > MENU_ITEM_PADDING ||
height_before_first_visible_item + visible_items_height < (S32)height)
{
if (NULL == mArrowUpItem)
{
LLMenuScrollItem::Params item_params;
item_params.name(ARROW_UP);
item_params.arrow_type(LLMenuScrollItem::ARROW_UP);
item_params.scroll_callback.function(boost::bind(&LLMenuGL::scrollItems, this, SD_UP));
mArrowUpItem = LLUICtrlFactory::create<LLMenuScrollItem>(item_params);
LLUICtrl::addChild(mArrowUpItem);
}
if (NULL == mArrowDownItem)
{
LLMenuScrollItem::Params item_params;
item_params.name(ARROW_DOWN);
item_params.arrow_type(LLMenuScrollItem::ARROW_DOWN);
item_params.scroll_callback.function(boost::bind(&LLMenuGL::scrollItems, this, SD_DOWN));
mArrowDownItem = LLUICtrlFactory::create<LLMenuScrollItem>(item_params);
LLUICtrl::addChild(mArrowDownItem);
}
LLRect rect;
mArrowUpItem->setRect(rect.setLeftTopAndSize( 0, cur_height, width, mArrowUpItem->getNominalHeight()));
mArrowUpItem->setVisible(TRUE);
mArrowUpItem->setEnabled(height_before_first_visible_item > MENU_ITEM_PADDING);
mArrowUpItem->reshape(width, mArrowUpItem->getNominalHeight());
mArrowDownItem->setRect(rect.setLeftTopAndSize( 0, mArrowDownItem->getNominalHeight(), width, mArrowDownItem->getNominalHeight()));
mArrowDownItem->setVisible(TRUE);
mArrowDownItem->setEnabled(height_before_first_visible_item + visible_items_height < (S32)height);
mArrowDownItem->reshape(width, mArrowDownItem->getNominalHeight());
cur_height -= mArrowUpItem->getNominalHeight();
offset = menu_region_rect.mRight; // This moves items behind visible area
}
else
{
if (NULL != mArrowUpItem)
{
mArrowUpItem->setVisible(FALSE);
}
if (NULL != mArrowDownItem)
{
mArrowDownItem->setVisible(FALSE);
}
}
}
item_list_t::iterator item_iter;
for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter)
{
if ((*item_iter)->getVisible())
{
if (mScrollable)
{
if (item_iter == first_visible_item_iter)
{
offset = 0;
}
else if (item_iter == first_hidden_item_iter)
{
offset = menu_region_rect.mRight; // This moves items behind visible area
}
}
// setup item rect to hold label
LLRect rect;
if (mHorizontalLayout)
{
rect.setLeftTopAndSize( cur_width, height, (*item_iter)->getNominalWidth(), height);
cur_width += (*item_iter)->getNominalWidth();
}
else
{
rect.setLeftTopAndSize( 0 + offset, cur_height, width, (*item_iter)->getNominalHeight());
if (offset == 0)
{
cur_height -= (*item_iter)->getNominalHeight();
}
}
(*item_iter)->setRect( rect );
}
}
if (getTornOff())
{
LLTearOffMenu * torn_off_menu = dynamic_cast<LLTearOffMenu*>(getParent());
if (torn_off_menu)
{
torn_off_menu->updateSize();
}
}
}
if (mKeepFixedSize)
{
reshape(initial_rect.getWidth(), initial_rect.getHeight());
}
}
void LLMenuGL::arrangeAndClear( void )
{
if (mNeedsArrange)
{
arrange();
mNeedsArrange = FALSE;
}
}
void LLMenuGL::createSpilloverBranch()
{
if (!mSpilloverBranch)
{
// should be NULL but delete anyway
delete mSpilloverMenu;
// technically, you can't tear off spillover menus, but we're passing the handle
// along just to be safe
LLMenuGL::Params p;
std::string label = LLTrans::getString("More");
p.name("More");
p.label(label);
p.bg_color(mBackgroundColor);
p.bg_visible(true);
p.can_tear_off(false);
mSpilloverMenu = new LLMenuGL(p);
mSpilloverMenu->updateParent(LLMenuGL::sMenuContainer);
LLMenuItemBranchGL::Params branch_params;
branch_params.name = "More";
branch_params.label = label;
branch_params.branch = mSpilloverMenu;
branch_params.font.style = "italic";
branch_params.highlight_bg_color=LLUIColorTable::instance().getColor("MenuItemHighlightBgColor");
branch_params.highlight_fg_color=LLUIColorTable::instance().getColor("MenuItemHighlightFgColor");
branch_params.enabled_color=LLUIColorTable::instance().getColor("MenuItemEnabledColor");
// <FS:Ansariel> FIRE-13101: Spillover menu label and highlight using wrong colors
branch_params.disabled_color=LLUIColorTable::instance().getColor("MenuItemDisabledColor");
// </FS:Ansariel>
mSpilloverBranch = LLUICtrlFactory::create<LLMenuItemBranchGL>(branch_params);
}
}
void LLMenuGL::cleanupSpilloverBranch()
{
if (mSpilloverBranch && mSpilloverBranch->getParent() == this)
{
// head-recursion to propagate items back up to root menu
mSpilloverMenu->cleanupSpilloverBranch();
// pop off spillover items
while (mSpilloverMenu->getItemCount())
{
LLMenuItemGL* itemp = mSpilloverMenu->getItem(0);
mSpilloverMenu->removeChild(itemp);
// put them at the end of our own list
addChild(itemp);
}
// Delete the branch, and since the branch will delete the menu,
// set the menu* to null.
delete mSpilloverBranch;
mSpilloverBranch = NULL;
mSpilloverMenu = NULL;
}
}
void LLMenuGL::createJumpKeys()
{
if (!mCreateJumpKeys) return;
mCreateJumpKeys = FALSE;
mJumpKeys.clear();
std::set<std::string> unique_words;
std::set<std::string> shared_words;
item_list_t::iterator item_it;
typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
boost::char_separator<char> sep(" ");
for(item_it = mItems.begin(); item_it != mItems.end(); ++item_it)
{
std::string uppercase_label = (*item_it)->getLabel();
LLStringUtil::toUpper(uppercase_label);
tokenizer tokens(uppercase_label, sep);
tokenizer::iterator token_iter;
for( token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter)
{
if (unique_words.find(*token_iter) != unique_words.end())
{
// this word exists in more than one menu instance
shared_words.insert(*token_iter);
}
else
{
// we have a new word, keep track of it
unique_words.insert(*token_iter);
}
}
}
// pre-assign specified jump keys
for(item_it = mItems.begin(); item_it != mItems.end(); ++item_it)
{
KEY jump_key = (*item_it)->getJumpKey();
if(jump_key != KEY_NONE)
{
if (mJumpKeys.find(jump_key) == mJumpKeys.end())
{
mJumpKeys.insert(std::pair<KEY, LLMenuItemGL*>(jump_key, (*item_it)));
}
else
{
// this key is already spoken for,
// so we need to reassign it below
(*item_it)->setJumpKey(KEY_NONE);
}
}
}
for(item_it = mItems.begin(); item_it != mItems.end(); ++item_it)
{
// skip over items that already have assigned jump keys
if ((*item_it)->getJumpKey() != KEY_NONE)
{
continue;
}
std::string uppercase_label = (*item_it)->getLabel();
LLStringUtil::toUpper(uppercase_label);
tokenizer tokens(uppercase_label, sep);
tokenizer::iterator token_iter;
BOOL found_key = FALSE;
for( token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter)
{
std::string uppercase_word = *token_iter;
// this word is not shared with other menu entries...
if (shared_words.find(*token_iter) == shared_words.end())
{
S32 i;
for(i = 0; i < (S32)uppercase_word.size(); i++)
{
char jump_key = uppercase_word[i];
if (LLStringOps::isDigit(jump_key) || (LLStringOps::isUpper(jump_key) &&
mJumpKeys.find(jump_key) == mJumpKeys.end()))
{
mJumpKeys.insert(std::pair<KEY, LLMenuItemGL*>(jump_key, (*item_it)));
(*item_it)->setJumpKey(jump_key);
found_key = TRUE;
break;
}
}
}
if (found_key)
{
break;
}
}
}
}
// remove all items on the menu
void LLMenuGL::empty( void )
{
cleanupSpilloverBranch();
mItems.clear();
mFirstVisibleItem = NULL;
mArrowUpItem = NULL;
mArrowDownItem = NULL;
deleteAllChildren();
}
// erase group of items from menu
void LLMenuGL::erase( S32 begin, S32 end, bool arrange/* = true*/)
{
S32 items = mItems.size();
if ( items == 0 || begin >= end || begin < 0 || end > items )
{
return;
}
item_list_t::iterator start_position = mItems.begin();
std::advance(start_position, begin);
item_list_t::iterator end_position = mItems.begin();
std::advance(end_position, end);
for (item_list_t::iterator position_iter = start_position; position_iter != end_position; position_iter++)
{
LLUICtrl::removeChild(*position_iter);
}
mItems.erase(start_position, end_position);
if (arrange)
{
needsArrange();
}
}
// add new item at position
void LLMenuGL::insert( S32 position, LLView * ctrl, bool arrange /*= true*/ )
{
LLMenuItemGL * item = dynamic_cast<LLMenuItemGL *>(ctrl);
if (NULL == item || position < 0 || position >= mItems.size())
{
return;
}
item_list_t::iterator position_iter = mItems.begin();
std::advance(position_iter, position);
mItems.insert(position_iter, item);
LLUICtrl::addChild(item);
if (arrange)
{
needsArrange();
}
}
// Adjust rectangle of the menu
void LLMenuGL::setLeftAndBottom(S32 left, S32 bottom)
{
setRect(LLRect(left, getRect().mTop, getRect().mRight, bottom));
needsArrange();
}
BOOL LLMenuGL::handleJumpKey(KEY key)
{
// must perform case-insensitive comparison, so just switch to uppercase input key
key = toupper(key);
navigation_key_map_t::iterator found_it = mJumpKeys.find(key);
if(found_it != mJumpKeys.end() && found_it->second->getEnabled())
{
// switch to keyboard navigation mode
LLMenuGL::setKeyboardMode(TRUE);
// force highlight to close old menus and open and sub-menus
found_it->second->setHighlight(TRUE);
found_it->second->onCommit();
}
// if we are navigating the menus, we need to eat the keystroke
// so rest of UI doesn't handle it
return TRUE;
}
// Add the menu item to this menu.
BOOL LLMenuGL::append( LLMenuItemGL* item )
{
if (!item) return FALSE;
mItems.push_back( item );
LLUICtrl::addChild(item);
needsArrange();
return TRUE;
}
// add a separator to this menu
BOOL LLMenuGL::addSeparator()
{
LLMenuItemSeparatorGL::Params p;
LLMenuItemGL* separator = LLUICtrlFactory::create<LLMenuItemSeparatorGL>(p);
return addChild(separator);
}
// add a menu - this will create a cascading menu
BOOL LLMenuGL::appendMenu( LLMenuGL* menu )
{
if( menu == this )
{
LL_ERRS() << "** Attempt to attach menu to itself. This is certainly "
<< "a logic error." << LL_ENDL;
}
BOOL success = TRUE;
LLMenuItemBranchGL::Params p;
p.name = menu->getName();
p.label = menu->getLabel();
p.branch = menu;
p.jump_key = menu->getJumpKey();
p.enabled_color=LLUIColorTable::instance().getColor("MenuItemEnabledColor");
p.disabled_color=LLUIColorTable::instance().getColor("MenuItemDisabledColor");
p.highlight_bg_color=LLUIColorTable::instance().getColor("MenuItemHighlightBgColor");
p.highlight_fg_color=LLUIColorTable::instance().getColor("MenuItemHighlightFgColor");
LLMenuItemBranchGL* branch = LLUICtrlFactory::create<LLMenuItemBranchGL>(p);
success &= append( branch );
// Inherit colors
menu->setBackgroundColor( mBackgroundColor );
menu->updateParent(LLMenuGL::sMenuContainer);
return success;
}
// add a context menu branch
BOOL LLMenuGL::appendContextSubMenu(LLMenuGL *menu)
{
if (menu == this)
{
LL_ERRS() << "Can't attach a context menu to itself" << LL_ENDL;
}
LLContextMenuBranch *item;
LLContextMenuBranch::Params p;
p.name = menu->getName();
p.label = menu->getLabel();
p.branch = (LLContextMenu *)menu;
p.enabled_color=LLUIColorTable::instance().getColor("MenuItemEnabledColor");
p.disabled_color=LLUIColorTable::instance().getColor("MenuItemDisabledColor");
p.highlight_bg_color=LLUIColorTable::instance().getColor("MenuItemHighlightBgColor");
p.highlight_fg_color=LLUIColorTable::instance().getColor("MenuItemHighlightFgColor");
item = LLUICtrlFactory::create<LLContextMenuBranch>(p);
LLMenuGL::sMenuContainer->addChild(item->getBranch());
return append( item );
}
void LLMenuGL::setEnabledSubMenus(BOOL enable)
{
setEnabled(enable);
item_list_t::iterator item_iter;
for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter)
{
(*item_iter)->setEnabledSubMenus( enable );
}
}
// setItemEnabled() - pass the label and the enable flag for a menu
// item. TRUE will make sure it's enabled, FALSE will disable it.
void LLMenuGL::setItemEnabled( const std::string& name, BOOL enable )
{
item_list_t::iterator item_iter;
for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter)
{
if( (*item_iter)->getName() == name )
{
(*item_iter)->setEnabled( enable );
(*item_iter)->setEnabledSubMenus( enable );
break;
}
}
}
void LLMenuGL::setItemVisible( const std::string& name, BOOL visible )
{
item_list_t::iterator item_iter;
for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter)
{
if( (*item_iter)->getName() == name )
{
(*item_iter)->setVisible( visible );
needsArrange();
break;
}
}
}
void LLMenuGL::setItemLabel(const std::string &name, const std::string &label)
{
LLMenuItemGL *item = getItem(name);
if (item)
item->setLabel(label);
}
void LLMenuGL::setItemLastSelected(LLMenuItemGL* item)
{
if (getVisible())
{
LLMenuHolderGL::setActivatedItem(item);
}
// update enabled and checkmark status
item->buildDrawLabel();
}
// Set whether drop shadowed
void LLMenuGL::setDropShadowed( const BOOL shadowed )
{
mDropShadowed = shadowed;
}
void LLMenuGL::setTornOff(BOOL torn_off)
{
mTornOff = torn_off;
}
U32 LLMenuGL::getItemCount()
{
return mItems.size();
}
LLMenuItemGL* LLMenuGL::getItem(S32 number)
{
if (number >= 0 && number < (S32)mItems.size())
{
item_list_t::iterator item_iter;
for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter)
{
if (number == 0)
{
return (*item_iter);
}
number--;
}
}
return NULL;
}
LLMenuItemGL* LLMenuGL::getItem(std::string name)
{
item_list_t::iterator item_iter;
for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter)
{
if ((*item_iter)->getName() == name)
{
return (*item_iter);
}
}
return NULL;
}
LLMenuItemGL* LLMenuGL::getHighlightedItem()
{
item_list_t::iterator item_iter;
for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter)
{
if ((*item_iter)->getHighlight())
{
return (*item_iter);
}
}
return NULL;
}
LLMenuItemGL* LLMenuGL::highlightNextItem(LLMenuItemGL* cur_item, BOOL skip_disabled)
{
if (mItems.empty()) return NULL;
// highlighting first item on a torn off menu is the
// same as giving focus to it
if (!cur_item && getTornOff())
{
LLFloater * parent = dynamic_cast<LLFloater *>(getParent());
if (parent)
{
parent->setFocus(TRUE);
}
}
// Current item position in the items list
item_list_t::iterator cur_item_iter = std::find(mItems.begin(), mItems.end(), cur_item);
item_list_t::iterator next_item_iter;
if (cur_item_iter == mItems.end())
{
next_item_iter = mItems.begin();
}
else
{
next_item_iter = cur_item_iter;
next_item_iter++;
// First visible item position in the items list
item_list_t::iterator first_visible_item_iter = std::find(mItems.begin(), mItems.end(), mFirstVisibleItem);
if (next_item_iter == mItems.end())
{
next_item_iter = mItems.begin();
// If current item is the last in the list, the menu is scrolled to the beginning
// and the first item is highlighted.
if (mScrollable && !scrollItems(SD_BEGIN))
{
return NULL;
}
}
// If current item is the last visible, the menu is scrolled one item down
// and the next item is highlighted.
else if (mScrollable &&
(U32)std::abs(std::distance(first_visible_item_iter, next_item_iter)) >= mMaxScrollableItems)
{
// Call highlightNextItem() recursively only if the menu was successfully scrolled down.
// If scroll timer hasn't expired yet the menu won't be scrolled and calling
// highlightNextItem() will result in an endless recursion.
if (scrollItems(SD_DOWN))
{
return highlightNextItem(cur_item, skip_disabled);
}
else
{
return NULL;
}
}
}
// when first highlighting a menu, skip over tear off menu item
if (mTearOffItem && !cur_item)
{
// we know the first item is the tear off menu item
cur_item_iter = mItems.begin();
next_item_iter++;
if (next_item_iter == mItems.end())
{
next_item_iter = mItems.begin();
}
}
while(1)
{
// skip separators and disabled/invisible items
if ((*next_item_iter)->getEnabled() && (*next_item_iter)->getVisible() && !dynamic_cast<LLMenuItemSeparatorGL*>(*next_item_iter))
{
if (cur_item)
{
cur_item->setHighlight(FALSE);
}
(*next_item_iter)->setHighlight(TRUE);
return (*next_item_iter);
}
if (!skip_disabled || next_item_iter == cur_item_iter)
{
break;
}
next_item_iter++;
if (next_item_iter == mItems.end())
{
if (cur_item_iter == mItems.end())
{
break;
}
next_item_iter = mItems.begin();
}
}
return NULL;
}
LLMenuItemGL* LLMenuGL::highlightPrevItem(LLMenuItemGL* cur_item, BOOL skip_disabled)
{
if (mItems.empty()) return NULL;
// highlighting first item on a torn off menu is the
// same as giving focus to it
if (!cur_item && getTornOff())
{
LLFloater * parent = dynamic_cast<LLFloater *>(getParent());
if (parent)
{
parent->setFocus(TRUE);
}
}
// Current item reverse position from the end of the list
item_list_t::reverse_iterator cur_item_iter = std::find(mItems.rbegin(), mItems.rend(), cur_item);
item_list_t::reverse_iterator prev_item_iter;
if (cur_item_iter == mItems.rend())
{
prev_item_iter = mItems.rbegin();
}
else
{
prev_item_iter = cur_item_iter;
prev_item_iter++;
// First visible item reverse position in the items list
item_list_t::reverse_iterator first_visible_item_iter = std::find(mItems.rbegin(), mItems.rend(), mFirstVisibleItem);
if (prev_item_iter == mItems.rend())
{
prev_item_iter = mItems.rbegin();
// If current item is the first in the list, the menu is scrolled to the end
// and the last item is highlighted.
if (mScrollable && !scrollItems(SD_END))
{
return NULL;
}
}
// If current item is the first visible, the menu is scrolled one item up
// and the previous item is highlighted.
else if (mScrollable &&
std::distance(first_visible_item_iter, cur_item_iter) <= 0)
{
// Call highlightNextItem() only if the menu was successfully scrolled up.
// If scroll timer hasn't expired yet the menu won't be scrolled and calling
// highlightNextItem() will result in an endless recursion.
if (scrollItems(SD_UP))
{
return highlightPrevItem(cur_item, skip_disabled);
}
else
{
return NULL;
}
}
}
while(1)
{
// skip separators and disabled/invisible items
if ((*prev_item_iter)->getEnabled() && (*prev_item_iter)->getVisible() && (*prev_item_iter)->getName() != SEPARATOR_NAME)
{
(*prev_item_iter)->setHighlight(TRUE);
return (*prev_item_iter);
}
if (!skip_disabled || prev_item_iter == cur_item_iter)
{
break;
}
prev_item_iter++;
if (prev_item_iter == mItems.rend())
{
if (cur_item_iter == mItems.rend())
{
break;
}
prev_item_iter = mItems.rbegin();
}
}
return NULL;
}
void LLMenuGL::buildDrawLabels()
{
item_list_t::iterator item_iter;
for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter)
{
(*item_iter)->buildDrawLabel();
}
}
void LLMenuGL::updateParent(LLView* parentp)
{
if (getParent())
{
getParent()->removeChild(this);
}
if (parentp)
{
parentp->addChild(this);
}
item_list_t::iterator item_iter;
for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter)
{
(*item_iter)->updateBranchParent(parentp);
}
}
bool LLMenuGL::hasAccelerator(const KEY &key, const MASK &mask) const
{
if (key == KEY_NONE)
{
return false;
}
// Note: checking this way because mAccelerators seems to be broken
// mAccelerators probably needs to be cleaned up or fixed
// It was used for dupplicate accelerator avoidance.
item_list_t::const_iterator item_iter;
for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter)
{
LLMenuItemGL* itemp = *item_iter;
if (itemp->hasAccelerator(key, mask))
{
return true;
}
}
return false;
}
BOOL LLMenuGL::handleAcceleratorKey(KEY key, MASK mask)
{
// don't handle if not enabled
if(!getEnabled())
{
return FALSE;
}
// Pass down even if not visible
item_list_t::iterator item_iter;
for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter)
{
LLMenuItemGL* itemp = *item_iter;
if (itemp->handleAcceleratorKey(key, mask))
{
return TRUE;
}
}
return FALSE;
}
BOOL LLMenuGL::handleUnicodeCharHere( llwchar uni_char )
{
if (jumpKeysActive())
{
return handleJumpKey((KEY)uni_char);
}
return FALSE;
}
BOOL LLMenuGL::handleHover( S32 x, S32 y, MASK mask )
{
// leave submenu in place if slope of mouse < MAX_MOUSE_SLOPE_SUB_MENU
BOOL no_mouse_data = mLastMouseX == 0 && mLastMouseY == 0;
S32 mouse_delta_x = no_mouse_data ? 0 : x - mLastMouseX;
S32 mouse_delta_y = no_mouse_data ? 0 : y - mLastMouseY;
LLVector2 mouse_dir((F32)mouse_delta_x, (F32)mouse_delta_y);
mouse_dir.normVec();
LLVector2 mouse_avg_dir((F32)mMouseVelX, (F32)mMouseVelY);
mouse_avg_dir.normVec();
F32 interp = 0.5f * (llclamp(mouse_dir * mouse_avg_dir, 0.f, 1.f));
mMouseVelX = ll_round(lerp((F32)mouse_delta_x, (F32)mMouseVelX, interp));
mMouseVelY = ll_round(lerp((F32)mouse_delta_y, (F32)mMouseVelY, interp));
mLastMouseX = x;
mLastMouseY = y;
// don't change menu focus unless mouse is moving or alt key is not held down
if ((llabs(mMouseVelX) > 0 ||
llabs(mMouseVelY) > 0) &&
(!mHasSelection ||
//(mouse_delta_x == 0 && mouse_delta_y == 0) ||
(mMouseVelX < 0) ||
llabs((F32)mMouseVelY) / llabs((F32)mMouseVelX) > MAX_MOUSE_SLOPE_SUB_MENU))
{
for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it)
{
LLView* viewp = *child_it;
S32 local_x = x - viewp->getRect().mLeft;
S32 local_y = y - viewp->getRect().mBottom;
if (!viewp->pointInView(local_x, local_y) && ((LLMenuItemGL*)viewp)->getHighlight())
{
// moving mouse always highlights new item
if (mouse_delta_x != 0 || mouse_delta_y != 0)
{
((LLMenuItemGL*)viewp)->setHighlight(FALSE);
}
}
}
for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it)
{
LLView* viewp = *child_it;
S32 local_x = x - viewp->getRect().mLeft;
S32 local_y = y - viewp->getRect().mBottom;
//RN: always call handleHover to track mGotHover status
// but only set highlight when mouse is moving
if( viewp->getVisible() &&
//RN: allow disabled items to be highlighted to preserve "active" menus when
// moving mouse through them
//viewp->getEnabled() &&
viewp->pointInView(local_x, local_y) &&
viewp->handleHover(local_x, local_y, mask))
{
// moving mouse always highlights new item
if (mouse_delta_x != 0 || mouse_delta_y != 0)
{
((LLMenuItemGL*)viewp)->setHighlight(TRUE);
LLMenuGL::setKeyboardMode(FALSE);
}
mHasSelection = true;
}
}
}
getWindow()->setCursor(UI_CURSOR_ARROW);
// *HACK Release the mouse capture
// This is done to release the mouse after the Navigation Bar "Back" or "Forward" button
// drop-down menu is shown. Otherwise any other view won't be able to handle mouse events
// until the user chooses one of the drop-down menu items.
return TRUE;
}
BOOL LLMenuGL::handleScrollWheel( S32 x, S32 y, S32 clicks )
{
if (!mScrollable)
return blockMouseEvent(x, y);
if( clicks > 0 )
{
while( clicks-- )
scrollItems(SD_DOWN);
}
else
{
while( clicks++ )
scrollItems(SD_UP);
}
return TRUE;
}
void LLMenuGL::draw( void )
{
if (mNeedsArrange)
{
arrange();
mNeedsArrange = FALSE;
}
if (mDropShadowed && !mTornOff)
{
static LLUICachedControl<S32> drop_shadow_floater ("DropShadowFloater", 0);
static LLUIColor color_drop_shadow = LLUIColorTable::instance().getColor("ColorDropShadow");
gl_drop_shadow(0, getRect().getHeight(), getRect().getWidth(), 0,
color_drop_shadow, drop_shadow_floater );
}
if( mBgVisible )
{
gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth(), 0, mBackgroundColor.get() );
}
LLView::draw();
}
void LLMenuGL::drawBackground(LLMenuItemGL* itemp, F32 alpha)
{
LLColor4 color = itemp->getHighlightBgColor() % alpha;
gGL.color4fv( color.mV );
LLRect item_rect = itemp->getRect();
gl_rect_2d( 0, item_rect.getHeight(), item_rect.getWidth(), 0);
}
void LLMenuGL::setVisible(BOOL visible)
{
if (visible != getVisible())
{
if (!visible)
{
mFadeTimer.start();
clearHoverItem();
// reset last known mouse coordinates so
// we don't spoof a mouse move next time we're opened
mLastMouseX = 0;
mLastMouseY = 0;
}
else
{
mHasSelection = true;
mFadeTimer.stop();
}
LLView::setVisible(visible);
}
}
LLMenuGL* LLMenuGL::findChildMenuByName(const std::string& name, BOOL recurse) const
{
LLView* view = findChildView(name, recurse);
if (view)
{
LLMenuItemBranchGL* branch = dynamic_cast<LLMenuItemBranchGL*>(view);
if (branch)
{
return branch->getBranch();
}
LLMenuGL* menup = dynamic_cast<LLMenuGL*>(view);
if (menup)
{
return menup;
}
}
LL_WARNS() << "Child Menu " << name << " not found in menu " << getName() << LL_ENDL;
return NULL;
}
BOOL LLMenuGL::clearHoverItem()
{
for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it)
{
LLMenuItemGL* itemp = (LLMenuItemGL*)*child_it;
if (itemp->getHighlight())
{
itemp->setHighlight(FALSE);
return TRUE;
}
}
return FALSE;
}
void hide_top_view( LLView* view )
{
if( view ) view->setVisible( FALSE );
}
// x and y are the desired location for the popup, in the spawning_view's
// coordinate frame, NOT necessarily the mouse location
// static
void LLMenuGL::showPopup(LLView* spawning_view, LLMenuGL* menu, S32 x, S32 y, S32 mouse_x, S32 mouse_y)
{
const S32 CURSOR_HEIGHT = 22; // Approximate "normal" cursor size
const S32 CURSOR_WIDTH = 12;
if (menu->getChildList()->empty())
{
return;
}
menu->setVisible( TRUE );
if(!menu->getAlwaysShowMenu())
{
//Do not show menu if all menu items are disabled
BOOL item_enabled = false;
for (LLView::child_list_t::const_iterator itor = menu->getChildList()->begin();
itor != menu->getChildList()->end();
++itor)
{
LLView *menu_item = (*itor);
item_enabled = item_enabled || menu_item->getEnabled();
}
if(!item_enabled)
{
menu->setVisible( FALSE );
return;
}
}
// Resetting scrolling position
if (menu->isScrollable() && menu->isScrollPositionOnShowReset())
{
menu->mFirstVisibleItem = NULL;
}
// Fix menu rect if needed.
menu->needsArrange();
menu->arrangeAndClear();
if ((mouse_x == 0) || (mouse_y == 0))
{
// Save click point for detecting cursor moves before mouse-up.
// Must be in local coords to compare with mouseUp events.
// If the mouse doesn't move, the menu will stay open ala the Mac.
// See also LLContextMenu::show()
LLUI::getInstance()->getMousePositionLocal(menu->getParent(), &mouse_x, &mouse_y);
}
LLMenuHolderGL::sContextMenuSpawnPos.set(mouse_x,mouse_y);
const LLRect menu_region_rect = LLMenuGL::sMenuContainer->getRect();
const S32 HPAD = 2;
LLRect rect = menu->getRect();
S32 left = x + HPAD;
S32 top = y;
spawning_view->localPointToOtherView(left, top, &left, &top, menu->getParent());
rect.setLeftTopAndSize( left, top,
rect.getWidth(), rect.getHeight() );
menu->setRect( rect );
// Adjust context menu to fit onscreen
LLRect mouse_rect;
const S32 MOUSE_CURSOR_PADDING = 5;
mouse_rect.setLeftTopAndSize(mouse_x - MOUSE_CURSOR_PADDING,
mouse_y + MOUSE_CURSOR_PADDING,
CURSOR_WIDTH + MOUSE_CURSOR_PADDING * 2,
CURSOR_HEIGHT + MOUSE_CURSOR_PADDING * 2);
menu->translateIntoRectWithExclusion( menu_region_rect, mouse_rect );
// <FS:Ansariel> FIRE-31065: Make sure menu is shown on the screen properly
//if (menu->getRect().mTop > menu_region_rect.mTop)
if (menu->getRect().mTop > menu_region_rect.mTop ||
menu->getRect().mBottom < menu_region_rect.mBottom)
// </FS:Ansariel>
{
// not enough space: align with top, ignore exclusion
menu->translateIntoRect( menu_region_rect );
}
menu->getParent()->sendChildToFront(menu);
}
///============================================================================
/// Class LLMenuBarGL
///============================================================================
static LLDefaultChildRegistry::Register<LLMenuBarGL> r2("menu_bar");
LLMenuBarGL::LLMenuBarGL( const Params& p )
: LLMenuGL(p),
mAltKeyTrigger(FALSE)
{}
// Default destructor
LLMenuBarGL::~LLMenuBarGL()
{
std::for_each(mAccelerators.begin(), mAccelerators.end(), DeletePointer());
mAccelerators.clear();
}
BOOL LLMenuBarGL::handleAcceleratorKey(KEY key, MASK mask)
{
if (getHighlightedItem() && mask == MASK_NONE)
{
// unmodified key accelerators are ignored when navigating menu
// (but are used as jump keys so will still work when appropriate menu is up)
return FALSE;
}
BOOL result = LLMenuGL::handleAcceleratorKey(key, mask);
if (result && mask & MASK_ALT)
{
// ALT key used to trigger hotkey, don't use as shortcut to open menu
mAltKeyTrigger = FALSE;
}
// <FS:Ansariel> FIRE-30003: (CTRL-)F10 can't be used as gesture trigger
//if(!result
// && (key == KEY_F10 && mask == MASK_CONTROL)
// && !gKeyboard->getKeyRepeated(key)
// && isInVisibleChain())
//{
// if (getHighlightedItem())
// {
// clearHoverItem();
// LLMenuGL::setKeyboardMode(FALSE);
// }
// else
// {
// // close menus originating from other menu bars when first opening menu via keyboard
// LLMenuGL::sMenuContainer->hideMenus();
// highlightNextItem(NULL);
// LLMenuGL::setKeyboardMode(TRUE);
// }
// return TRUE;
//}
// </FS:Ansariel>
if (result && !getHighlightedItem() && LLMenuGL::sMenuContainer->hasVisibleMenu())
{
// close menus originating from other menu bars
LLMenuGL::sMenuContainer->hideMenus();
}
return result;
}
BOOL LLMenuBarGL::handleKeyHere(KEY key, MASK mask)
{
static LLUICachedControl<bool> use_altkey_for_menus ("UseAltKeyForMenus", 0);
if(key == KEY_ALT && !gKeyboard->getKeyRepeated(key) && use_altkey_for_menus)
{
mAltKeyTrigger = TRUE;
}
else // if any key other than ALT hit, clear out waiting for Alt key mode
{
mAltKeyTrigger = FALSE;
}
if (key == KEY_ESCAPE && mask == MASK_NONE)
{
LLMenuGL::setKeyboardMode(FALSE);
// if any menus are visible, this will return TRUE, stopping further processing of ESCAPE key
return LLMenuGL::sMenuContainer->hideMenus();
}
// before processing any other key, check to see if ALT key has triggered menu access
checkMenuTrigger();
return LLMenuGL::handleKeyHere(key, mask);
}
BOOL LLMenuBarGL::handleJumpKey(KEY key)
{
// perform case-insensitive comparison
key = toupper(key);
navigation_key_map_t::iterator found_it = mJumpKeys.find(key);
if(found_it != mJumpKeys.end() && found_it->second->getEnabled())
{
// switch to keyboard navigation mode
LLMenuGL::setKeyboardMode(TRUE);
found_it->second->setHighlight(TRUE);
found_it->second->onCommit();
}
return TRUE;
}
BOOL LLMenuBarGL::handleMouseDown(S32 x, S32 y, MASK mask)
{
// clicks on menu bar closes existing menus from other contexts but leave
// own menu open so that we get toggle behavior
if (!getHighlightedItem() || !getHighlightedItem()->isActive())
{
LLMenuGL::sMenuContainer->hideMenus();
}
return LLMenuGL::handleMouseDown(x, y, mask);
}
BOOL LLMenuBarGL::handleDoubleClick(S32 x, S32 y, MASK mask)
{
return LLMenuGL::handleMouseDown(x, y, mask);
}
void LLMenuBarGL::draw()
{
LLMenuItemGL* itemp = getHighlightedItem();
// If we are in mouse-control mode and the mouse cursor is not hovering over
// the current highlighted menu item and it isn't open, then remove the
// highlight. This is done via a polling mechanism here, as we don't receive
// notifications when the mouse cursor moves off of us
if (itemp && !itemp->isOpen() && !itemp->getHover() && !LLMenuGL::getKeyboardMode())
{
clearHoverItem();
}
checkMenuTrigger();
LLMenuGL::draw();
}
void LLMenuBarGL::checkMenuTrigger()
{
// has the ALT key been pressed and subsequently released?
if (mAltKeyTrigger && !gKeyboard->getKeyDown(KEY_ALT))
{
// if alt key was released quickly, treat it as a menu access key
// otherwise it was probably an Alt-zoom or similar action
static LLUICachedControl<F32> menu_access_key_time ("MenuAccessKeyTime", 0);
if (gKeyboard->getKeyElapsedTime(KEY_ALT) <= menu_access_key_time ||
gKeyboard->getKeyElapsedFrameCount(KEY_ALT) < 2)
{
if (getHighlightedItem())
{
clearHoverItem();
}
else
{
// close menus originating from other menu bars
LLMenuGL::sMenuContainer->hideMenus();
highlightNextItem(NULL);
LLMenuGL::setKeyboardMode(TRUE);
}
}
mAltKeyTrigger = FALSE;
}
}
BOOL LLMenuBarGL::jumpKeysActive()
{
// require user to be in keyboard navigation mode to activate key triggers
// as menu bars are always visible and it is easy to leave the mouse cursor over them
return LLMenuGL::getKeyboardMode() && getHighlightedItem() && LLMenuGL::jumpKeysActive();
}
// rearrange the child rects so they fit the shape of the menu bar.
void LLMenuBarGL::arrange( void )
{
U32 pos = 0;
LLRect rect( 0, getRect().getHeight(), 0, 0 );
item_list_t::const_iterator item_iter;
for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter)
{
LLMenuItemGL* item = *item_iter;
if (item->getVisible())
{
rect.mLeft = pos;
pos += item->getNominalWidth();
rect.mRight = pos;
item->setRect( rect );
item->buildDrawLabel();
}
}
reshape(rect.mRight, rect.getHeight());
}
S32 LLMenuBarGL::getRightmostMenuEdge()
{
// Find the last visible menu
item_list_t::reverse_iterator item_iter;
for (item_iter = mItems.rbegin(); item_iter != mItems.rend(); ++item_iter)
{
if ((*item_iter)->getVisible())
{
break;
}
}
if (item_iter == mItems.rend())
{
return 0;
}
return (*item_iter)->getRect().mRight;
}
// add a vertical separator to this menu
BOOL LLMenuBarGL::addSeparator()
{
LLMenuItemGL* separator = new LLMenuItemVerticalSeparatorGL();
return append( separator );
}
// add a menu - this will create a drop down menu.
BOOL LLMenuBarGL::appendMenu( LLMenuGL* menu )
{
if( menu == this )
{
LL_ERRS() << "** Attempt to attach menu to itself. This is certainly "
<< "a logic error." << LL_ENDL;
}
BOOL success = TRUE;
// *TODO: Hack! Fix this
LLMenuItemBranchDownGL::Params p;
p.name = menu->getName();
p.label = menu->getLabel();
p.visible = menu->getVisible();
p.branch = menu;
p.enabled_color=LLUIColorTable::instance().getColor("MenuItemEnabledColor");
p.disabled_color=LLUIColorTable::instance().getColor("MenuItemDisabledColor");
p.highlight_bg_color=LLUIColorTable::instance().getColor("MenuItemHighlightBgColor");
p.highlight_fg_color=LLUIColorTable::instance().getColor("MenuItemHighlightFgColor");
LLMenuItemBranchDownGL* branch = LLUICtrlFactory::create<LLMenuItemBranchDownGL>(p);
success &= branch->addToAcceleratorList(&mAccelerators);
success &= append( branch );
branch->setJumpKey(branch->getJumpKey());
menu->updateParent(LLMenuGL::sMenuContainer);
return success;
}
BOOL LLMenuBarGL::handleHover( S32 x, S32 y, MASK mask )
{
BOOL handled = FALSE;
LLView* active_menu = NULL;
BOOL no_mouse_data = mLastMouseX == 0 && mLastMouseY == 0;
S32 mouse_delta_x = no_mouse_data ? 0 : x - mLastMouseX;
S32 mouse_delta_y = no_mouse_data ? 0 : y - mLastMouseY;
mMouseVelX = (mMouseVelX / 2) + (mouse_delta_x / 2);
mMouseVelY = (mMouseVelY / 2) + (mouse_delta_y / 2);
mLastMouseX = x;
mLastMouseY = y;
// if nothing currently selected or mouse has moved since last call, pick menu item via mouse
// otherwise let keyboard control it
if (!getHighlightedItem() || !LLMenuGL::getKeyboardMode() || llabs(mMouseVelX) > 0 || llabs(mMouseVelY) > 0)
{
// find current active menu
for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it)
{
LLView* viewp = *child_it;
if (((LLMenuItemGL*)viewp)->isOpen())
{
active_menu = viewp;
}
}
// check for new active menu
for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it)
{
LLView* viewp = *child_it;
S32 local_x = x - viewp->getRect().mLeft;
S32 local_y = y - viewp->getRect().mBottom;
if( viewp->getVisible() &&
viewp->getEnabled() &&
viewp->pointInView(local_x, local_y) &&
viewp->handleHover(local_x, local_y, mask))
{
((LLMenuItemGL*)viewp)->setHighlight(TRUE);
handled = TRUE;
if (active_menu && active_menu != viewp)
{
((LLMenuItemGL*)viewp)->onCommit();
LLMenuGL::setKeyboardMode(FALSE);
}
LLMenuGL::setKeyboardMode(FALSE);
}
}
if (handled)
{
// set hover false on inactive menus
for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it)
{
LLView* viewp = *child_it;
S32 local_x = x - viewp->getRect().mLeft;
S32 local_y = y - viewp->getRect().mBottom;
if (!viewp->pointInView(local_x, local_y) && ((LLMenuItemGL*)viewp)->getHighlight())
{
((LLMenuItemGL*)viewp)->setHighlight(FALSE);
}
}
}
}
getWindow()->setCursor(UI_CURSOR_ARROW);
return TRUE;
}
///============================================================================
/// Class LLMenuHolderGL
///============================================================================
LLCoordGL LLMenuHolderGL::sContextMenuSpawnPos(S32_MAX, S32_MAX);
LLMenuHolderGL::LLMenuHolderGL(const LLMenuHolderGL::Params& p)
: LLPanel(p)
{
sItemActivationTimer.stop();
mCanHide = TRUE;
}
void LLMenuHolderGL::draw()
{
LLView::draw();
// now draw last selected item as overlay
LLMenuItemGL* selecteditem = (LLMenuItemGL*)sItemLastSelectedHandle.get();
if (selecteditem && selecteditem->getVisible() && sItemActivationTimer.getStarted() && sItemActivationTimer.getElapsedTimeF32() < ACTIVATE_HIGHLIGHT_TIME)
{
// make sure toggle items, for example, show the proper state when fading out
selecteditem->buildDrawLabel();
LLRect item_rect;
selecteditem->localRectToOtherView(selecteditem->getLocalRect(), &item_rect, this);
F32 interpolant = sItemActivationTimer.getElapsedTimeF32() / ACTIVATE_HIGHLIGHT_TIME;
LLUI::pushMatrix();
{
LLUI::translate((F32)item_rect.mLeft, (F32)item_rect.mBottom);
selecteditem->getMenu()->drawBackground(selecteditem, interpolant);
selecteditem->draw();
}
LLUI::popMatrix();
}
}
BOOL LLMenuHolderGL::handleMouseDown( S32 x, S32 y, MASK mask )
{
BOOL handled = LLView::childrenHandleMouseDown(x, y, mask) != NULL;
if (!handled)
{
LLMenuGL* visible_menu = (LLMenuGL*)getVisibleMenu();
LLMenuItemGL* parent_menu = visible_menu ? visible_menu->getParentMenuItem() : NULL;
if (parent_menu && parent_menu->getVisible())
{
// don't hide menu if parent was hit
LLRect parent_rect;
parent_menu->localRectToOtherView(parent_menu->getLocalRect(), &parent_rect, this);
if (!parent_rect.pointInRect(x, y))
{
// clicked off of menu and parent, hide them all
hideMenus();
}
}
else
{
// no visible parent, clicked off of menu, hide them all
hideMenus();
}
}
return handled;
}
BOOL LLMenuHolderGL::handleRightMouseDown( S32 x, S32 y, MASK mask )
{
BOOL handled = LLView::childrenHandleRightMouseDown(x, y, mask) != NULL;
if (!handled)
{
// clicked off of menu, hide them all
hideMenus();
}
return handled;
}
// This occurs when you mouse-down to spawn a context menu, hold the button
// down, move off the menu, then mouse-up. We want this to close the menu.
BOOL LLMenuHolderGL::handleRightMouseUp( S32 x, S32 y, MASK mask )
{
const S32 SLOP = 2;
S32 spawn_dx = (x - sContextMenuSpawnPos.mX);
S32 spawn_dy = (y - sContextMenuSpawnPos.mY);
if (-SLOP <= spawn_dx && spawn_dx <= SLOP
&& -SLOP <= spawn_dy && spawn_dy <= SLOP)
{
// we're still inside the slop region from spawning this menu
// so interpret the mouse-up as a single-click to show and leave on
// screen
sContextMenuSpawnPos.set(S32_MAX, S32_MAX);
return TRUE;
}
BOOL handled = LLView::childrenHandleRightMouseUp(x, y, mask) != NULL;
if (!handled)
{
// clicked off of menu, hide them all
hideMenus();
}
return handled;
}
BOOL LLMenuHolderGL::handleKey(KEY key, MASK mask, BOOL called_from_parent)
{
BOOL handled = false;
LLMenuGL* const pMenu = dynamic_cast<LLMenuGL*>(getVisibleMenu());
if (pMenu)
{
//eat TAB key - EXT-7000
if (key == KEY_TAB && mask == MASK_NONE)
{
return TRUE;
}
//handle ESCAPE and RETURN key
handled = LLPanel::handleKey(key, mask, called_from_parent);
if (!handled)
{
if (pMenu->getHighlightedItem())
{
handled = pMenu->handleKey(key, mask, TRUE);
}
else if (mask == MASK_NONE || (key >= KEY_LEFT && key <= KEY_DOWN))
{
//highlight first enabled one
if(pMenu->highlightNextItem(NULL))
{
handled = true;
}
}
}
}
return handled;
}
void LLMenuHolderGL::reshape(S32 width, S32 height, BOOL called_from_parent)
{
if (width != getRect().getWidth() || height != getRect().getHeight())
{
hideMenus();
}
LLView::reshape(width, height, called_from_parent);
}
LLView* const LLMenuHolderGL::getVisibleMenu() const
{
for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it)
{
LLView* viewp = *child_it;
if (viewp->getVisible() && dynamic_cast<LLMenuGL*>(viewp) != NULL)
{
return viewp;
}
}
return NULL;
}
BOOL LLMenuHolderGL::hideMenus()
{
if (!mCanHide)
{
return FALSE;
}
LLMenuGL::setKeyboardMode(FALSE);
BOOL menu_visible = hasVisibleMenu();
if (menu_visible)
{
// clicked off of menu, hide them all
for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it)
{
LLView* viewp = *child_it;
if (dynamic_cast<LLMenuGL*>(viewp) != NULL && viewp->getVisible())
{
viewp->setVisible(FALSE);
}
}
}
//if (gFocusMgr.childHasKeyboardFocus(this))
//{
// gFocusMgr.setKeyboardFocus(NULL);
//}
return menu_visible;
}
void LLMenuHolderGL::setActivatedItem(LLMenuItemGL* item)
{
sItemLastSelectedHandle = item->getHandle();
sItemActivationTimer.start();
}
///============================================================================
/// Class LLTearOffMenu
///============================================================================
LLTearOffMenu::LLTearOffMenu(LLMenuGL* menup) :
LLFloater(LLSD()),
mQuitRequested(false)
{
S32 floater_header_size = getHeaderHeight();
setName(menup->getName());
setTitle(menup->getLabel());
setCanMinimize(FALSE);
// flag menu as being torn off
menup->setTornOff(TRUE);
// update menu layout as torn off menu (no spillover menus)
menup->needsArrange();
LLRect rect;
menup->localRectToOtherView(LLRect(-1, menup->getRect().getHeight(), menup->getRect().getWidth() + 3, 0), &rect, gFloaterView);
// make sure this floater is big enough for menu
mTargetHeight = rect.getHeight() + floater_header_size;
reshape(rect.getWidth(), rect.getHeight());
setRect(rect);
// attach menu to floater
menup->setFollows( FOLLOWS_LEFT | FOLLOWS_BOTTOM );
mOldParent = menup->getParent();
addChild(menup);
menup->setVisible(TRUE);
LLRect menu_rect = menup->getRect();
menu_rect.setOriginAndSize( 1, 1,
menu_rect.getWidth(), menu_rect.getHeight());
menup->setRect(menu_rect);
menup->setDropShadowed(FALSE);
mMenu = menup;
// highlight first item (tear off item will be disabled)
mMenu->highlightNextItem(NULL);
// Can't do this in postBuild() because that is only called for floaters
// constructed from XML.
mCloseSignal.connect(boost::bind(&LLTearOffMenu::closeTearOff, this));
}
LLTearOffMenu::~LLTearOffMenu()
{
}
void LLTearOffMenu::draw()
{
mMenu->setBackgroundVisible(isBackgroundOpaque());
// <FS:Ansariel> FIRE-31823: Do it before reshaping - needsArrange can change visbility status of items!
mMenu->needsArrange();
if (getRect().getHeight() != mTargetHeight)
{
// animate towards target height
reshape(getRect().getWidth(), llceil(lerp((F32)getRect().getHeight(), (F32)mTargetHeight, LLSmoothInterpolation::getInterpolant(0.05f))));
}
//mMenu->needsArrange(); // <FS:Ansariel> FIRE-31823: Do it before reshaping - needsArrange can change visbility status of items!
LLFloater::draw();
}
void LLTearOffMenu::onFocusReceived()
{
if (mQuitRequested)
{
return;
}
// if nothing is highlighted, just highlight first item
if (!mMenu->getHighlightedItem())
{
mMenu->highlightNextItem(NULL);
}
// parent menu items get highlights so navigation logic keeps working
LLMenuItemGL* parent_menu_item = mMenu->getParentMenuItem();
while(parent_menu_item)
{
if (parent_menu_item->getMenu()->getVisible())
{
parent_menu_item->setHighlight(TRUE);
parent_menu_item = parent_menu_item->getMenu()->getParentMenuItem();
}
else
{
break;
}
}
LLFloater::onFocusReceived();
}
void LLTearOffMenu::onFocusLost()
{
// remove highlight from parent item and our own menu
mMenu->clearHoverItem();
LLFloater::onFocusLost();
}
BOOL LLTearOffMenu::handleUnicodeChar(llwchar uni_char, BOOL called_from_parent)
{
// pass keystrokes down to menu
return mMenu->handleUnicodeChar(uni_char, TRUE);
}
BOOL LLTearOffMenu::handleKeyHere(KEY key, MASK mask)
{
if (!mMenu->getHighlightedItem())
{
if (key == KEY_UP)
{
mMenu->highlightPrevItem(NULL);
return TRUE;
}
else if (key == KEY_DOWN)
{
mMenu->highlightNextItem(NULL);
return TRUE;
}
}
// pass keystrokes down to menu
return mMenu->handleKey(key, mask, TRUE);
}
void LLTearOffMenu::translate(S32 x, S32 y)
{
if (x != 0 && y != 0)
{
// hide open sub-menus by clearing current hover item
mMenu->clearHoverItem();
}
LLFloater::translate(x, y);
}
//static
LLTearOffMenu* LLTearOffMenu::create(LLMenuGL* menup)
{
LLTearOffMenu* tearoffp = new LLTearOffMenu(menup);
// keep onscreen
gFloaterView->adjustToFitScreen(tearoffp, FALSE);
tearoffp->openFloater(LLSD());
return tearoffp;
}
void LLTearOffMenu::updateSize()
{
if (mMenu)
{
S32 floater_header_size = getHeaderHeight();
const LLRect &floater_rect = getRect();
LLRect new_rect;
mMenu->localRectToOtherView(LLRect(-1, mMenu->getRect().getHeight() + floater_header_size, mMenu->getRect().getWidth() + 3, 0), &new_rect, gFloaterView);
if (floater_rect.getWidth() != new_rect.getWidth()
|| mTargetHeight != new_rect.getHeight())
{
// make sure this floater is big enough for menu
mTargetHeight = new_rect.getHeight();
reshape(new_rect.getWidth(), mTargetHeight);
// Restore menu position
LLRect menu_rect = mMenu->getRect();
menu_rect.setOriginAndSize(1, 1,
menu_rect.getWidth(), menu_rect.getHeight());
mMenu->setRect(menu_rect);
}
}
}
void LLTearOffMenu::closeTearOff()
{
removeChild(mMenu);
mOldParent->addChild(mMenu);
mMenu->clearHoverItem();
mMenu->setFollowsNone();
mMenu->setBackgroundVisible(TRUE);
mMenu->setVisible(FALSE);
mMenu->setTornOff(FALSE);
mMenu->setDropShadowed(TRUE);
mQuitRequested = true;
}
LLContextMenuBranch::LLContextMenuBranch(const LLContextMenuBranch::Params& p)
: LLMenuItemGL(p)
{
LLContextMenu* branch = static_cast<LLContextMenu*>(p.branch);
if (branch)
{
mBranch = branch->getHandle();
branch->hide();
branch->setParentMenuItem(this);
}
}
LLContextMenuBranch::~LLContextMenuBranch()
{
if (mBranch.get())
{
mBranch.get()->die();
}
}
// called to rebuild the draw label
void LLContextMenuBranch::buildDrawLabel( void )
{
auto menu = getBranch();
if (menu)
{
// default enablement is this -- if any of the subitems are
// enabled, this item is enabled. JC
U32 sub_count = menu->getItemCount();
U32 i;
BOOL any_enabled = FALSE;
for (i = 0; i < sub_count; i++)
{
LLMenuItemGL* item = menu->getItem(i);
item->buildDrawLabel();
if (item->getEnabled() && !item->getDrawTextDisabled() )
{
any_enabled = TRUE;
break;
}
}
setDrawTextDisabled(!any_enabled);
setEnabled(TRUE);
}
mDrawAccelLabel.clear();
std::string st = mDrawAccelLabel;
appendAcceleratorString( st );
mDrawAccelLabel = st;
mDrawBranchLabel = LLMenuGL::BRANCH_SUFFIX;
}
void LLContextMenuBranch::showSubMenu()
{
auto menu = getBranch();
if(menu)
{
LLMenuItemGL* menu_item = menu->getParentMenuItem();
if (menu_item != NULL && menu_item->getVisible())
{
S32 center_x;
S32 center_y;
localPointToScreen(getRect().getWidth(), getRect().getHeight(), &center_x, &center_y);
menu->show(center_x, center_y);
}
}
}
// onCommit() - do the primary funcationality of the menu item.
void LLContextMenuBranch::onCommit( void )
{
showSubMenu();
}
void LLContextMenuBranch::setHighlight( BOOL highlight )
{
if (highlight == getHighlight()) return;
LLMenuItemGL::setHighlight(highlight);
auto menu = getBranch();
if (menu)
{
if (highlight)
{
showSubMenu();
}
else
{
menu->hide();
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
// class LLContextMenu
// A context menu
//-----------------------------------------------------------------------------
static LLDefaultChildRegistry::Register<LLContextMenu> context_menu_register("context_menu");
static MenuRegistry::Register<LLContextMenu> context_menu_register2("context_menu");
LLContextMenu::LLContextMenu(const Params& p)
: LLMenuGL(p),
mHoveredAnyItem(FALSE),
mHoverItem(NULL)
{
//setBackgroundVisible(TRUE);
}
void LLContextMenu::setVisible(BOOL visible)
{
if (!visible)
hide();
}
// Takes cursor position in screen space?
void LLContextMenu::show(S32 x, S32 y, LLView* spawning_view)
{
if (getChildList()->empty())
{
// nothing to show, so abort
return;
}
// Save click point for detecting cursor moves before mouse-up.
// Must be in local coords to compare with mouseUp events.
// If the mouse doesn't move, the menu will stay open ala the Mac.
// See also LLMenuGL::showPopup()
LLMenuHolderGL::sContextMenuSpawnPos.set(x,y);
arrangeAndClear();
S32 width = getRect().getWidth();
S32 height = getRect().getHeight();
const LLRect menu_region_rect = LLMenuGL::sMenuContainer->getMenuRect();
LLView* parent_view = getParent();
// Open upwards if menu extends past bottom
if (y - height < menu_region_rect.mBottom)
{
if (getParentMenuItem()) // Adjust if this is a submenu
{
y += height - getParentMenuItem()->getNominalHeight();
}
else
{
y += height;
}
// <FS:Ansariel> FIRE-10883: Prevent context menu from going off screen
y = llmin(y, menu_region_rect.mTop);
}
// Open out to the left if menu extends past right edge
if (x + width > menu_region_rect.mRight)
{
if (getParentMenuItem())
{
x -= getParentMenuItem()->getRect().getWidth() + width;
}
else
{
x -= width;
}
// <FS:Ansariel> FIRE-10883: Prevent context menu from going off screen
x = llmax(x, menu_region_rect.mLeft);
}
S32 local_x, local_y;
parent_view->screenPointToLocal(x, y, &local_x, &local_y);
LLRect rect;
rect.setLeftTopAndSize(local_x, local_y, width, height);
setRect(rect);
arrange();
if (spawning_view)
{
mSpawningViewHandle = spawning_view->getHandle();
}
else
{
mSpawningViewHandle.markDead();
}
LLView::setVisible(TRUE);
}
void LLContextMenu::hide()
{
if (!getVisible()) return;
LLView::setVisible(FALSE);
if (mHoverItem)
{
// <FS:ND>FIRE-9257; Check if mHoverItem really is still valid before touching it
// mHoverItem->setHighlight( FALSE );
if( !mHoverItemHandle.isDead() )
mHoverItem->setHighlight( FALSE );
else
LL_WARNS() << "Hoveritem is already dead" << LL_ENDL;
// </FS:ND>
}
mHoverItem = NULL;
// <FS:ND>FIRE-9257; Item is invalid, reset handle too
mHoverItemHandle = LLHandle< LLView >();
// </FS:ND>
}
BOOL LLContextMenu::handleHover( S32 x, S32 y, MASK mask )
{
LLMenuGL::handleHover(x,y,mask);
BOOL handled = FALSE;
LLMenuItemGL *item = getHighlightedItem();
if (item && item->getEnabled())
{
getWindow()->setCursor(UI_CURSOR_ARROW);
handled = TRUE;
if (item != mHoverItem)
{
if (mHoverItem)
{
// <FS:ND>FIRE-9257; Check if mHoverItem really is still valid before touching it
// mHoverItem->setHighlight( FALSE );
if( !mHoverItemHandle.isDead() )
mHoverItem->setHighlight( FALSE );
else
LL_WARNS() << "Hoveritem is already dead" << LL_ENDL;
// </FS:ND>
}
mHoverItem = item;
// <FS:ND> FIRE-9257; get the handle to our new item
mHoverItemHandle = item->getHandle();
// </FS:ND>
mHoverItem->setHighlight( TRUE );
}
mHoveredAnyItem = TRUE;
}
else
{
// clear out our selection
if (mHoverItem)
{
// <FS:ND>FIRE-9257; Check if mHoverItem really is still valid before touching it
// mHoverItem->setHighlight(FALSE);
if( !mHoverItemHandle.isDead() )
mHoverItem->setHighlight(FALSE);
else
LL_WARNS() << "Hoveritem is already dead" << LL_ENDL;
mHoverItemHandle = LLHandle< LLView >();
// </FS:ND>
mHoverItem = NULL;
}
}
if( !handled && pointInView( x, y ) )
{
getWindow()->setCursor(UI_CURSOR_ARROW);
handled = TRUE;
}
return handled;
}
// handleMouseDown and handleMouseUp are handled by LLMenuGL
BOOL LLContextMenu::handleRightMouseDown(S32 x, S32 y, MASK mask)
{
BOOL handled = FALSE;
// The click was somewhere within our rectangle
LLMenuItemGL *item = getHighlightedItem();
S32 local_x = x - getRect().mLeft;
S32 local_y = y - getRect().mBottom;
BOOL clicked_in_menu = pointInView(local_x, local_y) ;
// grab mouse if right clicking anywhere within pie (even deadzone in middle), to detect drag outside of pie
if (clicked_in_menu)
{
// capture mouse cursor as if on initial menu show
handled = TRUE;
}
if (item)
{
// lie to the item about where the click happened
// to make sure it's within the item's rectangle
if (item->handleMouseDown( 0, 0, mask ))
{
handled = TRUE;
}
}
return handled;
}
BOOL LLContextMenu::handleRightMouseUp( S32 x, S32 y, MASK mask )
{
S32 local_x = x - getRect().mLeft;
S32 local_y = y - getRect().mBottom;
if (!mHoveredAnyItem && !pointInView(local_x, local_y))
{
sMenuContainer->hideMenus();
return TRUE;
}
BOOL result = handleMouseUp( x, y, mask );
mHoveredAnyItem = FALSE;
return result;
}
bool LLContextMenu::addChild(LLView* view, S32 tab_group)
{
return addContextChild(view, tab_group);
}