/** * @file llmenugl.cpp * @brief LLMenuItemGL base class * * $LicenseInfo:firstyear=2001&license=viewergpl$ * * Copyright (c) 2001-2009, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at * http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ */ // ***************************************************************************** // // 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 using namespace LLOldEvents; // static LLMenuHolderGL *LLMenuGL::sMenuContainer = NULL; S32 MENU_BAR_HEIGHT = 0; S32 MENU_BAR_WIDTH = 0; ///============================================================================ /// Local function declarations, constants, enums, and typedefs ///============================================================================ const S32 LABEL_BOTTOM_PAD_PIXELS = 2; const U32 LEFT_PAD_PIXELS = 3; const U32 LEFT_WIDTH_PIXELS = 15; const U32 LEFT_PLAIN_PIXELS = LEFT_PAD_PIXELS + LEFT_WIDTH_PIXELS; const U32 RIGHT_PAD_PIXELS = 2; const U32 RIGHT_WIDTH_PIXELS = 15; const U32 RIGHT_PLAIN_PIXELS = RIGHT_PAD_PIXELS + RIGHT_WIDTH_PIXELS; const U32 ACCEL_PAD_PIXELS = 10; 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 SEPARATOR_LABEL( "-----------" ); const std::string VERTICAL_SEPARATOR_LABEL( "|" ); const std::string TEAROFF_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\xB6" ); // 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; const S32 PIE_GESTURE_ACTIVATE_DISTANCE = 10; LLColor4 LLMenuItemGL::sEnabledColor( 0.0f, 0.0f, 0.0f, 1.0f ); LLColor4 LLMenuItemGL::sDisabledColor( 0.5f, 0.5f, 0.5f, 1.0f ); LLColor4 LLMenuItemGL::sHighlightBackground( 0.0f, 0.0f, 0.7f, 1.0f ); LLColor4 LLMenuItemGL::sHighlightForeground( 1.0f, 1.0f, 1.0f, 1.0f ); LLColor4 LLMenuGL::sDefaultBackgroundColor( 0.25f, 0.25f, 0.25f, 0.75f ); BOOL LLMenuGL::sKeyboardMode = FALSE; LLHandle LLMenuHolderGL::sItemLastSelectedHandle; LLFrameTimer LLMenuHolderGL::sItemActivationTimer; //LLColor4 LLMenuGL::sBackgroundColor( 0.8f, 0.8f, 0.0f, 1.0f ); const S32 PIE_CENTER_SIZE = 20; // pixels, radius of center hole const F32 PIE_SCALE_FACTOR = 1.7f; // scale factor for pie menu when mouse is initially down const F32 PIE_SHRINK_TIME = 0.2f; // time of transition between unbounded and bounded display of pie menu const F32 ACTIVATE_HIGHLIGHT_TIME = 0.3f; ///============================================================================ /// Class LLMenuItemGL ///============================================================================ // Default constructor LLMenuItemGL::LLMenuItemGL( const std::string& name, const std::string& label, KEY key, MASK mask ) : LLUICtrl( name ), mJumpKey(KEY_NONE), mAllowKeyRepeat(FALSE), mHighlight( FALSE ), mGotHover( FALSE ), mBriefItem( FALSE ), mStyle(LLFontGL::NORMAL), mDrawTextDisabled( FALSE ), mFont( LLFontGL::getFontSansSerif() ), mAcceleratorKey( key ), mAcceleratorMask( mask ), mLabel( label ), mEnabledColor( sEnabledColor ), mDisabledColor( sDisabledColor ), mHighlightBackground( sHighlightBackground ), mHighlightForeground( sHighlightForeground ) { setLabel( label ); } LLMenuItemGL::~LLMenuItemGL() { lldebugs << "Menu destroyed:" << this->getName() << llendl; }; // virtual LLXMLNodePtr LLMenuItemGL::getXML(bool save_children) const { LLXMLNodePtr node = LLView::getXML(); node->setName(LL_MENU_ITEM_TAG); node->createChild("type", TRUE)->setStringValue(getType()); node->createChild("label", TRUE)->setStringValue(mLabel); if (mAcceleratorKey != KEY_NONE) { std::stringstream out; if (mAcceleratorMask & MASK_CONTROL) { out << "control|"; } if (mAcceleratorMask & MASK_ALT) { out << "alt|"; } if (mAcceleratorMask & MASK_SHIFT) { out << "shift|"; } out << LLKeyboard::stringFromKey(mAcceleratorKey); node->createChild("shortcut", TRUE)->setStringValue(out.str()); #ifdef LL_DARWIN // Write in special tag if this key is really a ctrl combination on the Mac if (mAcceleratorMask & MASK_MAC_CONTROL) { node->createChild("useMacCtrl", TRUE)->setBoolValue( TRUE ); } #endif // LL_DARWIN } return node; } //virtual void LLMenuItemGL::setValue(const LLSD& value) { setLabel(value.asString()); } //virtual LLSD LLMenuItemGL::getValue() const { return getLabel(); } //virtual BOOL LLMenuItemGL::handleAcceleratorKey(KEY key, MASK mask) { if( getEnabled() && (!gKeyboard->getKeyRepeated(key) || mAllowKeyRepeat) && (key == mAcceleratorKey) && (mask == (mAcceleratorMask & MASK_NORMALKEYS)) ) { doIt(); return TRUE; } return FALSE; } BOOL LLMenuItemGL::handleHover(S32 x, S32 y, MASK mask) { setHover(TRUE); getWindow()->setCursor(UI_CURSOR_ARROW); return TRUE; } //virtual BOOL LLMenuItemGL::handleRightMouseDown(S32 x, S32 y, MASK mask) { return LLUICtrl::handleRightMouseDown(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) { LLKeyBinding *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))) { return FALSE; } } if (!accelerator) { accelerator = new LLKeyBinding; 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 { // break early if this is a silly thing to do. if( KEY_NONE == mAcceleratorKey ) { return; } // Append any masks #ifdef LL_DARWIN // Standard Mac names for modifier keys in menu equivalents // We could use the symbol characters, but they only exist in certain fonts. if( mAcceleratorMask & MASK_CONTROL ) { if ( mAcceleratorMask & MASK_MAC_CONTROL ) { st.append( LLTrans::getString("accel-mac-control") ); } else { st.append( LLTrans::getString("accel-mac-command") ); // Symbol would be "\xE2\x8C\x98" } } if( mAcceleratorMask & MASK_ALT ) st.append( LLTrans::getString("accel-mac-option") ); // Symbol would be "\xE2\x8C\xA5" if( mAcceleratorMask & MASK_SHIFT ) st.append( LLTrans::getString("accel-mac-shift") ); // Symbol would be "\xE2\x8C\xA7" #else if( mAcceleratorMask & MASK_CONTROL ) st.append( LLTrans::getString("accel-win-control") ); if( mAcceleratorMask & MASK_ALT ) st.append( LLTrans::getString("accel-win-alt") ); if( mAcceleratorMask & MASK_SHIFT ) st.append( LLTrans::getString("accel-win-shift") ); #endif std::string keystr = LLKeyboard::stringFromKey( mAcceleratorKey ); if ((mAcceleratorMask & MASK_NORMALKEYS) && (keystr[0] == '-' || keystr[0] == '=')) { st.append( " " ); } st.append( keystr ); } void LLMenuItemGL::setJumpKey(KEY key) { mJumpKey = LLStringOps::toUpper((char)key); } // virtual U32 LLMenuItemGL::getNominalHeight( void ) const { return llround(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::doIt( 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(); } } // 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); doIt(); return TRUE; } } return FALSE; } BOOL LLMenuItemGL::handleMouseUp( S32 x, S32 y, MASK mask) { // switch to mouse navigation mode LLMenuGL::setKeyboardMode(FALSE); doIt(); 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 FALSE;//!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) { int debug_count = 0; if (dynamic_cast(this)) debug_count++; gGL.color4fv( mHighlightBackground.mV ); gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth(), 0 ); } LLColor4 color; LLFontGL::ShadowType font_shadow = LLFontGL::NO_SHADOW; if (getEnabled() && !mDrawTextDisabled ) { font_shadow = LLFontGL::DROP_SHADOW_SOFT; } if ( getEnabled() && getHighlight() ) { color = mHighlightForeground; } else if( getEnabled() && !mDrawTextDisabled ) { color = mEnabledColor; } else { color = mDisabledColor; } // Draw the text on top. if (mBriefItem) { mFont->render( mLabel, 0, BRIEF_PAD_PIXELS / 2, 0, color, LLFontGL::LEFT, LLFontGL::BOTTOM, mStyle ); } else { if( !mDrawBoolLabel.empty() ) { mFont->render( mDrawBoolLabel.getWString(), 0, (F32)LEFT_PAD_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f) + 1.f, color, LLFontGL::LEFT, LLFontGL::BOTTOM, mStyle, font_shadow, S32_MAX, S32_MAX, NULL, FALSE ); } mFont->render( mLabel.getWString(), 0, (F32)LEFT_PLAIN_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f) + 1.f, color, LLFontGL::LEFT, LLFontGL::BOTTOM, mStyle, font_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) + 1.f, color, LLFontGL::RIGHT, LLFontGL::BOTTOM, mStyle, font_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) + 1.f, (!getEnabled() || getHighlight()) ? color : color % .7f, LLFontGL::RIGHT, LLFontGL::BOTTOM, mStyle, font_shadow, S32_MAX, S32_MAX, NULL, FALSE ); } } // underline "jump" key only when keyboard navigation has been initiated if (getMenu()->jumpKeysActive() && LLMenuGL::getKeyboardMode()) { std::string upper_case_label = mLabel.getString(); LLStringUtil::toUpper(upper_case_label); std::string::size_type offset = upper_case_label.find(mJumpKey); if (offset != std::string::npos) { S32 x_begin = LEFT_PLAIN_PIXELS + mFont->getWidth(mLabel, 0, offset); S32 x_end = LEFT_PLAIN_PIXELS + mFont->getWidth(mLabel, 0, offset + 1); gl_line_2d(x_begin, (MENU_ITEM_PADDING / 2) + 1, x_end, (MENU_ITEM_PADDING / 2) + 1); } } // clear got hover every frame setHover(FALSE); } BOOL LLMenuItemGL::setLabelArg( const std::string& key, const LLStringExplicit& text ) { mLabel.setArg(key, text); return TRUE; } void LLMenuItemGL::handleVisibilityChange(BOOL new_visibility) { if (getMenu()) { getMenu()->needsArrange(); } LLView::handleVisibilityChange(new_visibility); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLMenuItemSeparatorGL // // This class represents a separator. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LLMenuItemSeparatorGL::LLMenuItemSeparatorGL( const std::string &name ) : LLMenuItemGL( name.empty() ? SEPARATOR_NAME : name, SEPARATOR_LABEL ) { } U32 LLMenuItemSeparatorGL::getNominalHeight( void ) const { return SEPARATOR_HEIGHT_PIXELS; } LLXMLNodePtr LLMenuItemSeparatorGL::getXML(bool save_children) const { LLXMLNodePtr node = LLMenuItemGL::getXML(); node->setName(LL_MENU_ITEM_SEPARATOR_GL_TAG); return node; } void LLMenuItemSeparatorGL::draw( void ) { gGL.color4fv( mDisabledColor.mV ); const S32 y = getRect().getHeight() / 2; const S32 PAD = 6; gl_line_2d( PAD, y, getRect().getWidth() - PAD, y ); } 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->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->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->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->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() : LLMenuItemGL(std::string("tear off"), TEAROFF_SEPARATOR_LABEL) { } LLXMLNodePtr LLMenuItemTearOffGL::getXML(bool save_children) const { LLXMLNodePtr node = LLMenuItemGL::getXML(); node->setName(LL_MENU_ITEM_TEAR_OFF_GL_TAG); return node; } // 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::doIt() { if (getMenu()->getTornOff()) { LLTearOffMenu* torn_off_menu = (LLTearOffMenu*)(getMenu()->getParent()); torn_off_menu->close(); } 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.mV ); gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth(), 0 ); } if (getEnabled()) { gGL.color4fv( mEnabledColor.mV ); } else { gGL.color4fv( mDisabledColor.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 LLMenuItemBlankGL // // This class represents a blank, non-functioning item. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ class LLMenuItemBlankGL : public LLMenuItemGL { public: LLMenuItemBlankGL( void ) : LLMenuItemGL( LLStringUtil::null, LLStringUtil::null ) { setEnabled(FALSE); } virtual void doIt( void ) {} virtual void draw( void ) {} }; ///============================================================================ /// Class LLMenuItemCallGL ///============================================================================ LLMenuItemCallGL::LLMenuItemCallGL( const std::string& name, const std::string& label, menu_callback clicked_cb, enabled_callback enabled_cb, void* user_data, KEY key, MASK mask, BOOL enabled, on_disabled_callback on_disabled_cb) : LLMenuItemGL( name, label, key, mask ), mCallback( clicked_cb ), mEnabledCallback( enabled_cb ), mLabelCallback(NULL), mUserData( user_data ), mOnDisabledCallback(on_disabled_cb) { if(!enabled) setEnabled(FALSE); } LLMenuItemCallGL::LLMenuItemCallGL( const std::string& name, menu_callback clicked_cb, enabled_callback enabled_cb, void* user_data, KEY key, MASK mask, BOOL enabled, on_disabled_callback on_disabled_cb) : LLMenuItemGL( name, name, key, mask ), mCallback( clicked_cb ), mEnabledCallback( enabled_cb ), mLabelCallback(NULL), mUserData( user_data ), mOnDisabledCallback(on_disabled_cb) { if(!enabled) setEnabled(FALSE); } LLMenuItemCallGL::LLMenuItemCallGL(const std::string& name, const std::string& label, menu_callback clicked_cb, enabled_callback enabled_cb, label_callback label_cb, void* user_data, KEY key, MASK mask, BOOL enabled, on_disabled_callback on_disabled_cb) : LLMenuItemGL(name, label, key, mask), mCallback(clicked_cb), mEnabledCallback(enabled_cb), mLabelCallback(label_cb), mUserData(user_data), mOnDisabledCallback(on_disabled_cb) { if(!enabled) setEnabled(FALSE); } LLMenuItemCallGL::LLMenuItemCallGL(const std::string& name, menu_callback clicked_cb, enabled_callback enabled_cb, label_callback label_cb, void* user_data, KEY key, MASK mask, BOOL enabled, on_disabled_callback on_disabled_cb) : LLMenuItemGL(name, name, key, mask), mCallback(clicked_cb), mEnabledCallback(enabled_cb), mLabelCallback(label_cb), mUserData(user_data), mOnDisabledCallback(on_disabled_cb) { if(!enabled) setEnabled(FALSE); } void LLMenuItemCallGL::setEnabledControl(std::string enabled_control, LLView *context) { // Register new listener if (!enabled_control.empty()) { LLControlVariable *control = context->findControl(enabled_control); if (!control) { context->addBoolControl(enabled_control, getEnabled()); control = context->findControl(enabled_control); llassert_always(control); } control->getSignal()->connect(boost::bind(&LLView::controlListener, _2, getHandle(), std::string("enabled"))); setEnabled(control->getValue()); } } void LLMenuItemCallGL::setVisibleControl(std::string visible_control, LLView *context) { // Register new listener if (!visible_control.empty()) { LLControlVariable *control = context->findControl(visible_control); if (!control) { context->addBoolControl(visible_control, getVisible()); control = context->findControl(visible_control); llassert_always(control); } control->getSignal()->connect(boost::bind(&LLView::controlListener, _2, getHandle(), std::string("visible"))); setVisible(control->getValue()); } } // virtual LLXMLNodePtr LLMenuItemCallGL::getXML(bool save_children) const { LLXMLNodePtr node = LLMenuItemGL::getXML(); node->setName(LL_MENU_ITEM_CALL_GL_TAG); // Contents std::vector listeners = mDispatcher->getListeners(); std::vector::iterator itor; for (itor = listeners.begin(); itor != listeners.end(); ++itor) { std::string listener_name = findEventListener((LLSimpleListener*)itor->listener); if (!listener_name.empty()) { // *FIX: It's not always on_click. It could be on_check, on_enable or on_visible, // but there's no way to get that from the data that is stored. LLXMLNodePtr child_node = node->createChild("on_click", FALSE); child_node->createChild("function", TRUE)->setStringValue(listener_name); child_node->createChild("filter", TRUE)->setStringValue(itor->filter.asString()); child_node->createChild("userdata", TRUE)->setStringValue(itor->userdata.asString()); } } return node; } // doIt() - Call the callback provided void LLMenuItemCallGL::doIt( void ) { // RN: menu item can be deleted in callback, so beware getMenu()->setItemLastSelected( this ); if( mCallback ) { mCallback( mUserData ); } LLPointer fired_event = new LLEvent(this); fireEvent(fired_event, "on_click"); LLMenuItemGL::doIt(); } void LLMenuItemCallGL::updateEnabled( void ) { if( mEnabledCallback ) { setEnabled( mEnabledCallback( mUserData ) ); } } void LLMenuItemCallGL::buildDrawLabel( void ) { updateEnabled(); LLPointer fired_event = new LLEvent(this); fireEvent(fired_event, "on_build"); if(mLabelCallback) { std::string label; mLabelCallback(label, mUserData); mLabel = label; } 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)) ) { LLPointer fired_event = new LLEvent(this); fireEvent(fired_event, "on_build"); updateEnabled(); if( !getEnabled() ) { if( mOnDisabledCallback ) { mOnDisabledCallback( mUserData ); } } } return LLMenuItemGL::handleAcceleratorKey(key, mask); } ///============================================================================ /// Class LLMenuItemCheckGL ///============================================================================ LLMenuItemCheckGL::LLMenuItemCheckGL ( const std::string& name, const std::string& label, menu_callback clicked_cb, enabled_callback enabled_cb, check_callback check_cb, void* user_data, KEY key, MASK mask ) : LLMenuItemCallGL( name, label, clicked_cb, enabled_cb, user_data, key, mask ), mCheckCallback( check_cb ), mChecked(FALSE) { } LLMenuItemCheckGL::LLMenuItemCheckGL ( const std::string& name, menu_callback clicked_cb, enabled_callback enabled_cb, check_callback check_cb, void* user_data, KEY key, MASK mask ) : LLMenuItemCallGL( name, name, clicked_cb, enabled_cb, user_data, key, mask ), mCheckCallback( check_cb ), mChecked(FALSE) { } LLMenuItemCheckGL::LLMenuItemCheckGL ( const std::string& name, const std::string& label, menu_callback clicked_cb, enabled_callback enabled_cb, std::string control_name, LLView *context, void* user_data, KEY key, MASK mask ) : LLMenuItemCallGL( name, label, clicked_cb, enabled_cb, user_data, key, mask ), mCheckCallback( NULL ) { setControlName(control_name, context); } //virtual void LLMenuItemCheckGL::setValue(const LLSD& value) { mChecked = value.asBoolean(); if(mChecked) { mDrawBoolLabel = LLMenuGL::BOOLEAN_TRUE_PREFIX; } else { mDrawBoolLabel.clear(); } } void LLMenuItemCheckGL::setCheckedControl(std::string checked_control, LLView *context) { // Register new listener if (!checked_control.empty()) { LLControlVariable *control = context->findControl(checked_control); if (!control) { context->addBoolControl(checked_control, mChecked); control = context->findControl(checked_control); llassert_always(control); } control->getSignal()->connect(boost::bind(&LLView::controlListener, _2, getHandle(), std::string("value"))); mChecked = control->getValue(); } } // virtual LLXMLNodePtr LLMenuItemCheckGL::getXML(bool save_children) const { LLXMLNodePtr node = LLMenuItemCallGL::getXML(); node->setName(LL_MENU_ITEM_CHECK_GL_TAG); return node; } // called to rebuild the draw label void LLMenuItemCheckGL::buildDrawLabel( void ) { if(mChecked || (mCheckCallback && mCheckCallback( getUserData() ) ) ) { mDrawBoolLabel = LLMenuGL::BOOLEAN_TRUE_PREFIX; } else { mDrawBoolLabel.clear(); } LLMenuItemCallGL::buildDrawLabel(); } ///============================================================================ /// Class LLMenuItemToggleGL ///============================================================================ LLMenuItemToggleGL::LLMenuItemToggleGL( const std::string& name, const std::string& label, BOOL* toggle, KEY key, MASK mask ) : LLMenuItemGL( name, label, key, mask ), mToggle( toggle ) { } LLMenuItemToggleGL::LLMenuItemToggleGL( const std::string& name, BOOL* toggle, KEY key, MASK mask ) : LLMenuItemGL( name, name, key, mask ), mToggle( toggle ) { } // called to rebuild the draw label void LLMenuItemToggleGL::buildDrawLabel( void ) { if( *mToggle ) { mDrawBoolLabel = LLMenuGL::BOOLEAN_TRUE_PREFIX; } else { mDrawBoolLabel.clear(); } mDrawAccelLabel.clear(); std::string st = mDrawAccelLabel; appendAcceleratorString( st ); mDrawAccelLabel = st; } // doIt() - do the primary funcationality of the menu item. void LLMenuItemToggleGL::doIt( void ) { getMenu()->setItemLastSelected( this ); //llinfos << "LLMenuItemToggleGL::doIt " << mLabel.c_str() << llendl; *mToggle = !(*mToggle); buildDrawLabel(); LLMenuItemGL::doIt(); } LLMenuItemBranchGL::LLMenuItemBranchGL( const std::string& name, const std::string& label, LLHandle branch_handle, KEY key, MASK mask ) : LLMenuItemGL( name, label, key, mask ) { LLMenuGL* branch = dynamic_cast(branch_handle.get()); if(!branch) { llerrs << "Non-menu handle passed as branch reference." << llendl; } if (branch) { mBranchHandle = branch->getHandle(); branch->setVisible(FALSE); branch->setParentMenuItem(this); } } LLMenuItemBranchGL::~LLMenuItemBranchGL() { if (mBranchHandle.get()) { mBranchHandle.get()->die(); } } // virtual LLView* LLMenuItemBranchGL::getChildView(const std::string& name, BOOL recurse, BOOL create_if_missing) const { LLMenuGL* branch = getBranch(); if (branch) { if (branch->getName() == name) { return branch; } // Always recurse on branches LLView* child = branch->getChildView(name, recurse, FALSE); if(child) { return child; } } return LLView::getChildView(name, recurse, create_if_missing); } // virtual BOOL LLMenuItemBranchGL::handleMouseUp(S32 x, S32 y, MASK mask) { // switch to mouse navigation mode LLMenuGL::setKeyboardMode(FALSE); doIt(); make_ui_sound("UISndClickRelease"); return TRUE; } BOOL LLMenuItemBranchGL::handleAcceleratorKey(KEY key, MASK mask) { return getBranch() && getBranch()->handleAcceleratorKey(key, mask); } // virtual LLXMLNodePtr LLMenuItemBranchGL::getXML(bool save_children) const { if (getBranch()) { return getBranch()->getXML(); } return LLMenuItemGL::getXML(); } // 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; } // doIt() - do the primary functionality of the menu item. void LLMenuItemBranchGL::doIt( 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); } } 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 if (getMenu()->getTornOff() && !((LLFloater*)getMenu()->getParent())->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->getParent())->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::handleVisibilityChange( BOOL new_visibility ) { if (new_visibility == FALSE && getBranch() && !getBranch()->getTornOff()) { lldebugs << "Forcing branch to visible. Menu: " << getName() << " Branch: " << getBranch()->getName() << llendl; getBranch()->setVisible(FALSE); } LLMenuItemGL::handleVisibilityChange(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->getParent())->setFocus(FALSE); } if (handled && getMenu()->getTornOff()) { ((LLFloater*)getMenu()->getParent())->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()) { gFloaterView->bringToFront((LLFloater*)branch->getParent()); // 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() ); F32 center_y = top - (getRect().getHeight() / 2.f); if( y < menu_region_rect.mBottom && center_y <= menu_region_rect.getCenterY()) { // open upwards if menu extends past bottom // adjust by the height of the menu item branch since it is a submenu 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); } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // 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 std::string& name, const std::string& label, LLHandle branch, KEY key = KEY_NONE, MASK mask = MASK_NONE ); virtual std::string getType() const { return "menu"; } // 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); }; LLMenuItemBranchDownGL::LLMenuItemBranchDownGL( const std::string& name, const std::string& label, LLHandle branch, KEY key, MASK mask ) : LLMenuItemBranchGL( name, label, branch, key, mask ) { } // 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()) { gFloaterView->bringToFront((LLFloater*)branch->getParent()); } 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->getParent())->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); doIt(); make_ui_sound("UISndClick"); setVisible(TRUE); 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 && getVisible()) { // flash this menu entry because we triggered an invisible menu item LLMenuHolderGL::setActivatedItem(this); } return handled; } 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->doIt(); } 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->doIt(); } return TRUE; } else if (key == KEY_DOWN) { // switch to keyboard navigation mode LLMenuGL::setKeyboardMode(TRUE); if (!isActive()) { doIt(); } getBranch()->highlightNextItem(NULL); return TRUE; } else if (key == KEY_UP) { // switch to keyboard navigation mode LLMenuGL::setKeyboardMode(TRUE); if (!isActive()) { doIt(); } 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.mV ); gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth(), 0 ); } LLFontGL::ShadowType font_shadow = LLFontGL::NO_SHADOW; if (getEnabled() && !getDrawTextDisabled() ) { font_shadow = LLFontGL::DROP_SHADOW_SOFT; } LLColor4 color; if (getHighlight()) { color = mHighlightForeground; } else if( getEnabled() ) { color = mEnabledColor; } else { color = mDisabledColor; } getFont()->render( mLabel.getWString(), 0, (F32)getRect().getWidth() / 2.f, (F32)LABEL_BOTTOM_PAD_PIXELS, color, LLFontGL::HCENTER, LLFontGL::BOTTOM, getFontStyle(), font_shadow ); // 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 = llround((F32)getRect().getWidth() / 2.f - getFont()->getWidthF32(mLabel.getString(), 0, S32_MAX) / 2.f); S32 x_begin = x_offset + getFont()->getWidth(mLabel, 0, offset); S32 x_end = x_offset + getFont()->getWidth(mLabel, 0, offset + 1); gl_line_2d(x_begin, LABEL_BOTTOM_PAD_PIXELS, x_end, LABEL_BOTTOM_PAD_PIXELS); } } // reset every frame so that we only show highlight // when we get hover events on that frame setHover(FALSE); } ///============================================================================ /// Class LLMenuGL ///============================================================================ static LLRegisterWidget r1("menu"); // Default constructor LLMenuGL::LLMenuGL( const std::string& name, const std::string& label ) : LLUICtrl( name, LLRect(), FALSE), mBackgroundColor( sDefaultBackgroundColor ), mBgVisible( TRUE ), mHasSelection( FALSE ), mLabel( label ), mDropShadowed( TRUE ), mHorizontalLayout( FALSE ), mKeepFixedSize( FALSE ), mLastMouseX(0), mLastMouseY(0), mMouseVelX(0), mMouseVelY(0), mTornOff(FALSE), mTearOffItem(NULL), mSpilloverBranch(NULL), mFirstVisibleItem(NULL), mSpilloverMenu(NULL), mJumpKey(KEY_NONE), mNeedsArrange(FALSE), mShortcutPad(ACCEL_PAD_PIXELS) { mFadeTimer.stop(); setTabStop(FALSE); } LLMenuGL::LLMenuGL( const std::string& label) : LLUICtrl( label, LLRect(), FALSE), mBackgroundColor( sDefaultBackgroundColor ), mBgVisible( TRUE ), mHasSelection( FALSE ), mLabel( label ), mDropShadowed( TRUE ), mHorizontalLayout( FALSE ), mKeepFixedSize( FALSE ), mLastMouseX(0), mLastMouseY(0), mMouseVelX(0), mMouseVelY(0), mTornOff(FALSE), mTearOffItem(NULL), mSpilloverBranch(NULL), mSpilloverMenu(NULL), mFirstVisibleItem(NULL), mJumpKey(KEY_NONE), mNeedsArrange(FALSE), mShortcutPad(ACCEL_PAD_PIXELS) { mFadeTimer.stop(); setTabStop(FALSE); } // 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) { mTearOffItem = new LLMenuItemTearOffGL(); addChild(mTearOffItem); } else if (!tear_off && mTearOffItem != NULL) { mItems.remove(mTearOffItem); removeChild(mTearOffItem); delete mTearOffItem; mTearOffItem = NULL; needsArrange(); } } // virtual LLXMLNodePtr LLMenuGL::getXML(bool save_children) const { LLXMLNodePtr node = LLView::getXML(); node->setName(LL_MENU_GL_TAG); // Attributes node->createChild("opaque", TRUE)->setBoolValue(mBgVisible); node->createChild("drop_shadow", TRUE)->setBoolValue(mDropShadowed); node->createChild("tear_off", TRUE)->setBoolValue((mTearOffItem != NULL)); if (mBgVisible) { // TomY TODO: this should save out the color control name node->createChild("color", TRUE)->setFloatValue(4, mBackgroundColor.mV); } // Contents item_list_t::const_iterator item_iter; for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) { LLView* child = (*item_iter); LLMenuItemGL* item = (LLMenuItemGL*)child; LLXMLNodePtr child_node = item->getXML(); node->addChild(child_node); } return node; } void LLMenuGL::parseChildXML(LLXMLNodePtr child, LLView *parent, LLUICtrlFactory *factory) { std::string name(child->getName()->mString); if (child->hasName(LL_MENU_GL_TAG)) { // SUBMENU LLMenuGL *submenu = (LLMenuGL*)LLMenuGL::fromXML(child, parent, factory); appendMenu(submenu, 0); if (LLMenuGL::sMenuContainer != NULL) { submenu->updateParent(LLMenuGL::sMenuContainer); } else { submenu->updateParent(parent); } } else if (child->hasName(LL_MENU_ITEM_SEPARATOR_GL_TAG)) { std::string item_name; child->getAttributeString("name", item_name); LLMenuItemGL* separator = new LLMenuItemSeparatorGL(item_name); addChild(separator); } else if(child->hasName(LL_MENU_ITEM_CALL_GL_TAG) || child->hasName(LL_MENU_ITEM_CHECK_GL_TAG)) { // ITEM std::string type; std::string item_name; std::string source_label; std::string item_label; KEY jump_key = KEY_NONE; child->getAttributeString("type", type); child->getAttributeString("name", item_name); child->getAttributeString("label", source_label); // parse jump key out of label typedef boost::tokenizer > tokenizer; boost::char_separator sep("_"); tokenizer tokens(source_label, sep); tokenizer::iterator token_iter; S32 token_count = 0; for( token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) { item_label += (*token_iter); if (token_count > 0) { jump_key = (*token_iter).c_str()[0]; } ++token_count; } MASK mask = 0; #ifdef LL_DARWIN // See if this Mac accelerator should really use the ctrl key and not get mapped to cmd BOOL useMacCtrl = FALSE; child->getAttributeBOOL("useMacCtrl", useMacCtrl); #endif // LL_DARWIN std::string shortcut; child->getAttributeString("shortcut", shortcut); if (shortcut.find("control") != shortcut.npos) { #ifdef LL_DARWIN if ( useMacCtrl ) { mask |= MASK_MAC_CONTROL; } #endif // LL_DARWIN mask |= MASK_CONTROL; } if (shortcut.find("alt") != shortcut.npos) { mask |= MASK_ALT; } if (shortcut.find("shift") != shortcut.npos) { mask |= MASK_SHIFT; } S32 pipe_pos = shortcut.rfind("|"); std::string key_str = shortcut.substr(pipe_pos+1); KEY key = KEY_NONE; LLKeyboard::keyFromString(key_str, &key); LLMenuItemCallGL *new_item; LLXMLNodePtr call_child; if (child->hasName(LL_MENU_ITEM_CHECK_GL_TAG)) { std::string control_name; child->getAttributeString("control_name", control_name); new_item = new LLMenuItemCheckGL(item_name, item_label, 0, 0, control_name, parent, 0, key, mask); for (call_child = child->getFirstChild(); call_child.notNull(); call_child = call_child->getNextSibling()) { if (call_child->hasName("on_check") || call_child->hasName(name+".on_check")) { std::string callback_name; std::string control_name; if (call_child->hasAttribute("function")) { call_child->getAttributeString("function", callback_name); control_name = callback_name; std::string callback_data = item_name; if (call_child->hasAttribute("userdata")) { call_child->getAttributeString("userdata", callback_data); if (!callback_data.empty()) { control_name = llformat("%s(%s)", callback_name.c_str(), callback_data.c_str()); } } LLSD userdata; userdata["control"] = control_name; userdata["data"] = callback_data; LLSimpleListener* callback = parent->getListenerByName(callback_name); if (!callback) { lldebugs << "Ignoring \"on_check\" \"" << item_name << "\" because \"" << callback_name << "\" is not registered" << llendl; continue; } new_item->addListener(callback, "on_build", userdata); } else if (call_child->hasAttribute("control")) { call_child->getAttributeString("control", control_name); } else { continue; } LLControlVariable *control = parent->findControl(control_name); if (!control) { parent->addBoolControl(control_name, FALSE); } ((LLMenuItemCheckGL*)new_item)->setCheckedControl(control_name, parent); } } } else { new_item = new LLMenuItemCallGL(item_name, item_label, 0, 0, 0, 0, key, mask); } for (call_child = child->getFirstChild(); call_child.notNull(); call_child = call_child->getNextSibling()) { if (call_child->hasName("on_click") || call_child->hasName(name+".on_click")) { std::string callback_name; call_child->getAttributeString("function", callback_name); std::string callback_data = item_name; if (call_child->hasAttribute("userdata")) { call_child->getAttributeString("userdata", callback_data); } LLSimpleListener* callback = parent->getListenerByName(callback_name); if (!callback) { lldebugs << "Ignoring \"on_click\" \"" << item_name << "\" because \"" << callback_name << "\" is not registered" << llendl; continue; } new_item->addListener(callback, "on_click", callback_data); } if (call_child->hasName("on_enable") || call_child->hasName(name+".on_enable")) { std::string callback_name; std::string control_name; if (call_child->hasAttribute("function")) { call_child->getAttributeString("function", callback_name); control_name = callback_name; std::string callback_data; if (call_child->hasAttribute("userdata")) { call_child->getAttributeString("userdata", callback_data); if (!callback_data.empty()) { control_name = llformat("%s(%s)", callback_name.c_str(), callback_data.c_str()); } } LLSD userdata; userdata["control"] = control_name; userdata["data"] = callback_data; LLSimpleListener* callback = parent->getListenerByName(callback_name); if (!callback) { lldebugs << "Ignoring \"on_enable\" \"" << item_name << "\" because \"" << callback_name << "\" is not registered" << llendl; continue; } new_item->addListener(callback, "on_build", userdata); } else if (call_child->hasAttribute("control")) { call_child->getAttributeString("control", control_name); } else { continue; } new_item->setEnabledControl(control_name, parent); } if (call_child->hasName("on_visible") || call_child->hasName(name+".on_visible")) { std::string callback_name; std::string control_name; if (call_child->hasAttribute("function")) { call_child->getAttributeString("function", callback_name); control_name = callback_name; std::string callback_data; if (call_child->hasAttribute("userdata")) { call_child->getAttributeString("userdata", callback_data); if (!callback_data.empty()) { control_name = llformat("%s(%s)", callback_name.c_str(), callback_data.c_str()); } } LLSD userdata; userdata["control"] = control_name; userdata["data"] = callback_data; LLSimpleListener* callback = parent->getListenerByName(callback_name); if (!callback) { lldebugs << "Ignoring \"on_visible\" \"" << item_name << "\" because \"" << callback_name << "\" is not registered" << llendl; continue; } new_item->addListener(callback, "on_build", userdata); } else if (call_child->hasAttribute("control")) { call_child->getAttributeString("control", control_name); } else { continue; } new_item->setVisibleControl(control_name, parent); } } new_item->setLabel(item_label); if (jump_key != KEY_NONE) new_item->setJumpKey(jump_key); append(new_item); } } // This wrapper is needed because the virtual linkage causes errors if default parameters are used bool LLMenuGL::addChild(LLView* view, S32 tab_group) { return addChild(view, 0, tab_group); } bool LLMenuGL::addChild(LLView* view, LLView* insert_before, S32 tab_group) { if (LLMenuGL* menup = dynamic_cast(view)) { lldebugs << "Adding menu " << menup->getName() << " to " << getName() << llendl; if (!insert_before) appendMenu(menup); else appendMenu(menup, insert_before); return true; } else if (LLMenuItemGL* itemp = dynamic_cast(view)) { lldebugs << "Adding " << itemp->getName() << " to " << getName() << llendl; append(itemp, insert_before); return true; } lldebugs << "Error adding unknown child '"<<(view ? view->getName() : std::string("NULL")) << "' to " << getName() << llendl; return false; } 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() { 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 (getTornOff()) { // activation of jump keys on torn off menus controlled by keyboard focus active = active && ((LLFloater*)getParent())->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 = 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 return ((LLFloater*)getParent())->hasFocus(); } else { // normally, menus are hidden as soon as the user focuses // on another menu, so just use the visibility criterion return getVisible(); } } // static LLView* LLMenuGL::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) { std::string name("menu"); node->getAttributeString("name", name); std::string label = name; node->getAttributeString("label", label); // parse jump key out of label std::string new_menu_label; typedef boost::tokenizer > tokenizer; boost::char_separator sep("_"); tokenizer tokens(label, sep); tokenizer::iterator token_iter; KEY jump_key = KEY_NONE; S32 token_count = 0; for( token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) { new_menu_label += (*token_iter); if (token_count > 0) { jump_key = (*token_iter).c_str()[0]; } ++token_count; } BOOL opaque = TRUE; node->getAttributeBOOL("opaque", opaque); LLMenuGL *menu = new LLMenuGL(name, new_menu_label); menu->setJumpKey(jump_key); BOOL tear_off = FALSE; node->getAttributeBOOL("tear_off", tear_off); menu->setCanTearOff(tear_off); if (node->hasAttribute("drop_shadow")) { BOOL drop_shadow = FALSE; node->getAttributeBOOL("drop_shadow", drop_shadow); menu->setDropShadowed(drop_shadow); } menu->setBackgroundVisible(opaque); LLColor4 color(0,0,0,1); if (opaque && LLUICtrlFactory::getAttributeColor(node,"color", color)) { menu->setBackgroundColor(color); } BOOL create_jump_keys = FALSE; node->getAttributeBOOL("create_jump_keys", create_jump_keys); LLXMLNodePtr child; for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) { menu->parseChildXML(child, parent, factory); } if (create_jump_keys) { menu->createJumpKeys(); } return menu; } // 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 = U32_MAX; if (!getTornOff()) { max_height = getRect().mTop - menu_region_rect.mBottom; if (menu_region_rect.mTop - getRect().mTop > (S32)max_height) { max_height = menu_region_rect.mTop - getRect().mTop; } } // *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 = llround(LLFontGL::getFontSansSerif()->getLineHeight()) + MENU_ITEM_PADDING; // Scrolling support item_list_t::iterator first_visible_item_iter; //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 { item_list_t::iterator item_iter; for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) { llassert_always((*item_iter)!=NULL); // do first so LLMenuGLItemCall can call on_visible to determine if visible (*item_iter)->buildDrawLabel(); if ((*item_iter)->getVisible()) { if (!getTornOff() && *item_iter != mSpilloverBranch && height + (*item_iter)->getNominalHeight() > max_height - spillover_item_height) { // 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; } else { // track our rect height += (*item_iter)->getNominalHeight(); width = llmax( width, (*item_iter)->getNominalWidth() ); } } } } S32 cur_height = (S32)llmin(max_height, height); setRect(LLRect(getRect().mLeft, getRect().mTop, getRect().mLeft + width, getRect().mTop - cur_height)); S32 cur_width = 0; S32 offset = 0; item_list_t::iterator item_iter; for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) { if ((*item_iter)->getVisible()) { // 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 (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 mSpilloverMenu = new LLMenuGL(std::string("More"), std::string("More")); mSpilloverMenu->updateParent(LLMenuGL::sMenuContainer); // Inherit colors mSpilloverMenu->setBackgroundColor( mBackgroundColor ); mSpilloverMenu->setCanTearOff(FALSE); mSpilloverBranch = new LLMenuItemBranchGL(std::string("More"), std::string("More"), mSpilloverMenu->getHandle()); mSpilloverBranch->setFontStyle(LLFontGL::ITALIC); } } 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() { 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; deleteAllChildren(); } // 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->doIt(); } // 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. // This wrapper is needed because the virtual linkage causes errors if default parameters are used BOOL LLMenuGL::append( LLMenuItemGL* item ) { return append(item, 0); } BOOL LLMenuGL::append(LLMenuItemGL* item, LLView* insert_before) { if (!item) return FALSE; if (!insert_before) { mItems.push_back( item ); } else { item_list_t::iterator i; for (i = mItems.begin(); i != mItems.end(); ++i) if (*i == insert_before) break; mItems.insert(i, item); } LLUICtrl::addChild(item); needsArrange(); return TRUE; } // add a separator to this menu BOOL LLMenuGL::addSeparator() { LLMenuItemGL* separator = new LLMenuItemSeparatorGL(); return addChild(separator); } // add a menu - this will create a cascading menu // This wrapper is needed because the virtual linkage causes errors if default parameters are used BOOL LLMenuGL::appendMenu( LLMenuGL* menu ) { return appendMenu(menu, 0); } BOOL LLMenuGL::appendMenu(LLMenuGL* menu, LLView* insert_before) { if( menu == this ) { llerrs << "** Attempt to attach menu to itself. This is certainly " << "a logic error." << llendl; } BOOL success = TRUE; LLMenuItemBranchGL* branch = NULL; branch = new LLMenuItemBranchGL( menu->getName(), menu->getLabel(), menu->getHandle() ); branch->setJumpKey(menu->getJumpKey()); success &= append( branch, insert_before ); // Inherit colors menu->setBackgroundColor( mBackgroundColor ); menu->updateParent(LLMenuGL::sMenuContainer); return success; } void LLMenuGL::setEnabledSubMenus(BOOL enable) { setEnabled(enable); item_list_t::iterator item_iter; for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) { (*item_iter)->setEnabledSubMenus( enable ); } } // setItemEnabled() - pass the label and the enable flag for a menu // item. TRUE will make sure it's enabled, FALSE will disable it. void LLMenuGL::setItemEnabled( const std::string& name, BOOL enable ) { item_list_t::iterator item_iter; for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) { if( (*item_iter)->getName() == name ) { (*item_iter)->setEnabled( enable ); (*item_iter)->setEnabledSubMenus( enable ); break; } } } void LLMenuGL::setItemVisible( const std::string& name, BOOL visible ) { item_list_t::iterator item_iter; for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) { if( (*item_iter)->getName() == name ) { (*item_iter)->setVisible( visible ); needsArrange(); break; } } } void LLMenuGL::setItemLastSelected(LLMenuItemGL* item) { if (getVisible()) { LLMenuHolderGL::setActivatedItem(item); } // fix the checkmarks item->buildDrawLabel(); } // Set whether drop shadowed void LLMenuGL::setDropShadowed( const BOOL shadowed ) { mDropShadowed = shadowed; } void LLMenuGL::setTornOff(BOOL torn_off) { mTornOff = torn_off; } U32 LLMenuGL::getItemCount() { return mItems.size(); } LLMenuItemGL* LLMenuGL::getItem(S32 number) { if (number >= 0 && number < (S32)mItems.size()) { item_list_t::iterator item_iter; for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) { if (number == 0) { return (*item_iter); } number--; } } return NULL; } LLMenuItemGL* LLMenuGL::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*)getParent())->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++; if (next_item_iter == mItems.end()) { next_item_iter = mItems.begin(); } } // 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*)getParent())->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++; if (prev_item_iter == mItems.rend()) { prev_item_iter = mItems.rbegin(); } } 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::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 = llround(lerp((F32)mouse_delta_x, (F32)mMouseVelX, interp)); mMouseVelY = llround(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 ) { return blockMouseEvent(x, y); } void LLMenuGL::draw( void ) { if (mNeedsArrange) { arrange(); mNeedsArrange = FALSE; } if (mDropShadowed && !mTornOff) { static S32 drop_shadow_floater = LLUI::sConfigGroup->getS32("DropShadowFloater"); static LLColor4 color_drop_shadow = LLUI::sColorsGroup->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 ); } /*LLRect rootRect = getRootView()->getRect(); for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) { LLMenuItemGL* itemp = static_cast(*child_it); if(itemp) { LLRect screenRect; localRectToScreen(itemp->getRect(),&screenRect); lldebugs << itemp->getName() << "Visible:" << itemp->getVisible() << " ValidRect: " << itemp->getRect().isValid() << " Overlaps: " << rootRect.overlaps(screenRect) << llendl; } }*/ LLView::draw(); } void LLMenuGL::drawBackground(LLMenuItemGL* itemp, LLColor4& color) { 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()) { LLView::setVisible(visible); if (!visible) { lldebugs << "Hiding " << getName() << llendl; 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 { lldebugs << "Showing " << getName() << llendl; mHasSelection = true; mFadeTimer.stop(); } } } LLMenuGL* LLMenuGL::getChildMenuByName(const std::string& name, BOOL recurse) const { LLView* view = getChildView(name, recurse, FALSE); if (view) { LLMenuItemBranchGL* branch = dynamic_cast(view); if (branch) { return branch->getBranch(); } LLMenuGL* menup = dynamic_cast(view); if (menup) { return menup; } } llwarns << "Child Menu " << name << " not found in menu " << getName() << llendl; 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) { const S32 CURSOR_HEIGHT = 22; // Approximate "normal" cursor size const S32 CURSOR_WIDTH = 12; if(menu->getChildList()->empty()) { 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 LLContextMenu::show() S32 mouse_x, mouse_y; // Resetting scrolling position if (menu->isScrollable() && menu->isScrollPositionOnShowReset()) { menu->mFirstVisibleItem = NULL; } menu->setVisible( TRUE ); // Fix menu rect if needed. menu->needsArrange(); menu->arrangeAndClear(); LLUI::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, FALSE ); menu->getParent()->sendChildToFront(menu); } //----------------------------------------------------------------------------- // class LLPieMenuBranch // A branch to another pie menu //----------------------------------------------------------------------------- class LLPieMenuBranch : public LLMenuItemGL { public: LLPieMenuBranch(const std::string& name, const std::string& label, LLPieMenu* branch); virtual LLXMLNodePtr getXML(bool save_children = true) const; // called to rebuild the draw label virtual void buildDrawLabel( void ); virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask) { LLMenuItemGL::handleMouseUp(x,y,mask); return TRUE; } // doIt() - do the primary funcationality of the menu item. virtual void doIt( void ); LLPieMenu* getBranch() { return mBranch; } protected: LLPieMenu* mBranch; }; const F32 PIE_MENU_WIDTH = 190; const F32 PIE_MENU_HEIGHT = 190; LLPieMenuBranch::LLPieMenuBranch(const std::string& name, const std::string& label, LLPieMenu* branch) : LLMenuItemGL( name, label, KEY_NONE, MASK_NONE ), mBranch( branch ) { mBranch->hide(FALSE); mBranch->setParentMenuItem(this); } // virtual LLXMLNodePtr LLPieMenuBranch::getXML(bool save_children) const { if (mBranch) { return mBranch->getXML(); } return LLMenuItemGL::getXML(); } // called to rebuild the draw label void LLPieMenuBranch::buildDrawLabel( void ) { { // default enablement is this -- if any of the subitems are // enabled, this item is enabled. JC U32 sub_count = mBranch->getItemCount(); U32 i; BOOL any_enabled = FALSE; for (i = 0; i < sub_count; i++) { LLMenuItemGL* item = mBranch->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; // No special branch suffix mDrawBranchLabel.clear(); } // doIt() - do the primary funcationality of the menu item. void LLPieMenuBranch::doIt( void ) { LLPieMenu *parent = (LLPieMenu *)getParent(); LLRect rect = parent->getRect(); S32 center_x; S32 center_y; parent->localPointToScreen(rect.getWidth() / 2, rect.getHeight() / 2, ¢er_x, ¢er_y); parent->hide(FALSE); mBranch->show( center_x, center_y, FALSE ); } //----------------------------------------------------------------------------- // class LLPieMenu // A circular menu of items, icons, etc. //----------------------------------------------------------------------------- LLPieMenu::LLPieMenu(const std::string& name, const std::string& label) : LLMenuGL(name, label), mFirstMouseDown(FALSE), mUseInfiniteRadius(FALSE), mHoverItem(NULL), mHoverThisFrame(FALSE), mHoveredAnyItem(FALSE), mOuterRingAlpha(1.f), mCurRadius(0.f), mRightMouseDown(FALSE) { setRect(LLRect(0,PIE_MENU_HEIGHT,PIE_MENU_WIDTH,0)); LLMenuGL::setVisible(FALSE); } LLPieMenu::LLPieMenu(const std::string& name) : LLMenuGL(name, name), mFirstMouseDown(FALSE), mUseInfiniteRadius(FALSE), mHoverItem(NULL), mHoverThisFrame(FALSE), mHoveredAnyItem(FALSE), mOuterRingAlpha(1.f), mCurRadius(0.f), mRightMouseDown(FALSE) { setRect(LLRect(0,PIE_MENU_HEIGHT,PIE_MENU_WIDTH,0)); LLMenuGL::setVisible(FALSE); } // virtual LLXMLNodePtr LLPieMenu::getXML(bool save_children) const { LLXMLNodePtr node = LLMenuGL::getXML(); node->setName(LL_PIE_MENU_TAG); return node; } void LLPieMenu::initXML(LLXMLNodePtr node, LLView *context, LLUICtrlFactory *factory) { LLXMLNodePtr child; for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) { if (child->hasName(LL_PIE_MENU_TAG)) { // SUBMENU std::string name("menu"); child->getAttributeString("name", name); std::string label(name); child->getAttributeString("label", label); LLPieMenu *submenu = new LLPieMenu(name, label); appendPieMenu(submenu); submenu->initXML(child, context, factory); } else { parseChildXML(child, context, factory); } } } bool LLPieMenu::addChild(LLView* view, S32 tab_group) { if(LLMenuGL::addChild(view, tab_group)) { LLMenuItemSeparatorGL* sep = dynamic_cast(view); if(sep) sep->setVisible(false); return true; } return false; } // virtual void LLPieMenu::setVisible(BOOL visible) { if (!visible) { hide(FALSE); } } BOOL LLPieMenu::handleHover( S32 x, S32 y, MASK mask ) { // This is mostly copied from the llview class, but it continues // the hover handle code after a hover handler has been found. BOOL handled = FALSE; // If we got a hover event, we've already moved the cursor // for any menu shifts, so subsequent mouseup messages will be in the // correct position. No need to correct them. //mShiftHoriz = 0; //mShiftVert = 0; // release mouse capture after short period of visibility if we're using a finite boundary // so that right click outside of boundary will trigger new pie menu if (hasMouseCapture() && !mRightMouseDown && mShrinkBorderTimer.getStarted() && mShrinkBorderTimer.getElapsedTimeF32() >= PIE_SHRINK_TIME) { gFocusMgr.setMouseCapture(NULL); mUseInfiniteRadius = FALSE; } LLMenuItemGL *item = pieItemFromXY( x, y ); if (item && item->getEnabled()) { getWindow()->setCursor(UI_CURSOR_ARROW); lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << llendl; handled = TRUE; if (item != mHoverItem) { if (mHoverItem) { mHoverItem->setHighlight( FALSE ); } mHoverItem = item; mHoverItem->setHighlight( TRUE ); switch(pieItemIndexFromXY(x, y)) { case 0: make_ui_sound("UISndPieMenuSliceHighlight0"); break; case 1: make_ui_sound("UISndPieMenuSliceHighlight1"); break; case 2: make_ui_sound("UISndPieMenuSliceHighlight2"); break; case 3: make_ui_sound("UISndPieMenuSliceHighlight3"); break; case 4: make_ui_sound("UISndPieMenuSliceHighlight4"); break; case 5: make_ui_sound("UISndPieMenuSliceHighlight5"); break; case 6: make_ui_sound("UISndPieMenuSliceHighlight6"); break; case 7: make_ui_sound("UISndPieMenuSliceHighlight7"); break; default: make_ui_sound("UISndPieMenuSliceHighlight0"); break; } } mHoveredAnyItem = TRUE; } else { // clear out our selection if (mHoverItem) { mHoverItem->setHighlight(FALSE); mHoverItem = NULL; } } if( !handled && pointInView( x, y ) ) { getWindow()->setCursor(UI_CURSOR_ARROW); lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << llendl; handled = TRUE; } mHoverThisFrame = TRUE; return handled; } BOOL LLPieMenu::handleMouseDown( S32 x, S32 y, MASK mask ) { BOOL handled = FALSE; // The click was somewhere within our rectangle LLMenuItemGL *item = pieItemFromXY( x, y ); if (item) { // lie to the item about where the click happened // to make sure it's within the item's rectangle handled = item->handleMouseDown( 0, 0, mask ); } else if (!mRightMouseDown) { // call hidemenus to make sure transient selections get cleared ((LLMenuHolderGL*)getParent())->hideMenus(); } // always handle mouse down as mouse up will close open menus return TRUE; } BOOL LLPieMenu::handleRightMouseDown(S32 x, S32 y, MASK mask) { BOOL handled = FALSE; mRightMouseDown = TRUE; // The click was somewhere within our rectangle LLMenuItemGL *item = pieItemFromXY( x, y ); S32 delta_x = x /*+ mShiftHoriz*/ - getLocalRect().getCenterX(); S32 delta_y = y /*+ mShiftVert*/ - getLocalRect().getCenterY(); BOOL clicked_in_pie = ((delta_x * delta_x) + (delta_y * delta_y) < mCurRadius*mCurRadius) || mUseInfiniteRadius; // grab mouse if right clicking anywhere within pie (even deadzone in middle), to detect drag outside of pie if (clicked_in_pie) { // capture mouse cursor as if on initial menu show gFocusMgr.setMouseCapture(this); mShrinkBorderTimer.stop(); mUseInfiniteRadius = TRUE; 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 LLPieMenu::handleRightMouseUp( S32 x, S32 y, MASK mask ) { // release mouse capture when right mouse button released, and we're past the shrink time if (mShrinkBorderTimer.getStarted() && mShrinkBorderTimer.getElapsedTimeF32() > PIE_SHRINK_TIME) { mUseInfiniteRadius = FALSE; gFocusMgr.setMouseCapture(NULL); } S32 delta_x = x /*+ mShiftHoriz*/ - getLocalRect().getCenterX(); S32 delta_y = y /*+ mShiftVert*/ - getLocalRect().getCenterY(); if (!mHoveredAnyItem && !mFirstMouseDown && (delta_x * delta_x) + (delta_y * delta_y) < PIE_CENTER_SIZE * PIE_CENTER_SIZE) { // user released right mouse button in middle of pie, interpret this as closing the menu sMenuContainer->hideMenus(); return TRUE; } BOOL result = handleMouseUp( x, y, mask ); mRightMouseDown = FALSE; mHoveredAnyItem = FALSE; return result; } BOOL LLPieMenu::handleMouseUp( S32 x, S32 y, MASK mask ) { BOOL handled = FALSE; // The click was somewhere within our rectangle LLMenuItemGL *item = pieItemFromXY( x, y ); if (item) { // lie to the item about where the click happened // to make sure it's within the item's rectangle if (item->getEnabled()) { handled = item->handleMouseUp( 0, 0, mask ); hide(TRUE); } } else if (!mRightMouseDown) { // call hidemenus to make sure transient selections get cleared ((LLMenuHolderGL*)getParent())->hideMenus(); } if (handled) { make_ui_sound("UISndClickRelease"); } if (!handled && !mUseInfiniteRadius) { // call hidemenus to make sure transient selections get cleared sMenuContainer->hideMenus(); } if (mFirstMouseDown) { make_ui_sound("UISndPieMenuAppear"); mFirstMouseDown = FALSE; } // *FIX: is this necessary? if (!mShrinkBorderTimer.getStarted()) { mShrinkBorderTimer.start(); } return handled; } // virtual void LLPieMenu::draw() { // clear hover if mouse moved away if (!mHoverThisFrame && mHoverItem) { mHoverItem->setHighlight(FALSE); mHoverItem = NULL; } F32 width = (F32) getRect().getWidth(); F32 height = (F32) getRect().getHeight(); mCurRadius = PIE_SCALE_FACTOR * llmax( width/2, height/2 ); mOuterRingAlpha = mUseInfiniteRadius ? 0.f : 1.f; if (mShrinkBorderTimer.getStarted()) { mOuterRingAlpha = clamp_rescale(mShrinkBorderTimer.getElapsedTimeF32(), 0.f, PIE_SHRINK_TIME, 0.f, 1.f); mCurRadius *= clamp_rescale(mShrinkBorderTimer.getElapsedTimeF32(), 0.f, PIE_SHRINK_TIME, 1.f, 1.f / PIE_SCALE_FACTOR); } // correct for non-square pixels F32 center_x = width/2; F32 center_y = height/2; S32 steps = 100; gGL.pushUIMatrix(); { gGL.translateUI(center_x, center_y, 0.f); F32 line_width = LLUI::sConfigGroup->getF32("PieMenuLineWidth"); LLColor4 line_color = LLUI::sColorsGroup->getColor("PieMenuLineColor"); LLColor4 bg_color = LLUI::sColorsGroup->getColor("PieMenuBgColor"); LLColor4 selected_color = LLUI::sColorsGroup->getColor("PieMenuSelectedColor"); // main body LLColor4 outer_color = bg_color; outer_color.mV[VALPHA] *= mOuterRingAlpha; gl_washer_2d( mCurRadius, (F32) PIE_CENTER_SIZE, steps, bg_color, outer_color ); // selected wedge item_list_t::iterator item_iter; S32 i = 0; for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) { if ((*item_iter)->getHighlight()) { F32 arc_size = F_PI * 0.25f; F32 start_radians = (i * arc_size) - (arc_size * 0.5f); F32 end_radians = start_radians + arc_size; LLColor4 outer_color = selected_color; outer_color.mV[VALPHA] *= mOuterRingAlpha; gl_washer_segment_2d( mCurRadius, (F32)PIE_CENTER_SIZE, start_radians, end_radians, steps / 8, selected_color, outer_color ); } i++; } LLUI::setLineWidth( line_width ); // inner lines outer_color = line_color; outer_color.mV[VALPHA] *= mOuterRingAlpha; gl_washer_spokes_2d( mCurRadius, (F32)PIE_CENTER_SIZE, 8, line_color, outer_color ); // inner circle gGL.color4fv( line_color.mV ); gl_circle_2d( 0, 0, (F32)PIE_CENTER_SIZE, steps, FALSE ); // outer circle gGL.color4fv( outer_color.mV ); gl_circle_2d( 0, 0, mCurRadius, steps, FALSE ); LLUI::setLineWidth(1.0f); } gGL.popUIMatrix(); mHoverThisFrame = FALSE; LLView::draw(); } void LLPieMenu::drawBackground(LLMenuItemGL* itemp, LLColor4& color) { F32 width = (F32) getRect().getWidth(); F32 height = (F32) getRect().getHeight(); F32 center_x = width/2; F32 center_y = height/2; S32 steps = 100; gGL.color4fv( color.mV ); gGL.pushUIMatrix(); { gGL.translateUI(center_x - itemp->getRect().mLeft, center_y - itemp->getRect().mBottom, 0.f); item_list_t::iterator item_iter; S32 i = 0; for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) { if ((*item_iter) == itemp) { F32 arc_size = F_PI * 0.25f; F32 start_radians = (i * arc_size) - (arc_size * 0.5f); F32 end_radians = start_radians + arc_size; LLColor4 outer_color = color; outer_color.mV[VALPHA] *= mOuterRingAlpha; gl_washer_segment_2d( mCurRadius, (F32)PIE_CENTER_SIZE, start_radians, end_radians, steps / 8, color, outer_color ); } i++; } } gGL.popUIMatrix(); } // virtual BOOL LLPieMenu::append(LLMenuItemGL *item) { item->setBriefItem(TRUE); item->setFont( LLFontGL::getFontSansSerifSmall() ); return LLMenuGL::append(item); } // virtual BOOL LLPieMenu::addSeparator() { LLMenuItemGL* separator = new LLMenuItemBlankGL(); separator->setFont( LLFontGL::getFontSansSerifSmall() ); return append( separator ); } BOOL LLPieMenu::appendPieMenu(LLPieMenu *menu) { if (menu == this) { llerrs << "Can't attach a pie menu to itself" << llendl; } LLPieMenuBranch *item; item = new LLPieMenuBranch(menu->getName(), menu->getLabel(), menu); getParent()->addChild(item->getBranch()); item->setFont( LLFontGL::getFontSansSerifSmall() ); return append( item ); } // virtual void LLPieMenu::arrange() { // all divide by 6 const S32 CARD_X = 60; const S32 DIAG_X = 48; const S32 CARD_Y = 76; const S32 DIAG_Y = 42; const S32 ITEM_CENTER_X[] = { CARD_X, DIAG_X, 0, -DIAG_X, -CARD_X, -DIAG_X, 0, DIAG_X }; const S32 ITEM_CENTER_Y[] = { 0, DIAG_Y, CARD_Y, DIAG_Y, 0, -DIAG_Y, -CARD_Y, -DIAG_Y }; S32 font_height = 0; if( mItems.size() ) { font_height = (*mItems.begin())->getNominalHeight(); } S32 item_width = 0; // F32 sin_delta = OO_SQRT2; // sin(45 deg) // F32 cos_delta = OO_SQRT2; // cos(45 deg) // TODO: Compute actual bounding rect for menu LLRect rect = getRect(); // place items around a circle, with item 0 at positive X, // rotating counter-clockwise item_list_t::iterator item_iter; S32 i = 0; for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) { LLMenuItemGL *item = *item_iter; item_width = item->getNominalWidth(); // Put in the right place around a circle centered at 0,0 rect.setCenterAndSize(ITEM_CENTER_X[i], ITEM_CENTER_Y[i], item_width, font_height ); // Correct for the actual rectangle size rect.translate( getRect().getWidth()/2, getRect().getHeight()/2 ); item->setRect( rect ); // Make sure enablement is correct item->buildDrawLabel(); i++; } } LLMenuItemGL *LLPieMenu::pieItemFromXY(S32 x, S32 y) { // We might have shifted this menu on draw. If so, we need // to shift over mouseup events until we get a hover event. //x += mShiftHoriz; //y += mShiftVert; // An arc of the pie menu is 45 degrees const F32 ARC_DEG = 45.f; S32 delta_x = x - getRect().getWidth() / 2; S32 delta_y = y - getRect().getHeight() / 2; // circle safe zone in the center S32 dist_squared = delta_x*delta_x + delta_y*delta_y; if (dist_squared < PIE_CENTER_SIZE*PIE_CENTER_SIZE) { return NULL; } // infinite radius is only used with right clicks S32 radius = llmax( getRect().getWidth()/2, getRect().getHeight()/2 ); if (!(mUseInfiniteRadius && mRightMouseDown) && dist_squared > radius * radius) { return NULL; } F32 angle = RAD_TO_DEG * (F32) atan2((F32)delta_y, (F32)delta_x); // rotate marks CCW so that east = [0, ARC_DEG) instead of // [-ARC_DEG/2, ARC_DEG/2) angle += ARC_DEG / 2.f; // make sure we're only using positive angles if (angle < 0.f) angle += 360.f; S32 which = S32( angle / ARC_DEG ); if (0 <= which && which < (S32)mItems.size() ) { item_list_t::iterator item_iter; for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) { if (which == 0) { if((*item_iter)->getVisible()) return (*item_iter); else return NULL; } which--; } } return NULL; } S32 LLPieMenu::pieItemIndexFromXY(S32 x, S32 y) { // An arc of the pie menu is 45 degrees const F32 ARC_DEG = 45.f; // correct for non-square pixels S32 delta_x = x - getRect().getWidth() / 2; S32 delta_y = y - getRect().getHeight() / 2; // circle safe zone in the center if (delta_x*delta_x + delta_y*delta_y < PIE_CENTER_SIZE*PIE_CENTER_SIZE) { return -1; } F32 angle = RAD_TO_DEG * (F32) atan2((F32)delta_y, (F32)delta_x); // rotate marks CCW so that east = [0, ARC_DEG) instead of // [-ARC_DEG/2, ARC_DEG/2) angle += ARC_DEG / 2.f; // make sure we're only using positive angles if (angle < 0.f) angle += 360.f; S32 which = S32( angle / ARC_DEG ); return which; } void LLPieMenu::show(S32 x, S32 y, BOOL mouse_down) { S32 width = getRect().getWidth(); S32 height = getRect().getHeight(); const LLRect menu_region_rect = LLMenuGL::sMenuContainer->getMenuRect(); LLView* parent_view = getParent(); S32 local_x, local_y; parent_view->screenPointToLocal(x, y, &local_x, &local_y); LLRect rect; rect.setCenterAndSize(local_x, local_y, width, height); setRect(rect); arrange(); // Adjust the pie rectangle to keep it on screen if(!menu_region_rect.contains(rect)) { S32 trans[2]={0,0}; if (rect.mLeft < menu_region_rect.mLeft) { trans[0] = menu_region_rect.mLeft - rect.mLeft; } else if (rect.mRight > menu_region_rect.mRight) { trans[0] = menu_region_rect.mRight - rect.mRight; } if (rect.mBottom < menu_region_rect.mBottom) { trans[1] = menu_region_rect.mBottom - rect.mBottom; } else if (rect.mTop > menu_region_rect.mTop) { trans[1] = menu_region_rect.mTop - rect.mTop; } setRect(rect.translate(trans[0],trans[1])); LLUI::setMousePositionLocal(getParent(),rect.getCenterX(), rect.getCenterY()); } // *FIX: what happens when mouse buttons reversed? mRightMouseDown = mouse_down; mFirstMouseDown = mouse_down; mUseInfiniteRadius = TRUE; mHoveredAnyItem = FALSE; if (!mFirstMouseDown) { make_ui_sound("UISndPieMenuAppear"); } LLView::setVisible(TRUE); // we want all mouse events in case user does quick right click again off of pie menu // rectangle, to support gestural menu traversal gFocusMgr.setMouseCapture(this); if (mouse_down) { mShrinkBorderTimer.stop(); } else { mShrinkBorderTimer.start(); } } void LLPieMenu::hide(BOOL item_selected) { if (!getVisible()) return; if (mHoverItem) { mHoverItem->setHighlight( FALSE ); mHoverItem = NULL; } make_ui_sound("UISndPieMenuHide"); mFirstMouseDown = FALSE; mRightMouseDown = FALSE; mUseInfiniteRadius = FALSE; mHoveredAnyItem = FALSE; LLView::setVisible(FALSE); gFocusMgr.setMouseCapture(NULL); } ///============================================================================ /// Class LLMenuBarGL ///============================================================================ static LLRegisterWidget r2("menu_bar"); // Default constructor LLMenuBarGL::LLMenuBarGL( const std::string& name ) : LLMenuGL ( name, name ) { mHorizontalLayout = TRUE; mKeepFixedSize = TRUE; mAltKeyTrigger = FALSE; } // Default destructor LLMenuBarGL::~LLMenuBarGL() { std::for_each(mAccelerators.begin(), mAccelerators.end(), DeletePointer()); mAccelerators.clear(); } // virtual LLXMLNodePtr LLMenuBarGL::getXML(bool save_children) const { // Sorty of hacky: reparent items to this and then back at the end of the export LLView *orig_parent = NULL; item_list_t::const_iterator item_iter; for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) { LLMenuItemGL* child = *item_iter; LLMenuItemBranchGL* branch = (LLMenuItemBranchGL*)child; LLMenuGL *menu = branch->getBranch(); orig_parent = menu->getParent(); menu->updateParent((LLView *)this); } LLXMLNodePtr node = LLMenuGL::getXML(); node->setName(LL_MENU_BAR_GL_TAG); for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) { LLMenuItemGL* child = *item_iter; LLMenuItemBranchGL* branch = (LLMenuItemBranchGL*)child; LLMenuGL *menu = branch->getBranch(); menu->updateParent(orig_parent); } return node; } LLView* LLMenuBarGL::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) { BOOL opaque = FALSE; node->getAttributeBOOL("opaque", opaque); LLMenuBarGL *menubar = new LLMenuBarGL("menu"); LLHandle parent_handle; LLFloater* parent_floater = dynamic_cast(parent); if (parent_floater) { parent_handle = parent_floater->getHandle(); } // We need to have the rect early so that it's around when building // the menu items LLRect view_rect; createRect(node, view_rect, parent, menubar->getRequiredRect()); menubar->setRect(view_rect); if (node->hasAttribute("drop_shadow")) { BOOL drop_shadow = FALSE; node->getAttributeBOOL("drop_shadow", drop_shadow); menubar->setDropShadowed(drop_shadow); } menubar->setBackgroundVisible(opaque); LLColor4 color(0,0,0,0); if (opaque && LLUICtrlFactory::getAttributeColor(node,"color", color)) { menubar->setBackgroundColor(color); } LLXMLNodePtr child; for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) { if (child->hasName("menu")) { LLMenuGL *menu = (LLMenuGL*)LLMenuGL::fromXML(child, parent, factory); menubar->appendMenu(menu); if (LLMenuGL::sMenuContainer != NULL) { menu->updateParent(LLMenuGL::sMenuContainer); } else { menu->updateParent(parent); } } } menubar->initFromXML(node, parent); BOOL create_jump_keys = FALSE; node->getAttributeBOOL("create_jump_keys", create_jump_keys); if (create_jump_keys) { menubar->createJumpKeys(); } return menubar; } 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(); } else { // close menus originating from other menu bars when first opening menu via keyboard LLMenuGL::sMenuContainer->hideMenus(); highlightNextItem(NULL); LLMenuGL::setKeyboardMode(TRUE); } return TRUE; } return result; } BOOL LLMenuBarGL::handleKeyHere(KEY key, MASK mask) { if(key == KEY_ALT && !gKeyboard->getKeyRepeated(key) && LLUI::sConfigGroup->getBOOL("UseAltKeyForMenus")) { 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->doIt(); } 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); } void LLMenuBarGL::setVisible(BOOL visible) { if(visible != getVisible()) { if(!visible) { lldebugs << "Hiding " << getName() << llendl; } else { lldebugs << "Showing " << getName() << llendl; } } LLUICtrl::setVisible(visible); } 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 if (gKeyboard->getKeyElapsedTime(KEY_ALT) <= LLUI::sConfigGroup->getF32("MenuAccessKeyTime") || 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 ) { llerrs << "** Attempt to attach menu to itself. This is certainly " << "a logic error." << llendl; } BOOL success = TRUE; LLMenuItemBranchGL* branch = NULL; branch = new LLMenuItemBranchDownGL( menu->getName(), menu->getLabel(), menu->getHandle()); 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)->doIt(); 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() : LLPanel(std::string("Menu Holder")) { setMouseOpaque(FALSE); sItemActivationTimer.stop(); mCanHide = TRUE; } LLMenuHolderGL::LLMenuHolderGL(const std::string& name, const LLRect& rect, BOOL mouse_opaque, U32 follows) : LLPanel(name, rect, FALSE) { setMouseOpaque(mouse_opaque); 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; F32 alpha = lerp(LLMenuItemGL::sHighlightBackground.mV[VALPHA], 0.f, interpolant); LLColor4 bg_color(LLMenuItemGL::sHighlightBackground.mV[VRED], LLMenuItemGL::sHighlightBackground.mV[VGREEN], LLMenuItemGL::sHighlightBackground.mV[VBLUE], alpha); LLUI::pushMatrix(); { LLUI::translate((F32)item_rect.mLeft, (F32)item_rect.mBottom, 0.f); selecteditem->getMenu()->drawBackground(selecteditem, bg_color); selecteditem->draw(); } LLUI::popMatrix(); } } BOOL LLMenuHolderGL::handleMouseDown( S32 x, S32 y, MASK mask ) { BOOL handled = LLView::childrenHandleMouseDown(x, y, mask) != NULL; if (!handled) { // 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 { //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 && !dynamic_cast(viewp)) { return viewp; } } return NULL; } BOOL LLMenuHolderGL::hideMenus() { if (!mCanHide) { return FALSE; } sItemActivationTimer.stop(); BOOL menu_visible = hasVisibleMenu(); if (menu_visible) { LLMenuGL::setKeyboardMode(FALSE); // 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() && !dynamic_cast(viewp)) { 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(menup->getName(), LLRect(0, 100, 100, 0), menup->getLabel(), FALSE, DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT, FALSE, FALSE) { S32 floater_header_size = LLFLOATER_HEADER_SIZE; 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 = (F32)(rect.getHeight() + floater_header_size); reshape(rect.getWidth(), rect.getHeight()); setRect(rect); // attach menu to floater menup->setFollows(FOLLOWS_BOTTOM|FOLLOWS_LEFT); 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); } void LLTearOffMenu::draw() { mMenu->setBackgroundVisible(isBackgroundOpaque()); mMenu->needsArrange(); if (getRect().getHeight() != mTargetHeight) { // animate towards target height reshape(getRect().getWidth(), llceil(lerp((F32)getRect().getHeight(), mTargetHeight, LLCriticalDamp::getInterpolant(0.05f)))); } LLFloater::draw(); } void LLTearOffMenu::onFocusReceived() { // 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->open(); /* Flawfinder: ignore */ return tearoffp; } void LLTearOffMenu::onClose(bool app_quitting) { removeChild(mMenu); mOldParent->addChild(mMenu); mMenu->clearHoverItem(); mMenu->setFollowsNone(); mMenu->setBackgroundVisible(TRUE); mMenu->setVisible(FALSE); mMenu->setTornOff(FALSE); mMenu->setDropShadowed(TRUE); destroy(); }