/** * @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 #include // static LLMenuHolderGL *LLMenuGL::sMenuContainer = NULL; view_listener_t::listener_map_t view_listener_t::sListeners; S32 MENU_BAR_HEIGHT = 18; S32 MENU_BAR_WIDTH = 410; ///============================================================================ /// 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 LLMenuHolderGL::sItemLastSelectedHandle; LLFrameTimer LLMenuHolderGL::sItemActivationTimer; const F32 ACTIVATE_HIGHLIGHT_TIME = 0.3f; static MenuRegistry::Register register_menu_item("menu_item"); static MenuRegistry::Register register_separator("menu_item_separator"); static MenuRegistry::Register register_menu_item_call("menu_item_call"); static MenuRegistry::Register register_menu_item_check("menu_item_check"); // Created programmatically but we need to specify custom colors in xml static MenuRegistry::Register register_menu_item_tear_off("menu_item_tear_off"); static MenuRegistry::Register register_menu("menu"); static LLDefaultChildRegistry::Register register_menu_default("menu"); ///============================================================================ /// Class LLMenuItemGL ///============================================================================ LLMenuItemGL::Params::Params() : shortcut("shortcut"), 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; 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; } auto 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) { 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 *listp) { LLMenuKeyboardBinding *accelerator = NULL; if (mAcceleratorKey != KEY_NONE) { std::list::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.getWString().c_str(), 0, static_cast(offset)); S32 x_end = LEFT_PLAIN_PIXELS + mFont->getWidth(mLabel.getWString().c_str(), 0, static_cast(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(parent_view)) { return dynamic_cast(parent_view); } bool parent_is_menu = dynamic_cast(parent_view) && !dynamic_cast(parent_view); if (parent_is_menu) { // use menu parent parent_view = dynamic_cast(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(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(std::string_view 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(std::string_view 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 *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(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(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 && 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(branch->getParent()); if (branch_parent) { branch_parent->setFocus(false); } } if (handled && getMenu()->getTornOff()) { LLFloater * menu_parent = dynamic_cast(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(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(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(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(); LLMenuGL::setKeyboardMode(false); setHighlight(false); } void LLMenuItemBranchDownGL::setFocus(bool b) { // needed for tab-based selection LLMenuItemBranchGL::setFocus(b); 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.getWString().c_str(), 0, S32_MAX) / 2.f); S32 x_begin = x_offset + getFont()->getWidth(mLabel.getWString().c_str(), 0, static_cast(offset)); S32 x_end = x_offset + getFont()->getWidth(mLabel.getWString().c_str(), 0, static_cast(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 { static void declareValues() { declare("up", ARROW_UP); declare("down", ARROW_DOWN); } }; struct Params : public LLInitParam::Block { Optional arrow_type; Optional 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(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), mFont(p.font) { typedef boost::tokenizer > tokenizer; boost::char_separator 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(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(view); if (menup) { return appendMenu(menup); } LLMenuItemGL* itemp = dynamic_cast(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(view); if (context) { return appendContextSubMenu(context); } LLMenuItemSeparatorGL* separator = dynamic_cast(view); if (separator) { return append(separator); } LLMenuItemGL* item = dynamic_cast(view); if (item) { return append(item); } LLMenuGL* menup = dynamic_cast(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(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(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(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 ? static_cast(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? S32 spillover_item_width = PLAIN_PAD_PIXELS + LLFontGL::getFontSansSerif()->getWidth( std::string("More") ); // *TODO: Translate 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 items_to_remove; std::copy(item_iter, mItems.end(), std::back_inserter(items_to_remove)); std::vector::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 items_to_remove; std::copy(item_iter, mItems.end(), std::back_inserter(items_to_remove)); std::vector::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(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(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(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"); mSpilloverBranch = LLUICtrlFactory::create(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 unique_words; std::set shared_words; item_list_t::iterator item_it; typedef boost::tokenizer > tokenizer; boost::char_separator 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(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(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*/) { auto 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(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(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(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(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(std::string_view 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(std::string_view 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 static_cast(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(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(*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(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 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(std::string_view name, bool recurse) const { LLView* view = findChildView(name, recurse); if (view) { LLMenuItemBranchGL* branch = dynamic_cast(view); if (branch) { return branch->getBranch(); } LLMenuGL* menup = dynamic_cast(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 ); if (menu->getRect().mTop > menu_region_rect.mTop) { // not enough space: align with top, ignore exclusion menu->translateIntoRect( menu_region_rect ); } menu->getParent()->sendChildToFront(menu); } ///============================================================================ /// Class LLMenuBarGL ///============================================================================ static LLDefaultChildRegistry::Register 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; } 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; } 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 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 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"); p.font = menu->getFont(); LLMenuItemBranchDownGL* branch = LLUICtrlFactory::create(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(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(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(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()); if (getRect().getHeight() != mTargetHeight) { // animate towards target height reshape(getRect().getWidth(), llceil(lerp((F32)getRect().getHeight(), (F32)mTargetHeight, LLSmoothInterpolation::getInterpolant(0.05f)))); } mMenu->needsArrange(); 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(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(), ¢er_x, ¢er_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 context_menu_register("context_menu"); static MenuRegistry::Register 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; } } // 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; } } 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) { mHoverItem->setHighlight( false ); } mHoverItem = NULL; } 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) { mHoverItem->setHighlight( false ); } mHoverItem = item; mHoverItem->setHighlight( true ); } mHoveredAnyItem = true; } else { // clear out our selection if (mHoverItem) { mHoverItem->setHighlight(false); 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); }