/** * @file llbutton.cpp * @brief LLButton base class * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "linden_common.h" #define LLBUTTON_CPP #include "llbutton.h" // Linden library includes #include "v4color.h" #include "llstring.h" // Project includes #include "llkeyboard.h" #include "llui.h" #include "lluiconstants.h" #include "llresmgr.h" #include "llcriticaldamp.h" #include "llfloater.h" #include "llfloaterreg.h" #include "llfocusmgr.h" #include "llwindow.h" #include "llnotificationsutil.h" #include "llrender.h" #include "lluictrlfactory.h" #include "llhelp.h" #include "lldockablefloater.h" static LLDefaultChildRegistry::Register r("button"); // Compiler optimization, generate extern template template class LLButton* LLView::getChild( const std::string& name, BOOL recurse) const; // globals loaded from settings.xml S32 LLBUTTON_H_PAD = 0; S32 BTN_HEIGHT_SMALL= 0; S32 BTN_HEIGHT = 0; LLButton::Params::Params() : label_selected("label_selected"), // requires is_toggle true label_shadow("label_shadow", true), auto_resize("auto_resize", false), use_ellipses("use_ellipses", false), image_unselected("image_unselected"), image_selected("image_selected"), image_hover_selected("image_hover_selected"), image_hover_unselected("image_hover_unselected"), image_disabled_selected("image_disabled_selected"), image_disabled("image_disabled"), image_pressed("image_pressed"), image_pressed_selected("image_pressed_selected"), image_overlay("image_overlay"), image_overlay_alignment("image_overlay_alignment", std::string("center")), image_top_pad("image_top_pad"), image_bottom_pad("image_bottom_pad"), imgoverlay_label_space("imgoverlay_label_space", 1), label_color("label_color"), label_color_selected("label_color_selected"), // requires is_toggle true label_color_disabled("label_color_disabled"), label_color_disabled_selected("label_color_disabled_selected"), image_color("image_color"), image_color_disabled("image_color_disabled"), image_overlay_color("image_overlay_color", LLColor4::white % 0.75f), image_overlay_disabled_color("image_overlay_disabled_color", LLColor4::white % 0.3f), image_overlay_selected_color("image_overlay_selected_color", LLColor4::white), flash_color("flash_color"), pad_right("pad_right", LLUI::sSettingGroups["config"]->getS32("ButtonHPad")), pad_left("pad_left", LLUI::sSettingGroups["config"]->getS32("ButtonHPad")), pad_bottom("pad_bottom"), click_callback("click_callback"), mouse_down_callback("mouse_down_callback"), mouse_up_callback("mouse_up_callback"), mouse_held_callback("mouse_held_callback"), is_toggle("is_toggle", false), scale_image("scale_image", true), hover_glow_amount("hover_glow_amount"), commit_on_return("commit_on_return", true), display_pressed_state("display_pressed_state", true), use_draw_context_alpha("use_draw_context_alpha", true), badge("badge"), handle_right_mouse("handle_right_mouse"), held_down_delay("held_down_delay"), button_flash_count("button_flash_count"), button_flash_rate("button_flash_rate") { addSynonym(is_toggle, "toggle"); changeDefault(initial_value, LLSD(false)); } LLButton::LLButton(const LLButton::Params& p) : LLUICtrl(p), LLBadgeOwner(getHandle()), mMouseDownFrame(0), mMouseHeldDownCount(0), mBorderEnabled( FALSE ), mFlashing( FALSE ), mCurGlowStrength(0.f), mNeedsHighlight(FALSE), mUnselectedLabel(p.label()), mSelectedLabel(p.label_selected()), mGLFont(p.font), mHeldDownDelay(p.held_down_delay.seconds), // seconds until held-down callback is called mHeldDownFrameDelay(p.held_down_delay.frames), mImageUnselected(p.image_unselected), mImageSelected(p.image_selected), mImageDisabled(p.image_disabled), mImageDisabledSelected(p.image_disabled_selected), mImageFlash(p.image_flash), mImagePressed(p.image_pressed), mImagePressedSelected(p.image_pressed_selected), mImageHoverSelected(p.image_hover_selected), mImageHoverUnselected(p.image_hover_unselected), mUnselectedLabelColor(p.label_color()), mSelectedLabelColor(p.label_color_selected()), mDisabledLabelColor(p.label_color_disabled()), mDisabledSelectedLabelColor(p.label_color_disabled_selected()), mImageColor(p.image_color()), mFlashBgColor(p.flash_color()), mDisabledImageColor(p.image_color_disabled()), mImageOverlay(p.image_overlay()), mImageOverlayColor(p.image_overlay_color()), mImageOverlayDisabledColor(p.image_overlay_disabled_color()), mImageOverlaySelectedColor(p.image_overlay_selected_color()), mImageOverlayAlignment(LLFontGL::hAlignFromName(p.image_overlay_alignment)), mImageOverlayTopPad(p.image_top_pad), mImageOverlayBottomPad(p.image_bottom_pad), mImgOverlayLabelSpace(p.imgoverlay_label_space), mIsToggle(p.is_toggle), mScaleImage(p.scale_image), mDropShadowedText(p.label_shadow), mAutoResize(p.auto_resize), mUseEllipses( p.use_ellipses ), mHAlign(p.font_halign), mLeftHPad(p.pad_left), mRightHPad(p.pad_right), mBottomVPad(p.pad_bottom), mHoverGlowStrength(p.hover_glow_amount), mCommitOnReturn(p.commit_on_return), mFadeWhenDisabled(FALSE), mForcePressedState(false), mDisplayPressedState(p.display_pressed_state), mLastDrawCharsCount(0), mMouseDownSignal(NULL), mMouseUpSignal(NULL), mHeldDownSignal(NULL), mUseDrawContextAlpha(p.use_draw_context_alpha), mHandleRightMouse(p.handle_right_mouse), mButtonFlashCount(p.button_flash_count), mButtonFlashRate(p.button_flash_rate) { static LLUICachedControl llbutton_orig_h_pad ("UIButtonOrigHPad", 0); static Params default_params(LLUICtrlFactory::getDefaultParams()); if (!p.label_selected.isProvided()) { mSelectedLabel = mUnselectedLabel; } // Hack to make sure there is space for at least one character if (getRect().getWidth() - (mRightHPad + mLeftHPad) < mGLFont->getWidth(std::string(" "))) { // Use old defaults mLeftHPad = llbutton_orig_h_pad; mRightHPad = llbutton_orig_h_pad; } mMouseDownTimer.stop(); // if custom unselected button image provided... if (p.image_unselected != default_params.image_unselected) { //...fade it out for disabled image by default... if (p.image_disabled() == default_params.image_disabled() ) { mImageDisabled = p.image_unselected; mFadeWhenDisabled = TRUE; } if (p.image_pressed_selected == default_params.image_pressed_selected) { mImagePressedSelected = mImageUnselected; } } // if custom selected button image provided... if (p.image_selected != default_params.image_selected) { //...fade it out for disabled image by default... if (p.image_disabled_selected() == default_params.image_disabled_selected()) { mImageDisabledSelected = p.image_selected; mFadeWhenDisabled = TRUE; } if (p.image_pressed == default_params.image_pressed) { mImagePressed = mImageSelected; } } if (!p.image_pressed.isProvided()) { mImagePressed = mImageSelected; } if (!p.image_pressed_selected.isProvided()) { mImagePressedSelected = mImageUnselected; } if (mImageUnselected.isNull()) { llwarns << "Button: " << getName() << " with no image!" << llendl; } if (p.click_callback.isProvided()) { setCommitCallback(initCommitCallback(p.click_callback)); // alias -> commit_callback } if (p.mouse_down_callback.isProvided()) { setMouseDownCallback(initCommitCallback(p.mouse_down_callback)); } if (p.mouse_up_callback.isProvided()) { setMouseUpCallback(initCommitCallback(p.mouse_up_callback)); } if (p.mouse_held_callback.isProvided()) { setHeldDownCallback(initCommitCallback(p.mouse_held_callback)); } if (p.badge.isProvided()) { LLBadgeOwner::initBadgeParams(p.badge()); } } LLButton::~LLButton() { delete mMouseDownSignal; delete mMouseUpSignal; delete mHeldDownSignal; } // HACK: Committing a button is the same as instantly clicking it. // virtual void LLButton::onCommit() { // WARNING: Sometimes clicking a button destroys the floater or // panel containing it. Therefore we need to call LLUICtrl::onCommit() // LAST, otherwise this becomes deleted memory. if (mMouseDownSignal) (*mMouseDownSignal)(this, LLSD()); if (mMouseUpSignal) (*mMouseUpSignal)(this, LLSD()); if (getSoundFlags() & MOUSE_DOWN) { make_ui_sound("UISndClick"); } if (getSoundFlags() & MOUSE_UP) { make_ui_sound("UISndClickRelease"); } if (mIsToggle) { toggleState(); } // do this last, as it can result in destroying this button LLUICtrl::onCommit(); } boost::signals2::connection LLButton::setClickedCallback(const CommitCallbackParam& cb) { return setClickedCallback(initCommitCallback(cb)); } boost::signals2::connection LLButton::setMouseDownCallback(const CommitCallbackParam& cb) { return setMouseDownCallback(initCommitCallback(cb)); } boost::signals2::connection LLButton::setMouseUpCallback(const CommitCallbackParam& cb) { return setMouseUpCallback(initCommitCallback(cb)); } boost::signals2::connection LLButton::setHeldDownCallback(const CommitCallbackParam& cb) { return setHeldDownCallback(initCommitCallback(cb)); } boost::signals2::connection LLButton::setClickedCallback( const commit_signal_t::slot_type& cb ) { if (!mCommitSignal) mCommitSignal = new commit_signal_t(); return mCommitSignal->connect(cb); } boost::signals2::connection LLButton::setMouseDownCallback( const commit_signal_t::slot_type& cb ) { if (!mMouseDownSignal) mMouseDownSignal = new commit_signal_t(); return mMouseDownSignal->connect(cb); } boost::signals2::connection LLButton::setMouseUpCallback( const commit_signal_t::slot_type& cb ) { if (!mMouseUpSignal) mMouseUpSignal = new commit_signal_t(); return mMouseUpSignal->connect(cb); } boost::signals2::connection LLButton::setHeldDownCallback( const commit_signal_t::slot_type& cb ) { if (!mHeldDownSignal) mHeldDownSignal = new commit_signal_t(); return mHeldDownSignal->connect(cb); } // *TODO: Deprecate (for backwards compatibility only) boost::signals2::connection LLButton::setClickedCallback( button_callback_t cb, void* data ) { return setClickedCallback(boost::bind(cb, data)); } boost::signals2::connection LLButton::setMouseDownCallback( button_callback_t cb, void* data ) { return setMouseDownCallback(boost::bind(cb, data)); } boost::signals2::connection LLButton::setMouseUpCallback( button_callback_t cb, void* data ) { return setMouseUpCallback(boost::bind(cb, data)); } boost::signals2::connection LLButton::setHeldDownCallback( button_callback_t cb, void* data ) { return setHeldDownCallback(boost::bind(cb, data)); } BOOL LLButton::postBuild() { autoResize(); addBadgeToParentPanel(); return LLUICtrl::postBuild(); } BOOL LLButton::handleUnicodeCharHere(llwchar uni_char) { BOOL handled = FALSE; if(' ' == uni_char && !gKeyboard->getKeyRepeated(' ')) { if (mIsToggle) { toggleState(); } LLUICtrl::onCommit(); handled = TRUE; } return handled; } BOOL LLButton::handleKeyHere(KEY key, MASK mask ) { BOOL handled = FALSE; if( mCommitOnReturn && KEY_RETURN == key && mask == MASK_NONE && !gKeyboard->getKeyRepeated(key)) { if (mIsToggle) { toggleState(); } handled = TRUE; LLUICtrl::onCommit(); } return handled; } BOOL LLButton::handleMouseDown(S32 x, S32 y, MASK mask) { if (!childrenHandleMouseDown(x, y, mask)) { // Route future Mouse messages here preemptively. (Release on mouse up.) gFocusMgr.setMouseCapture( this ); if (hasTabStop() && !getIsChrome()) { setFocus(TRUE); } /* * ATTENTION! This call fires another mouse down callback. * If you wish to remove this call emit that signal directly * by calling LLUICtrl::mMouseDownSignal(x, y, mask); */ LLUICtrl::handleMouseDown(x, y, mask); if(mMouseDownSignal) (*mMouseDownSignal)(this, LLSD()); mMouseDownTimer.start(); mMouseDownFrame = (S32) LLFrameTimer::getFrameCount(); mMouseHeldDownCount = 0; if (getSoundFlags() & MOUSE_DOWN) { make_ui_sound("UISndClick"); } } return TRUE; } BOOL LLButton::handleMouseUp(S32 x, S32 y, MASK mask) { // We only handle the click if the click both started and ended within us if( hasMouseCapture() ) { // Always release the mouse gFocusMgr.setMouseCapture( NULL ); /* * ATTENTION! This call fires another mouse up callback. * If you wish to remove this call emit that signal directly * by calling LLUICtrl::mMouseUpSignal(x, y, mask); */ LLUICtrl::handleMouseUp(x, y, mask); // Regardless of where mouseup occurs, handle callback if(mMouseUpSignal) (*mMouseUpSignal)(this, LLSD()); resetMouseDownTimer(); // DO THIS AT THE VERY END to allow the button to be destroyed as a result of being clicked. // If mouseup in the widget, it's been clicked if (pointInView(x, y)) { if (getSoundFlags() & MOUSE_UP) { make_ui_sound("UISndClickRelease"); } if (mIsToggle) { toggleState(); } LLUICtrl::onCommit(); } } else { childrenHandleMouseUp(x, y, mask); } return TRUE; } BOOL LLButton::handleRightMouseDown(S32 x, S32 y, MASK mask) { if (mHandleRightMouse && !childrenHandleRightMouseDown(x, y, mask)) { // Route future Mouse messages here preemptively. (Release on mouse up.) gFocusMgr.setMouseCapture( this ); if (hasTabStop() && !getIsChrome()) { setFocus(TRUE); } // if (pointInView(x, y)) // { // } // send the mouse down signal LLUICtrl::handleRightMouseDown(x,y,mask); // *TODO: Return result of LLUICtrl call above? Should defer to base class // but this might change the mouse handling of existing buttons in a bad way // if they are not mouse opaque. } return TRUE; } BOOL LLButton::handleRightMouseUp(S32 x, S32 y, MASK mask) { if (mHandleRightMouse) { // We only handle the click if the click both started and ended within us if( hasMouseCapture() ) { // Always release the mouse gFocusMgr.setMouseCapture( NULL ); // if (pointInView(x, y)) // { // mRightMouseUpSignal(this, x,y,mask); // } } else { childrenHandleRightMouseUp(x, y, mask); } // send the mouse up signal LLUICtrl::handleRightMouseUp(x,y,mask); // *TODO: Return result of LLUICtrl call above? Should defer to base class // but this might change the mouse handling of existing buttons in a bad way. // if they are not mouse opaque. } return TRUE; } void LLButton::onMouseLeave(S32 x, S32 y, MASK mask) { LLUICtrl::onMouseLeave(x, y, mask); mNeedsHighlight = FALSE; } void LLButton::setHighlight(bool b) { mNeedsHighlight = b; } BOOL LLButton::handleHover(S32 x, S32 y, MASK mask) { if (isInEnabledChain() && (!gFocusMgr.getMouseCapture() || gFocusMgr.getMouseCapture() == this)) mNeedsHighlight = TRUE; if (!childrenHandleHover(x, y, mask)) { if (mMouseDownTimer.getStarted()) { F32 elapsed = getHeldDownTime(); if( mHeldDownDelay <= elapsed && mHeldDownFrameDelay <= (S32)LLFrameTimer::getFrameCount() - mMouseDownFrame) { LLSD param; param["count"] = mMouseHeldDownCount++; if (mHeldDownSignal) (*mHeldDownSignal)(this, param); } } // We only handle the click if the click both started and ended within us getWindow()->setCursor(UI_CURSOR_ARROW); lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << llendl; } return TRUE; } void LLButton::getOverlayImageSize(S32& overlay_width, S32& overlay_height) { overlay_width = mImageOverlay->getWidth(); overlay_height = mImageOverlay->getHeight(); F32 scale_factor = llmin((F32)getRect().getWidth() / (F32)overlay_width, (F32)getRect().getHeight() / (F32)overlay_height, 1.f); overlay_width = llround((F32)overlay_width * scale_factor); overlay_height = llround((F32)overlay_height * scale_factor); } // virtual void LLButton::draw() { static LLCachedControl sEnableButtonFlashing(*LLUI::sSettingGroups["config"], "EnableButtonFlashing", true); F32 alpha = mUseDrawContextAlpha ? getDrawContext().mAlpha : getCurrentTransparency(); bool flash = FALSE; if( mFlashing) { if ( sEnableButtonFlashing) { F32 elapsed = mFlashingTimer.getElapsedTimeF32(); S32 flash_count = S32(elapsed * mButtonFlashRate * 2.f); // flash on or off? flash = (flash_count % 2 == 0) || flash_count > S32((F32)mButtonFlashCount * 2.f); } else { // otherwise just highlight button in flash color flash = true; } } bool pressed_by_keyboard = FALSE; if (hasFocus()) { pressed_by_keyboard = gKeyboard->getKeyDown(' ') || (mCommitOnReturn && gKeyboard->getKeyDown(KEY_RETURN)); } bool mouse_pressed_and_over = false; if (hasMouseCapture()) { S32 local_mouse_x ; S32 local_mouse_y; LLUI::getMousePositionLocal(this, &local_mouse_x, &local_mouse_y); mouse_pressed_and_over = pointInView(local_mouse_x, local_mouse_y); } bool enabled = isInEnabledChain(); bool pressed = pressed_by_keyboard || mouse_pressed_and_over || mForcePressedState; bool selected = getToggleState(); bool use_glow_effect = FALSE; LLColor4 glow_color = LLColor4::white; LLRender::eBlendType glow_type = LLRender::BT_ADD_WITH_ALPHA; LLUIImage* imagep = NULL; if (pressed && mDisplayPressedState) { imagep = selected ? mImagePressedSelected : mImagePressed; } else if ( mNeedsHighlight ) { if (selected) { if (mImageHoverSelected) { imagep = mImageHoverSelected; } else { imagep = mImageSelected; use_glow_effect = TRUE; } } else { if (mImageHoverUnselected) { imagep = mImageHoverUnselected; } else { imagep = mImageUnselected; use_glow_effect = TRUE; } } } else { imagep = selected ? mImageSelected : mImageUnselected; } // Override if more data is available // HACK: Use gray checked state to mean either: // enabled and tentative // or // disabled but checked if (!mImageDisabledSelected.isNull() && ( (enabled && getTentative()) || (!enabled && selected ) ) ) { imagep = mImageDisabledSelected; } else if (!mImageDisabled.isNull() && !enabled && !selected) { imagep = mImageDisabled; } if (mFlashing) { // if button should flash and we have icon for flashing, use it as image for button if(flash && mImageFlash) { // setting flash to false to avoid its further influence on glow flash = false; imagep = mImageFlash; } // else use usual flashing via flash_color else { LLColor4 flash_color = mFlashBgColor.get(); use_glow_effect = TRUE; glow_type = LLRender::BT_ALPHA; // blend the glow if (mNeedsHighlight) // highlighted AND flashing glow_color = (glow_color*0.5f + flash_color*0.5f) % 2.0f; // average between flash and highlight colour, with sum of the opacity else glow_color = flash_color; } } if (mNeedsHighlight && !imagep) { use_glow_effect = TRUE; } // Figure out appropriate color for the text LLColor4 label_color; // label changes when button state changes, not when pressed if ( enabled ) { if ( getToggleState() ) { label_color = mSelectedLabelColor.get(); } else { label_color = mUnselectedLabelColor.get(); } } else { if ( getToggleState() ) { label_color = mDisabledSelectedLabelColor.get(); } else { label_color = mDisabledLabelColor.get(); } } // Unselected label assignments LLWString label = getCurrentLabel(); // overlay with keyboard focus border if (hasFocus()) { F32 lerp_amt = gFocusMgr.getFocusFlashAmt(); drawBorder(imagep, gFocusMgr.getFocusColor() % alpha, llround(lerp(1.f, 3.f, lerp_amt))); } if (use_glow_effect) { mCurGlowStrength = lerp(mCurGlowStrength, mFlashing ? (flash? 1.0 : 0.0) : mHoverGlowStrength, LLSmoothInterpolation::getInterpolant(0.05f)); } else { mCurGlowStrength = lerp(mCurGlowStrength, 0.f, LLSmoothInterpolation::getInterpolant(0.05f)); } // Draw button image, if available. // Otherwise draw basic rectangular button. if (imagep != NULL) { // apply automatic 50% alpha fade to disabled image LLColor4 disabled_color = mFadeWhenDisabled ? mDisabledImageColor.get() % 0.5f : mDisabledImageColor.get(); if ( mScaleImage) { imagep->draw(getLocalRect(), (enabled ? mImageColor.get() : disabled_color) % alpha ); if (mCurGlowStrength > 0.01f) { gGL.setSceneBlendType(glow_type); imagep->drawSolid(0, 0, getRect().getWidth(), getRect().getHeight(), glow_color % (mCurGlowStrength * alpha)); gGL.setSceneBlendType(LLRender::BT_ALPHA); } } else { imagep->draw(0, 0, (enabled ? mImageColor.get() : disabled_color) % alpha ); if (mCurGlowStrength > 0.01f) { gGL.setSceneBlendType(glow_type); imagep->drawSolid(0, 0, glow_color % (mCurGlowStrength * alpha)); gGL.setSceneBlendType(LLRender::BT_ALPHA); } } } else { // no image lldebugs << "No image for button " << getName() << llendl; // draw it in pink so we can find it gl_rect_2d(0, getRect().getHeight(), getRect().getWidth(), 0, LLColor4::pink1 % alpha, FALSE); } // let overlay image and text play well together S32 text_left = mLeftHPad; S32 text_right = getRect().getWidth() - mRightHPad; S32 text_width = getRect().getWidth() - mLeftHPad - mRightHPad; // draw overlay image if (mImageOverlay.notNull()) { // get max width and height (discard level 0) S32 overlay_width; S32 overlay_height; getOverlayImageSize(overlay_width, overlay_height); S32 center_x = getLocalRect().getCenterX(); S32 center_y = getLocalRect().getCenterY(); //FUGLY HACK FOR "DEPRESSED" BUTTONS if (pressed && mDisplayPressedState) { center_y--; center_x++; } center_y += (mImageOverlayBottomPad - mImageOverlayTopPad); // fade out overlay images on disabled buttons LLColor4 overlay_color = mImageOverlayColor.get(); if (!enabled) { overlay_color = mImageOverlayDisabledColor.get(); } else if (getToggleState()) { overlay_color = mImageOverlaySelectedColor.get(); } overlay_color.mV[VALPHA] *= alpha; switch(mImageOverlayAlignment) { case LLFontGL::LEFT: text_left += overlay_width + mImgOverlayLabelSpace; text_width -= overlay_width + mImgOverlayLabelSpace; mImageOverlay->draw( mLeftHPad, center_y - (overlay_height / 2), overlay_width, overlay_height, overlay_color); break; case LLFontGL::HCENTER: mImageOverlay->draw( center_x - (overlay_width / 2), center_y - (overlay_height / 2), overlay_width, overlay_height, overlay_color); break; case LLFontGL::RIGHT: text_right -= overlay_width + mImgOverlayLabelSpace; text_width -= overlay_width + mImgOverlayLabelSpace; mImageOverlay->draw( getRect().getWidth() - mRightHPad - overlay_width, center_y - (overlay_height / 2), overlay_width, overlay_height, overlay_color); break; default: // draw nothing break; } } // Draw label if( !label.empty() ) { LLWStringUtil::trim(label); S32 x; switch( mHAlign ) { case LLFontGL::RIGHT: x = text_right; break; case LLFontGL::HCENTER: x = text_left + (text_width / 2); break; case LLFontGL::LEFT: default: x = text_left; break; } S32 y_offset = 2 + (getRect().getHeight() - 20)/2; if (pressed && mDisplayPressedState) { y_offset--; x++; } // *NOTE: mantipov: before mUseEllipses is implemented in EXT-279 U32_MAX has been passed as // max_chars. // LLFontGL::render expects S32 max_chars variable but process in a separate way -1 value. // Due to U32_MAX is equal to S32 -1 value I have rest this value for non-ellipses mode. // Not sure if it is really needed. Probably S32_MAX should be always passed as max_chars. mLastDrawCharsCount = mGLFont->render(label, 0, (F32)x, (F32)(getRect().getHeight() / 2 + mBottomVPad), label_color % alpha, mHAlign, LLFontGL::VCENTER, LLFontGL::NORMAL, mDropShadowedText ? LLFontGL::DROP_SHADOW_SOFT : LLFontGL::NO_SHADOW, S32_MAX, text_width, NULL, mUseEllipses); } LLUICtrl::draw(); } void LLButton::drawBorder(LLUIImage* imagep, const LLColor4& color, S32 size) { if (imagep == NULL) return; if (mScaleImage) { imagep->drawBorder(getLocalRect(), color, size); } else { imagep->drawBorder(0, 0, color, size); } } BOOL LLButton::getToggleState() const { return getValue().asBoolean(); } void LLButton::setToggleState(BOOL b) { if( b != getToggleState() ) { setControlValue(b); // will fire LLControlVariable callbacks (if any) setValue(b); // may or may not be redundant // Unselected label assignments autoResize(); } } void LLButton::setFlashing( BOOL b ) { if ((bool)b != mFlashing) { mFlashing = b; mFlashingTimer.reset(); } } BOOL LLButton::toggleState() { bool flipped = ! getToggleState(); setToggleState(flipped); return flipped; } void LLButton::setLabel( const LLStringExplicit& label ) { setLabelUnselected(label); setLabelSelected(label); } //virtual BOOL LLButton::setLabelArg( const std::string& key, const LLStringExplicit& text ) { mUnselectedLabel.setArg(key, text); mSelectedLabel.setArg(key, text); return TRUE; } void LLButton::setLabelUnselected( const LLStringExplicit& label ) { mUnselectedLabel = label; } void LLButton::setLabelSelected( const LLStringExplicit& label ) { mSelectedLabel = label; } bool LLButton::labelIsTruncated() const { return getCurrentLabel().getString().size() > mLastDrawCharsCount; } const LLUIString& LLButton::getCurrentLabel() const { if( getToggleState() ) { return mSelectedLabel; } else { return mUnselectedLabel; } } void LLButton::setImageUnselected(LLPointer image) { mImageUnselected = image; if (mImageUnselected.isNull()) { llwarns << "Setting default button image for: " << getName() << " to NULL" << llendl; } } void LLButton::autoResize() { resize(getCurrentLabel()); } void LLButton::resize(LLUIString label) { // get label length S32 label_width = mGLFont->getWidth(label.getString()); // get current btn length S32 btn_width =getRect().getWidth(); // check if it need resize if (mAutoResize) { S32 min_width = label_width + mLeftHPad + mRightHPad; if (mImageOverlay) { S32 overlay_width = mImageOverlay->getWidth(); F32 scale_factor = (getRect().getHeight() - (mImageOverlayBottomPad + mImageOverlayTopPad)) / (F32)mImageOverlay->getHeight(); overlay_width = llround((F32)overlay_width * scale_factor); switch(mImageOverlayAlignment) { case LLFontGL::LEFT: case LLFontGL::RIGHT: min_width += overlay_width + mImgOverlayLabelSpace; break; case LLFontGL::HCENTER: min_width = llmax(min_width, overlay_width + mLeftHPad + mRightHPad); break; default: // draw nothing break; } } if (btn_width < min_width) { reshape(min_width, getRect().getHeight()); } } } void LLButton::setImages( const std::string &image_name, const std::string &selected_name ) { setImageUnselected(LLUI::getUIImage(image_name)); setImageSelected(LLUI::getUIImage(selected_name)); } void LLButton::setImageSelected(LLPointer image) { mImageSelected = image; } void LLButton::setImageColor(const LLColor4& c) { mImageColor = c; } void LLButton::setColor(const LLColor4& color) { setImageColor(color); } void LLButton::setImageDisabled(LLPointer image) { mImageDisabled = image; mDisabledImageColor = mImageColor; mFadeWhenDisabled = TRUE; } void LLButton::setImageDisabledSelected(LLPointer image) { mImageDisabledSelected = image; mDisabledImageColor = mImageColor; mFadeWhenDisabled = TRUE; } void LLButton::setImagePressed(LLPointer image) { mImagePressed = image; } void LLButton::setImageHoverSelected(LLPointer image) { mImageHoverSelected = image; } void LLButton::setImageHoverUnselected(LLPointer image) { mImageHoverUnselected = image; } void LLButton::setImageFlash(LLPointer image) { mImageFlash = image; } void LLButton::setImageOverlay(const std::string& image_name, LLFontGL::HAlign alignment, const LLColor4& color) { if (image_name.empty()) { mImageOverlay = NULL; } else { mImageOverlay = LLUI::getUIImage(image_name); mImageOverlayAlignment = alignment; mImageOverlayColor = color; } } void LLButton::setImageOverlay(const LLUUID& image_id, LLFontGL::HAlign alignment, const LLColor4& color) { if (image_id.isNull()) { mImageOverlay = NULL; } else { mImageOverlay = LLUI::getUIImageByID(image_id); mImageOverlayAlignment = alignment; mImageOverlayColor = color; } } void LLButton::onMouseCaptureLost() { resetMouseDownTimer(); } //------------------------------------------------------------------------- // Utilities //------------------------------------------------------------------------- S32 round_up(S32 grid, S32 value) { S32 mod = value % grid; if (mod > 0) { // not even multiple return value + (grid - mod); } else { return value; } } void LLButton::addImageAttributeToXML(LLXMLNodePtr node, const std::string& image_name, const LLUUID& image_id, const std::string& xml_tag_name) const { if( !image_name.empty() ) { node->createChild(xml_tag_name.c_str(), TRUE)->setStringValue(image_name); } else if( image_id != LLUUID::null ) { node->createChild((xml_tag_name + "_id").c_str(), TRUE)->setUUIDValue(image_id); } } // static void LLButton::toggleFloaterAndSetToggleState(LLUICtrl* ctrl, const LLSD& sdname) { bool floater_vis = LLFloaterReg::toggleInstance(sdname.asString()); LLButton* button = dynamic_cast(ctrl); if (button) button->setToggleState(floater_vis); } // static // Gets called once void LLButton::setFloaterToggle(LLUICtrl* ctrl, const LLSD& sdname) { LLButton* button = dynamic_cast(ctrl); if (!button) return; // Get the visibility control name for the floater std::string vis_control_name = LLFloaterReg::declareVisibilityControl(sdname.asString()); // Set the button control value (toggle state) to the floater visibility control (Sets the value as well) button->setControlVariable(LLFloater::getControlGroup()->getControl(vis_control_name)); // Set the clicked callback to toggle the floater button->setClickedCallback(boost::bind(&LLFloaterReg::toggleInstance, sdname, LLSD())); } // static void LLButton::setDockableFloaterToggle(LLUICtrl* ctrl, const LLSD& sdname) { LLButton* button = dynamic_cast(ctrl); if (!button) return; // Get the visibility control name for the floater std::string vis_control_name = LLFloaterReg::declareVisibilityControl(sdname.asString()); // Set the button control value (toggle state) to the floater visibility control (Sets the value as well) button->setControlVariable(LLFloater::getControlGroup()->getControl(vis_control_name)); // Set the clicked callback to toggle the floater button->setClickedCallback(boost::bind(&LLDockableFloater::toggleInstance, sdname)); } // static void LLButton::showHelp(LLUICtrl* ctrl, const LLSD& sdname) { // search back through the button's parents for a panel // with a help_topic string defined std::string help_topic; if (LLUI::sHelpImpl && ctrl->findHelpTopic(help_topic)) { LLUI::sHelpImpl->showTopic(help_topic); return; // success } // display an error if we can't find a help_topic string. // fix this by adding a help_topic attribute to the xui file LLNotificationsUtil::add("UnableToFindHelpTopic"); } void LLButton::resetMouseDownTimer() { mMouseDownTimer.stop(); mMouseDownTimer.reset(); } BOOL LLButton::handleDoubleClick(S32 x, S32 y, MASK mask) { // just treat a double click as a second click return handleMouseDown(x, y, mask); }