diff options
author | James Cook <james@lindenlab.com> | 2007-01-02 08:33:20 +0000 |
---|---|---|
committer | James Cook <james@lindenlab.com> | 2007-01-02 08:33:20 +0000 |
commit | 420b91db29485df39fd6e724e782c449158811cb (patch) | |
tree | b471a94563af914d3ed3edd3e856d21cb1b69945 /indra/llui |
Print done when done.
Diffstat (limited to 'indra/llui')
78 files changed, 41343 insertions, 0 deletions
diff --git a/indra/llui/llbutton.cpp b/indra/llui/llbutton.cpp new file mode 100644 index 0000000000..c02be6bb8d --- /dev/null +++ b/indra/llui/llbutton.cpp @@ -0,0 +1,1012 @@ +/** + * @file llbutton.cpp + * @brief LLButton base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llbutton.h" + +// Linden library includes +#include "v4color.h" +#include "llstring.h" + +// Project includes +#include "llkeyboard.h" +#include "llgl.h" +#include "llui.h" +#include "lluiconstants.h" +//#include "llcallbacklist.h" +#include "llresmgr.h" +#include "llcriticaldamp.h" +#include "llglheaders.h" +#include "llfocusmgr.h" +#include "llwindow.h" + +// globals loaded from settings.xml +S32 LLBUTTON_ORIG_H_PAD = 6; // Pre-zoomable UI +S32 LLBUTTON_H_PAD = 0; +S32 LLBUTTON_V_PAD = 0; +S32 BTN_HEIGHT_SMALL= 0; +S32 BTN_HEIGHT = 0; + +S32 BTN_GRID = 12; +S32 BORDER_SIZE = 1; + +// static +LLFrameTimer LLButton::sFlashingTimer; + +LLButton::LLButton( const LLString& name, const LLRect& rect, const LLString& control_name, void (*click_callback)(void*), void *callback_data) +: LLUICtrl(name, rect, TRUE, NULL, NULL), + mClickedCallback( click_callback ), + mMouseDownCallback( NULL ), + mMouseUpCallback( NULL ), + mHeldDownCallback( NULL ), + mGLFont( NULL ), + mHeldDownDelay( 0.5f ), // seconds until held-down callback is called + mImageUnselected( NULL ), + mImageSelected( NULL ), + mImageHoverSelected( NULL ), + mImageHoverUnselected( NULL ), + mImageDisabled( NULL ), + mImageDisabledSelected( NULL ), + mToggleState( FALSE ), + mScaleImage( TRUE ), + mDropShadowedText( TRUE ), + mBorderEnabled( FALSE ), + mFlashing( FALSE ), + mHAlign( LLFontGL::HCENTER ), + mLeftHPad( LLBUTTON_H_PAD ), + mRightHPad( LLBUTTON_H_PAD ), + mFixedWidth( 16 ), + mFixedHeight( 16 ), + mHoverGlowStrength(0.15f), + mCurGlowStrength(0.f), + mNeedsHighlight(FALSE), + mCommitOnReturn(TRUE), + mImagep( NULL ) +{ + mUnselectedLabel = name; + mSelectedLabel = name; + + setImageUnselected("button_enabled_32x128.tga"); + setImageSelected("button_enabled_selected_32x128.tga"); + setImageDisabled("button_disabled_32x128.tga"); + setImageDisabledSelected("button_disabled_32x128.tga"); + + mImageColor = LLUI::sColorsGroup->getColor( "ButtonImageColor" ); + mDisabledImageColor = LLUI::sColorsGroup->getColor( "ButtonImageColor" ); + + init(click_callback, callback_data, NULL, control_name); +} + + +LLButton::LLButton(const LLString& name, const LLRect& rect, + const LLString &unselected_image_name, + const LLString &selected_image_name, + const LLString& control_name, + void (*click_callback)(void*), + void *callback_data, + const LLFontGL *font, + const LLString& unselected_label, + const LLString& selected_label ) +: LLUICtrl(name, rect, TRUE, NULL, NULL), + mClickedCallback( click_callback ), + mMouseDownCallback( NULL ), + mMouseUpCallback( NULL ), + mHeldDownCallback( NULL ), + mGLFont( NULL ), + mHeldDownDelay( 0.5f ), // seconds until held-down callback is called + mImageUnselected( NULL ), + mImageSelected( NULL ), + mImageHoverSelected( NULL ), + mImageHoverUnselected( NULL ), + mImageDisabled( NULL ), + mImageDisabledSelected( NULL ), + mToggleState( FALSE ), + mScaleImage( TRUE ), + mDropShadowedText( TRUE ), + mBorderEnabled( FALSE ), + mFlashing( FALSE ), + mHAlign( LLFontGL::HCENTER ), + mLeftHPad( LLBUTTON_H_PAD ), + mRightHPad( LLBUTTON_H_PAD ), + mFixedWidth( 16 ), + mFixedHeight( 16 ), + mHoverGlowStrength(0.25f), + mCurGlowStrength(0.f), + mNeedsHighlight(FALSE), + mCommitOnReturn(TRUE), + mImagep( NULL ) +{ + mUnselectedLabel = unselected_label; + mSelectedLabel = selected_label; + + // by default, disabled color is same as enabled + mImageColor = LLUI::sColorsGroup->getColor( "ButtonImageColor" ); + mDisabledImageColor = LLUI::sColorsGroup->getColor( "ButtonImageColor" ); + + if( unselected_image_name != "" ) + { + setImageUnselected(unselected_image_name); + setImageDisabled(unselected_image_name); + + mDisabledImageColor.mV[VALPHA] = 0.5f; + mImageDisabled = mImageUnselected; + mDisabledImageColor.mV[VALPHA] = 0.5f; + // user-specified image - don't use fixed borders unless requested + mFixedWidth = 0; + mFixedHeight = 0; + mScaleImage = FALSE; + } + else + { + setImageUnselected("button_enabled_32x128.tga"); + setImageDisabled("button_disabled_32x128.tga"); + } + + if( selected_image_name != "" ) + { + setImageSelected(selected_image_name); + setImageDisabledSelected(selected_image_name); + + mDisabledImageColor.mV[VALPHA] = 0.5f; + // user-specified image - don't use fixed borders unless requested + mFixedWidth = 0; + mFixedHeight = 0; + mScaleImage = FALSE; + } + else + { + setImageSelected("button_enabled_selected_32x128.tga"); + setImageDisabledSelected("button_disabled_32x128.tga"); + } + + init(click_callback, callback_data, font, control_name); +} + +void LLButton::init(void (*click_callback)(void*), void *callback_data, const LLFontGL* font, const LLString& control_name) +{ + mGLFont = ( font ? font : LLFontGL::sSansSerif); + + // Hack to make sure there is space for at least one character + if (mRect.getWidth() - (mRightHPad + mLeftHPad) < mGLFont->getWidth(" ")) + { + // Use old defaults + mLeftHPad = LLBUTTON_ORIG_H_PAD; + mRightHPad = LLBUTTON_ORIG_H_PAD; + } + + mCallbackUserData = callback_data; + mMouseDownTimer.stop(); + + setControlName(control_name, NULL); + + mUnselectedLabelColor = ( LLUI::sColorsGroup->getColor( "ButtonLabelColor" ) ); + mSelectedLabelColor = ( LLUI::sColorsGroup->getColor( "ButtonLabelSelectedColor" ) ); + mDisabledLabelColor = ( LLUI::sColorsGroup->getColor( "ButtonLabelDisabledColor" ) ); + mDisabledSelectedLabelColor = ( LLUI::sColorsGroup->getColor( "ButtonLabelSelectedDisabledColor" ) ); + mHighlightColor = ( LLUI::sColorsGroup->getColor( "ButtonUnselectedFgColor" ) ); + mUnselectedBgColor = ( LLUI::sColorsGroup->getColor( "ButtonUnselectedBgColor" ) ); + mSelectedBgColor = ( LLUI::sColorsGroup->getColor( "ButtonSelectedBgColor" ) ); +} + +LLButton::~LLButton() +{ + if( this == gFocusMgr.getMouseCapture() ) + { + gFocusMgr.setMouseCapture( NULL, NULL ); + } +} + +// virtual +EWidgetType LLButton::getWidgetType() const +{ + return WIDGET_TYPE_BUTTON; +} + +// virtual +LLString LLButton::getWidgetTag() const +{ + return LL_BUTTON_TAG; +} + +// 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 mClickedCallback + // LAST, otherwise this becomes deleted memory. + LLUICtrl::onCommit(); + + if (mMouseDownCallback) + { + (*mMouseDownCallback)(mCallbackUserData); + } + + if (mMouseUpCallback) + { + (*mMouseUpCallback)(mCallbackUserData); + } + + if (mSoundFlags & MOUSE_DOWN) + { + make_ui_sound("UISndClick"); + } + + if (mSoundFlags & MOUSE_UP) + { + make_ui_sound("UISndClickRelease"); + } + + if (mClickedCallback) + { + (*mClickedCallback)( mCallbackUserData ); + } +} + +BOOL LLButton::handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent) +{ + BOOL handled = FALSE; + if( getVisible() && mEnabled && !called_from_parent && ' ' == uni_char && !gKeyboard->getKeyRepeated(' ')) + { + if (mClickedCallback) + { + (*mClickedCallback)( mCallbackUserData ); + } + handled = TRUE; + } + return handled; +} + +BOOL LLButton::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent ) +{ + BOOL handled = FALSE; + if( getVisible() && mEnabled && !called_from_parent ) + { + if( mCommitOnReturn && KEY_RETURN == key && mask == MASK_NONE && !gKeyboard->getKeyRepeated(key)) + { + if (mClickedCallback) + { + (*mClickedCallback)( mCallbackUserData ); + } + handled = TRUE; + } + } + return handled; +} + + +BOOL LLButton::handleMouseDown(S32 x, S32 y, MASK mask) +{ + // Route future Mouse messages here preemptively. (Release on mouse up.) + gFocusMgr.setMouseCapture( this, &LLButton::onMouseCaptureLost ); + + if (hasTabStop() && !getIsChrome()) + { + setFocus(TRUE); + } + + if (mMouseDownCallback) + { + (*mMouseDownCallback)(mCallbackUserData); + } + + mMouseDownTimer.start(); + + if (mSoundFlags & 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( this == gFocusMgr.getMouseCapture() ) + { + // Regardless of where mouseup occurs, handle callback + if (mMouseUpCallback) + { + (*mMouseUpCallback)(mCallbackUserData); + } + + mMouseDownTimer.stop(); + + // Always release the mouse + gFocusMgr.setMouseCapture( NULL, NULL ); + + // 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 (mSoundFlags & MOUSE_UP) + { + make_ui_sound("UISndClickRelease"); + } + + if (mClickedCallback) + { + (*mClickedCallback)( mCallbackUserData ); + } + } + } + + return TRUE; +} + + +BOOL LLButton::handleHover(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + LLMouseHandler* other_captor = gFocusMgr.getMouseCapture(); + mNeedsHighlight = other_captor == NULL || + other_captor == this || + // this following bit is to support modal dialogs + (other_captor->isView() && hasAncestor((LLView*)other_captor)); + + if (mMouseDownTimer.getStarted() && NULL != mHeldDownCallback) + { + F32 elapsed = mMouseDownTimer.getElapsedTimeF32(); + if( mHeldDownDelay < elapsed ) + { + mHeldDownCallback( mCallbackUserData ); + } + } + + // We only handle the click if the click both started and ended within us + if( this == gFocusMgr.getMouseCapture() ) + { + handled = TRUE; + } + else if( getVisible() ) + { + // Opaque + handled = TRUE; + } + + if( handled ) + { + getWindow()->setCursor(UI_CURSOR_ARROW); + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << llendl; + } + + return handled; +} + + +// virtual +void LLButton::draw() +{ + if( getVisible() ) + { + BOOL flash = FALSE; + if( mFlashing ) + { + F32 elapsed = LLButton::sFlashingTimer.getElapsedTimeF32(); + flash = S32(elapsed * 2) & 1; + } + + BOOL pressed_by_keyboard = FALSE; + if (hasFocus()) + { + pressed_by_keyboard = gKeyboard->getKeyDown(' ') || (mCommitOnReturn && gKeyboard->getKeyDown(KEY_RETURN)); + } + + // Unselected image assignments + S32 local_mouse_x; + S32 local_mouse_y; + LLCoordWindow cursor_pos_window; + getWindow()->getCursorPosition(&cursor_pos_window); + LLCoordGL cursor_pos_gl; + getWindow()->convertCoords(cursor_pos_window, &cursor_pos_gl); + cursor_pos_gl.mX = llround((F32)cursor_pos_gl.mX / LLUI::sGLScaleFactor.mV[VX]); + cursor_pos_gl.mY = llround((F32)cursor_pos_gl.mY / LLUI::sGLScaleFactor.mV[VY]); + screenPointToLocal(cursor_pos_gl.mX, cursor_pos_gl.mY, &local_mouse_x, &local_mouse_y); + + BOOL pressed = pressed_by_keyboard || (this == gFocusMgr.getMouseCapture() && pointInView(local_mouse_x, local_mouse_y)); + + BOOL display_state = FALSE; + if( pressed ) + { + mImagep = mImageSelected; + // show the resulting state after releasing the mouse button while it is down + display_state = mToggleState ? FALSE : TRUE; + } + else + { + display_state = mToggleState || flash; + } + + BOOL use_glow_effect = FALSE; + if ( mNeedsHighlight ) + { + if (display_state) + { + if (mImageHoverSelected) + { + mImagep = mImageHoverSelected; + } + else + { + mImagep = mImageSelected; + use_glow_effect = TRUE; + } + } + else + { + if (mImageHoverUnselected) + { + mImagep = mImageHoverUnselected; + } + else + { + mImagep = mImageUnselected; + use_glow_effect = TRUE; + } + } + } + else if ( display_state ) + { + mImagep = mImageSelected; + } + else + { + mImagep = 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() && ( (mEnabled && mTentative) || (!mEnabled && display_state ) ) ) + { + mImagep = mImageDisabledSelected; + } + else if (!mImageDisabled.isNull() && !mEnabled && !display_state) + { + mImagep = mImageDisabled; + } + + if (mNeedsHighlight && !mImagep) + { + use_glow_effect = TRUE; + } + + // Figure out appropriate color for the text + LLColor4 label_color; + + if ( mEnabled ) + { + if ( !display_state ) + { + label_color = mUnselectedLabelColor; + } + else + { + label_color = mSelectedLabelColor; + } + } + else + { + if ( !display_state ) + { + label_color = mDisabledLabelColor; + } + else + { + label_color = mDisabledSelectedLabelColor; + } + } + + // Unselected label assignments + LLWString label; + + if( display_state ) + { + if( mEnabled || mDisabledSelectedLabel.empty() ) + { + label = mSelectedLabel; + } + else + { + label = mDisabledSelectedLabel; + } + } + else + { + if( mEnabled || mDisabledLabel.empty() ) + { + label = mUnselectedLabel; + } + else + { + label = mDisabledLabel; + } + } + + // draw default button border + if (mEnabled && mBorderEnabled && gFocusMgr.getAppHasFocus()) // because we're the default button in a panel + { + drawBorder(LLUI::sColorsGroup->getColor( "ButtonBorderColor" ), BORDER_SIZE); + } + + // overlay with keyboard focus border + if (hasFocus()) + { + F32 lerp_amt = gFocusMgr.getFocusFlashAmt(); + drawBorder(gFocusMgr.getFocusColor(), llround(lerp(1.f, 3.f, lerp_amt))); + } + + if (use_glow_effect) + { + mCurGlowStrength = lerp(mCurGlowStrength, mHoverGlowStrength, LLCriticalDamp::getInterpolant(0.05f)); + } + else + { + mCurGlowStrength = lerp(mCurGlowStrength, 0.f, LLCriticalDamp::getInterpolant(0.05f)); + } + + // Draw button image, if available. + // Otherwise draw basic rectangular button. + if( mImagep.notNull() && !mScaleImage) + { + gl_draw_image( 0, 0, mImagep, mEnabled ? mImageColor : mDisabledImageColor ); + if (mCurGlowStrength > 0.01f) + { + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + gl_draw_scaled_image_with_border(0, 0, 0, 0, mImagep->getWidth(), mImagep->getHeight(), mImagep, LLColor4(1.f, 1.f, 1.f, mCurGlowStrength), TRUE); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + } + else + if ( mImagep.notNull() && mScaleImage) + { + gl_draw_scaled_image_with_border(0, 0, mFixedWidth, mFixedHeight, mRect.getWidth(), mRect.getHeight(), + mImagep, mEnabled ? mImageColor : mDisabledImageColor ); + if (mCurGlowStrength > 0.01f) + { + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + gl_draw_scaled_image_with_border(0, 0, mFixedWidth, mFixedHeight, mRect.getWidth(), mRect.getHeight(), + mImagep, LLColor4(1.f, 1.f, 1.f, mCurGlowStrength), TRUE); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + } + else + { + // no image + llalerts << "No image for button " << mName << llendl; + // draw it in pink so we can find it + gl_rect_2d(0, mRect.getHeight(), mRect.getWidth(), 0, LLColor4::pink1, FALSE); + } + + // Draw label + if( !label.empty() ) + { + S32 drawable_width = mRect.getWidth() - mLeftHPad - mRightHPad; + + LLWString::trim(label); + + S32 x; + switch( mHAlign ) + { + case LLFontGL::RIGHT: + x = mRect.getWidth() - mRightHPad; + break; + case LLFontGL::HCENTER: + x = mRect.getWidth() / 2; + break; + case LLFontGL::LEFT: + default: + x = mLeftHPad; + break; + } + + S32 y_offset = 2 + (mRect.getHeight() - 20)/2; + + if (pressed || display_state) + { + y_offset--; + x++; + } + + mGLFont->render(label, 0, (F32)x, (F32)(LLBUTTON_V_PAD + y_offset), + label_color, + mHAlign, LLFontGL::BOTTOM, + mDropShadowedText ? LLFontGL::DROP_SHADOW : LLFontGL::NORMAL, + U32_MAX, drawable_width, + NULL, FALSE, FALSE); + } + + if (sDebugRects + || (LLView::sEditingUI && this == LLView::sEditingUIView)) + { + drawDebugRect(); + } + } + // reset hover status for next frame + mNeedsHighlight = FALSE; +} + +void LLButton::drawBorder(const LLColor4& color, S32 size) +{ + S32 left = -size; + S32 top = mRect.getHeight() + size; + S32 right = mRect.getWidth() + size; + S32 bottom = -size; + + if (mImagep.isNull()) + { + gl_rect_2d(left, top, right, bottom, color, FALSE); + return; + } + + if (mScaleImage) + { + gl_draw_scaled_image_with_border(left, bottom, mFixedWidth, mFixedHeight, right-left, top-bottom, + mImagep, color, TRUE ); + } + else + { + gl_draw_scaled_image_with_border(left, bottom, 0, 0, mImagep->getWidth() + size * 2, + mImagep->getHeight() + size * 2, mImagep, color, TRUE ); + } +} + +void LLButton::setClickedCallback(void (*cb)(void*), void* userdata) +{ + mClickedCallback = cb; + if (userdata) + { + mCallbackUserData = userdata; + } +} + + +void LLButton::setToggleState(BOOL b) +{ + if( b != mToggleState ) + { + mToggleState = b; + LLValueChangedEvent *evt = new LLValueChangedEvent(this, mToggleState); + fireEvent(evt, ""); + } +} + +void LLButton::setValue(const LLSD& value ) +{ + mToggleState = value.asBoolean(); +} + +LLSD LLButton::getValue() const +{ + return mToggleState; +} + +void LLButton::setLabel( const LLString& label ) +{ + setLabelUnselected(label); + setLabelSelected(label); +} + +//virtual +BOOL LLButton::setLabelArg( const LLString& key, const LLString& text ) +{ + mUnselectedLabel.setArg(key, text); + mSelectedLabel.setArg(key, text); + return TRUE; +} + +void LLButton::setLabelUnselected( const LLString& label ) +{ + mUnselectedLabel = label; +} + +void LLButton::setLabelSelected( const LLString& label ) +{ + mSelectedLabel = label; +} + +void LLButton::setDisabledLabel( const LLString& label ) +{ + mDisabledLabel = label; +} + +void LLButton::setDisabledSelectedLabel( const LLString& label ) +{ + mDisabledSelectedLabel = label; +} + +void LLButton::setImageUnselectedID( const LLUUID &image_id ) +{ + mImageUnselectedName = ""; + mImageUnselected = LLUI::sImageProvider->getUIImageByID(image_id); +} + +void LLButton::setImages( const LLString &image_name, const LLString &selected_name ) +{ + setImageUnselected(image_name); + setImageSelected(selected_name); + +} + +void LLButton::setImageSelectedID( const LLUUID &image_id ) +{ + mImageSelectedName = ""; + mImageSelected = LLUI::sImageProvider->getUIImageByID(image_id); +} + +void LLButton::setImageColor(const LLColor4& c) +{ + mImageColor = c; +} + + +void LLButton::setImageDisabledID( const LLUUID &image_id ) +{ + mImageDisabledName = ""; + mImageDisabled = LLUI::sImageProvider->getUIImageByID(image_id); + mDisabledImageColor = mImageColor; + mDisabledImageColor.mV[VALPHA] *= 0.5f; +} + +void LLButton::setImageDisabledSelectedID( const LLUUID &image_id ) +{ + mImageDisabledSelectedName = ""; + mImageDisabledSelected = LLUI::sImageProvider->getUIImageByID(image_id); + mDisabledImageColor = mImageColor; + mDisabledImageColor.mV[VALPHA] *= 0.5f; +} + +void LLButton::setDisabledImages( const LLString &image_name, const LLString &selected_name, const LLColor4& c ) +{ + setImageDisabled(image_name); + setImageDisabledSelected(selected_name); + mDisabledImageColor = c; +} + + +void LLButton::setImageHoverSelectedID( const LLUUID& image_id ) +{ + mImageHoverSelectedName = ""; + mImageHoverSelected = LLUI::sImageProvider->getUIImageByID(image_id); +} + +void LLButton::setDisabledImages( const LLString &image_name, const LLString &selected_name) +{ + LLColor4 clr = mImageColor; + clr.mV[VALPHA] *= .5f; + setDisabledImages( image_name, selected_name, clr ); +} + +void LLButton::setImageHoverUnselectedID( const LLUUID& image_id ) +{ + mImageHoverUnselectedName = ""; + mImageHoverUnselected = LLUI::sImageProvider->getUIImageByID(image_id); +} + +void LLButton::setHoverImages( const LLString& image_name, const LLString& selected_name ) +{ + setImageHoverUnselected(image_name); + setImageHoverSelected(selected_name); +} + +// static +void LLButton::onMouseCaptureLost( LLMouseHandler* old_captor ) +{ + LLButton* self = (LLButton*) old_captor; + self->mMouseDownTimer.stop(); +} + +//------------------------------------------------------------------------- +// LLSquareButton +//------------------------------------------------------------------------- +LLSquareButton::LLSquareButton(const LLString& name, const LLRect& rect, + const LLString& label, + const LLFontGL *font, + const LLString& control_name, + void (*click_callback)(void*), + void *callback_data, + const LLString& selected_label ) +: LLButton(name, rect, "","", + control_name, + click_callback, callback_data, + font, + label, + (selected_label.empty() ? label : selected_label) ) +{ + setImageUnselected("square_btn_32x128.tga"); + // mImageUnselected = LLUI::sImageProvider->getUIImageByID(LLUUID(LLUI::sAssetsGroup->getString("square_btn_32x128.tga"))); + setImageSelected("square_btn_selected_32x128.tga"); + // mImageSelectedImage = LLUI::sImageProvider->getUIImageByID(LLUUID(LLUI::sAssetsGroup->getString("square_btn_selected_32x128.tga"))); + setImageDisabled("square_btn_32x128.tga"); + //mDisabledImage = LLUI::sImageProvider->getUIImageByID(LLUUID(LLUI::sAssetsGroup->getString("square_btn_32x128.tga"))); + setImageDisabledSelected("square_btn_selected_32x128.tga"); + //mDisabledSelectedImage = LLUI::sImageProvider->getUIImageByID(LLUUID(LLUI::sAssetsGroup->getString("square_btn_selected_32x128.tga"))); + mImageColor = LLUI::sColorsGroup->getColor("ButtonColor"); +} + +//------------------------------------------------------------------------- +// 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::setImageUnselected(const LLString &image_name) +{ + setImageUnselectedID(LLUI::findAssetUUIDByName(image_name)); + mImageUnselectedName = image_name; +} + +void LLButton::setImageSelected(const LLString &image_name) +{ + setImageSelectedID(LLUI::findAssetUUIDByName(image_name)); + mImageSelectedName = image_name; +} + +void LLButton::setImageHoverSelected(const LLString &image_name) +{ + setImageHoverSelectedID(LLUI::findAssetUUIDByName(image_name)); + mImageHoverSelectedName = image_name; +} + +void LLButton::setImageHoverUnselected(const LLString &image_name) +{ + setImageHoverUnselectedID(LLUI::findAssetUUIDByName(image_name)); + mImageHoverUnselectedName = image_name; +} + +void LLButton::setImageDisabled(const LLString &image_name) +{ + setImageDisabledID(LLUI::findAssetUUIDByName(image_name)); + mImageDisabledName = image_name; +} + +void LLButton::setImageDisabledSelected(const LLString &image_name) +{ + setImageDisabledSelectedID(LLUI::findAssetUUIDByName(image_name)); + mImageDisabledSelectedName = image_name; +} + +void LLButton::addImageAttributeToXML(LLXMLNodePtr node, + const LLString& image_name, + const LLUUID& image_id, + const LLString& xml_tag_name) const +{ + if( !image_name.empty() ) + { + node->createChild(xml_tag_name, TRUE)->setStringValue(image_name); + } + else if( image_id != LLUUID::null ) + { + node->createChild(xml_tag_name + "_id", TRUE)->setUUIDValue(image_id); + } +} + +// virtual +LLXMLNodePtr LLButton::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLUICtrl::getXML(); + + node->createChild("label", TRUE)->setStringValue(getLabelUnselected()); + node->createChild("label_selected", TRUE)->setStringValue(getLabelSelected()); + node->createChild("font", TRUE)->setStringValue(LLFontGL::nameFromFont(mGLFont)); + node->createChild("halign", TRUE)->setStringValue(LLFontGL::nameFromHAlign(mHAlign)); + node->createChild("border_width", TRUE)->setIntValue(mFixedWidth); + node->createChild("border_height", TRUE)->setIntValue(mFixedHeight); + + addImageAttributeToXML(node,mImageUnselectedName,mImageUnselectedID,"image_unselected"); + addImageAttributeToXML(node,mImageSelectedName,mImageSelectedID,"image_selected"); + addImageAttributeToXML(node,mImageHoverSelectedName,mImageHoverSelectedID,"image_hover_selected"); + addImageAttributeToXML(node,mImageHoverUnselectedName,mImageHoverUnselectedID,"image_hover_unselected"); + addImageAttributeToXML(node,mImageDisabledName,mImageDisabledID,"image_disabled"); + addImageAttributeToXML(node,mImageDisabledSelectedName,mImageDisabledSelectedID,"image_disabled_selected"); + + node->createChild("scale_image", TRUE)->setBoolValue(mScaleImage); + + return node; +} + +// static +LLView* LLButton::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("button"); + node->getAttributeString("name", name); + + LLString label = name; + node->getAttributeString("label", label); + + LLString label_selected = label; + node->getAttributeString("label_selected", label_selected); + + LLFontGL* font = selectFont(node); + + LLString image_unselected; + if (node->hasAttribute("image_unselected")) node->getAttributeString("image_unselected",image_unselected); + + LLString image_selected; + if (node->hasAttribute("image_selected")) node->getAttributeString("image_selected",image_selected); + + LLString image_hover_selected; + if (node->hasAttribute("image_hover_selected")) node->getAttributeString("image_hover_selected",image_hover_selected); + + LLString image_hover_unselected; + if (node->hasAttribute("image_hover_unselected")) node->getAttributeString("image_hover_unselected",image_hover_unselected); + + LLString image_disabled_selected; + if (node->hasAttribute("image_disabled_selected")) node->getAttributeString("image_disabled_selected",image_disabled_selected); + + LLString image_disabled; + if (node->hasAttribute("image_disabled")) node->getAttributeString("image_disabled",image_disabled); + + LLButton *button = new LLButton(name, + LLRect(), + image_unselected, + image_selected, + "", + NULL, + parent, + font, + label, + label_selected); + + node->getAttributeS32("border_width", button->mFixedWidth); + node->getAttributeS32("border_height", button->mFixedHeight); + + if(image_hover_selected != LLString::null) button->setImageHoverSelected(image_hover_selected); + + if(image_hover_unselected != LLString::null) button->setImageHoverUnselected(image_hover_unselected); + + if(image_disabled_selected != LLString::null) button->setImageDisabledSelected(image_disabled_selected ); + + if(image_disabled != LLString::null) button->setImageDisabled(image_disabled); + + + if (node->hasAttribute("halign")) + { + LLFontGL::HAlign halign = selectFontHAlign(node); + button->setHAlign(halign); + } + + if (node->hasAttribute("scale_image")) + { + BOOL needsScale = FALSE; + node->getAttributeBOOL("scale_image",needsScale); + button->setScaleImage( needsScale ); + } + + if(label.empty()) + { + button->setLabelUnselected(node->getTextContents()); + } + if (label_selected.empty()) + { + button->setLabelSelected(node->getTextContents()); + } + + button->initFromXML(node, parent); + + return button; +} + diff --git a/indra/llui/llbutton.h b/indra/llui/llbutton.h new file mode 100644 index 0000000000..0a4e41b017 --- /dev/null +++ b/indra/llui/llbutton.h @@ -0,0 +1,261 @@ +/** + * @file llbutton.h + * @brief Header for buttons + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLBUTTON_H +#define LL_LLBUTTON_H + +#include "lluuid.h" +#include "llcontrol.h" +#include "lluictrl.h" +#include "v4color.h" +#include "llframetimer.h" +#include "llfontgl.h" +#include "llimage.h" +#include "lluistring.h" + +// +// Constants +// + +// PLEASE please use these "constants" when building your own buttons. +// They are loaded from settings.xml at run time. +extern S32 LLBUTTON_H_PAD; +extern S32 LLBUTTON_V_PAD; +extern S32 BTN_HEIGHT_SMALL; +extern S32 BTN_HEIGHT; + + +// All button widths should be rounded up to this size +extern S32 BTN_GRID; + +// +// Classes +// + +class LLButton +: public LLUICtrl +{ +public: + // simple button with text label + LLButton(const LLString& name, const LLRect &rect, const LLString& control_name = "", + void (*on_click)(void*) = NULL, void *data = NULL); + + LLButton(const LLString& name, const LLRect& rect, + const LLString &unselected_image, + const LLString &selected_image, + const LLString& control_name, + void (*click_callback)(void*), + void *callback_data = NULL, + const LLFontGL* mGLFont = NULL, + const LLString& unselected_label = LLString::null, + const LLString& selected_label = LLString::null ); + + virtual ~LLButton(); + void init(void (*click_callback)(void*), void *callback_data, const LLFontGL* font, const LLString& control_name); + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + void addImageAttributeToXML(LLXMLNodePtr node, const LLString& imageName, + const LLUUID& imageID,const LLString& xmlTagName) const; + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + + virtual BOOL handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent); + virtual BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); + virtual BOOL handleHover(S32 x, S32 y, MASK mask); + virtual void draw(); + + // HACK: "committing" a button is the same as clicking on it. + virtual void onCommit(); + + void setUnselectedLabelColor( const LLColor4& c ) { mUnselectedLabelColor = c; } + void setSelectedLabelColor( const LLColor4& c ) { mSelectedLabelColor = c; } + + void setClickedCallback( void (*cb)(void *data), void* data = NULL ); // mouse down and up within button + void setMouseDownCallback( void (*cb)(void *data) ) { mMouseDownCallback = cb; } // mouse down within button + void setMouseUpCallback( void (*cb)(void *data) ) { mMouseUpCallback = cb; } // mouse up, EVEN IF NOT IN BUTTON + void setHeldDownCallback( void (*cb)(void *data) ) { mHeldDownCallback = cb; } // Mouse button held down and in button + void setHeldDownDelay( F32 seconds) { mHeldDownDelay = seconds; } + + F32 getHeldDownTime() const { return mMouseDownTimer.getElapsedTimeF32(); } + + BOOL toggleState() { setToggleState( !mToggleState ); return mToggleState; } + BOOL getToggleState() const { return mToggleState; } + void setToggleState(BOOL b); + + void setFlashing( BOOL b ) { mFlashing = b; } + BOOL getFlashing() const { return mFlashing; } + + void setHAlign( LLFontGL::HAlign align ) { mHAlign = align; } + void setLeftHPad( S32 pad ) { mLeftHPad = pad; } + void setRightHPad( S32 pad ) { mRightHPad = pad; } + + const LLString getLabelUnselected() const { return wstring_to_utf8str(mUnselectedLabel); } + const LLString getLabelSelected() const { return wstring_to_utf8str(mSelectedLabel); } + + + // HACK to allow images to be freed when the caller knows he's done with it. + LLImageGL* getImageUnselected() const { return mImageUnselected; } + + void setImageColor(const LLString& color_control); + void setImages(const LLString &image_name, const LLString &selected_name); + void setImageColor(const LLColor4& c); + + void setDisabledImages(const LLString &image_name, const LLString &selected_name); + void setDisabledImages(const LLString &image_name, const LLString &selected_name, const LLColor4& c); + + void setHoverImages(const LLString &image_name, const LLString &selected_name); + + void setDisabledImageColor(const LLColor4& c) { mDisabledImageColor = c; } + + void setDisabledSelectedLabelColor( const LLColor4& c ) { mDisabledSelectedLabelColor = c; } + + virtual void setValue(const LLSD& value ); + virtual LLSD getValue() const; + + void setLabel( const LLString& label); + virtual BOOL setLabelArg( const LLString& key, const LLString& text ); + void setLabelUnselected(const LLString& label); + void setLabelSelected(const LLString& label); + void setDisabledLabel(const LLString& disabled_label); + void setDisabledSelectedLabel(const LLString& disabled_label); + void setDisabledLabelColor( const LLColor4& c ) { mDisabledLabelColor = c; } + + void setFont(const LLFontGL *font) + { mGLFont = ( font ? font : LLFontGL::sSansSerif); } + void setScaleImage(BOOL scale) { mScaleImage = scale; } + + void setDropShadowedText(BOOL b) { mDropShadowedText = b; } + + void setBorderEnabled(BOOL b) { mBorderEnabled = b; } + + static void onHeldDown(void *userdata); // to be called by gIdleCallbacks + static void onMouseCaptureLost(LLMouseHandler* old_captor); + + void setFixedBorder(S32 width, S32 height) { mFixedWidth = width; mFixedHeight = height; } + void setHoverGlowStrength(F32 strength) { mHoverGlowStrength = strength; } + +private: + void setImageUnselectedID(const LLUUID &image_id); + void setImageSelectedID(const LLUUID &image_id); + void setImageHoverSelectedID(const LLUUID &image_id); + void setImageHoverUnselectedID(const LLUUID &image_id); + void setImageDisabledID(const LLUUID &image_id); + void setImageDisabledSelectedID(const LLUUID &image_id); +public: + void setImageUnselected(const LLString &image_name); + void setImageSelected(const LLString &image_name); + void setImageHoverSelected(const LLString &image_name); + void setImageHoverUnselected(const LLString &image_name); + void setImageDisabled(const LLString &image_name); + void setImageDisabledSelected(const LLString &image_name); + void setCommitOnReturn(BOOL commit) { mCommitOnReturn = commit; } + BOOL getCommitOnReturn() { return mCommitOnReturn; } + +protected: + virtual void drawBorder(const LLColor4& color, S32 size); + +protected: + + void (*mClickedCallback)(void* data ); + void (*mMouseDownCallback)(void *data); + void (*mMouseUpCallback)(void *data); + void (*mHeldDownCallback)(void *data); + + const LLFontGL *mGLFont; + + LLFrameTimer mMouseDownTimer; + F32 mHeldDownDelay; // seconds, after which held-down callbacks get called + + LLPointer<LLImageGL> mImageUnselected; + LLUIString mUnselectedLabel; + LLColor4 mUnselectedLabelColor; + + LLPointer<LLImageGL> mImageSelected; + LLUIString mSelectedLabel; + LLColor4 mSelectedLabelColor; + + LLPointer<LLImageGL> mImageHoverSelected; + + LLPointer<LLImageGL> mImageHoverUnselected; + + LLPointer<LLImageGL> mImageDisabled; + LLUIString mDisabledLabel; + LLColor4 mDisabledLabelColor; + + LLPointer<LLImageGL> mImageDisabledSelected; + LLUIString mDisabledSelectedLabel; + LLColor4 mDisabledSelectedLabelColor; + + + LLUUID mImageUnselectedID; + LLUUID mImageSelectedID; + LLUUID mImageHoverSelectedID; + LLUUID mImageHoverUnselectedID; + LLUUID mImageDisabledID; + LLUUID mImageDisabledSelectedID; + LLString mImageUnselectedName; + LLString mImageSelectedName; + LLString mImageHoverSelectedName; + LLString mImageHoverUnselectedName; + LLString mImageDisabledName; + LLString mImageDisabledSelectedName; + + LLColor4 mHighlightColor; + LLColor4 mUnselectedBgColor; + LLColor4 mSelectedBgColor; + + LLColor4 mImageColor; + LLColor4 mDisabledImageColor; + + BOOL mToggleState; + BOOL mScaleImage; + + BOOL mDropShadowedText; + + BOOL mBorderEnabled; + + BOOL mFlashing; + + LLFontGL::HAlign mHAlign; + S32 mLeftHPad; + S32 mRightHPad; + + S32 mFixedWidth; + S32 mFixedHeight; + + F32 mHoverGlowStrength; + F32 mCurGlowStrength; + + BOOL mNeedsHighlight; + BOOL mCommitOnReturn; + + LLPointer<LLImageGL> mImagep; + + static LLFrameTimer sFlashingTimer; +}; + +class LLSquareButton +: public LLButton +{ +public: + LLSquareButton(const LLString& name, const LLRect& rect, + const LLString& label, + const LLFontGL *font = NULL, + const LLString& control_name = "", + void (*click_callback)(void*) = NULL, + void *callback_data = NULL, + const LLString& selected_label = LLString::null ); +}; + +// Helpful functions +S32 round_up(S32 grid, S32 value); + +#endif // LL_LLBUTTON_H diff --git a/indra/llui/llcallbackmap.h b/indra/llui/llcallbackmap.h new file mode 100644 index 0000000000..dfc965aa08 --- /dev/null +++ b/indra/llui/llcallbackmap.h @@ -0,0 +1,36 @@ +/** + * @file llcallbackmap.h + * @brief LLCallbackMap base class + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// llcallbackmap.h +// +// Copyright 2006, Linden Research, Inc. + +#ifndef LL_CALLBACK_MAP_H +#define LL_CALLBACK_MAP_H + +#include <map> +#include "llstring.h" + +class LLCallbackMap +{ +public: + // callback definition. + typedef void* (*callback_t)(void* data); + + typedef std::map<LLString, LLCallbackMap> map_t; + typedef map_t::iterator map_iter_t; + typedef map_t::const_iterator map_const_iter_t; + + LLCallbackMap() : mCallback(NULL), mData(NULL) { } + LLCallbackMap(callback_t callback, void* data) : mCallback(callback), mData(data) { } + + callback_t mCallback; + void* mData; +}; + +#endif // LL_CALLBACK_MAP_H diff --git a/indra/llui/llcheckboxctrl.cpp b/indra/llui/llcheckboxctrl.cpp new file mode 100644 index 0000000000..3b054d2fec --- /dev/null +++ b/indra/llui/llcheckboxctrl.cpp @@ -0,0 +1,315 @@ +/** + * @file llcheckboxctrl.cpp + * @brief LLCheckBoxCtrl base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// The mutants are coming! + +#include "linden_common.h" + +#include "llcheckboxctrl.h" + +#include "llgl.h" +#include "llui.h" +#include "lluiconstants.h" +#include "lluictrlfactory.h" +#include "llcontrol.h" + +#include "llstring.h" +#include "llfontgl.h" +#include "lltextbox.h" +#include "llkeyboard.h" +#include "llviewborder.h" + +const U32 MAX_STRING_LENGTH = 10; + + +LLCheckBoxCtrl::LLCheckBoxCtrl(const LLString& name, const LLRect& rect, + const LLString& label, + const LLFontGL* font, + void (*commit_callback)(LLUICtrl* ctrl, void* userdata), + void* callback_user_data, + BOOL initial_value, + BOOL use_radio_style, + const LLString& control_which) +: LLUICtrl(name, rect, TRUE, commit_callback, callback_user_data, FOLLOWS_LEFT | FOLLOWS_TOP), + mTextEnabledColor( LLUI::sColorsGroup->getColor( "LabelTextColor" ) ), + mTextDisabledColor( LLUI::sColorsGroup->getColor( "LabelDisabledColor" ) ), + mRadioStyle( use_radio_style ), + mInitialValue( initial_value ) +{ + if (font) + { + mFont = font; + } + else + { + mFont = LLFontGL::sSansSerifSmall; + } + + // must be big enough to hold all children + setSpanChildren(TRUE); + + mKeyboardFocusOnClick = TRUE; + + // Label (add a little space to make sure text actually renders) + const S32 FUDGE = 10; + S32 text_width = mFont->getWidth( label ) + FUDGE; + S32 text_height = llround(mFont->getLineHeight()); + LLRect label_rect; + label_rect.setOriginAndSize( + LLCHECKBOXCTRL_HPAD + LLCHECKBOXCTRL_BTN_SIZE + LLCHECKBOXCTRL_SPACING, + LLCHECKBOXCTRL_VPAD + 1, // padding to get better alignment + text_width + LLCHECKBOXCTRL_HPAD, + text_height ); + mLabel = new LLTextBox( "CheckboxCtrl Label", label_rect, label.c_str(), mFont ); + mLabel->setFollowsLeft(); + mLabel->setFollowsBottom(); + addChild(mLabel); + + // Button + // Note: button cover the label by extending all the way to the right. + LLRect btn_rect; + btn_rect.setOriginAndSize( + LLCHECKBOXCTRL_HPAD, + LLCHECKBOXCTRL_VPAD, + LLCHECKBOXCTRL_BTN_SIZE + LLCHECKBOXCTRL_SPACING + text_width + LLCHECKBOXCTRL_HPAD, + llmax( text_height, LLCHECKBOXCTRL_BTN_SIZE ) + LLCHECKBOXCTRL_VPAD); + LLString active_true_id, active_false_id; + LLString inactive_true_id, inactive_false_id; + if (mRadioStyle) + { + active_true_id = "UIImgRadioActiveSelectedUUID"; + active_false_id = "UIImgRadioActiveUUID"; + inactive_true_id = "UIImgRadioInactiveSelectedUUID"; + inactive_false_id = "UIImgRadioInactiveUUID"; + mButton = new LLButton( + "Radio control button", btn_rect, + active_false_id, active_true_id, control_which, + &LLCheckBoxCtrl::onButtonPress, this, LLFontGL::sSansSerif ); + mButton->setDisabledImages( inactive_false_id, inactive_true_id ); + mButton->setHoverGlowStrength(0.35f); + } + else + { + active_false_id = "UIImgCheckboxActiveUUID"; + active_true_id = "UIImgCheckboxActiveSelectedUUID"; + inactive_true_id = "UIImgCheckboxInactiveSelectedUUID"; + inactive_false_id = "UIImgCheckboxInactiveUUID"; + mButton = new LLButton( + "Checkbox control button", btn_rect, + active_false_id, active_true_id, control_which, + &LLCheckBoxCtrl::onButtonPress, this, LLFontGL::sSansSerif ); + mButton->setDisabledImages( inactive_false_id, inactive_true_id ); + mButton->setHoverGlowStrength(0.35f); + } + mButton->setToggleState( initial_value ); + mButton->setFollowsLeft(); + mButton->setFollowsBottom(); + mButton->setCommitOnReturn(FALSE); + addChild(mButton); +} + +LLCheckBoxCtrl::~LLCheckBoxCtrl() +{ + // Children all cleaned up by default view destructor. +} + + +// static +void LLCheckBoxCtrl::onButtonPress( void *userdata ) +{ + LLCheckBoxCtrl* self = (LLCheckBoxCtrl*) userdata; + + if (self->mRadioStyle) + { + if (!self->getValue()) + { + self->setValue(TRUE); + } + } + else + { + self->toggle(); + } + self->setControlValue(self->getValue()); + self->onCommit(); + + if (self->mKeyboardFocusOnClick) + { + self->setFocus( TRUE ); + self->onFocusReceived(); + } +} + +void LLCheckBoxCtrl::onCommit() +{ + if( getEnabled() ) + { + setTentative(FALSE); + LLUICtrl::onCommit(); + } +} + +void LLCheckBoxCtrl::setEnabled(BOOL b) +{ + LLUICtrl::setEnabled(b); + mButton->setEnabled(b); +} + +void LLCheckBoxCtrl::clear() +{ + setValue( FALSE ); +} + +void LLCheckBoxCtrl::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + //stretch or shrink bounding rectangle of label when rebuilding UI at new scale + const S32 FUDGE = 10; + S32 text_width = mFont->getWidth( mLabel->getText() ) + FUDGE; + S32 text_height = llround(mFont->getLineHeight()); + LLRect label_rect; + label_rect.setOriginAndSize( + LLCHECKBOXCTRL_HPAD + LLCHECKBOXCTRL_BTN_SIZE + LLCHECKBOXCTRL_SPACING, + LLCHECKBOXCTRL_VPAD, + text_width, + text_height ); + mLabel->setRect(label_rect); + + LLRect btn_rect; + btn_rect.setOriginAndSize( + LLCHECKBOXCTRL_HPAD, + LLCHECKBOXCTRL_VPAD, + LLCHECKBOXCTRL_BTN_SIZE + LLCHECKBOXCTRL_SPACING + text_width, + llmax( text_height, LLCHECKBOXCTRL_BTN_SIZE ) ); + mButton->setRect( btn_rect ); + + LLUICtrl::reshape(width, height, called_from_parent); +} + +void LLCheckBoxCtrl::draw() +{ + if (mEnabled) + { + mLabel->setColor( mTextEnabledColor ); + } + else + { + mLabel->setColor( mTextDisabledColor ); + } + + // Draw children + LLUICtrl::draw(); +} + +//virtual +void LLCheckBoxCtrl::setValue(const LLSD& value ) +{ + mButton->setToggleState( value.asBoolean() ); +} + +//virtual +LLSD LLCheckBoxCtrl::getValue() const +{ + return mButton->getToggleState(); +} + +void LLCheckBoxCtrl::setLabel( const LLString& label ) +{ + mLabel->setText( label ); + reshape(getRect().getWidth(), getRect().getHeight(), FALSE); +} + +LLString LLCheckBoxCtrl::getLabel() const +{ + return mLabel->getText(); +} + +BOOL LLCheckBoxCtrl::setLabelArg( const LLString& key, const LLString& text ) +{ + BOOL res = mLabel->setTextArg(key, text); + reshape(getRect().getWidth(), getRect().getHeight(), FALSE); + return res; +} + +//virtual +LLString LLCheckBoxCtrl::getControlName() const +{ + return mButton->getControlName(); +} + +// virtual +void LLCheckBoxCtrl::setControlName(const LLString& control_name, LLView* context) +{ + mButton->setControlName(control_name, context); +} + +// virtual +LLXMLNodePtr LLCheckBoxCtrl::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLUICtrl::getXML(); + + node->createChild("label", TRUE)->setStringValue(mLabel->getText()); + + LLString control_name = mButton->getControlName(); + + node->createChild("initial_value", TRUE)->setBoolValue(mInitialValue); + + node->createChild("font", TRUE)->setStringValue(LLFontGL::nameFromFont(mFont)); + + node->createChild("radio_style", TRUE)->setBoolValue(mRadioStyle); + + return node; +} + +// static +LLView* LLCheckBoxCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("checkbox"); + node->getAttributeString("name", name); + + LLString label(""); + node->getAttributeString("label", label); + + BOOL initial_value = FALSE; + + LLFontGL* font = LLView::selectFont(node); + + BOOL radio_style = FALSE; + node->getAttributeBOOL("radio_style", radio_style); + + LLUICtrlCallback callback = NULL; + + if (label.empty()) + { + label.assign(node->getTextContents()); + } + + LLRect rect; + createRect(node, rect, parent, LLRect()); + + LLCheckBoxCtrl* checkbox = new LLCheckboxCtrl(name, + rect, + label, + font, + callback, + NULL, + initial_value, + radio_style); // if true, draw radio button style icons + + LLColor4 color; + color = LLUI::sColorsGroup->getColor( "LabelTextColor" ); + LLUICtrlFactory::getAttributeColor(node,"text_enabled_color", color); + checkbox->setEnabledColor(color); + + color = LLUI::sColorsGroup->getColor( "LabelDisabledColor" ); + LLUICtrlFactory::getAttributeColor(node,"text_disabled_color", color); + checkbox->setDisabledColor(color); + + checkbox->initFromXML(node, parent); + + return checkbox; +} diff --git a/indra/llui/llcheckboxctrl.h b/indra/llui/llcheckboxctrl.h new file mode 100644 index 0000000000..b2f9c95974 --- /dev/null +++ b/indra/llui/llcheckboxctrl.h @@ -0,0 +1,112 @@ +/** + * @file llcheckboxctrl.h + * @brief LLCheckBoxCtrl base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLCHECKBOXCTRL_H +#define LL_LLCHECKBOXCTRL_H + + +#include "stdtypes.h" +#include "lluictrl.h" +#include "llbutton.h" +#include "v4color.h" +#include "llrect.h" + +// +// Constants +// +const S32 LLCHECKBOXCTRL_BTN_SIZE = 13; +const S32 LLCHECKBOXCTRL_VPAD = 2; +const S32 LLCHECKBOXCTRL_HPAD = 2; +const S32 LLCHECKBOXCTRL_SPACING = 5; +const S32 LLCHECKBOXCTRL_HEIGHT = 16; + +// Deprecated, don't use. +#define CHECKBOXCTRL_HEIGHT LLCHECKBOXCTRL_HEIGHT + +const BOOL RADIO_STYLE = TRUE; +const BOOL CHECK_STYLE = FALSE; + +// +// Classes +// +class LLFontGL; +class LLTextBox; +class LLViewBorder; + +class LLCheckBoxCtrl +: public LLUICtrl +{ +public: + LLCheckBoxCtrl(const LLString& name, const LLRect& rect, const LLString& label, + const LLFontGL* font = NULL, + void (*commit_callback)(LLUICtrl*, void*) = NULL, + void* callback_userdata = NULL, + BOOL initial_value = FALSE, + BOOL use_radio_style = FALSE, // if true, draw radio button style icons + const LLString& control_which = LLString::null); + virtual ~LLCheckBoxCtrl(); + + // LLView interface + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_CHECKBOX; } + virtual LLString getWidgetTag() const { return LL_CHECK_BOX_CTRL_TAG; } + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + + virtual void setEnabled( BOOL b ); + + virtual void draw(); + virtual void reshape(S32 width, S32 height, BOOL called_from_parent); + + // LLUICtrl interface + virtual void setValue(const LLSD& value ); + virtual LLSD getValue() const; + BOOL get() { return (BOOL)getValue().asBoolean(); } + void set(BOOL value) { setValue(value); } + + virtual void setTentative(BOOL b) { mButton->setTentative(b); } + virtual BOOL getTentative() const { return mButton->getTentative(); } + + virtual BOOL setLabelArg( const LLString& key, const LLString& text ); + + virtual void clear(); + virtual void onCommit(); + + // LLCheckBoxCtrl interface + virtual BOOL toggle() { return mButton->toggleState(); } // returns new state + + void setEnabledColor( const LLColor4 &color ) { mTextEnabledColor = color; } + void setDisabledColor( const LLColor4 &color ) { mTextDisabledColor = color; } + + void setLabel( const LLString& label ); + LLString getLabel() const; + + virtual void setControlName(const LLString& control_name, LLView* context); + virtual LLString getControlName() const; + + static void onButtonPress(void *userdata); + +protected: + // note: value is stored in toggle state of button + LLButton* mButton; + LLTextBox* mLabel; + const LLFontGL* mFont; + LLColor4 mTextEnabledColor; + LLColor4 mTextDisabledColor; + BOOL mRadioStyle; + BOOL mInitialValue; + BOOL mKeyboardFocusOnClick; + LLViewBorder* mBorder; +}; + + +// HACK: fix old capitalization problem +//typedef LLCheckBoxCtrl LLCheckboxCtrl; +#define LLCheckboxCtrl LLCheckBoxCtrl + + +#endif // LL_LLCHECKBOXCTRL_H diff --git a/indra/llui/llclipboard.cpp b/indra/llui/llclipboard.cpp new file mode 100644 index 0000000000..f2b546ec28 --- /dev/null +++ b/indra/llui/llclipboard.cpp @@ -0,0 +1,71 @@ +/** + * @file llclipboard.cpp + * @brief LLClipboard base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llclipboard.h" + +#include "llerror.h" +#include "llmath.h" +#include "llstring.h" +#include "llview.h" +#include "llwindow.h" + +// Global singleton +LLClipboard gClipboard; + + +LLClipboard::LLClipboard() +{ +} + + +LLClipboard::~LLClipboard() +{ +} + + +void LLClipboard::copyFromSubstring(const LLWString &src, S32 pos, S32 len, const LLUUID& source_id ) +{ + mSourceID = source_id; + mString = src.substr(pos, len); + LLView::getWindow()->copyTextToClipboard( mString ); +} + + +LLWString LLClipboard::getPasteWString( LLUUID* source_id ) +{ + if( mSourceID.notNull() ) + { + LLWString temp_string; + LLView::getWindow()->pasteTextFromClipboard(temp_string); + + if( temp_string != mString ) + { + mSourceID.setNull(); + mString = temp_string; + } + } + else + { + LLView::getWindow()->pasteTextFromClipboard(mString); + } + + if( source_id ) + { + *source_id = mSourceID; + } + + return mString; +} + + +BOOL LLClipboard::canPasteString() +{ + return LLView::getWindow()->isClipboardTextAvailable(); +} diff --git a/indra/llui/llclipboard.h b/indra/llui/llclipboard.h new file mode 100644 index 0000000000..a5bb4fc790 --- /dev/null +++ b/indra/llui/llclipboard.h @@ -0,0 +1,41 @@ +/** + * @file llclipboard.h + * @brief LLClipboard base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLCLIPBOARD_H +#define LL_LLCLIPBOARD_H + + +#include "llstring.h" +#include "lluuid.h" + +// +// Classes +// +class LLClipboard +{ +protected: + LLUUID mSourceID; + LLWString mString; + +public: + LLClipboard(); + ~LLClipboard(); + + void copyFromSubstring(const LLWString ©_from, S32 pos, S32 len, const LLUUID& source_id = LLUUID::null ); + + + BOOL canPasteString(); + LLWString getPasteWString(LLUUID* source_id = NULL); +}; + + +// Global singleton +extern LLClipboard gClipboard; + + +#endif // LL_LLCLIPBOARD_H diff --git a/indra/llui/llcombobox.cpp b/indra/llui/llcombobox.cpp new file mode 100644 index 0000000000..84c5d354be --- /dev/null +++ b/indra/llui/llcombobox.cpp @@ -0,0 +1,1133 @@ +/** + * @file llcombobox.cpp + * @brief LLComboBox base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// A control that displays the name of the chosen item, which when +// clicked shows a scrolling box of options. + +#include "linden_common.h" + +// file includes +#include "llcombobox.h" + +// common includes +#include "llstring.h" + +// newview includes +#include "llbutton.h" +#include "llkeyboard.h" +#include "llscrolllistctrl.h" +#include "llwindow.h" +#include "llfloater.h" +#include "llscrollbar.h" +#include "llcontrol.h" +#include "llfocusmgr.h" +#include "lllineeditor.h" +#include "v2math.h" + +// Globals +S32 LLCOMBOBOX_HEIGHT = 0; +S32 LLCOMBOBOX_WIDTH = 0; + +LLComboBox::LLComboBox( const LLString& name, const LLRect &rect, const LLString& label, + void (*commit_callback)(LLUICtrl*,void*), + void *callback_userdata, + S32 list_width + ) +: LLUICtrl(name, rect, TRUE, commit_callback, callback_userdata, + FOLLOWS_LEFT | FOLLOWS_TOP), + mDrawButton(TRUE), + mTextEntry(NULL), + mArrowImage(NULL), + mAllowTextEntry(FALSE), + mMaxChars(20), + mTextEntryTentative(TRUE), + mPrearrangeCallback( NULL ), + mTextEntryCallback( NULL ), + mListWidth(list_width) +{ + // For now, all comboboxes don't take keyboard focus when clicked. + // This might change if it is part of a modal dialog. + // mKeyboardFocusOnClick = FALSE; + + // Revert to standard behavior. When this control's parent is hidden, it needs to + // hide this ctrl--which won't just happen automatically since when LLComboBox is + // showing its list, it's also set to TopView. When keyboard focus is cleared all + // controls (including this one) know that they are no longer editing. + mKeyboardFocusOnClick = TRUE; + + LLRect r; + r.setOriginAndSize(0, 0, rect.getWidth(), rect.getHeight()); + + // Always use text box + // Text label button + mButton = new LLSquareButton("comboxbox button", + r, label, NULL, LLString::null, + &LLComboBox::onButtonClick, this); + mButton->setFont(LLFontGL::sSansSerifSmall); + mButton->setFollows(FOLLOWS_LEFT | FOLLOWS_BOTTOM | FOLLOWS_RIGHT); + mButton->setHAlign( LLFontGL::LEFT ); + + const S32 ARROW_WIDTH = 16; + mButton->setRightHPad( ARROW_WIDTH ); + addChild(mButton); + + // Default size, will be set by arrange() call in button callback. + if (list_width == 0) + { + list_width = mRect.getWidth() + SCROLLBAR_SIZE; + } + r.setOriginAndSize(0, 16, list_width, 220); + + // disallow multiple selection + mList = new LLScrollListCtrl( + "ComboBox", r, + &LLComboBox::onItemSelected, this, FALSE); + mList->setVisible(FALSE); + mList->setBgWriteableColor( LLColor4(1,1,1,1) ); + mList->setCommitOnKeyboardMovement(FALSE); + addChild(mList); + + LLRect border_rect(0, mRect.getHeight(), mRect.getWidth(), 0); + mBorder = new LLViewBorder( "combo border", border_rect ); + addChild( mBorder ); + mBorder->setFollows(FOLLOWS_LEFT|FOLLOWS_RIGHT|FOLLOWS_TOP|FOLLOWS_BOTTOM); + + LLUUID arrow_image_id( LLUI::sAssetsGroup->getString("combobox_arrow.tga") ); + mArrowImage = LLUI::sImageProvider->getUIImageByID(arrow_image_id); +} + + +LLComboBox::~LLComboBox() +{ + // children automatically deleted, including mMenu, mButton +} + +// virtual +LLXMLNodePtr LLComboBox::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLUICtrl::getXML(); + + // Attributes + + node->createChild("allow_text_entry", TRUE)->setBoolValue(mAllowTextEntry); + + node->createChild("max_chars", TRUE)->setIntValue(mMaxChars); + + // Contents + + std::vector<LLScrollListItem*> data_list = mList->getAllData(); + std::vector<LLScrollListItem*>::iterator data_itor; + for (data_itor = data_list.begin(); data_itor != data_list.end(); ++data_itor) + { + LLScrollListItem* item = *data_itor; + LLScrollListCell* cell = item->getColumn(0); + if (cell) + { + LLXMLNodePtr item_node = node->createChild("combo_item", FALSE); + LLSD value = item->getValue(); + item_node->createChild("value", TRUE)->setStringValue(value.asString()); + item_node->createChild("enabled", TRUE)->setBoolValue(item->getEnabled()); + item_node->setStringValue(cell->getText()); + } + } + + return node; +} + +// static +LLView* LLComboBox::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("combo_box"); + node->getAttributeString("name", name); + + LLString label(""); + node->getAttributeString("label", label); + + LLRect rect; + createRect(node, rect, parent, LLRect()); + + BOOL allow_text_entry = FALSE; + node->getAttributeBOOL("allow_text_entry", allow_text_entry); + + S32 max_chars = 20; + node->getAttributeS32("max_chars", max_chars); + + LLUICtrlCallback callback = NULL; + + LLComboBox* combo_box = new LLComboBox(name, + rect, + label, + callback, + NULL); + combo_box->setAllowTextEntry(allow_text_entry, max_chars); + + combo_box->initFromXML(node, parent); + + const LLString& contents = node->getValue(); + + if (contents.find_first_not_of(" \n\t") != contents.npos) + { + llerrs << "Legacy combo box item format used! Please convert to <combo_item> tags!" << llendl; + } + else + { + LLXMLNodePtr child; + for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) + { + if (child->hasName("combo_item")) + { + LLString label = child->getTextContents(); + + LLString value = label; + child->getAttributeString("value", value); + + combo_box->add(label, LLSD(value) ); + } + } + } + + combo_box->selectFirstItem(); + + return combo_box; +} + +void LLComboBox::setEnabled(BOOL enabled) +{ + LLUICtrl::setEnabled(enabled); + mButton->setEnabled(enabled); +} + +//FIXME: these are all hacks to support the fact that the combobox has mouse capture +// so we can hide the list when we don't handle the mouse up event +BOOL LLComboBox::handleHover(S32 x, S32 y, MASK mask) +{ + if (mList->getVisible()) + { + S32 local_x, local_y; + LLView::localPointToOtherView(x, y, &local_x, &local_y, mList); + if (mList->pointInView(local_x, local_y)) + { + return mList->handleHover(local_x, local_y, mask); + } + } + return LLUICtrl::handleHover(x, y, mask); +} + +BOOL LLComboBox::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if (mList->getVisible()) + { + S32 local_x, local_y; + LLView::localPointToOtherView(x, y, &local_x, &local_y, mList); + if (mList->pointInView(local_x, local_y)) + { + return mList->handleMouseDown(local_x, local_y, mask); + } + } + BOOL has_focus_now = hasFocus(); + BOOL handled = LLUICtrl::handleMouseDown(x, y, mask); + if (handled && !has_focus_now) + { + onFocusReceived(); + } + + return handled; +} + +BOOL LLComboBox::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + if (mList->getVisible()) + { + S32 local_x, local_y; + LLView::localPointToOtherView(x, y, &local_x, &local_y, mList); + if (mList->pointInView(local_x, local_y)) + { + return mList->handleRightMouseDown(local_x, local_y, mask); + } + } + return LLUICtrl::handleRightMouseDown(x, y, mask); +} + +BOOL LLComboBox::handleRightMouseUp(S32 x, S32 y, MASK mask) +{ + if (mList->getVisible()) + { + S32 local_x, local_y; + LLView::localPointToOtherView(x, y, &local_x, &local_y, mList); + if (mList->pointInView(local_x, local_y)) + { + return mList->handleRightMouseUp(local_x, local_y, mask); + } + } + return LLUICtrl::handleRightMouseUp(x, y, mask); +} + +BOOL LLComboBox::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + if (mList->getVisible()) + { + S32 local_x, local_y; + LLView::localPointToOtherView(x, y, &local_x, &local_y, mList); + if (mList->pointInView(local_x, local_y)) + { + return mList->handleDoubleClick(local_x, local_y, mask); + } + } + return LLUICtrl::handleDoubleClick(x, y, mask); +} + +BOOL LLComboBox::handleMouseUp(S32 x, S32 y, MASK mask) +{ + BOOL handled = childrenHandleMouseUp(x, y, mask) != NULL; + + if (!handled && mList->getVisible()) + { + S32 local_x, local_y; + LLView::localPointToOtherView(x, y, &local_x, &local_y, mList); + if (mList->pointInView(local_x, local_y)) + { + handled = mList->handleMouseUp(local_x, local_y, mask); + } + } + + if( !handled && gFocusMgr.getMouseCapture() == this ) + { + // Mouse events that we didn't handle cause the list to be hidden. + // Eat mouse event, regardless of where on the screen it happens. + hideList(); + handled = TRUE; + } + + return handled; +} + +void LLComboBox::clear() +{ + if (mTextEntry) + { + mTextEntry->setText(""); + } + mButton->setLabelSelected(""); + mButton->setLabelUnselected(""); + mButton->setDisabledLabel(""); + mButton->setDisabledSelectedLabel(""); + mList->deselectAllItems(); +} + +void LLComboBox::onCommit() +{ + if (mAllowTextEntry && getCurrentIndex() != -1) + { + // we have selected an existing item, blitz the manual text entry with + // the properly capitalized item + mTextEntry->setValue(getSimple()); + mTextEntry->setTentative(FALSE); + } + LLUICtrl::onCommit(); +} + +// add item "name" to menu +void LLComboBox::add(const LLString& name, EAddPosition pos, BOOL enabled) +{ + mList->addSimpleItem(name, pos, enabled); + mList->selectFirstItem(); +} + +// add item "name" with a unique id to menu +void LLComboBox::add(const LLString& name, const LLUUID& id, EAddPosition pos, BOOL enabled ) +{ + mList->addSimpleItem(name, LLSD(id), pos, enabled); + mList->selectFirstItem(); +} + +// add item "name" with attached userdata +void LLComboBox::add(const LLString& name, void* userdata, EAddPosition pos, BOOL enabled ) +{ + LLScrollListItem* item = mList->addSimpleItem(name, pos, enabled); + item->setUserdata( userdata ); + mList->selectFirstItem(); +} + +// add item "name" with attached generic data +void LLComboBox::add(const LLString& name, LLSD value, EAddPosition pos, BOOL enabled ) +{ + mList->addSimpleItem(name, value, pos, enabled); + mList->selectFirstItem(); +} + + +void LLComboBox::sortByName() +{ + mList->sortByColumn(0, TRUE); +} + + +// Choose an item with a given name in the menu. +// Returns TRUE if the item was found. +BOOL LLComboBox::setSimple(const LLString& name) +{ + BOOL found = mList->selectSimpleItem(name, FALSE); + + if (found) + { + setLabel(name); + } + + return found; +} + +// virtual +void LLComboBox::setValue(const LLSD& value) +{ + BOOL found = mList->selectByValue(value); + if (found) + { + LLScrollListItem* item = mList->getFirstSelected(); + if (item) + { + setLabel( mList->getSimpleSelectedItem() ); + } + } +} + +const LLString& LLComboBox::getSimple() const +{ + const LLString& res = mList->getSimpleSelectedItem(); + if (res.empty() && mAllowTextEntry) + { + return mTextEntry->getText(); + } + else + { + return res; + } +} + +const LLString& LLComboBox::getSimpleSelectedItem(S32 column) const +{ + return mList->getSimpleSelectedItem(column); +} + +// virtual +LLSD LLComboBox::getValue() const +{ + LLScrollListItem* item = mList->getFirstSelected(); + if( item ) + { + return item->getValue(); + } + else if (mAllowTextEntry) + { + return mTextEntry->getValue(); + } + else + { + return LLSD(); + } +} + +void LLComboBox::setLabel(const LLString& name) +{ + if ( mAllowTextEntry ) + { + mTextEntry->setText(name); + if (mList->selectSimpleItem(name, FALSE)) + { + mTextEntry->setTentative(FALSE); + } + else + { + mTextEntry->setTentative(mTextEntryTentative); + } + } + else + { + mButton->setLabelUnselected(name); + mButton->setLabelSelected(name); + mButton->setDisabledLabel(name); + mButton->setDisabledSelectedLabel(name); + } +} + + +BOOL LLComboBox::remove(const LLString& name) +{ + BOOL found = mList->selectSimpleItem(name); + + if (found) + { + LLScrollListItem* item = mList->getFirstSelected(); + if (item) + { + mList->deleteSingleItem(mList->getItemIndex(item)); + } + } + + return found; +} + +BOOL LLComboBox::remove(S32 index) +{ + if (index < mList->getItemCount()) + { + mList->deleteSingleItem(index); + return TRUE; + } + return FALSE; +} + +// Keyboard focus lost. +void LLComboBox::onFocusLost() +{ + hideList(); + // if valid selection + if (mAllowTextEntry && getCurrentIndex() != -1) + { + mTextEntry->selectAll(); + } +} + +void LLComboBox::setButtonVisible(BOOL visible) +{ + mButton->setVisible(visible); + mDrawButton = visible; + if (mTextEntry) + { + LLRect text_entry_rect(0, mRect.getHeight(), mRect.getWidth(), 0); + if (visible) + { + text_entry_rect.mRight -= mArrowImage->getWidth() + 2 * LLUI::sConfigGroup->getS32("DropShadowButton"); + } + //mTextEntry->setRect(text_entry_rect); + mTextEntry->reshape(text_entry_rect.getWidth(), text_entry_rect.getHeight(), TRUE); + } +} + +void LLComboBox::draw() +{ + if( getVisible() ) + { + mBorder->setKeyboardFocusHighlight(hasFocus()); + + mButton->setEnabled(mEnabled /*&& !mList->isEmpty()*/); + + // Draw children + LLUICtrl::draw(); + + if (mDrawButton) + { + // Paste the graphic on the right edge + if (!mArrowImage.isNull()) + { + S32 left = mRect.getWidth() - mArrowImage->getWidth() - LLUI::sConfigGroup->getS32("DropShadowButton"); + + gl_draw_image( left, 0, mArrowImage, + LLColor4::white); + } + } + } +} + +BOOL LLComboBox::setCurrentByIndex( S32 index ) +{ + BOOL found = mList->selectNthItem( index ); + if (found) + { + setLabel(mList->getSimpleSelectedItem()); + } + return found; +} + +S32 LLComboBox::getCurrentIndex() const +{ + LLScrollListItem* item = mList->getFirstSelected(); + if( item ) + { + return mList->getItemIndex( item ); + } + return -1; +} + + +void* LLComboBox::getCurrentUserdata() +{ + LLScrollListItem* item = mList->getFirstSelected(); + if( item ) + { + return item->getUserdata(); + } + return NULL; +} + + +void LLComboBox::showList() +{ + // Make sure we don't go off top of screen. + LLCoordWindow window_size; + getWindow()->getSize(&window_size); + //HACK: shouldn't have to know about scale here + mList->arrange( 192, llfloor((F32)window_size.mY / LLUI::sGLScaleFactor.mV[VY]) - 50 ); + + // Move rect so it hangs off the bottom of this view + LLRect rect = mList->getRect(); + + rect.setLeftTopAndSize(0, 0, rect.getWidth(), rect.getHeight() ); + mList->setRect(rect); + + // Make sure that we can see the whole list + LLRect floater_area_screen; + LLRect floater_area_local; + gFloaterView->getParent()->localRectToScreen( gFloaterView->getRect(), &floater_area_screen ); + screenRectToLocal( floater_area_screen, &floater_area_local ); + mList->translateIntoRect( floater_area_local, FALSE ); + + // Make sure we didn't go off bottom of screen + S32 x, y; + mList->localPointToScreen(0, 0, &x, &y); + + if (y < 0) + { + mList->translate(0, -y); + } + + gFocusMgr.setMouseCapture( this, LLComboBox::onMouseCaptureLost ); + // NB: this call will trigger the focuslost callback which will hide the list, so do it first + // before finally showing the list + + if (!mList->getFirstSelected()) + { + // if nothing is selected, select the first item + // so that the callback is not immediately triggered on setFocus() + mList->selectFirstItem(); + } + gFocusMgr.setKeyboardFocus(mList, onListFocusLost); + + // Show the list and push the button down + mButton->setToggleState(TRUE); + mList->setVisible(TRUE); + + gFocusMgr.setTopView(mList, LLComboBox::onTopViewLost ); + +} + +void LLComboBox::hideList() +{ + mButton->setToggleState(FALSE); + mList->setVisible(FALSE); + mList->highlightNthItem(-1); + + if( gFocusMgr.getTopView() == mList ) + { + gFocusMgr.setTopView(NULL, NULL); + } + + if( gFocusMgr.getMouseCapture() == this ) + { + gFocusMgr.setMouseCapture( NULL, NULL ); + } + + if( gFocusMgr.getKeyboardFocus() == mList ) + { + if (mAllowTextEntry) + { + mTextEntry->setFocus(TRUE); + } + else + { + setFocus(TRUE); + } + } +} + + + +//------------------------------------------------------------------ +// static functions +//------------------------------------------------------------------ + +// static +void LLComboBox::onButtonClick(void *userdata) +{ + LLComboBox *self = (LLComboBox *)userdata; + + if (!self->mList->getVisible()) + { + LLScrollListItem* last_selected_item = self->mList->getLastSelectedItem(); + if (last_selected_item) + { + // highlight the original selection before potentially selecting a new item + self->mList->highlightNthItem(self->mList->getItemIndex(last_selected_item)); + } + + if( self->mPrearrangeCallback ) + { + self->mPrearrangeCallback( self, self->mCallbackUserData ); + } + + if (self->mList->getItemCount() != 0) + { + self->showList(); + } + + if (self->mKeyboardFocusOnClick && !self->hasFocus()) + { + self->setFocus( TRUE ); + } + } + else + { + // hide and release keyboard focus + self->hideList(); + + self->onCommit(); + } +} + + + +// static +void LLComboBox::onItemSelected(LLUICtrl* item, void *userdata) +{ + // Note: item is the LLScrollListCtrl + LLComboBox *self = (LLComboBox *) userdata; + + const LLString& name = self->mList->getSimpleSelectedItem(); + + self->hideList(); + + S32 cur_id = self->getCurrentIndex(); + if (cur_id != -1) + { + self->setLabel(self->mList->getSimpleSelectedItem()); + + if (self->mAllowTextEntry) + { + self->mTextEntry->setText(name); + self->mTextEntry->setTentative(FALSE); + gFocusMgr.setKeyboardFocus(self->mTextEntry, NULL); + self->mTextEntry->selectAll(); + } + else + { + self->mButton->setLabelUnselected( name ); + self->mButton->setLabelSelected( name ); + self->mButton->setDisabledLabel( name ); + self->mButton->setDisabledSelectedLabel( name ); + } + } + self->onCommit(); +} + +// static +void LLComboBox::onTopViewLost(LLView* old_focus) +{ + LLComboBox *self = (LLComboBox *) old_focus->getParent(); + self->hideList(); +} + + +// static +void LLComboBox::onMouseCaptureLost(LLMouseHandler*) +{ + // Can't hide the list here. If the list scrolls off the screen, + // and you click in the arrow buttons of the scroll bar, they must capture + // the mouse to handle scrolling-while-mouse-down. +} + +BOOL LLComboBox::handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect_screen) +{ + + LLString tool_tip; + + if (LLUI::sShowXUINames) + { + tool_tip = mName; + } + else + { + tool_tip = mToolTipMsg; + } + + if( getVisible() && pointInView( x, y ) ) + { + if( !tool_tip.empty() ) + { + msg = tool_tip; + + // Convert rect local to screen coordinates + localPointToScreen( + 0, 0, + &(sticky_rect_screen->mLeft), &(sticky_rect_screen->mBottom) ); + localPointToScreen( + mRect.getWidth(), mRect.getHeight(), + &(sticky_rect_screen->mRight), &(sticky_rect_screen->mTop) ); + } + return TRUE; + } + return FALSE; +} + +BOOL LLComboBox::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent) +{ + BOOL result = FALSE; + if (gFocusMgr.childHasKeyboardFocus(this)) + { + //give list a chance to pop up and handle key + LLScrollListItem* last_selected_item = mList->getLastSelectedItem(); + if (last_selected_item) + { + // highlight the original selection before potentially selecting a new item + mList->highlightNthItem(mList->getItemIndex(last_selected_item)); + } + result = mList->handleKeyHere(key, mask, FALSE); + // if selection has changed, pop open list + if (mList->getLastSelectedItem() != last_selected_item) + { + showList(); + } + } + return result; +} + +BOOL LLComboBox::handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent) +{ + BOOL result = FALSE; + if (gFocusMgr.childHasKeyboardFocus(this)) + { + // space bar just shows the list + if (' ' != uni_char ) + { + LLScrollListItem* last_selected_item = mList->getLastSelectedItem(); + if (last_selected_item) + { + // highlight the original selection before potentially selecting a new item + mList->highlightNthItem(mList->getItemIndex(last_selected_item)); + } + result = mList->handleUnicodeCharHere(uni_char, called_from_parent); + if (mList->getLastSelectedItem() != last_selected_item) + { + showList(); + } + } + } + return result; +} + +void LLComboBox::setAllowTextEntry(BOOL allow, S32 max_chars, BOOL set_tentative) +{ + LLRect rect( 0, mRect.getHeight(), mRect.getWidth(), 0); + if (allow && !mAllowTextEntry) + { + S32 shadow_size = LLUI::sConfigGroup->getS32("DropShadowButton"); + mButton->setRect(LLRect( mRect.getWidth() - mArrowImage->getWidth() - 2 * shadow_size, + rect.mTop, rect.mRight, rect.mBottom)); + mButton->setTabStop(FALSE); + + // clear label on button + LLString cur_label = mButton->getLabelSelected(); + setLabel(""); + if (!mTextEntry) + { + LLRect text_entry_rect(0, mRect.getHeight(), mRect.getWidth(), 0); + text_entry_rect.mRight -= mArrowImage->getWidth() + 2 * LLUI::sConfigGroup->getS32("DropShadowButton"); + mTextEntry = new LLLineEditor("combo_text_entry", + text_entry_rect, + "", + LLFontGL::sSansSerifSmall, + max_chars, + onTextCommit, + onTextEntry, + NULL, + this, + NULL, // prevalidate func + LLViewBorder::BEVEL_NONE, + LLViewBorder::STYLE_LINE, + 0); // no border + mTextEntry->setSelectAllonFocusReceived(TRUE); + mTextEntry->setHandleEditKeysDirectly(TRUE); + mTextEntry->setCommitOnFocusLost(FALSE); + mTextEntry->setText(cur_label); + mTextEntry->setIgnoreTab(TRUE); + addChild(mTextEntry); + mMaxChars = max_chars; + } + else + { + mTextEntry->setVisible(TRUE); + } + } + else if (!allow && mAllowTextEntry) + { + mButton->setRect(rect); + mButton->setTabStop(TRUE); + + if (mTextEntry) + { + mTextEntry->setVisible(FALSE); + } + } + mAllowTextEntry = allow; + mTextEntryTentative = set_tentative; +} + +void LLComboBox::setTextEntry(const LLString& text) +{ + if (mTextEntry) + { + mTextEntry->setText(text); + updateSelection(); + } +} + +//static +void LLComboBox::onTextEntry(LLLineEditor* line_editor, void* user_data) +{ + LLComboBox* self = (LLComboBox*)user_data; + + if (self->mTextEntryCallback) + { + (*self->mTextEntryCallback)(line_editor, self->mCallbackUserData); + } + + KEY key = gKeyboard->currentKey(); + if (key == KEY_BACKSPACE || + key == KEY_DELETE) + { + if (self->mList->selectSimpleItem(line_editor->getText(), FALSE)) + { + line_editor->setTentative(FALSE); + } + else + { + line_editor->setTentative(self->mTextEntryTentative); + } + return; + } + + if (key == KEY_LEFT || + key == KEY_RIGHT) + { + return; + } + + if (key == KEY_DOWN) + { + self->setCurrentByIndex(llmin(self->getItemCount() - 1, self->getCurrentIndex() + 1)); + if (!self->mList->getVisible()) + { + if( self->mPrearrangeCallback ) + { + self->mPrearrangeCallback( self, self->mCallbackUserData ); + } + + if (self->mList->getItemCount() != 0) + { + self->showList(); + } + } + line_editor->selectAll(); + line_editor->setTentative(FALSE); + } + else if (key == KEY_UP) + { + self->setCurrentByIndex(llmax(0, self->getCurrentIndex() - 1)); + if (!self->mList->getVisible()) + { + if( self->mPrearrangeCallback ) + { + self->mPrearrangeCallback( self, self->mCallbackUserData ); + } + + if (self->mList->getItemCount() != 0) + { + self->showList(); + } + } + line_editor->selectAll(); + line_editor->setTentative(FALSE); + } + else + { + // RN: presumably text entry + self->updateSelection(); + } +} + +void LLComboBox::updateSelection() +{ + LLWString left_wstring = mTextEntry->getWText().substr(0, mTextEntry->getCursor()); + // user-entered portion of string, based on assumption that any selected + // text was a result of auto-completion + LLWString user_wstring = mTextEntry->hasSelection() ? left_wstring : mTextEntry->getWText(); + LLString full_string = mTextEntry->getText(); + + // go ahead and arrange drop down list on first typed character, even + // though we aren't showing it... some code relies on prearrange + // callback to populate content + if( mTextEntry->getWText().size() == 1 ) + { + if (mPrearrangeCallback) + { + mPrearrangeCallback( this, mCallbackUserData ); + } + } + + if (mList->selectSimpleItem(full_string, FALSE)) + { + mTextEntry->setTentative(FALSE); + } + else if (!mList->selectSimpleItemByPrefix(left_wstring, FALSE)) + { + mList->deselectAllItems(); + mTextEntry->setText(wstring_to_utf8str(user_wstring)); + mTextEntry->setTentative(mTextEntryTentative); + } + else + { + LLWString selected_item = utf8str_to_wstring(mList->getSimpleSelectedItem()); + LLWString wtext = left_wstring + selected_item.substr(left_wstring.size(), selected_item.size()); + mTextEntry->setText(wstring_to_utf8str(wtext)); + mTextEntry->setSelection(left_wstring.size(), mTextEntry->getWText().size()); + mTextEntry->endSelection(); + mTextEntry->setTentative(FALSE); + } +} + +//static +void LLComboBox::onTextCommit(LLUICtrl* caller, void* user_data) +{ + LLComboBox* self = (LLComboBox*)user_data; + LLString text = self->mTextEntry->getText(); + self->setSimple(text); + self->onCommit(); + self->mTextEntry->selectAll(); +} + +void LLComboBox::setFocus(BOOL b) +{ + LLUICtrl::setFocus(b); + + if (b) + { + mList->clearSearchString(); + } +} + +//============================================================================ +// LLCtrlListInterface functions + +S32 LLComboBox::getItemCount() const +{ + return mList->getItemCount(); +} + +void LLComboBox::addColumn(const LLSD& column, EAddPosition pos) +{ + mList->clearColumns(); + mList->addColumn(column, pos); +} + +void LLComboBox::clearColumns() +{ + mList->clearColumns(); +} + +void LLComboBox::setColumnLabel(const LLString& column, const LLString& label) +{ + mList->setColumnLabel(column, label); +} + +LLScrollListItem* LLComboBox::addElement(const LLSD& value, EAddPosition pos, void* userdata) +{ + return mList->addElement(value, pos, userdata); +} + +LLScrollListItem* LLComboBox::addSimpleElement(const LLString& value, EAddPosition pos, const LLSD& id) +{ + return mList->addSimpleElement(value, pos, id); +} + +void LLComboBox::clearRows() +{ + mList->clearRows(); +} + +void LLComboBox::sortByColumn(LLString name, BOOL ascending) +{ +} + +//============================================================================ +//LLCtrlSelectionInterface functions + +BOOL LLComboBox::setCurrentByID(const LLUUID& id) +{ + BOOL found = mList->selectByID( id ); + + if (found) + { + setLabel(mList->getSimpleSelectedItem()); + } + + return found; +} + +LLUUID LLComboBox::getCurrentID() +{ + return mList->getStringUUIDSelectedItem(); +} +BOOL LLComboBox::setSelectedByValue(LLSD value, BOOL selected) +{ + BOOL found = mList->setSelectedByValue(value, selected); + if (found) + { + setLabel(mList->getSimpleSelectedItem()); + } + return found; +} + +LLSD LLComboBox::getSimpleSelectedValue() +{ + return mList->getSimpleSelectedValue(); +} + +BOOL LLComboBox::isSelected(LLSD value) +{ + return mList->isSelected(value); +} + +BOOL LLComboBox::operateOnSelection(EOperation op) +{ + if (op == OP_DELETE) + { + mList->deleteSelectedItems(); + return TRUE; + } + return FALSE; +} + +BOOL LLComboBox::operateOnAll(EOperation op) +{ + if (op == OP_DELETE) + { + clearRows(); + return TRUE; + } + return FALSE; +} + +//static +void LLComboBox::onListFocusLost(LLUICtrl* old_focus) +{ + // if focus is going to nothing (user hit ESC), take it back + LLComboBox* combo = (LLComboBox*)old_focus->getParent(); + combo->hideList(); + if (gFocusMgr.getKeyboardFocus() == NULL) + { + combo->focusFirstItem(); + } +} diff --git a/indra/llui/llcombobox.h b/indra/llui/llcombobox.h new file mode 100644 index 0000000000..1ec31ec1c0 --- /dev/null +++ b/indra/llui/llcombobox.h @@ -0,0 +1,178 @@ +/** + * @file llcombobox.h + * @brief LLComboBox base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// A control that displays the name of the chosen item, which when clicked +// shows a scrolling box of choices. + +#ifndef LL_LLCOMBOBOX_H +#define LL_LLCOMBOBOX_H + +#include "llbutton.h" +#include "lluictrl.h" +#include "llctrlselectioninterface.h" +#include "llimagegl.h" +#include "llrect.h" + +// Classes + +class LLFontGL; +class LLButton; +class LLSquareButton; +class LLScrollListCtrl; +class LLLineEditor; +class LLViewBorder; + +extern S32 LLCOMBOBOX_HEIGHT; +extern S32 LLCOMBOBOX_WIDTH; + +class LLComboBox +: public LLUICtrl, public LLCtrlListInterface +{ +public: + LLComboBox( + const LLString& name, + const LLRect &rect, + const LLString& label, + void (*commit_callback)(LLUICtrl*, void*) = NULL, + void *callback_userdata = NULL, + S32 list_width = 0 + ); + virtual ~LLComboBox(); + + // LLView interface + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_COMBO_BOX; } + virtual LLString getWidgetTag() const { return LL_COMBO_BOX_TAG; } + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + + virtual void draw(); + virtual void onFocusLost(); + + virtual void setEnabled(BOOL enabled); + + virtual BOOL handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect); + virtual BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent); + virtual BOOL handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent); + virtual BOOL handleHover(S32 x, S32 y, MASK mask); + virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleRightMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleRightMouseUp(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); + + // LLUICtrl interface + virtual void clear(); // select nothing + virtual void onCommit(); + virtual BOOL acceptsTextInput() const { return mAllowTextEntry; } + + virtual void setFocus(BOOL b); + + // Selects item by underlying LLSD value, using LLSD::asString() matching. + // For simple items, this is just the name of the label. + virtual void setValue(const LLSD& value ); + + // Gets underlying LLSD value for currently selected items. For simple + // items, this is just the label. + virtual LLSD getValue() const; + + void setAllowTextEntry(BOOL allow, S32 max_chars = 50, BOOL make_tentative = TRUE); + void setTextEntry(const LLString& text); + + void add(const LLString& name, EAddPosition pos = ADD_BOTTOM, BOOL enabled = TRUE); // add item "name" to menu + void add(const LLString& name, const LLUUID& id, EAddPosition pos = ADD_BOTTOM, BOOL enabled = TRUE); + void add(const LLString& name, void* userdata, EAddPosition pos = ADD_BOTTOM, BOOL enabled = TRUE); + void add(const LLString& name, LLSD value, EAddPosition pos = ADD_BOTTOM, BOOL enabled = TRUE); + BOOL remove( S32 index ); // remove item by index, return TRUE if found and removed + void removeall() { clearRows(); } + + void sortByName(); // Sort the entries in the combobox by name + + // Select current item by name using selectSimpleItem. Returns FALSE if not found. + BOOL setSimple(const LLString& name); + // Get name of current item. Returns an empty string if not found. + const LLString& getSimple() const; + // Get contents of column x of selected row + const LLString& getSimpleSelectedItem(S32 column = 0) const; + + // Sets the label, which doesn't have to exist in the label. + // This is probably a UI abuse. + void setLabel(const LLString& name); + + BOOL remove(const LLString& name); // remove item "name", return TRUE if found and removed + + BOOL setCurrentByIndex( S32 index ); + S32 getCurrentIndex() const; + + //======================================================================== + LLCtrlSelectionInterface* getSelectionInterface() { return (LLCtrlSelectionInterface*)this; }; + LLCtrlListInterface* getListInterface() { return (LLCtrlListInterface*)this; }; + + // LLCtrlListInterface functions + // See llscrolllistctrl.h + virtual S32 getItemCount() const; + // Overwrites the default column (See LLScrollListCtrl for format) + virtual void addColumn(const LLSD& column, EAddPosition pos = ADD_BOTTOM); + virtual void clearColumns(); + virtual void setColumnLabel(const LLString& column, const LLString& label); + virtual LLScrollListItem* addElement(const LLSD& value, EAddPosition pos = ADD_BOTTOM, void* userdata = NULL); + virtual LLScrollListItem* addSimpleElement(const LLString& value, EAddPosition pos = ADD_BOTTOM, const LLSD& id = LLSD()); + virtual void clearRows(); + virtual void sortByColumn(LLString name, BOOL ascending); + + // LLCtrlSelectionInterface functions + virtual BOOL getCanSelect() const { return TRUE; } + virtual BOOL selectFirstItem() { return setCurrentByIndex(0); } + virtual BOOL selectNthItem( S32 index ) { return setCurrentByIndex(index); } + virtual S32 getFirstSelectedIndex() { return getCurrentIndex(); } + virtual BOOL setCurrentByID( const LLUUID& id ); + virtual LLUUID getCurrentID(); // LLUUID::null if no items in menu + virtual BOOL setSelectedByValue(LLSD value, BOOL selected); + virtual LLSD getSimpleSelectedValue(); + virtual BOOL isSelected(LLSD value); + virtual BOOL operateOnSelection(EOperation op); + virtual BOOL operateOnAll(EOperation op); + + //======================================================================== + + void* getCurrentUserdata(); + + void setPrearrangeCallback( void (*cb)(LLUICtrl*,void*) ) { mPrearrangeCallback = cb; } + void setTextEntryCallback( void (*cb)(LLLineEditor*, void*) ) { mTextEntryCallback = cb; } + + void setButtonVisible(BOOL visible); + + static void onButtonClick(void *userdata); + static void onItemSelected(LLUICtrl* item, void *userdata); + static void onTopViewLost(LLView* old_focus); + static void onMouseCaptureLost(LLMouseHandler* old_captor); + static void onTextEntry(LLLineEditor* line_editor, void* user_data); + static void onTextCommit(LLUICtrl* caller, void* user_data); + + void updateSelection(); + void showList(); + void hideList(); + + static void onListFocusLost(LLUICtrl* old_focus); + +protected: + LLButton* mButton; + LLScrollListCtrl* mList; + LLViewBorder* mBorder; + BOOL mKeyboardFocusOnClick; + BOOL mDrawButton; + LLLineEditor* mTextEntry; + LLPointer<LLImageGL> mArrowImage; + BOOL mAllowTextEntry; + S32 mMaxChars; + BOOL mTextEntryTentative; + void (*mPrearrangeCallback)(LLUICtrl*,void*); + void (*mTextEntryCallback)(LLLineEditor*, void*); + S32 mListWidth; // width of pop-up list, 0 = use combobox width +}; + +#endif diff --git a/indra/llui/llctrlselectioninterface.cpp b/indra/llui/llctrlselectioninterface.cpp new file mode 100644 index 0000000000..a58fb88e75 --- /dev/null +++ b/indra/llui/llctrlselectioninterface.cpp @@ -0,0 +1,44 @@ +/**
+ * @file llctrlselectioninterface.cpp
+ * @brief Programmatic selection of items in a list.
+ *
+ * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc.
+ * $License$
+ */
+
+#include "llctrlselectioninterface.h"
+
+#include "llsd.h"
+
+// virtual
+LLCtrlSelectionInterface::~LLCtrlSelectionInterface()
+{ }
+
+BOOL LLCtrlSelectionInterface::selectByValue(LLSD value)
+{
+ return setSelectedByValue(value, TRUE);
+}
+
+BOOL LLCtrlSelectionInterface::deselectByValue(LLSD value)
+{
+ return setSelectedByValue(value, FALSE);
+}
+
+
+// virtual
+LLCtrlListInterface::~LLCtrlListInterface()
+{ }
+
+LLScrollListItem* LLCtrlListInterface::addSimpleElement(const LLString& value)
+{
+ return addSimpleElement(value, ADD_BOTTOM, LLSD());
+}
+
+LLScrollListItem* LLCtrlListInterface::addSimpleElement(const LLString& value, EAddPosition pos)
+{
+ return addSimpleElement(value, pos, LLSD());
+}
+
+// virtual
+LLCtrlScrollInterface::~LLCtrlScrollInterface()
+{ }
diff --git a/indra/llui/llctrlselectioninterface.h b/indra/llui/llctrlselectioninterface.h new file mode 100644 index 0000000000..4e2807e9a1 --- /dev/null +++ b/indra/llui/llctrlselectioninterface.h @@ -0,0 +1,84 @@ +/**
+ * @file llctrlselectioninterface.h
+ * @brief Programmatic selection of items in a list.
+ *
+ * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc.
+ * $License$
+ */
+
+#ifndef LLCTRLSELECTIONINTERFACE_H
+#define LLCTRLSELECTIONINTERFACE_H
+
+#include "stdtypes.h"
+#include "stdenums.h"
+#include "llstring.h"
+
+class LLSD;
+class LLUUID;
+class LLScrollListItem;
+
+class LLCtrlSelectionInterface
+{
+public:
+ virtual ~LLCtrlSelectionInterface();
+
+ enum EOperation
+ {
+ OP_DELETE = 1,
+ OP_SELECT,
+ OP_DESELECT,
+ };
+
+ virtual BOOL getCanSelect() const = 0;
+
+ virtual BOOL selectFirstItem() = 0;
+ virtual BOOL selectNthItem( S32 index ) = 0;
+
+ virtual S32 getFirstSelectedIndex() = 0;
+
+ // TomY TODO: Simply cast the UUIDs to LLSDs, using the selectByValue function
+ virtual BOOL setCurrentByID( const LLUUID& id ) = 0;
+ virtual LLUUID getCurrentID() = 0;
+
+ BOOL selectByValue(LLSD value);
+ BOOL deselectByValue(LLSD value);
+ virtual BOOL setSelectedByValue(LLSD value, BOOL selected) = 0;
+ virtual LLSD getSimpleSelectedValue() = 0;
+
+ virtual BOOL isSelected(LLSD value) = 0;
+
+ virtual BOOL operateOnSelection(EOperation op) = 0;
+ virtual BOOL operateOnAll(EOperation op) = 0;
+};
+
+class LLCtrlListInterface : public LLCtrlSelectionInterface
+{
+public:
+ virtual ~LLCtrlListInterface();
+
+ virtual S32 getItemCount() const = 0;
+ virtual void addColumn(const LLSD& column, EAddPosition pos = ADD_BOTTOM) = 0;
+ virtual void clearColumns() = 0;
+ virtual void setColumnLabel(const LLString& column, const LLString& label) = 0;
+ // TomY TODO: Document this
+ virtual LLScrollListItem* addElement(const LLSD& value, EAddPosition pos = ADD_BOTTOM, void* userdata = NULL) = 0;
+
+ LLScrollListItem* addSimpleElement(const LLString& value); // defaults to bottom
+ LLScrollListItem* addSimpleElement(const LLString& value, EAddPosition pos); // defaults to no LLSD() id
+ virtual LLScrollListItem* addSimpleElement(const LLString& value, EAddPosition pos, const LLSD& id) = 0;
+
+ virtual void clearRows() = 0;
+ virtual void sortByColumn(LLString name, BOOL ascending) = 0;
+};
+
+class LLCtrlScrollInterface
+{
+public:
+ virtual ~LLCtrlScrollInterface();
+
+ virtual S32 getScrollPos() = 0;
+ virtual void setScrollPos( S32 pos ) = 0;
+ virtual void scrollToShowSelected() = 0;
+};
+
+#endif
diff --git a/indra/llui/lldraghandle.cpp b/indra/llui/lldraghandle.cpp new file mode 100644 index 0000000000..a88fbb7744 --- /dev/null +++ b/indra/llui/lldraghandle.cpp @@ -0,0 +1,353 @@ +/** + * @file lldraghandle.cpp + * @brief LLDragHandle base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// A widget for dragging a view around the screen using the mouse. + +#include "linden_common.h" + +#include "lldraghandle.h" + +#include "llmath.h" + +//#include "llviewerwindow.h" +#include "llui.h" +#include "llmenugl.h" +#include "lltextbox.h" +#include "llcontrol.h" +#include "llresmgr.h" +#include "llfontgl.h" +#include "llwindow.h" +#include "llfocusmgr.h" + +const S32 LEADING_PAD = 5; +const S32 TITLE_PAD = 8; +const S32 BORDER_PAD = 1; +const S32 LEFT_PAD = BORDER_PAD + TITLE_PAD + LEADING_PAD; +const S32 RIGHT_PAD = BORDER_PAD + 32; // HACK: space for close btn and minimize btn + +S32 LLDragHandle::sSnapMargin = 5; + +LLDragHandle::LLDragHandle( const LLString& name, const LLRect& rect, const LLString& title ) +: LLView( name, rect, TRUE ), + mDragLastScreenX( 0 ), + mDragLastScreenY( 0 ), + mLastMouseScreenX( 0 ), + mLastMouseScreenY( 0 ), + mDragHighlightColor( LLUI::sColorsGroup->getColor( "DefaultHighlightLight" ) ), + mDragShadowColor( LLUI::sColorsGroup->getColor( "DefaultShadowDark" ) ), + mTitleBox( NULL ), + mMaxTitleWidth( 0 ), + mForeground( TRUE ) +{ + sSnapMargin = LLUI::sConfigGroup->getS32("SnapMargin"); + + setSaveToXML(false); +} + +void LLDragHandle::setTitleVisible(BOOL visible) +{ + mTitleBox->setVisible(visible); +} + +LLDragHandleTop::LLDragHandleTop(const LLString& name, const LLRect &rect, const LLString& title) +: LLDragHandle(name, rect, title) +{ + setFollowsAll(); + setTitle( title ); +} + +EWidgetType LLDragHandleTop::getWidgetType() const +{ + return WIDGET_TYPE_DRAG_HANDLE_TOP; +} + +LLString LLDragHandleTop::getWidgetTag() const +{ + return LL_DRAG_HANDLE_TOP_TAG; +} + +LLDragHandleLeft::LLDragHandleLeft(const LLString& name, const LLRect &rect, const LLString& title) +: LLDragHandle(name, rect, title) +{ + setFollowsAll(); + setTitle( title ); +} + +EWidgetType LLDragHandleLeft::getWidgetType() const +{ + return WIDGET_TYPE_DRAG_HANDLE_LEFT; +} + +LLString LLDragHandleLeft::getWidgetTag() const +{ + return LL_DRAG_HANDLE_LEFT_TAG; +} + +void LLDragHandleTop::setTitle(const LLString& title) +{ + if( mTitleBox ) + { + removeChild(mTitleBox); + delete mTitleBox; + } + + LLString trimmed_title = title; + LLString::trim(trimmed_title); + + const LLFontGL* font = gResMgr->getRes( LLFONT_SANSSERIF ); + mTitleBox = new LLTextBox( "Drag Handle Title", mRect, trimmed_title, font ); + mTitleBox->setFollows(FOLLOWS_TOP | FOLLOWS_LEFT | FOLLOWS_RIGHT); + reshapeTitleBox(); + + // allow empty titles, as default behavior replaces them with title box name + if (trimmed_title.empty()) + { + mTitleBox->setText(LLString::null); + } + addChild( mTitleBox ); +} + + +const LLString& LLDragHandleTop::getTitle() const +{ + return mTitleBox->getText(); +} + + +void LLDragHandleLeft::setTitle(const LLString& ) +{ + if( mTitleBox ) + { + removeChild(mTitleBox); + delete mTitleBox; + } + + mTitleBox = NULL; + + /* no title on left edge */ +} + + +const LLString& LLDragHandleLeft::getTitle() const +{ + return LLString::null; +} + + +void LLDragHandleTop::draw() +{ + /* Disable lines. Can drag anywhere in most windows. JC + if( getVisible() && mEnabled && mForeground) + { + const S32 BORDER_PAD = 2; + const S32 HPAD = 2; + const S32 VPAD = 2; + S32 left = BORDER_PAD + HPAD; + S32 top = mRect.getHeight() - 2 * VPAD; + S32 right = mRect.getWidth() - HPAD; +// S32 bottom = VPAD; + + // draw lines for drag areas + + const S32 LINE_SPACING = (DRAG_HANDLE_HEIGHT - 2 * VPAD) / 4; + S32 line = top - LINE_SPACING; + + LLRect title_rect = mTitleBox->getRect(); + S32 title_right = title_rect.mLeft + mTitleWidth; + BOOL show_right_side = title_right < mRect.getWidth(); + + for( S32 i=0; i<4; i++ ) + { + gl_line_2d(left, line+1, title_rect.mLeft - LEADING_PAD, line+1, mDragHighlightColor); + if( show_right_side ) + { + gl_line_2d(title_right, line+1, right, line+1, mDragHighlightColor); + } + + gl_line_2d(left, line, title_rect.mLeft - LEADING_PAD, line, mDragShadowColor); + if( show_right_side ) + { + gl_line_2d(title_right, line, right, line, mDragShadowColor); + } + line -= LINE_SPACING; + } + } + */ + + // Colorize the text to match the frontmost state + if (mTitleBox) + { + mTitleBox->setEnabled(mForeground); + } + + LLView::draw(); +} + + +// assumes GL state is set for 2D +void LLDragHandleLeft::draw() +{ + /* Disable lines. Can drag anywhere in most windows. JC + if( getVisible() && mEnabled && mForeground ) + { + const S32 BORDER_PAD = 2; +// const S32 HPAD = 2; + const S32 VPAD = 2; + const S32 LINE_SPACING = 3; + + S32 left = BORDER_PAD + LINE_SPACING; + S32 top = mRect.getHeight() - 2 * VPAD; +// S32 right = mRect.getWidth() - HPAD; + S32 bottom = VPAD; + + // draw lines for drag areas + + // no titles yet + //LLRect title_rect = mTitleBox->getRect(); + //S32 title_right = title_rect.mLeft + mTitleWidth; + //BOOL show_right_side = title_right < mRect.getWidth(); + + S32 line = left; + for( S32 i=0; i<4; i++ ) + { + gl_line_2d(line, top, line, bottom, mDragHighlightColor); + + gl_line_2d(line+1, top, line+1, bottom, mDragShadowColor); + + line += LINE_SPACING; + } + } + */ + + // Colorize the text to match the frontmost state + if (mTitleBox) + { + mTitleBox->setEnabled(mForeground); + } + + LLView::draw(); +} + +void LLDragHandleTop::reshapeTitleBox() +{ + const LLFontGL* font = gResMgr->getRes( LLFONT_SANSSERIF ); + S32 title_width = font->getWidth( mTitleBox->getText() ) + TITLE_PAD; + if (mMaxTitleWidth > 0) + title_width = llmin(title_width, mMaxTitleWidth); + S32 title_height = llround(font->getLineHeight()); + LLRect title_rect; + title_rect.setLeftTopAndSize( + LEFT_PAD, + mRect.getHeight() - BORDER_PAD, + mRect.getWidth() - LEFT_PAD - RIGHT_PAD, + title_height); + + mTitleBox->setRect( title_rect ); +} + +void LLDragHandleTop::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + LLView::reshape(width, height, called_from_parent); + reshapeTitleBox(); +} + +void LLDragHandleLeft::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + LLView::reshape(width, height, called_from_parent); +} + +//------------------------------------------------------------- +// UI event handling +//------------------------------------------------------------- + +BOOL LLDragHandle::handleMouseDown(S32 x, S32 y, MASK mask) +{ + // Route future Mouse messages here preemptively. (Release on mouse up.) + // No handler needed for focus lost since this clas has no state that depends on it. + gFocusMgr.setMouseCapture(this, NULL ); + + localPointToScreen(x, y, &mDragLastScreenX, &mDragLastScreenY); + mLastMouseScreenX = mDragLastScreenX; + mLastMouseScreenY = mDragLastScreenY; + + // Note: don't pass on to children + return TRUE; +} + + +BOOL LLDragHandle::handleMouseUp(S32 x, S32 y, MASK mask) +{ + if( gFocusMgr.getMouseCapture() == this ) + { + // Release the mouse + gFocusMgr.setMouseCapture( NULL, NULL ); + } + + // Note: don't pass on to children + return TRUE; +} + + +BOOL LLDragHandle::handleHover(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + // We only handle the click if the click both started and ended within us + if( gFocusMgr.getMouseCapture() == this ) + { + S32 screen_x; + S32 screen_y; + localPointToScreen(x, y, &screen_x, &screen_y); + + // Resize the parent + S32 delta_x = screen_x - mDragLastScreenX; + S32 delta_y = screen_y - mDragLastScreenY; + getParent()->translate(delta_x, delta_y); + S32 pre_snap_x = getParent()->getRect().mLeft; + S32 pre_snap_y = getParent()->getRect().mBottom; + mDragLastScreenX = screen_x; + mDragLastScreenY = screen_y; + + LLRect new_rect; + LLCoordGL mouse_dir; + // use hysteresis on mouse motion to preserve user intent when mouse stops moving + mouse_dir.mX = (screen_x == mLastMouseScreenX) ? mLastMouseDir.mX : screen_x - mLastMouseScreenX; + mouse_dir.mY = (screen_y == mLastMouseScreenY) ? mLastMouseDir.mY : screen_y - mLastMouseScreenY; + mLastMouseDir = mouse_dir; + mLastMouseScreenX = screen_x; + mLastMouseScreenY = screen_y; + + LLView* snap_view = getParent()->findSnapRect(new_rect, mouse_dir, SNAP_PARENT_AND_SIBLINGS, sSnapMargin); + + getParent()->snappedTo(snap_view); + delta_x = new_rect.mLeft - pre_snap_x; + delta_y = new_rect.mBottom - pre_snap_y; + getParent()->translate(delta_x, delta_y); + mDragLastScreenX += delta_x; + mDragLastScreenY += delta_y; + + getWindow()->setCursor(UI_CURSOR_ARROW); + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" <<llendl; + handled = TRUE; + } + else if( getVisible() ) + { + getWindow()->setCursor(UI_CURSOR_ARROW); + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (inactive)" << llendl; + handled = TRUE; + } + + // Note: don't pass on to children + + return handled; +} + +void LLDragHandle::setValue(const LLSD& value) +{ + setTitle(value.asString()); +} diff --git a/indra/llui/lldraghandle.h b/indra/llui/lldraghandle.h new file mode 100644 index 0000000000..557c01cec6 --- /dev/null +++ b/indra/llui/lldraghandle.h @@ -0,0 +1,98 @@ +/** + * @file lldraghandle.h + * @brief LLDragHandle base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// A widget for dragging a view around the screen using the mouse. + +#ifndef LL_DRAGHANDLE_H +#define LL_DRAGHANDLE_H + +#include "llview.h" +#include "v4color.h" +#include "llrect.h" +#include "llcoord.h" + +class LLTextBox; + +class LLDragHandle : public LLView +{ +public: + LLDragHandle(const LLString& name, const LLRect& rect, const LLString& title ); + + virtual void setValue(const LLSD& value); + + void setForeground(BOOL b) { mForeground = b; } + void setMaxTitleWidth(S32 max_width) {mMaxTitleWidth = llmin(max_width, mMaxTitleWidth); } + void setTitleVisible(BOOL visible); + + virtual void setTitle( const LLString& title ) = 0; + virtual const LLString& getTitle() const = 0; + virtual void draw() = 0; + virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE) = 0; + + virtual BOOL handleHover(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); + +protected: + S32 mDragLastScreenX; + S32 mDragLastScreenY; + S32 mLastMouseScreenX; + S32 mLastMouseScreenY; + LLCoordGL mLastMouseDir; + LLColor4 mDragHighlightColor; + LLColor4 mDragShadowColor; + LLTextBox* mTitleBox; + S32 mMaxTitleWidth; + BOOL mForeground; + + // Pixels near the edge to snap floaters. + static S32 sSnapMargin; +}; + + +// Use this one for traditional top-of-window draggers +class LLDragHandleTop +: public LLDragHandle +{ +public: + LLDragHandleTop(const LLString& name, const LLRect& rect, const LLString& title ); + + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + virtual void setTitle( const LLString& title ); + virtual const LLString& getTitle() const; + virtual void draw(); + virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); + +private: + void reshapeTitleBox(); +}; + + +// Use this for left-side, vertical text draggers +class LLDragHandleLeft +: public LLDragHandle +{ +public: + LLDragHandleLeft(const LLString& name, const LLRect& rect, const LLString& title ); + + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + virtual void setTitle( const LLString& title ); + virtual const LLString& getTitle() const; + virtual void draw(); + virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); + +}; + +const S32 DRAG_HANDLE_HEIGHT = 16; +const S32 DRAG_HANDLE_WIDTH = 16; + +#endif // LL_DRAGHANDLE_H diff --git a/indra/llui/lleditmenuhandler.cpp b/indra/llui/lleditmenuhandler.cpp new file mode 100644 index 0000000000..656eaff563 --- /dev/null +++ b/indra/llui/lleditmenuhandler.cpp @@ -0,0 +1,107 @@ +/** +* @file lleditmenuhandler.cpp +* @authors Aaron Yonas, James Cook +* +* Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. +* $License$ +*/ + +#include "stdtypes.h" + +#include "lleditmenuhandler.h" + +LLEditMenuHandler* gEditMenuHandler = NULL; + +// virtual +LLEditMenuHandler::~LLEditMenuHandler() +{ } + +// virtual +void LLEditMenuHandler::undo() +{ } + +// virtual +BOOL LLEditMenuHandler::canUndo() +{ + return FALSE; +} + +// virtual +void LLEditMenuHandler::redo() +{ } + +// virtual +BOOL LLEditMenuHandler::canRedo() +{ + return FALSE; +} + +// virtual +void LLEditMenuHandler::cut() +{ } + +// virtual +BOOL LLEditMenuHandler::canCut() +{ + return FALSE; +} + +// virtual +void LLEditMenuHandler::copy() +{ } + +// virtual +BOOL LLEditMenuHandler::canCopy() +{ + return FALSE; +} + +// virtual +void LLEditMenuHandler::paste() +{ } + +// virtual +BOOL LLEditMenuHandler::canPaste() +{ + return FALSE; +} + +// virtual +void LLEditMenuHandler::doDelete() +{ } + +// virtual +BOOL LLEditMenuHandler::canDoDelete() +{ + return FALSE; +} + +// virtual +void LLEditMenuHandler::selectAll() +{ } + +// virtual +BOOL LLEditMenuHandler::canSelectAll() +{ + return FALSE; +} + +// virtual +void LLEditMenuHandler::deselect() +{ } + +// virtual +BOOL LLEditMenuHandler::canDeselect() +{ + return FALSE; +} + +// virtual +void LLEditMenuHandler::duplicate() +{ } + +// virtual +BOOL LLEditMenuHandler::canDuplicate() +{ + return FALSE; +} diff --git a/indra/llui/lleditmenuhandler.h b/indra/llui/lleditmenuhandler.h new file mode 100644 index 0000000000..3f49f2c6e8 --- /dev/null +++ b/indra/llui/lleditmenuhandler.h @@ -0,0 +1,50 @@ +/** +* @file lleditmenuhandler.h +* @authors Aaron Yonas, James Cook +* +* Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. +* $License$ +*/ + +#ifndef LLEDITMENUHANDLER_H +#define LLEDITMENUHANDLER_H + +// Interface used by menu system for plug-in hotkey/menu handling +class LLEditMenuHandler +{ +public: + // this is needed even though this is just an interface class. + virtual ~LLEditMenuHandler(); + + virtual void undo(); + virtual BOOL canUndo(); + + virtual void redo(); + virtual BOOL canRedo(); + + virtual void cut(); + virtual BOOL canCut(); + + virtual void copy(); + virtual BOOL canCopy(); + + virtual void paste(); + virtual BOOL canPaste(); + + // "delete" is a keyword + virtual void doDelete(); + virtual BOOL canDoDelete(); + + virtual void selectAll(); + virtual BOOL canSelectAll(); + + virtual void deselect(); + virtual BOOL canDeselect(); + + virtual void duplicate(); + virtual BOOL canDuplicate(); +}; + +extern LLEditMenuHandler* gEditMenuHandler; + +#endif diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp new file mode 100644 index 0000000000..3f9139fe86 --- /dev/null +++ b/indra/llui/llfloater.cpp @@ -0,0 +1,2933 @@ +/** + * @file llfloater.cpp + * @brief LLFloater base class + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// Floating "windows" within the GL display, like the inventory floater, +// mini-map floater, etc. + +#include "linden_common.h" + +#include "llfloater.h" + +#include "llfocusmgr.h" + +#include "lluictrlfactory.h" +#include "llbutton.h" +#include "llcheckboxctrl.h" +#include "lldraghandle.h" +#include "llfocusmgr.h" +#include "llresizebar.h" +#include "llresizehandle.h" +#include "llkeyboard.h" +#include "llmenugl.h" // MENU_BAR_HEIGHT +#include "lltextbox.h" +#include "llresmgr.h" +#include "llui.h" +#include "llviewborder.h" +#include "llwindow.h" +#include "llstl.h" +#include "llcontrol.h" +#include "lltabcontainer.h" +#include "v2math.h" + +extern BOOL gNoRender; + +const S32 MINIMIZED_WIDTH = 160; +const S32 CLOSE_BOX_FROM_TOP = 1; + +LLString LLFloater::sButtonActiveImageNames[BUTTON_COUNT] = +{ + "UIImgBtnCloseActiveUUID", //BUTTON_CLOSE + "UIImgBtnRestoreActiveUUID", //BUTTON_RESTORE + "UIImgBtnMinimizeActiveUUID", //BUTTON_MINIMIZE + "UIImgBtnTearOffActiveUUID", //BUTTON_TEAR_OFF + "UIImgBtnCloseActiveUUID", //BUTTON_EDIT +}; + +LLString LLFloater::sButtonInactiveImageNames[BUTTON_COUNT] = +{ + "UIImgBtnCloseInactiveUUID", //BUTTON_CLOSE + "UIImgBtnRestoreInactiveUUID", //BUTTON_RESTORE + "UIImgBtnMinimizeInactiveUUID", //BUTTON_MINIMIZE + "UIImgBtnTearOffInactiveUUID", //BUTTON_TEAR_OFF + "UIImgBtnCloseInactiveUUID", //BUTTON_EDIT +}; + +LLString LLFloater::sButtonPressedImageNames[BUTTON_COUNT] = +{ + "UIImgBtnClosePressedUUID", //BUTTON_CLOSE + "UIImgBtnRestorePressedUUID", //BUTTON_RESTORE + "UIImgBtnMinimizePressedUUID", //BUTTON_MINIMIZE + "UIImgBtnTearOffPressedUUID", //BUTTON_TEAR_OFF + "UIImgBtnClosePressedUUID", //BUTTON_EDIT +}; + +LLString LLFloater::sButtonNames[BUTTON_COUNT] = +{ + "llfloater_close_btn", //BUTTON_CLOSE + "llfloater_restore_btn", //BUTTON_RESTORE + "llfloater_minimize_btn", //BUTTON_MINIMIZE + "llfloater_tear_off_btn", //BUTTON_TEAR_OFF + "llfloater_edit_btn", //BUTTON_EDIT +}; + +LLString LLFloater::sButtonToolTips[BUTTON_COUNT] = +{ +#ifdef LL_DARWIN + "Close (Cmd-W)", //BUTTON_CLOSE +#else + "Close (Ctrl-W)", //BUTTON_CLOSE +#endif + "Restore", //BUTTON_RESTORE + "Minimize", //BUTTON_MINIMIZE + "Tear Off", //BUTTON_TEAR_OFF + "Edit", //BUTTON_EDIT +}; + +LLFloater::click_callback LLFloater::sButtonCallbacks[BUTTON_COUNT] = +{ + LLFloater::onClickClose, //BUTTON_CLOSE + LLFloater::onClickMinimize, //BUTTON_RESTORE + LLFloater::onClickMinimize, //BUTTON_MINIMIZE + LLFloater::onClickTearOff, //BUTTON_TEAR_OFF + LLFloater::onClickEdit, //BUTTON_EDIT +}; + +LLMultiFloater* LLFloater::sHostp = NULL; +BOOL LLFloater::sEditModeEnabled; +LLFloater::handle_map_t LLFloater::sFloaterMap; + +LLFloaterView* gFloaterView = NULL; + +LLFloater::LLFloater() +{ + // automatically take focus when opened + mAutoFocus = TRUE; + for (S32 i = 0; i < BUTTON_COUNT; i++) + { + mButtonsEnabled[i] = FALSE; + mButtons[i] = NULL; + } + mDragHandle = NULL; +} + +LLFloater::LLFloater(const LLString& name) +: LLPanel(name) +{ + for (S32 i = 0; i < BUTTON_COUNT; i++) + { + mButtonsEnabled[i] = FALSE; + mButtons[i] = NULL; + } + + LLString title; // null string + // automatically take focus when opened + mAutoFocus = TRUE; + init(title, FALSE, DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT, FALSE, TRUE, TRUE); // defaults +} + + +LLFloater::LLFloater(const LLString& name, const LLRect& rect, const LLString& title, + BOOL resizable, + S32 min_width, + S32 min_height, + BOOL drag_on_left, + BOOL minimizable, + BOOL close_btn, + BOOL bordered) +: LLPanel(name, rect, bordered) +{ + for (S32 i = 0; i < BUTTON_COUNT; i++) + { + mButtonsEnabled[i] = FALSE; + mButtons[i] = NULL; + } + // automatically take focus when opened + mAutoFocus = TRUE; + init( title, resizable, min_width, min_height, drag_on_left, minimizable, close_btn); +} + +LLFloater::LLFloater(const LLString& name, const LLString& rect_control, const LLString& title, + BOOL resizable, + S32 min_width, + S32 min_height, + BOOL drag_on_left, + BOOL minimizable, + BOOL close_btn, + BOOL bordered) +: LLPanel(name, rect_control, bordered) +{ + for (S32 i = 0; i < BUTTON_COUNT; i++) + { + mButtonsEnabled[i] = FALSE; + mButtons[i] = NULL; + } + // automatically take focus when opened + mAutoFocus = TRUE; + init( title, resizable, min_width, min_height, drag_on_left, minimizable, close_btn); +} + + +// Note: Floaters constructed from XML call init() twice! +void LLFloater::init(const LLString& title, + BOOL resizable, S32 min_width, S32 min_height, + BOOL drag_on_left, BOOL minimizable, BOOL close_btn) +{ + // Init function can be called more than once, so clear out old data. + for (S32 i = 0; i < BUTTON_COUNT; i++) + { + mButtonsEnabled[i] = FALSE; + if (mButtons[i] != NULL) + { + removeChild(mButtons[i]); + delete mButtons[i]; + mButtons[i] = NULL; + } + } + mButtonScale = 1.f; + + LLPanel::deleteAllChildren(); + //sjb: HACK! we had a border which was just deleted, so re-create it + if (mBorder != NULL) + { + addBorder(); + } + + // chrome floaters don't take focus at all + mIsFocusRoot = !getIsChrome(); + + // Reset cached pointers + mDragHandle = NULL; + for (S32 i = 0; i < 4; i++) + { + mResizeBar[i] = NULL; + mResizeHandle[i] = NULL; + } + mCanTearOff = TRUE; + mEditing = FALSE; + + // Clicks stop here. + setMouseOpaque(TRUE); + + mFirstLook = TRUE; + mForeground = FALSE; + mDragOnLeft = drag_on_left == TRUE; + + // Floaters always draw their background, unlike every other panel. + setBackgroundVisible(TRUE); + + // Floaters start not minimized. When minimized, they save their + // prior rectangle to be used on restore. + mMinimized = FALSE; + mPreviousRect.set(0,0,0,0); + + S32 close_pad; // space to the right of close box + S32 close_box_size; // For layout purposes, how big is the close box? + if (close_btn) + { + close_box_size = LLFLOATER_CLOSE_BOX_SIZE; + close_pad = 0; + } + else + { + close_box_size = 0; + close_pad = 0; + } + + S32 minimize_box_size; + S32 minimize_pad; + if (minimizable && !drag_on_left) + { + minimize_box_size = LLFLOATER_CLOSE_BOX_SIZE; + minimize_pad = 0; + } + else + { + minimize_box_size = 0; + minimize_pad = 0; + } + + // Drag Handle + // Add first so it's in the background. +// const S32 drag_pad = 2; + LLRect drag_handle_rect; + if (!drag_on_left) + { + drag_handle_rect.set( 0, mRect.getHeight(), mRect.getWidth(), 0 ); + + /* + drag_handle_rect.setLeftTopAndSize( + 0, mRect.getHeight(), + mRect.getWidth() + - LLPANEL_BORDER_WIDTH + - drag_pad + - minimize_box_size - minimize_pad + - close_box_size - close_pad, + DRAG_HANDLE_HEIGHT); + */ + mDragHandle = new LLDragHandleTop( "Drag Handle", drag_handle_rect, title ); + } + else + { + drag_handle_rect.setOriginAndSize( + 0, 0, + DRAG_HANDLE_WIDTH, + mRect.getHeight() - LLPANEL_BORDER_WIDTH - close_box_size); + mDragHandle = new LLDragHandleLeft("drag", drag_handle_rect, title ); + } + mDragHandle->setSaveToXML(false); + addChild(mDragHandle); + + // Resize Handle + mResizable = resizable; + mMinWidth = min_width; + mMinHeight = min_height; + + if( mResizable ) + { + // Resize bars (sides) + const S32 RESIZE_BAR_THICKNESS = 3; + mResizeBar[0] = new LLResizeBar( + "resizebar_left", + LLRect( 0, mRect.getHeight(), RESIZE_BAR_THICKNESS, 0), + min_width, min_height, LLResizeBar::LEFT ); + mResizeBar[0]->setSaveToXML(false); + addChild( mResizeBar[0] ); + + mResizeBar[1] = new LLResizeBar( + "resizebar_top", + LLRect( 0, mRect.getHeight(), mRect.getWidth(), mRect.getHeight() - RESIZE_BAR_THICKNESS), + min_width, min_height, LLResizeBar::TOP ); + mResizeBar[1]->setSaveToXML(false); + addChild( mResizeBar[1] ); + + mResizeBar[2] = new LLResizeBar( + "resizebar_right", + LLRect( mRect.getWidth() - RESIZE_BAR_THICKNESS, mRect.getHeight(), mRect.getWidth(), 0), + min_width, min_height, LLResizeBar::RIGHT ); + mResizeBar[2]->setSaveToXML(false); + addChild( mResizeBar[2] ); + + mResizeBar[3] = new LLResizeBar( + "resizebar_bottom", + LLRect( 0, RESIZE_BAR_THICKNESS, mRect.getWidth(), 0), + min_width, min_height, LLResizeBar::BOTTOM ); + mResizeBar[3]->setSaveToXML(false); + addChild( mResizeBar[3] ); + + + // Resize handles (corners) + mResizeHandle[0] = new LLResizeHandle( + "Resize Handle", + LLRect( mRect.getWidth() - RESIZE_HANDLE_WIDTH, RESIZE_HANDLE_HEIGHT, mRect.getWidth(), 0), + min_width, + min_height, + LLResizeHandle::RIGHT_BOTTOM); + mResizeHandle[0]->setSaveToXML(false); + addChild(mResizeHandle[0]); + + mResizeHandle[1] = new LLResizeHandle( "resize", + LLRect( mRect.getWidth() - RESIZE_HANDLE_WIDTH, mRect.getHeight(), mRect.getWidth(), mRect.getHeight() - RESIZE_HANDLE_HEIGHT), + min_width, + min_height, + LLResizeHandle::RIGHT_TOP ); + mResizeHandle[1]->setSaveToXML(false); + addChild(mResizeHandle[1]); + + mResizeHandle[2] = new LLResizeHandle( "resize", + LLRect( 0, RESIZE_HANDLE_HEIGHT, RESIZE_HANDLE_WIDTH, 0 ), + min_width, + min_height, + LLResizeHandle::LEFT_BOTTOM ); + mResizeHandle[2]->setSaveToXML(false); + addChild(mResizeHandle[2]); + + mResizeHandle[3] = new LLResizeHandle( "resize", + LLRect( 0, mRect.getHeight(), RESIZE_HANDLE_WIDTH, mRect.getHeight() - RESIZE_HANDLE_HEIGHT ), + min_width, + min_height, + LLResizeHandle::LEFT_TOP ); + mResizeHandle[3]->setSaveToXML(false); + addChild(mResizeHandle[3]); + } + else + { + mResizeBar[0] = NULL; + mResizeBar[1] = NULL; + mResizeBar[2] = NULL; + mResizeBar[3] = NULL; + + mResizeHandle[0] = NULL; + mResizeHandle[1] = NULL; + mResizeHandle[2] = NULL; + mResizeHandle[3] = NULL; + } + + // Close button. + if (close_btn) + { + mButtonsEnabled[BUTTON_CLOSE] = TRUE; + } + + // Minimize button only for top draggers + if ( !drag_on_left && minimizable ) + { + mButtonsEnabled[BUTTON_MINIMIZE] = TRUE; + } + + buildButtons(); + + // JC - Don't do this here, because many floaters first construct themselves, + // then show themselves. Put it in setVisibleAndFrontmost. + // make_ui_sound("UISndWindowOpen"); + + // RN: floaters are created in the invisible state + setVisible(FALSE); + + // add self to handle->floater map + sFloaterMap[mViewHandle] = this; + + if (!getParent()) + { + gFloaterView->addChild(this); + } +} + +// virtual +LLFloater::~LLFloater() +{ + control_map_t::iterator itor; + for (itor = mFloaterControls.begin(); itor != mFloaterControls.end(); ++itor) + { + delete itor->second; + } + mFloaterControls.clear(); + + //// am I not hosted by another floater? + //if (mHostHandle.isDead()) + //{ + // LLFloaterView* parent = (LLFloaterView*) getParent(); + + // if( parent ) + // { + // parent->removeChild( this ); + // } + //} + + // Just in case we might still have focus here, release it. + releaseFocus(); + + // This is important so that floaters with persistent rects (i.e., those + // created with rect control rather than an LLRect) are restored in their + // correct, non-minimized positions. + setMinimized( FALSE ); + + sFloaterMap.erase(mViewHandle); + + delete mDragHandle; + for (S32 i = 0; i < 4; i++) + { + delete mResizeBar[i]; + delete mResizeHandle[i]; + } +} + +// virtual +EWidgetType LLFloater::getWidgetType() const +{ + return WIDGET_TYPE_FLOATER; +} + +// virtual +LLString LLFloater::getWidgetTag() const +{ + return LL_FLOATER_TAG; +} + +void LLFloater::destroy() +{ + die(); +} + +void LLFloater::setVisible( BOOL visible ) +{ + LLPanel::setVisible(visible); + if( visible && mFirstLook ) + { + mFirstLook = FALSE; + } + + if( !visible ) + { + if( gFocusMgr.childIsTopView( this ) ) + { + gFocusMgr.setTopView(NULL, NULL); + } + + if( gFocusMgr.childHasMouseCapture( this ) ) + { + gFocusMgr.setMouseCapture(NULL, NULL); + } + } + + for(handle_set_iter_t dependent_it = mDependents.begin(); + dependent_it != mDependents.end(); ) + { + LLFloater* floaterp = LLFloater::getFloaterByHandle(*dependent_it); + + if (floaterp) + { + floaterp->setVisible(visible); + } + ++dependent_it; + } +} + +LLView* LLFloater::getRootMostFastFrameView() +{ + // trying to render a background floater in a fast frame, abort!!! + //if (!isFrontmost()) + //{ + // gViewerWindow->finishFastFrame(); + //} + + return LLView::getRootMostFastFrameView(); +} + +void LLFloater::open() +{ + //RN: for now, we don't allow rehosting from one multifloater to another + // just need to fix the bugs + LLMultiFloater* hostp = getHost(); + if (sHostp != NULL && hostp == NULL) + { + // needs a host + sHostp->addFloater(this, TRUE); + } + else if (hostp != NULL) + { + // already hosted + hostp->showFloater(this); + } + else + { + setMinimized(FALSE); + setVisibleAndFrontmost(mAutoFocus); + } + + if (mSoundFlags != SILENT) + { + if (!getVisible() || isMinimized()) + { + make_ui_sound("UISndWindowOpen"); + } + } +} + +void LLFloater::close(bool app_quitting) +{ + // Always unminimize before trying to close. + // Most of the time the user will never see this state. + setMinimized(FALSE); + + if (canClose()) + { + if (getHost()) + { + ((LLMultiFloater*)getHost())->removeFloater(this); + } + + if (mSoundFlags != SILENT + && getVisible() + && !app_quitting) + { + make_ui_sound("UISndWindowClose"); + } + + // now close dependent floater + for(handle_set_iter_t dependent_it = mDependents.begin(); + dependent_it != mDependents.end(); ) + { + + LLFloater* floaterp = LLFloater::getFloaterByHandle(*dependent_it); + if (floaterp) + { + ++dependent_it; + floaterp->close(); + } + else + { + mDependents.erase(dependent_it++); + } + } + + cleanupHandles(); + gFocusMgr.clearLastFocusForGroup(this); + + // Do this early, so UI controls will commit before the + // window is taken down. + releaseFocus(); + + // give focus to dependee floater if it exists, and we had focus first + if (isDependent()) + { + LLFloater* dependee = LLFloater::getFloaterByHandle(mDependeeHandle); + if (dependee && !dependee->isDead()) + { + dependee->setFocus(TRUE); + } + } + + // Let floater do cleanup. + onClose(app_quitting); + } +} + + +void LLFloater::releaseFocus() +{ + if( gFocusMgr.childIsTopView( this ) ) + { + gFocusMgr.setTopView(NULL, NULL); + } + + if( gFocusMgr.childHasKeyboardFocus( this ) ) + { + gFocusMgr.setKeyboardFocus(NULL, NULL); + } + + if( gFocusMgr.childHasMouseCapture( this ) ) + { + gFocusMgr.setMouseCapture(NULL, NULL); + } +} + + +void LLFloater::setResizeLimits( S32 min_width, S32 min_height ) +{ + mMinWidth = min_width; + mMinHeight = min_height; + + for( S32 i = 0; i < 4; i++ ) + { + if( mResizeBar[i] ) + { + mResizeBar[i]->setResizeLimits( min_width, min_height ); + } + if( mResizeHandle[i] ) + { + mResizeHandle[i]->setResizeLimits( min_width, min_height ); + } + } +} + + +void LLFloater::center() +{ + if(getHost()) + { + // hosted floaters can't move + return; + } + const LLRect &window = gFloaterView->getRect(); + + S32 left = window.mLeft + (window.getWidth() - mRect.getWidth()) / 2; + S32 bottom = window.mBottom + (window.getHeight() - mRect.getHeight()) / 2; + + translate( left - mRect.mLeft, bottom - mRect.mBottom ); +} + +void LLFloater::applyRectControl() +{ + if (!mRectControl.empty()) + { + const LLRect& rect = LLUI::sConfigGroup->getRect(mRectControl); + translate( rect.mLeft - mRect.mLeft, rect.mBottom - mRect.mBottom); + if (mResizable) + { + reshape(llmax(mMinWidth, rect.getWidth()), llmax(mMinHeight, rect.getHeight())); + } + } +} + +void LLFloater::setTitle( const LLString& title ) +{ + if (gNoRender) + { + return; + } + mDragHandle->setTitle( title ); +} + +const LLString& LLFloater::getTitle() const +{ + return mDragHandle ? mDragHandle->getTitle() : LLString::null; +} + +void LLFloater::translate(S32 x, S32 y) +{ + LLView::translate(x, y); + + if (x != 0 || y != 0) + { + for(handle_set_iter_t dependent_it = mDependents.begin(); + dependent_it != mDependents.end(); ++dependent_it) + { + LLFloater* floaterp = LLFloater::getFloaterByHandle(*dependent_it); + // is a dependent snapped to us? + if (floaterp && floaterp->getSnapTarget() == mViewHandle) + { + floaterp->translate(x, y); + } + } + } +} + +BOOL LLFloater::canSnapTo(LLView* other_view) +{ + if (other_view && other_view != getParent()) + { + LLFloater* other_floaterp = (LLFloater*)other_view; + + if (other_floaterp->getSnapTarget() == mViewHandle && mDependents.find(other_floaterp->getHandle()) != mDependents.end()) + { + // this is a dependent that is already snapped to us, so don't snap back to it + return FALSE; + } + } + + return LLView::canSnapTo(other_view); +} + +void LLFloater::snappedTo(LLView* snap_view) +{ + if (!snap_view || snap_view == getParent()) + { + clearSnapTarget(); + } + else + { + //RN: assume it's a floater as it must be a sibling to our parent floater + LLFloater* floaterp = (LLFloater*)snap_view; + + setSnapTarget(floaterp->getHandle()); + } +} + +void LLFloater::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + S32 old_width = mRect.getWidth(); + S32 old_height = mRect.getHeight(); + + LLView::reshape(width, height, called_from_parent); + + if (width != old_width || height != old_height) + { + // gather all snapped dependents + for(handle_set_iter_t dependent_it = mDependents.begin(); + dependent_it != mDependents.end(); ++dependent_it) + { + LLFloater* floaterp = LLFloater::getFloaterByHandle(*dependent_it); + // is a dependent snapped to us? + if (floaterp && floaterp->getSnapTarget() == mViewHandle) + { + S32 delta_x = 0; + S32 delta_y = 0; + // check to see if it snapped to right or top + LLRect floater_rect = floaterp->getRect(); + if (floater_rect.mLeft - mRect.mLeft >= old_width || + floater_rect.mRight == mRect.mLeft + old_width) + { + // was snapped directly onto right side or aligned with it + delta_x += width - old_width; + } + if (floater_rect.mBottom - mRect.mBottom >= old_height || + floater_rect.mTop == mRect.mBottom + old_height) + { + // was snapped directly onto top side or aligned with it + delta_y += height - old_height; + } + + floaterp->translate(delta_x, delta_y); + } + } + } +} + +void LLFloater::setMinimized(BOOL minimize) +{ + if (minimize == mMinimized) return; + + if (minimize) + { + mMinimized = TRUE; + + mPreviousRect = mRect; + + reshape( MINIMIZED_WIDTH, LLFLOATER_HEADER_SIZE, TRUE); + + S32 left, bottom; + gFloaterView->getMinimizePosition(&left, &bottom); + setOrigin( left, bottom ); + + if (mButtonsEnabled[BUTTON_MINIMIZE]) + { + mButtonsEnabled[BUTTON_MINIMIZE] = FALSE; + mButtonsEnabled[BUTTON_RESTORE] = TRUE; + } + + mMinimizedHiddenChildren.clear(); + // hide all children + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + if (!viewp->getVisible()) + { + mMinimizedHiddenChildren.push_back(viewp); + } + viewp->setVisible(FALSE); + } + + // except the special controls + if (mDragHandle) + { + mDragHandle->setVisible(TRUE); + } + + setBorderVisible(TRUE); + + for(handle_set_iter_t dependent_it = mDependents.begin(); + dependent_it != mDependents.end(); ) + { + LLFloater* floaterp = LLFloater::getFloaterByHandle(*dependent_it); + if (floaterp) + { + floaterp->setVisible(FALSE); + } + ++dependent_it; + } + + // Lose keyboard focus when minimized + releaseFocus(); + } + else + { + reshape( mPreviousRect.getWidth(), mPreviousRect.getHeight(), TRUE ); + setOrigin( mPreviousRect.mLeft, mPreviousRect.mBottom ); + + mMinimized = FALSE; + + if (mButtonsEnabled[BUTTON_RESTORE]) + { + mButtonsEnabled[BUTTON_MINIMIZE] = TRUE; + mButtonsEnabled[BUTTON_RESTORE] = FALSE; + } + + // show all children + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + viewp->setVisible(TRUE); + } + + std::vector<LLView*>::iterator itor = mMinimizedHiddenChildren.begin(); + while (itor != mMinimizedHiddenChildren.end()) + { + (*itor)->setVisible(FALSE); + ++itor; + } + mMinimizedHiddenChildren.clear(); + + // show dependent floater + for(handle_set_iter_t dependent_it = mDependents.begin(); + dependent_it != mDependents.end(); ) + { + LLFloater* floaterp = LLFloater::getFloaterByHandle(*dependent_it); + if (floaterp) + { + floaterp->setVisible(TRUE); + } + ++dependent_it; + } + } + make_ui_sound("UISndWindowClose"); + updateButtons(); +} + +void LLFloater::setFocus( BOOL b ) +{ + if (b && getIsChrome()) + { + return; + } + LLUICtrl* last_focus = gFocusMgr.getLastFocusForGroup(this); + // a descendent already has focus + BOOL child_had_focus = gFocusMgr.childHasKeyboardFocus(this); + + // give focus to first valid descendent + LLPanel::setFocus(b); + + if (b) + { + // only push focused floaters to front of stack if not in midst of ctrl-tab cycle + if (!getHost() && !((LLFloaterView*)getParent())->getCycleMode()) + { + if (!isFrontmost()) + { + setFrontmost(); + } + } + + // when getting focus, delegate to last descendent which had focus + if (last_focus && !child_had_focus && + last_focus->isInEnabledChain() && + last_focus->isInVisibleChain()) + { + // FIXME: should handle case where focus doesn't stick + last_focus->setFocus(TRUE); + } + } +} + +void LLFloater::setIsChrome(BOOL is_chrome) +{ + // chrome floaters don't take focus at all + if (is_chrome) + { + // remove focus if we're changing to chrome + setFocus(FALSE); + // can't Ctrl-Tab to "chrome" floaters + mIsFocusRoot = FALSE; + } + + // no titles displayed on "chrome" floaters + mDragHandle->setTitleVisible(!is_chrome); + + LLPanel::setIsChrome(is_chrome); +} + +// Change the draw style to account for the foreground state. +void LLFloater::setForeground(BOOL front) +{ + if (front != mForeground) + { + mForeground = front; + mDragHandle->setForeground( front ); + + if (!front) + { + releaseFocus(); + } + + setBackgroundOpaque( front ); + } +} + +void LLFloater::cleanupHandles() +{ + // remove handles to non-existent dependents + for(handle_set_iter_t dependent_it = mDependents.begin(); + dependent_it != mDependents.end(); ) + { + LLFloater* floaterp = LLFloater::getFloaterByHandle(*dependent_it); + if (!floaterp) + { + mDependents.erase(dependent_it++); + } + else + { + ++dependent_it; + } + } +} + +void LLFloater::setHost(LLMultiFloater* host) +{ + if (mHostHandle.isDead() && host) + { + // make buttons smaller for hosted windows to differentiate from parent + mButtonScale = 0.9f; + + // add tear off button + if (mCanTearOff) + { + mButtonsEnabled[BUTTON_TEAR_OFF] = TRUE; + } + + mIsFocusRoot = FALSE; + } + else if (!mHostHandle.isDead() && !host) + { + mButtonScale = 1.f; + mIsFocusRoot = TRUE; + //mButtonsEnabled[BUTTON_TEAR_OFF] = FALSE; + } + updateButtons(); + if (host) + { + mHostHandle = host->getHandle(); + mLastHostHandle = host->getHandle(); + } + else + { + mHostHandle.markDead(); + } +} + +void LLFloater::moveResizeHandleToFront() +{ + // 0 is the bottom right + if( mResizeHandle[0] ) + { + sendChildToFront(mResizeHandle[0]); + } +} + +BOOL LLFloater::isFrontmost() +{ + return gFloaterView && gFloaterView->getFrontmost() == this && getVisible(); +} + +void LLFloater::addDependentFloater(LLFloater* floaterp, BOOL reposition) +{ + mDependents.insert(floaterp->getHandle()); + floaterp->mDependeeHandle = getHandle(); + + if (reposition) + { + floaterp->setRect(gFloaterView->findNeighboringPosition(this, floaterp)); + floaterp->setSnapTarget(mViewHandle); + } + gFloaterView->adjustToFitScreen(floaterp, FALSE); + if (floaterp->isFrontmost()) + { + // make sure to bring self and sibling floaters to front + gFloaterView->bringToFront(floaterp); + } +} + +void LLFloater::addDependentFloater(LLViewHandle dependent, BOOL reposition) +{ + LLFloater* dependent_floaterp = LLFloater::getFloaterByHandle(dependent); + if(dependent_floaterp) + { + addDependentFloater(dependent_floaterp, reposition); + } +} + +void LLFloater::removeDependentFloater(LLFloater* floaterp) +{ + mDependents.erase(floaterp->getHandle()); + floaterp->mDependeeHandle = LLViewHandle::sDeadHandle; +} + +// virtual +BOOL LLFloater::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if( mMinimized ) + { + // Offer the click to the close button. + // Any other click = restore + if( mButtonsEnabled[BUTTON_CLOSE] ) + { + S32 local_x = x - mButtons[BUTTON_CLOSE]->getRect().mLeft; + S32 local_y = y - mButtons[BUTTON_CLOSE]->getRect().mBottom; + + if (mButtons[BUTTON_CLOSE]->pointInView(local_x, local_y) + && mButtons[BUTTON_CLOSE]->handleMouseDown(local_x, local_y, mask)) + { + // close button handled it, return + return TRUE; + } + } + + // restore + bringToFront( x, y ); + return TRUE; + } + else + { + bringToFront( x, y ); + return LLPanel::handleMouseDown( x, y, mask ); + } +} + +// virtual +BOOL LLFloater::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + BOOL was_minimized = mMinimized; + bringToFront( x, y ); + return was_minimized || LLPanel::handleRightMouseDown( x, y, mask ); +} + + +// virtual +BOOL LLFloater::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + BOOL was_minimized = mMinimized; + setMinimized(FALSE); + return was_minimized || LLPanel::handleDoubleClick(x, y, mask); +} + +void LLFloater::bringToFront( S32 x, S32 y ) +{ + if (getVisible() && pointInView(x, y)) + { + LLMultiFloater* hostp = getHost(); + if (hostp) + { + hostp->showFloater(this); + } + else + { + LLFloaterView* parent = (LLFloaterView*) getParent(); + if (parent) + { + parent->bringToFront( this ); + } + } + } +} + + +// virtual +void LLFloater::setVisibleAndFrontmost(BOOL take_focus) +{ + setVisible(TRUE); + setFrontmost(take_focus); +} + +void LLFloater::setFrontmost(BOOL take_focus) +{ + LLMultiFloater* hostp = getHost(); + if (hostp) + { + // this will bring the host floater to the front and select + // the appropriate panel + hostp->showFloater(this); + } + else + { + // there are more than one floater view + // so we need to query our parent directly + ((LLFloaterView*)getParent())->bringToFront(this, take_focus); + } +} + +// static +LLFloater* LLFloater::getFloaterByHandle(LLViewHandle handle) +{ + LLFloater* floater = NULL; + if (sFloaterMap.count(handle)) + { + floater = sFloaterMap[handle]; + } + if (floater && !floater->isDead()) + { + return floater; + } + else + { + return NULL; + } +} + +//static +void LLFloater::setEditModeEnabled(BOOL enable) +{ + if (enable != sEditModeEnabled) + { + S32 count = 0; + std::map<LLViewHandle, LLFloater*>::iterator iter; + for(iter = sFloaterMap.begin(); iter != sFloaterMap.end(); ++iter) + { + LLFloater* floater = iter->second; + if (!floater->isDead()) + { + iter->second->mButtonsEnabled[BUTTON_EDIT] = enable; + iter->second->updateButtons(); + } + count++; + } + } + + sEditModeEnabled = enable; +} + +//static +BOOL LLFloater::getEditModeEnabled() +{ + return sEditModeEnabled; +} + +// static +void LLFloater::onClickMinimize(void *userdata) +{ + LLFloater* self = (LLFloater*) userdata; + if (!self) return; + + self->setMinimized( !self->isMinimized() ); +} + +void LLFloater::onClickTearOff(void *userdata) +{ + LLFloater* self = (LLFloater*) userdata; + if (!self) return; + + LLMultiFloater* host_floater = self->getHost(); + if (host_floater) //Tear off + { + LLRect new_rect; + host_floater->removeFloater(self); + // reparent to floater view + gFloaterView->addChild(self); + + new_rect.setLeftTopAndSize(host_floater->getRect().mLeft + 5, host_floater->getRect().mTop - LLFLOATER_HEADER_SIZE - 5, self->mRect.getWidth(), self->mRect.getHeight()); + + self->open(); + self->setRect(new_rect); + gFloaterView->adjustToFitScreen(self, FALSE); + self->setCanDrag(TRUE); + self->setCanResize(TRUE); + self->setCanMinimize(TRUE); + } + else //Attach to parent. + { + LLMultiFloater* new_host = (LLMultiFloater*)LLFloater::getFloaterByHandle(self->mLastHostHandle); + if (new_host) + { + new_host->showFloater(self); + } + } +} + +// static +void LLFloater::onClickEdit(void *userdata) +{ + LLFloater* self = (LLFloater*) userdata; + if (!self) return; + + self->mEditing = self->mEditing ? FALSE : TRUE; +} + +// static +void LLFloater::closeByMenu( void* userdata ) +{ + LLFloater* self = (LLFloater*) userdata; + if (!self || self->getHost()) return; + + LLFloaterView* parent = (LLFloaterView*) self->getParent(); + + // grab focus status before close just in case floater is deleted + BOOL has_focus = gFocusMgr.childHasKeyboardFocus(self); + self->close(); + + // if this floater used to have focus and now nothing took focus + // give it to next floater (to allow closing multiple windows via keyboard in rapid succession) + if (has_focus && gFocusMgr.getKeyboardFocus() == NULL) + { + parent->focusFrontFloater(); + } + +} + + +// static +void LLFloater::onClickClose( void* userdata ) +{ + LLFloater* self = (LLFloater*) userdata; + if (!self) return; + + self->close(); +} + + +// virtual +void LLFloater::draw() +{ + if( getVisible() ) + { + // draw background + if( mBgVisible ) + { + S32 left = LLPANEL_BORDER_WIDTH; + S32 top = mRect.getHeight() - LLPANEL_BORDER_WIDTH; + S32 right = mRect.getWidth() - LLPANEL_BORDER_WIDTH; + S32 bottom = LLPANEL_BORDER_WIDTH; + + LLColor4 shadow_color = LLUI::sColorsGroup->getColor("ColorDropShadow"); + F32 shadow_offset = (F32)LLUI::sConfigGroup->getS32("DropShadowFloater"); + if (!mBgOpaque) + { + shadow_offset *= 0.2f; + shadow_color.mV[VALPHA] *= 0.5f; + } + gl_drop_shadow(left, top, right, bottom, + shadow_color, + llround(shadow_offset)); + + // No transparent windows in simple UI + if (mBgOpaque) + { + gl_rect_2d( left, top, right, bottom, mBgColorOpaque ); + } + else + { + gl_rect_2d( left, top, right, bottom, mBgColorAlpha ); + } + + if(gFocusMgr.childHasKeyboardFocus(this) && !getIsChrome() && !getTitle().empty()) + { + // draw highlight on title bar to indicate focus. RDW + const LLFontGL* font = gResMgr->getRes( LLFONT_SANSSERIF ); + LLRect r = getRect(); + gl_rect_2d_offset_local(0, r.getHeight(), r.getWidth(), r.getHeight() - (S32)font->getLineHeight() - 1, + LLUI::sColorsGroup->getColor("TitleBarFocusColor"), 0, TRUE); + } + } + + if( mDefaultBtn) + { + if (gFocusMgr.childHasKeyboardFocus( this ) && mDefaultBtn->getEnabled()) + { + LLUICtrl* focus_ctrl = gFocusMgr.getKeyboardFocus(); + // is this button a direct descendent and not a nested widget (e.g. checkbox)? + BOOL focus_is_child_button = focus_ctrl->getWidgetType() == WIDGET_TYPE_BUTTON && focus_ctrl->getParent() == this; + // only enable default button when current focus is not a button + mDefaultBtn->setBorderEnabled(!focus_is_child_button); + } + else + { + mDefaultBtn->setBorderEnabled(FALSE); + } + } + + // draw children + LLView* focused_child = gFocusMgr.getKeyboardFocus(); + BOOL focused_child_visible = FALSE; + if (focused_child && focused_child->getParent() == this) + { + focused_child_visible = focused_child->getVisible(); + focused_child->setVisible(FALSE); + } + + LLView::draw(); + + if( mBgVisible ) + { + // add in a border to improve spacialized visual aclarity ;) + // use lines instead of gl_rect_2d so we can round the edges as per james' recommendation + LLUI::setLineWidth(1.5f); + LLColor4 outlineColor = gFocusMgr.childHasKeyboardFocus(this) ? LLUI::sColorsGroup->getColor("FloaterFocusBorderColor") : LLUI::sColorsGroup->getColor("FloaterUnfocusBorderColor"); + gl_rect_2d_offset_local(0, mRect.getHeight() + 1, mRect.getWidth() + 1, 0, outlineColor, -LLPANEL_BORDER_WIDTH, FALSE); + LLUI::setLineWidth(1.f); + } + + if (focused_child_visible) + { + focused_child->setVisible(TRUE); + } + drawChild(focused_child); + } +} + +// virtual +void LLFloater::onClose(bool app_quitting) +{ + destroy(); +} + +// virtual +BOOL LLFloater::canClose() +{ + return TRUE; +} + +// virtual +BOOL LLFloater::canSaveAs() +{ + return FALSE; +} + +// virtual +void LLFloater::saveAs() +{ +} + +void LLFloater::setCanMinimize(BOOL can_minimize) +{ + // removing minimize/restore button programmatically, + // go ahead and uniminimize floater + if (!can_minimize) + { + setMinimized(FALSE); + } + + if (can_minimize) + { + if (isMinimized()) + { + mButtonsEnabled[BUTTON_MINIMIZE] = FALSE; + mButtonsEnabled[BUTTON_RESTORE] = TRUE; + } + else + { + mButtonsEnabled[BUTTON_MINIMIZE] = TRUE; + mButtonsEnabled[BUTTON_RESTORE] = FALSE; + } + } + else + { + mButtonsEnabled[BUTTON_MINIMIZE] = FALSE; + mButtonsEnabled[BUTTON_RESTORE] = FALSE; + } + + updateButtons(); +} + +void LLFloater::setCanClose(BOOL can_close) +{ + mButtonsEnabled[BUTTON_CLOSE] = can_close; + + updateButtons(); +} + +void LLFloater::setCanTearOff(BOOL can_tear_off) +{ + mCanTearOff = can_tear_off; + mButtonsEnabled[BUTTON_TEAR_OFF] = mCanTearOff && !mHostHandle.isDead(); + + updateButtons(); +} + + +void LLFloater::setCanResize(BOOL can_resize) +{ + if (mResizable && !can_resize) + { + removeChild(mResizeBar[0]); + removeChild(mResizeBar[1]); + removeChild(mResizeBar[2]); + removeChild(mResizeBar[3]); + removeChild(mResizeHandle[0]); + removeChild(mResizeHandle[1]); + removeChild(mResizeHandle[2]); + removeChild(mResizeHandle[3]); + delete mResizeBar[0]; + delete mResizeBar[1]; + delete mResizeBar[2]; + delete mResizeBar[3]; + delete mResizeHandle[0]; + delete mResizeHandle[1]; + delete mResizeHandle[2]; + mResizeHandle[3] = NULL; + mResizeBar[0] = NULL; + mResizeBar[1] = NULL; + mResizeBar[2] = NULL; + mResizeBar[3] = NULL; + mResizeHandle[0] = NULL; + mResizeHandle[1] = NULL; + mResizeHandle[2] = NULL; + mResizeHandle[3] = NULL; + } + else if (!mResizable && can_resize) + { + // Resize bars (sides) + const S32 RESIZE_BAR_THICKNESS = 3; + mResizeBar[0] = new LLResizeBar( + "resizebar_left", + LLRect( 0, mRect.getHeight(), RESIZE_BAR_THICKNESS, 0), + mMinWidth, mMinHeight, LLResizeBar::LEFT ); + mResizeBar[0]->setSaveToXML(false); + addChild( mResizeBar[0] ); + + mResizeBar[1] = new LLResizeBar( + "resizebar_top", + LLRect( 0, mRect.getHeight(), mRect.getWidth(), mRect.getHeight() - RESIZE_BAR_THICKNESS), + mMinWidth, mMinHeight, LLResizeBar::TOP ); + mResizeBar[1]->setSaveToXML(false); + addChild( mResizeBar[1] ); + + mResizeBar[2] = new LLResizeBar( + "resizebar_right", + LLRect( mRect.getWidth() - RESIZE_BAR_THICKNESS, mRect.getHeight(), mRect.getWidth(), 0), + mMinWidth, mMinHeight, LLResizeBar::RIGHT ); + mResizeBar[2]->setSaveToXML(false); + addChild( mResizeBar[2] ); + + mResizeBar[3] = new LLResizeBar( + "resizebar_bottom", + LLRect( 0, RESIZE_BAR_THICKNESS, mRect.getWidth(), 0), + mMinWidth, mMinHeight, LLResizeBar::BOTTOM ); + mResizeBar[3]->setSaveToXML(false); + addChild( mResizeBar[3] ); + + + // Resize handles (corners) + mResizeHandle[0] = new LLResizeHandle( + "Resize Handle", + LLRect( mRect.getWidth() - RESIZE_HANDLE_WIDTH, RESIZE_HANDLE_HEIGHT, mRect.getWidth(), 0), + mMinWidth, + mMinHeight, + LLResizeHandle::RIGHT_BOTTOM); + mResizeHandle[0]->setSaveToXML(false); + addChild(mResizeHandle[0]); + + mResizeHandle[1] = new LLResizeHandle( "resize", + LLRect( mRect.getWidth() - RESIZE_HANDLE_WIDTH, mRect.getHeight(), mRect.getWidth(), mRect.getHeight() - RESIZE_HANDLE_HEIGHT), + mMinWidth, + mMinHeight, + LLResizeHandle::RIGHT_TOP ); + mResizeHandle[1]->setSaveToXML(false); + addChild(mResizeHandle[1]); + + mResizeHandle[2] = new LLResizeHandle( "resize", + LLRect( 0, RESIZE_HANDLE_HEIGHT, RESIZE_HANDLE_WIDTH, 0 ), + mMinWidth, + mMinHeight, + LLResizeHandle::LEFT_BOTTOM ); + mResizeHandle[2]->setSaveToXML(false); + addChild(mResizeHandle[2]); + + mResizeHandle[3] = new LLResizeHandle( "resize", + LLRect( 0, mRect.getHeight(), RESIZE_HANDLE_WIDTH, mRect.getHeight() - RESIZE_HANDLE_HEIGHT ), + mMinWidth, + mMinHeight, + LLResizeHandle::LEFT_TOP ); + mResizeHandle[3]->setSaveToXML(false); + addChild(mResizeHandle[3]); + } + mResizable = can_resize; +} + +void LLFloater::setCanDrag(BOOL can_drag) +{ + // if we delete drag handle, we no longer have access to the floater's title + // so just enable/disable it + if (!can_drag && mDragHandle->getEnabled()) + { + mDragHandle->setEnabled(FALSE); + } + else if (can_drag && !mDragHandle->getEnabled()) + { + mDragHandle->setEnabled(TRUE); + } +} + +void LLFloater::updateButtons() +{ + S32 button_count = 0; + for (S32 i = 0; i < BUTTON_COUNT; i++) + { + if (mButtonsEnabled[i]) + { + button_count++; + + LLRect btn_rect; + if (mDragOnLeft) + { + btn_rect.setLeftTopAndSize( + LLPANEL_BORDER_WIDTH, + mRect.getHeight() - CLOSE_BOX_FROM_TOP - (LLFLOATER_CLOSE_BOX_SIZE + 1) * button_count, + llround((F32)LLFLOATER_CLOSE_BOX_SIZE * mButtonScale), + llround((F32)LLFLOATER_CLOSE_BOX_SIZE * mButtonScale)); + } + else + { + btn_rect.setLeftTopAndSize( + mRect.getWidth() - LLPANEL_BORDER_WIDTH - (LLFLOATER_CLOSE_BOX_SIZE + 1) * button_count, + mRect.getHeight() - CLOSE_BOX_FROM_TOP, + llround((F32)LLFLOATER_CLOSE_BOX_SIZE * mButtonScale), + llround((F32)LLFLOATER_CLOSE_BOX_SIZE * mButtonScale)); + } + + mButtons[i]->setRect(btn_rect); + mButtons[i]->setVisible(TRUE); + mButtons[i]->setEnabled(TRUE); + // the restore button should have a tab stop so that it takes action when you Ctrl-Tab to a minimized floater + mButtons[i]->setTabStop(i == BUTTON_RESTORE); + } + else if (mButtons[i]) + { + mButtons[i]->setVisible(FALSE); + mButtons[i]->setEnabled(FALSE); + } + } + + mDragHandle->setMaxTitleWidth(mRect.getWidth() - (button_count * (LLFLOATER_CLOSE_BOX_SIZE + 1))); +} + +void LLFloater::buildButtons() +{ + for (S32 i = 0; i < BUTTON_COUNT; i++) + { + LLRect btn_rect; + if (mDragOnLeft) + { + btn_rect.setLeftTopAndSize( + LLPANEL_BORDER_WIDTH, + mRect.getHeight() - CLOSE_BOX_FROM_TOP - (LLFLOATER_CLOSE_BOX_SIZE + 1) * (i + 1), + llround(LLFLOATER_CLOSE_BOX_SIZE * mButtonScale), + llround(LLFLOATER_CLOSE_BOX_SIZE * mButtonScale)); + } + else + { + btn_rect.setLeftTopAndSize( + mRect.getWidth() - LLPANEL_BORDER_WIDTH - (LLFLOATER_CLOSE_BOX_SIZE + 1) * (i + 1), + mRect.getHeight() - CLOSE_BOX_FROM_TOP, + llround(LLFLOATER_CLOSE_BOX_SIZE * mButtonScale), + llround(LLFLOATER_CLOSE_BOX_SIZE * mButtonScale)); + } + + LLButton* buttonp = new LLButton( + sButtonNames[i], + btn_rect, + sButtonActiveImageNames[i], + sButtonPressedImageNames[i], + "", + sButtonCallbacks[i], + this, + LLFontGL::sSansSerif); + + buttonp->setTabStop(FALSE); + buttonp->setFollowsTop(); + buttonp->setFollowsRight(); + buttonp->setToolTip( sButtonToolTips[i] ); + buttonp->setImageColor(LLUI::sColorsGroup->getColor("FloaterButtonImageColor")); + buttonp->setHoverImages(sButtonPressedImageNames[i], + sButtonPressedImageNames[i]); + buttonp->setScaleImage(TRUE); + buttonp->setSaveToXML(false); + addChild(buttonp); + mButtons[i] = buttonp; + } + + updateButtons(); +} + +///////////////////////////////////////////////////// +// LLFloaterView + +LLFloaterView::LLFloaterView( const LLString& name, const LLRect& rect ) +: LLUICtrl( name, rect, FALSE, NULL, NULL, FOLLOWS_ALL ), + mFocusCycleMode(FALSE), + mSnapOffsetBottom(0) +{ + setTabStop(FALSE); + resetStartingFloaterPosition(); +} + +EWidgetType LLFloaterView::getWidgetType() const +{ + return WIDGET_TYPE_FLOATER_VIEW; +} + +LLString LLFloaterView::getWidgetTag() const +{ + return LL_FLOATER_VIEW_TAG; +} + +// By default, adjust vertical. +void LLFloaterView::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + reshape(width, height, called_from_parent, ADJUST_VERTICAL_YES); +} + +// When reshaping this view, make the floaters follow their closest edge. +void LLFloaterView::reshape(S32 width, S32 height, BOOL called_from_parent, BOOL adjust_vertical) +{ + S32 old_width = mRect.getWidth(); + S32 old_height = mRect.getHeight(); + + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + LLFloater* floaterp = (LLFloater*)viewp; + if (floaterp->isDependent()) + { + // dependents use same follow flags as their "dependee" + continue; + } + LLRect r = floaterp->getRect(); + + // Compute absolute distance from each edge of screen + S32 left_offset = llabs(r.mLeft - 0); + S32 right_offset = llabs(old_width - r.mRight); + + S32 top_offset = llabs(old_height - r.mTop); + S32 bottom_offset = llabs(r.mBottom - 0); + + // Make if follow the edge it is closest to + U32 follow_flags = 0x0; + + if (left_offset < right_offset) + { + follow_flags |= FOLLOWS_LEFT; + } + else + { + follow_flags |= FOLLOWS_RIGHT; + } + + // "No vertical adjustment" usually means that the bottom of the view + // has been pushed up or down. Hence we want the floaters to follow + // the top. + if (!adjust_vertical) + { + follow_flags |= FOLLOWS_TOP; + } + else if (top_offset < bottom_offset) + { + follow_flags |= FOLLOWS_TOP; + } + else + { + follow_flags |= FOLLOWS_BOTTOM; + } + + floaterp->setFollows(follow_flags); + + //RN: all dependent floaters copy follow behavior of "parent" + for(LLFloater::handle_set_iter_t dependent_it = floaterp->mDependents.begin(); + dependent_it != floaterp->mDependents.end(); ++dependent_it) + { + LLFloater* dependent_floaterp = getFloaterByHandle(*dependent_it); + if (dependent_floaterp) + { + dependent_floaterp->setFollows(follow_flags); + } + } + } + + LLView::reshape(width, height, called_from_parent); +} + + +void LLFloaterView::restoreAll() +{ + // make sure all subwindows aren't minimized + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLFloater* floaterp = (LLFloater*)*child_it; + floaterp->setMinimized(FALSE); + } + + //FIXME: make sure dependents are restored + + // children then deleted by default view constructor +} + + +void LLFloaterView::getNewFloaterPosition(S32* left,S32* top) +{ + // Workaround: mRect may change between when this object is created and the first time it is used. + static BOOL first = TRUE; + if( first ) + { + resetStartingFloaterPosition(); + first = FALSE; + } + + const S32 FLOATER_PAD = 16; + LLCoordWindow window_size; + getWindow()->getSize(&window_size); + LLRect full_window(0, window_size.mY, window_size.mX, 0); + LLRect floater_creation_rect( + 160, + full_window.getHeight() - 2 * MENU_BAR_HEIGHT, + full_window.getWidth() * 2 / 3, + 130 ); + floater_creation_rect.stretch( -FLOATER_PAD ); + + *left = mNextLeft; + *top = mNextTop; + + const S32 STEP = 25; + S32 bottom = floater_creation_rect.mBottom + 2 * STEP; + S32 right = floater_creation_rect.mRight - 4 * STEP; + + mNextTop -= STEP; + mNextLeft += STEP; + + if( (mNextTop < bottom ) || (mNextLeft > right) ) + { + mColumn++; + mNextTop = floater_creation_rect.mTop; + mNextLeft = STEP * mColumn; + + if( (mNextTop < bottom) || (mNextLeft > right) ) + { + // Advancing the column didn't work, so start back at the beginning + resetStartingFloaterPosition(); + } + } +} + +void LLFloaterView::resetStartingFloaterPosition() +{ + const S32 FLOATER_PAD = 16; + LLCoordWindow window_size; + getWindow()->getSize(&window_size); + LLRect full_window(0, window_size.mY, window_size.mX, 0); + LLRect floater_creation_rect( + 160, + full_window.getHeight() - 2 * MENU_BAR_HEIGHT, + full_window.getWidth() * 2 / 3, + 130 ); + floater_creation_rect.stretch( -FLOATER_PAD ); + + mNextLeft = floater_creation_rect.mLeft; + mNextTop = floater_creation_rect.mTop; + mColumn = 0; +} + +LLRect LLFloaterView::findNeighboringPosition( LLFloater* reference_floater, LLFloater* neighbor ) +{ + LLRect base_rect = reference_floater->getRect(); + S32 width = neighbor->getRect().getWidth(); + S32 height = neighbor->getRect().getHeight(); + LLRect new_rect = neighbor->getRect(); + + LLRect expanded_base_rect = base_rect; + expanded_base_rect.stretch(10); + for(LLFloater::handle_set_iter_t dependent_it = reference_floater->mDependents.begin(); + dependent_it != reference_floater->mDependents.end(); ++dependent_it) + { + LLFloater* sibling = LLFloater::getFloaterByHandle(*dependent_it); + // check for dependents within 10 pixels of base floater + if (sibling && + sibling != neighbor && + sibling->getVisible() && + expanded_base_rect.rectInRect(&sibling->getRect())) + { + base_rect |= sibling->getRect(); + } + } + + S32 left_margin = llmax(0, base_rect.mLeft); + S32 right_margin = llmax(0, mRect.getWidth() - base_rect.mRight); + S32 top_margin = llmax(0, mRect.getHeight() - base_rect.mTop); + S32 bottom_margin = llmax(0, base_rect.mBottom); + + // find position for floater in following order + // right->left->bottom->top + for (S32 i = 0; i < 5; i++) + { + if (right_margin > width) + { + new_rect.translate(base_rect.mRight - neighbor->mRect.mLeft, base_rect.mTop - neighbor->mRect.mTop); + return new_rect; + } + else if (left_margin > width) + { + new_rect.translate(base_rect.mLeft - neighbor->mRect.mRight, base_rect.mTop - neighbor->mRect.mTop); + return new_rect; + } + else if (bottom_margin > height) + { + new_rect.translate(base_rect.mLeft - neighbor->mRect.mLeft, base_rect.mBottom - neighbor->mRect.mTop); + return new_rect; + } + else if (top_margin > height) + { + new_rect.translate(base_rect.mLeft - neighbor->mRect.mLeft, base_rect.mTop - neighbor->mRect.mBottom); + return new_rect; + } + + // keep growing margins to find "best" fit + left_margin += 20; + right_margin += 20; + top_margin += 20; + bottom_margin += 20; + } + + // didn't find anything, return initial rect + return new_rect; +} + +void LLFloaterView::setCycleMode(BOOL mode) +{ + mFocusCycleMode = mode; +} + +BOOL LLFloaterView::getCycleMode() +{ + return mFocusCycleMode; +} + +void LLFloaterView::bringToFront(LLFloater* child, BOOL give_focus) +{ + //FIXME: make this respect floater's mAutoFocus value, instead of using parameter + if (child->getHost()) + { + // this floater is hosted elsewhere and hence not one of our children, abort + return; + } + std::vector<LLView*> floaters_to_move; + // Look at all floaters...tab + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + LLFloater *floater = (LLFloater *)viewp; + + // ...but if I'm a dependent floater... + if (child->isDependent()) + { + // ...look for floaters that have me as a dependent... + LLFloater::handle_set_iter_t found_dependent = floater->mDependents.find(child->getHandle()); + + if (found_dependent != floater->mDependents.end()) + { + // ...and make sure all children of that floater (including me) are brought to front... + for(LLFloater::handle_set_iter_t dependent_it = floater->mDependents.begin(); + dependent_it != floater->mDependents.end(); ) + { + LLFloater* sibling = LLFloater::getFloaterByHandle(*dependent_it); + if (sibling) + { + floaters_to_move.push_back(sibling); + } + ++dependent_it; + } + //...before bringing my parent to the front... + floaters_to_move.push_back(floater); + } + } + } + + std::vector<LLView*>::iterator view_it; + for(view_it = floaters_to_move.begin(); view_it != floaters_to_move.end(); ++view_it) + { + LLFloater* floaterp = (LLFloater*)(*view_it); + sendChildToFront(floaterp); + + floaterp->setMinimized(FALSE); + } + floaters_to_move.clear(); + + // ...then bringing my own dependents to the front... + for(LLFloater::handle_set_iter_t dependent_it = child->mDependents.begin(); + dependent_it != child->mDependents.end(); ) + { + LLFloater* dependent = getFloaterByHandle(*dependent_it); + if (dependent) + { + sendChildToFront(dependent); + dependent->setMinimized(FALSE); + } + ++dependent_it; + } + + // ...and finally bringing myself to front + // (do this last, so that I'm left in front at end of this call) + if( *getChildList()->begin() != child ) + { + sendChildToFront(child); + } + child->setMinimized(FALSE); + if (give_focus && !gFocusMgr.childHasKeyboardFocus(child)) + { + child->setFocus(TRUE); + } +} + +void LLFloaterView::highlightFocusedFloater() +{ + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLFloater *floater = (LLFloater *)(*child_it); + + // skip dependent floaters, as we'll handle them in a batch along with their dependee(?) + if (floater->isDependent()) + { + continue; + } + + BOOL floater_or_dependent_has_focus = gFocusMgr.childHasKeyboardFocus(floater); + for(LLFloater::handle_set_iter_t dependent_it = floater->mDependents.begin(); + dependent_it != floater->mDependents.end(); + ++dependent_it) + { + LLFloater* dependent_floaterp = getFloaterByHandle(*dependent_it); + if (dependent_floaterp && gFocusMgr.childHasKeyboardFocus(dependent_floaterp)) + { + floater_or_dependent_has_focus = TRUE; + } + } + + // now set this floater and all its dependents + floater->setForeground(floater_or_dependent_has_focus); + + for(LLFloater::handle_set_iter_t dependent_it = floater->mDependents.begin(); + dependent_it != floater->mDependents.end(); ) + { + LLFloater* dependent_floaterp = getFloaterByHandle(*dependent_it); + if (dependent_floaterp) + { + dependent_floaterp->setForeground(floater_or_dependent_has_focus); + } + ++dependent_it; + } + + floater->cleanupHandles(); + } +} + +void LLFloaterView::unhighlightFocusedFloater() +{ + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLFloater *floater = (LLFloater *)(*child_it); + + floater->setForeground(FALSE); + } +} + +void LLFloaterView::focusFrontFloater() +{ + LLFloater* floaterp = getFrontmost(); + if (floaterp) + { + floaterp->setFocus(TRUE); + } +} + +void LLFloaterView::getMinimizePosition(S32 *left, S32 *bottom) +{ + // count the number of minimized children + S32 count = 0; + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + LLFloater *floater = (LLFloater *)viewp; + if (floater->isMinimized()) + { + count++; + } + } + + // space over for that many and up if necessary + S32 tiles_per_row = mRect.getWidth() / MINIMIZED_WIDTH; + + *left = (count % tiles_per_row) * MINIMIZED_WIDTH; + *bottom = (count / tiles_per_row) * LLFLOATER_HEADER_SIZE; +} + + +void LLFloaterView::destroyAllChildren() +{ + LLView::deleteAllChildren(); +} + +void LLFloaterView::closeAllChildren(bool app_quitting) +{ + // iterate over a copy of the list, because closing windows will destroy + // some windows on the list. + child_list_t child_list = *(getChildList()); + + for (child_list_const_iter_t it = child_list.begin(); it != child_list.end(); ++it) + { + LLView* viewp = *it; + child_list_const_iter_t exists = std::find(getChildList()->begin(), getChildList()->end(), viewp); + if (exists == getChildList()->end()) + { + // this floater has already been removed + continue; + } + + LLFloater* floaterp = (LLFloater*)viewp; + + // Attempt to close floater. This will cause the "do you want to save" + // dialogs to appear. + if (floaterp->canClose()) + { + floaterp->close(app_quitting); + } + } +} + + +BOOL LLFloaterView::allChildrenClosed() +{ + // see if there are any visible floaters (some floaters "close" + // by setting themselves invisible) + S32 visible_count = 0; + for (child_list_const_iter_t it = getChildList()->begin(); it != getChildList()->end(); ++it) + { + LLView* viewp = *it; + LLFloater* floaterp = (LLFloater*)viewp; + + if (floaterp->getVisible() && floaterp->canClose()) + { + visible_count++; + } + } + + return (visible_count == 0); +} + + +void LLFloaterView::refresh() +{ + // Constrain children to be entirely on the screen + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLFloater* floaterp = (LLFloater*)*child_it; + if( floaterp->getVisible() ) + { + adjustToFitScreen(floaterp, TRUE); + } + } +} + +void LLFloaterView::adjustToFitScreen(LLFloater* floater, BOOL allow_partial_outside) +{ + if (floater->getParent() != this) + { + // floater is hosted elsewhere, so ignore + return; + } + S32 screen_width = getSnapRect().getWidth(); + S32 screen_height = getSnapRect().getHeight(); + // convert to local coordinate frame + LLRect snap_rect_local = getSnapRect(); + snap_rect_local.translate(-mRect.mLeft, -mRect.mBottom); + + if( floater->isResizable() ) + { + LLRect view_rect = floater->getRect(); + S32 view_width = view_rect.getWidth(); + S32 view_height = view_rect.getHeight(); + S32 min_width; + S32 min_height; + floater->getResizeLimits( &min_width, &min_height ); + + S32 new_width = llmax( min_width, view_width ); + S32 new_height = llmax( min_height, view_height ); + + if( (new_width > screen_width) || (new_height > screen_height) ) + { + new_width = llmin(new_width, screen_width); + new_height = llmin(new_height, screen_height); + + floater->reshape( new_width, new_height, TRUE ); + + // Make sure the damn thing is actually onscreen. + if (floater->translateIntoRect(snap_rect_local, FALSE)) + { + floater->clearSnapTarget(); + } + } + else if (!floater->isMinimized()) + { + floater->reshape(new_width, new_height, TRUE); + } + } + + if (floater->translateIntoRect( snap_rect_local, allow_partial_outside )) + { + floater->clearSnapTarget(); + } +} + +void LLFloaterView::draw() +{ + if( getVisible() ) + { + refresh(); + + // hide focused floater if in cycle mode, so that it can be drawn on top + LLFloater* focused_floater = getFocusedFloater(); + BOOL floater_visible = FALSE; + if (mFocusCycleMode && focused_floater) + { + floater_visible = focused_floater->getVisible(); + focused_floater->setVisible(FALSE); + } + + // And actually do the draw + LLView::draw(); + + // manually draw focused floater on top when in cycle mode + if (mFocusCycleMode && focused_floater) + { + // draw focused item on top for better feedback + focused_floater->setVisible(floater_visible); + if (floater_visible) + { + drawChild(focused_floater); + } + } + } +} + +const LLRect LLFloaterView::getSnapRect() const +{ + LLRect snap_rect = mRect; + snap_rect.mBottom += mSnapOffsetBottom; + + return snap_rect; +} + +LLFloater *LLFloaterView::getFocusedFloater() +{ + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLUICtrl* ctrlp = (*child_it)->isCtrl() ? static_cast<LLUICtrl*>(*child_it) : NULL; + if ( ctrlp && ctrlp->hasFocus() ) + { + return static_cast<LLFloater *>(ctrlp); + } + } + return NULL; +} + +LLFloater *LLFloaterView::getFrontmost() +{ + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + if ( viewp->getVisible() ) + { + return (LLFloater *)viewp; + } + } + return NULL; +} + +LLFloater *LLFloaterView::getBackmost() +{ + LLFloater* back_most = NULL; + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + if ( viewp->getVisible() ) + { + back_most = (LLFloater *)viewp; + } + } + return back_most; +} + +void LLFloaterView::syncFloaterTabOrder() +{ + // bring focused floater to front + for ( child_list_const_reverse_iter_t child_it = getChildList()->rbegin(); child_it != getChildList()->rend(); ++child_it) + { + LLFloater* floaterp = (LLFloater*)*child_it; + if (gFocusMgr.childHasKeyboardFocus(floaterp)) + { + bringToFront(floaterp, FALSE); + break; + } + } + + // then sync draw order to tab order + for ( child_list_const_reverse_iter_t child_it = getChildList()->rbegin(); child_it != getChildList()->rend(); ++child_it) + { + LLFloater* floaterp = (LLFloater*)*child_it; + moveChildToFrontOfTabGroup(floaterp); + } +} + +LLFloater* LLFloaterView::getFloaterByHandle(LLViewHandle handle) +{ + if (handle == LLViewHandle::sDeadHandle) + { + return NULL; + } + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + if (((LLFloater*)viewp)->getHandle() == handle) + { + return (LLFloater*)viewp; + } + } + return NULL; +} + +LLFloater* LLFloaterView::getParentFloater(LLView* viewp) +{ + LLView* parentp = viewp->getParent(); + + while(parentp && parentp != this) + { + viewp = parentp; + parentp = parentp->getParent(); + } + + if (parentp == this) + { + return (LLFloater*)viewp; + } + + return NULL; +} + +S32 LLFloaterView::getZOrder(LLFloater* child) +{ + S32 rv = 0; + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + if(viewp == child) + { + break; + } + ++rv; + } + return rv; +} + +void LLFloaterView::pushVisibleAll(BOOL visible, const skip_list_t& skip_list) +{ + for (child_list_const_iter_t child_iter = getChildList()->begin(); + child_iter != getChildList()->end(); ++child_iter) + { + LLView *view = *child_iter; + if (skip_list.find(view) == skip_list.end()) + { + view->pushVisible(visible); + } + } +} + +void LLFloaterView::popVisibleAll(const skip_list_t& skip_list) +{ + for (child_list_const_iter_t child_iter = getChildList()->begin(); + child_iter != getChildList()->end(); ++child_iter) + { + LLView *view = *child_iter; + if (skip_list.find(view) == skip_list.end()) + { + view->popVisible(); + } + } +} + +// +// LLMultiFloater +// + +LLMultiFloater::LLMultiFloater() : + mTabContainer(NULL), + mTabPos(LLTabContainerCommon::TOP), + mAutoResize(FALSE) +{ + +} + +LLMultiFloater::LLMultiFloater(LLTabContainerCommon::TabPosition tab_pos) : + mTabContainer(NULL), + mTabPos(tab_pos), + mAutoResize(FALSE) +{ + +} + +LLMultiFloater::LLMultiFloater(const LLString &name) : + LLFloater(name), + mTabContainer(NULL), + mTabPos(LLTabContainerCommon::TOP), + mAutoResize(FALSE) +{ +} + +LLMultiFloater::LLMultiFloater( + const LLString& name, + const LLRect& rect, + LLTabContainer::TabPosition tab_pos, + BOOL auto_resize) : + LLFloater(name, rect, name), + mTabContainer(NULL), + mTabPos(LLTabContainerCommon::TOP), + mAutoResize(auto_resize) +{ + mTabContainer = new LLTabContainer("Preview Tabs", + LLRect(LLPANEL_BORDER_WIDTH, mRect.getHeight() - LLFLOATER_HEADER_SIZE, mRect.getWidth() - LLPANEL_BORDER_WIDTH, 0), + mTabPos, + NULL, + NULL); + mTabContainer->setFollowsAll(); + if (mResizable) + { + mTabContainer->setRightTabBtnOffset(RESIZE_HANDLE_WIDTH); + } + + addChild(mTabContainer); +} + +LLMultiFloater::LLMultiFloater( + const LLString& name, + const LLString& rect_control, + LLTabContainer::TabPosition tab_pos, + BOOL auto_resize) : + LLFloater(name, rect_control, name), + mTabContainer(NULL), + mTabPos(tab_pos), + mAutoResize(auto_resize) +{ + mTabContainer = new LLTabContainer("Preview Tabs", + LLRect(LLPANEL_BORDER_WIDTH, mRect.getHeight() - LLFLOATER_HEADER_SIZE, mRect.getWidth() - LLPANEL_BORDER_WIDTH, 0), + mTabPos, + NULL, + NULL); + mTabContainer->setFollowsAll(); + if (mResizable && mTabPos == LLTabContainerCommon::BOTTOM) + { + mTabContainer->setRightTabBtnOffset(RESIZE_HANDLE_WIDTH); + } + + addChild(mTabContainer); + +} + +LLMultiFloater::~LLMultiFloater() +{ +} + +// virtual +EWidgetType LLMultiFloater::getWidgetType() const +{ + return WIDGET_TYPE_MULTI_FLOATER; +} + +// virtual +LLString LLMultiFloater::getWidgetTag() const +{ + return LL_MULTI_FLOATER_TAG; +} + +void LLMultiFloater::init(const LLString& title, BOOL resizable, + S32 min_width, S32 min_height, BOOL drag_on_left, + BOOL minimizable, BOOL close_btn) +{ + LLFloater::init(title, resizable, min_width, min_height, drag_on_left, minimizable, close_btn); + + /*mTabContainer = new LLTabContainer("Preview Tabs", + LLRect(LLPANEL_BORDER_WIDTH, mRect.getHeight() - LLFLOATER_HEADER_SIZE, mRect.getWidth() - LLPANEL_BORDER_WIDTH, 0), + mTabPos, + NULL, + NULL); + mTabContainer->setFollowsAll(); + if (mResizable && mTabPos == LLTabContainerCommon::BOTTOM) + { + mTabContainer->setRightTabBtnOffset(RESIZE_HANDLE_WIDTH); + } + + addChild(mTabContainer);*/ +} + +void LLMultiFloater::open() +{ + if (mTabContainer->getTabCount() > 0) + { + LLFloater::open(); + } + else + { + // for now, don't allow multifloaters + // without any child floaters + close(); + } +} + +void LLMultiFloater::onClose(bool app_quitting) +{ + if(closeAllFloaters() == TRUE) + { + LLFloater::onClose(app_quitting ? true : false); + }//else not all tabs could be closed... +} + +void LLMultiFloater::draw() +{ + if (mTabContainer->getTabCount() == 0) + { + //RN: could this potentially crash in draw hierarchy? + close(); + } + else + { + for (S32 i = 0; i < mTabContainer->getTabCount(); i++) + { + LLFloater* floaterp = (LLFloater*)mTabContainer->getPanelByIndex(i); + if (floaterp->getTitle() != mTabContainer->getPanelTitle(i)) + { + mTabContainer->setPanelTitle(i, floaterp->getTitle()); + } + } + LLFloater::draw(); + } +} + +BOOL LLMultiFloater::closeAllFloaters() +{ + S32 tabToClose = 0; + S32 lastTabCount = mTabContainer->getTabCount(); + while (tabToClose < mTabContainer->getTabCount()) + { + LLFloater* first_floater = (LLFloater*)mTabContainer->getPanelByIndex(tabToClose); + first_floater->close(); + if(lastTabCount == mTabContainer->getTabCount()) + { + //Tab did not actually close, possibly due to a pending Save Confirmation dialog.. + //so try and close the next one in the list... + tabToClose++; + }else + { + //Tab closed ok. + lastTabCount = mTabContainer->getTabCount(); + } + } + if( mTabContainer->getTabCount() != 0 ) + return FALSE; // Couldn't close all the tabs (pending save dialog?) so return FALSE. + return TRUE; //else all tabs were successfully closed... +} + +void LLMultiFloater::growToFit(LLFloater* floaterp, S32 width, S32 height) +{ + floater_data_map_t::iterator found_data_it; + found_data_it = mFloaterDataMap.find(floaterp->getHandle()); + if (found_data_it != mFloaterDataMap.end()) + { + // store new width and height with this floater so that it will keep its size when detached + found_data_it->second.mWidth = width; + found_data_it->second.mHeight = height; + + S32 cur_height = mRect.getHeight(); + reshape(llmax(mRect.getWidth(), width + LLPANEL_BORDER_WIDTH * 2), llmax(mRect.getHeight(), height + LLFLOATER_HEADER_SIZE + TABCNTR_HEADER_HEIGHT + (LLPANEL_BORDER_WIDTH * 2))); + + // make sure upper left corner doesn't move + translate(0, mRect.getHeight() - cur_height); + + // Try to keep whole view onscreen, don't allow partial offscreen. + gFloaterView->adjustToFitScreen(this, FALSE); + } +} + +/** + void addFloater(LLFloater* floaterp, BOOL select_added_floater) + + Adds the LLFloater pointed to by floaterp to this. + If floaterp is already hosted by this, then it is re-added to get + new titles, etc. + If select_added_floater is true, the LLFloater pointed to by floaterp will + become the selected tab in this + + Affects: mTabContainer, floaterp +**/ +void LLMultiFloater::addFloater(LLFloater* floaterp, BOOL select_added_floater, LLTabContainer::eInsertionPoint insertion_point) +{ + if (!floaterp) + { + return; + } + + if (!mTabContainer) + { + llerrs << "Tab Container used without having been initialized." << llendl; + } + + if (floaterp->getHost() == this) + { + // already hosted by me, remove + // do this so we get updated title, etc. + mFloaterDataMap.erase(floaterp->getHandle()); + mTabContainer->removeTabPanel(floaterp); + } + else if (floaterp->getHost()) + { + // floaterp is hosted by somebody else and + // this is adding it, so remove it from it's old host + floaterp->getHost()->removeFloater(floaterp); + } + else if (floaterp->getParent() == gFloaterView) + { + // rehost preview floater as child panel + gFloaterView->removeChild(floaterp); + } + + // store original configuration + LLFloaterData floater_data; + floater_data.mWidth = floaterp->getRect().getWidth(); + floater_data.mHeight = floaterp->getRect().getHeight(); + floater_data.mCanMinimize = floaterp->isMinimizeable(); + floater_data.mCanResize = floaterp->isResizable(); + + // remove minimize and close buttons + floaterp->setCanMinimize(FALSE); + floaterp->setCanResize(FALSE); + floaterp->setCanDrag(FALSE); + + S32 new_width = llmax(mRect.getWidth(), floaterp->getRect().getWidth()); + S32 new_height = llmax(mRect.getHeight(), floaterp->getRect().getHeight() + LLFLOATER_HEADER_SIZE + TABCNTR_HEADER_HEIGHT); + + reshape(new_width, new_height); + + //add the panel, add it to proper maps + mTabContainer->addTabPanel(floaterp, floaterp->getTitle(), FALSE, onTabSelected, this, 0, FALSE, insertion_point); + mFloaterDataMap[floaterp->getHandle()] = floater_data; + + if ( select_added_floater ) + { + mTabContainer->selectLastTab(); + // explicitly call tabopen to load preview assets, etc. + tabOpen((LLFloater*)mTabContainer->getCurrentPanel(), true); + } + + floaterp->setHost(this); + if (mMinimized) + { + floaterp->setVisible(FALSE); + } +} + +/** + BOOL selectFloater(LLFloater* floaterp) + + If the LLFloater pointed to by floaterp is hosted by this, + then its tab is selected and returns true. Otherwise returns false. + + Affects: mTabContainer +**/ +BOOL LLMultiFloater::selectFloater(LLFloater* floaterp) +{ + return mTabContainer->selectTabPanel(floaterp); +} + +// virtual +void LLMultiFloater::selectNextFloater() +{ + mTabContainer->selectNextTab(); +} + +// virtual +void LLMultiFloater::selectPrevFloater() +{ + mTabContainer->selectPrevTab(); +} + +void LLMultiFloater::showFloater(LLFloater* floaterp) +{ + // we won't select a panel that already is selected + // it is hard to do this internally to tab container + // as tab selection is handled via index and the tab at a given + // index might have changed + if (floaterp != mTabContainer->getCurrentPanel() && + !mTabContainer->selectTabPanel(floaterp)) + { + addFloater(floaterp, TRUE); + } + setVisibleAndFrontmost(); +} + +void LLMultiFloater::removeFloater(LLFloater* floaterp) +{ + if ( floaterp->getHost() != this ) + return; + + floater_data_map_t::iterator found_data_it = mFloaterDataMap.find(floaterp->getHandle()); + if (found_data_it != mFloaterDataMap.end()) + { + LLFloaterData& floater_data = found_data_it->second; + floaterp->setCanMinimize(floater_data.mCanMinimize); + if (!floater_data.mCanResize) + { + // restore original size + floaterp->reshape(floater_data.mWidth, floater_data.mHeight); + } + floaterp->setCanResize(floater_data.mCanResize); + mFloaterDataMap.erase(found_data_it); + } + mTabContainer->removeTabPanel(floaterp); + floaterp->setBackgroundVisible(TRUE); + floaterp->setHost(NULL); + + if (mAutoResize) + { + floater_data_map_t::iterator floater_it; + S32 new_width = 0; + S32 new_height = 0; + for (floater_it = mFloaterDataMap.begin(); floater_it != mFloaterDataMap.end(); ++floater_it) + { + new_width = llmax(new_width, floater_it->second.mWidth + LLPANEL_BORDER_WIDTH * 2); + new_height = llmax(new_height, floater_it->second.mHeight + LLFLOATER_HEADER_SIZE + TABCNTR_HEADER_HEIGHT); + } + + S32 cur_height = mRect.getHeight(); + + reshape(new_width, new_height); + + // make sure upper left corner doesn't move + translate(0, cur_height - new_height); + + // Try to keep whole view onscreen, don't allow partial offscreen. + gFloaterView->adjustToFitScreen(this, FALSE); + } + + tabOpen((LLFloater*)mTabContainer->getCurrentPanel(), false); +} + +void LLMultiFloater::tabOpen(LLFloater* opened_floater, bool from_click) +{ + // default implementation does nothing +} + +void LLMultiFloater::tabClose() +{ + if (mTabContainer->getTabCount() == 0) + { + // no more children, close myself + close(); + } +} + +void LLMultiFloater::setVisible(BOOL visible) +{ + //FIXME: shouldn't have to do this, fix adding to minimized multifloater + LLFloater::setVisible(visible); + + if (mTabContainer) + { + LLPanel* cur_floaterp = mTabContainer->getCurrentPanel(); + + if (cur_floaterp) + { + cur_floaterp->setVisible(visible); + } + } +} + +BOOL LLMultiFloater::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent) +{ + if (getEnabled() + && mask == (MASK_CONTROL|MASK_SHIFT)) + { + if (key == 'W') + { + LLFloater* floater = getActiveFloater(); + if (floater && floater->canClose()) + { + floater->close(); + } + return TRUE; + } + } + + return LLFloater::handleKeyHere(key, mask, called_from_parent); +} + +LLFloater* LLMultiFloater::getActiveFloater() +{ + return (LLFloater*)mTabContainer->getCurrentPanel(); +} + +S32 LLMultiFloater::getFloaterCount() +{ + return mTabContainer->getTabCount(); +} + +/** + BOOL isFloaterFlashing(LLFloater* floaterp) + + Returns true if the LLFloater pointed to by floaterp + is currently in a flashing state and is hosted by this. + False otherwise. + + Requires: floaterp != NULL +**/ +BOOL LLMultiFloater::isFloaterFlashing(LLFloater* floaterp) +{ + if ( floaterp && floaterp->getHost() == this ) + return mTabContainer->getTabPanelFlashing(floaterp); + + return FALSE; +} + +/** + BOOL setFloaterFlashing(LLFloater* floaterp, BOOL flashing) + + Sets the current flashing state of the LLFloater pointed + to by floaterp to be the BOOL flashing if the LLFloater pointed + to by floaterp is hosted by this. + + Requires: floaterp != NULL +**/ +void LLMultiFloater::setFloaterFlashing(LLFloater* floaterp, BOOL flashing) +{ + if ( floaterp && floaterp->getHost() == this ) + mTabContainer->setTabPanelFlashing(floaterp, flashing); +} + +//static +void LLMultiFloater::onTabSelected(void* userdata, bool from_click) +{ + LLMultiFloater* floaterp = (LLMultiFloater*)userdata; + + floaterp->tabOpen((LLFloater*)floaterp->mTabContainer->getCurrentPanel(), from_click); +} + +void LLMultiFloater::setCanResize(BOOL can_resize) +{ + LLFloater::setCanResize(can_resize); + if (mResizable && mTabContainer->getTabPosition() == LLTabContainer::BOTTOM) + { + mTabContainer->setRightTabBtnOffset(RESIZE_HANDLE_WIDTH); + } + else + { + mTabContainer->setRightTabBtnOffset(0); + } +} + +BOOL LLMultiFloater::postBuild() +{ + if (mTabContainer) + { + return TRUE; + } + + requires("Preview Tabs", WIDGET_TYPE_TAB_CONTAINER); + if (checkRequirements()) + { + mTabContainer = LLUICtrlFactory::getTabContainerByName(this, "Preview Tabs"); + return TRUE; + } + + return FALSE; +} + +// virtual +LLXMLNodePtr LLFloater::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLPanel::getXML(); + + node->createChild("title", TRUE)->setStringValue(getTitle()); + + node->createChild("can_resize", TRUE)->setBoolValue(isResizable()); + + node->createChild("can_minimize", TRUE)->setBoolValue(isMinimizeable()); + + node->createChild("can_close", TRUE)->setBoolValue(isCloseable()); + + node->createChild("can_drag_on_left", TRUE)->setBoolValue(isDragOnLeft()); + + node->createChild("min_width", TRUE)->setIntValue(getMinWidth()); + + node->createChild("min_height", TRUE)->setIntValue(getMinHeight()); + + node->createChild("can_tear_off", TRUE)->setBoolValue(mCanTearOff); + + return node; +} + +// static +LLView* LLFloater::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("floater"); + node->getAttributeString("name", name); + + LLFloater *floaterp = new LLFloater(name); + + LLString filename; + node->getAttributeString("filename", filename); + + if (filename.empty()) + { + // Load from node + floaterp->initFloaterXML(node, parent, factory); + } + else + { + // Load from file + factory->buildFloater(floaterp, filename); + } + + return floaterp; +} + +void LLFloater::initFloaterXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory, BOOL open) +{ + LLString name(getName()); + LLString title(getTitle()); + LLString rect_control(""); + BOOL resizable = isResizable(); + S32 min_width = getMinWidth(); + S32 min_height = getMinHeight(); + BOOL drag_on_left = isDragOnLeft(); + BOOL minimizable = isMinimizeable(); + BOOL close_btn = isCloseable(); + LLRect rect; + + node->getAttributeString("name", name); + node->getAttributeString("title", title); + node->getAttributeString("rect_control", rect_control); + node->getAttributeBOOL("can_resize", resizable); + node->getAttributeBOOL("can_minimize", minimizable); + node->getAttributeBOOL("can_close", close_btn); + node->getAttributeBOOL("can_drag_on_left", drag_on_left); + node->getAttributeS32("min_width", min_width); + node->getAttributeS32("min_height", min_height); + + if (! rect_control.empty()) + { + setRectControl(rect_control); + } + + createRect(node, rect, parent, LLRect()); + + setRect(rect); + setName(name); + + init(title, + resizable, + min_width, + min_height, + drag_on_left, + minimizable, + close_btn); + + BOOL can_tear_off; + if (node->getAttributeBOOL("can_tear_off", can_tear_off)) + { + setCanTearOff(can_tear_off); + } + + initFromXML(node, parent); + + LLMultiFloater* last_host = LLFloater::getFloaterHost(); + if (node->hasName("multi_floater")) + { + LLFloater::setFloaterHost((LLMultiFloater*) this); + } + + LLXMLNodePtr child; + for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) + { + factory->createWidget(this, child); + } + if (node->hasName("multi_floater")) + { + LLFloater::setFloaterHost(last_host); + } + + + BOOL result = postBuild(); + + if (!result) + { + llerrs << "Failed to construct floater " << name << llendl; + } + + applyRectControl(); + if (open) + { + this->open(); + } +} diff --git a/indra/llui/llfloater.h b/indra/llui/llfloater.h new file mode 100644 index 0000000000..d682c7a36a --- /dev/null +++ b/indra/llui/llfloater.h @@ -0,0 +1,399 @@ +/** + * @file llfloater.h + * @brief LLFloater base class + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// Floating "windows" within the GL display, like the inventory floater, +// mini-map floater, etc. + + +#ifndef LL_FLOATER_H +#define LL_FLOATER_H + +#include "llpanel.h" +#include "lluuid.h" +#include "lltabcontainer.h" +#include <set> + +class LLDragHandle; +class LLResizeHandle; +class LLResizeBar; +class LLButton; +class LLMultiFloater; + +const S32 LLFLOATER_VPAD = 6; +const S32 LLFLOATER_HPAD = 6; +const S32 LLFLOATER_CLOSE_BOX_SIZE = 16; +const S32 LLFLOATER_HEADER_SIZE = 16; + +const BOOL RESIZE_YES = TRUE; +const BOOL RESIZE_NO = FALSE; + +const S32 DEFAULT_MIN_WIDTH = 100; +const S32 DEFAULT_MIN_HEIGHT = 100; + +const BOOL DRAG_ON_TOP = FALSE; +const BOOL DRAG_ON_LEFT = TRUE; + +const BOOL MINIMIZE_YES = TRUE; +const BOOL MINIMIZE_NO = FALSE; + +const BOOL CLOSE_YES = TRUE; +const BOOL CLOSE_NO = FALSE; + +const BOOL ADJUST_VERTICAL_YES = TRUE; +const BOOL ADJUST_VERTICAL_NO = FALSE; + + +class LLFloater : public LLPanel +{ +friend class LLFloaterView; +public: + enum EFloaterButtons + { + BUTTON_CLOSE, + BUTTON_RESTORE, + BUTTON_MINIMIZE, + BUTTON_TEAR_OFF, + BUTTON_EDIT, + BUTTON_COUNT + }; + + LLFloater(); + LLFloater(const LLString& name); //simple constructor for data-driven initialization + LLFloater( const LLString& name, const LLRect& rect, const LLString& title, + BOOL resizable = FALSE, + S32 min_width = DEFAULT_MIN_WIDTH, + S32 min_height = DEFAULT_MIN_HEIGHT, + BOOL drag_on_left = FALSE, + BOOL minimizable = TRUE, + BOOL close_btn = TRUE, + BOOL bordered = BORDER_NO); + + LLFloater( const LLString& name, const LLString& rect_control, const LLString& title, + BOOL resizable = FALSE, + S32 min_width = DEFAULT_MIN_WIDTH, + S32 min_height = DEFAULT_MIN_HEIGHT, + BOOL drag_on_left = FALSE, + BOOL minimizable = TRUE, + BOOL close_btn = TRUE, + BOOL bordered = BORDER_NO); + + virtual ~LLFloater(); + + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + void initFloaterXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory, BOOL open = TRUE); + + /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = 1); + /*virtual*/ void translate(S32 x, S32 y); + /*virtual*/ BOOL canSnapTo(LLView* other_view); + /*virtual*/ void snappedTo(LLView* snap_view); + /*virtual*/ void setFocus( BOOL b ); + /*virtual*/ void setIsChrome(BOOL is_chrome); + + // Can be called multiple times to reset floater parameters. + // Deletes all children of the floater. + virtual void init(const LLString& title, BOOL resizable, + S32 min_width, S32 min_height, BOOL drag_on_left, + BOOL minimizable, BOOL close_btn); + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + virtual void open(); + + // If allowed, close the floater cleanly, releasing focus. + // app_quitting is passed to onClose() below. + virtual void close(bool app_quitting = false); + + void setAutoFocus(BOOL focus) { mAutoFocus = focus; setFocus(focus); } + + // Release keyboard and mouse focus + void releaseFocus(); + + // moves to center of gFloaterView + void center(); + // applies rectangle stored in mRectControl, if any + void applyRectControl(); + + + LLMultiFloater* getHost() { return (LLMultiFloater*)LLFloater::getFloaterByHandle(mHostHandle); } + + void setTitle( const LLString& title ); + const LLString& getTitle() const; + virtual void setMinimized(BOOL b); + void moveResizeHandleToFront(); + void addDependentFloater(LLFloater* dependent, BOOL reposition = TRUE); + void addDependentFloater(LLViewHandle dependent_handle, BOOL reposition = TRUE); + LLFloater* getDependee() { return (LLFloater*)LLFloater::getFloaterByHandle(mDependeeHandle); } + void removeDependentFloater(LLFloater* dependent); + BOOL isMinimized() { return mMinimized; } + BOOL isFrontmost(); + BOOL isDependent() { return !mDependeeHandle.isDead(); } + void setCanMinimize(BOOL can_minimize); + void setCanClose(BOOL can_close); + void setCanTearOff(BOOL can_tear_off); + virtual void setCanResize(BOOL can_resize); + void setCanDrag(BOOL can_drag); + void setHost(LLMultiFloater* host); + BOOL isResizable() const { return mResizable; } + void setResizeLimits( S32 min_width, S32 min_height ); + void getResizeLimits( S32* min_width, S32* min_height ) { *min_width = mMinWidth; *min_height = mMinHeight; } + + + bool isMinimizeable() const{ return mButtonsEnabled[BUTTON_MINIMIZE]; } + // Does this window have a close button, NOT can we close it right now. + bool isCloseable() const{ return (mButtonsEnabled[BUTTON_CLOSE] ? true : false); } + bool isDragOnLeft() const{ return mDragOnLeft; } + S32 getMinWidth() const{ return mMinWidth; } + S32 getMinHeight() const{ return mMinHeight; } + + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleRightMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask); + + virtual void draw(); + + // Call destroy() to free memory, or setVisible(FALSE) to keep it + // If app_quitting, you might not want to save your visibility. + // Defaults to destroy(). + virtual void onClose(bool app_quitting); + + // Defaults to true. + virtual BOOL canClose(); + + virtual void setVisible(BOOL visible); + void setFrontmost(BOOL take_focus = TRUE); + + // Defaults to false. + virtual BOOL canSaveAs(); + + // Defaults to no-op. + virtual void saveAs(); + + void setSnapTarget(LLViewHandle handle) { mSnappedTo = handle; } + void clearSnapTarget() { mSnappedTo.markDead(); } + LLViewHandle getSnapTarget() { return mSnappedTo; } + + /*virtual*/ LLView* getRootMostFastFrameView(); + + static void closeByMenu(void *userdata); + static void onClickClose(void *userdata); + static void onClickMinimize(void *userdata); + static void onClickTearOff(void *userdata); + static void onClickEdit(void *userdata); + + static void setFloaterHost(LLMultiFloater* hostp) {sHostp = hostp; } + static void setEditModeEnabled(BOOL enable); + static BOOL getEditModeEnabled(); + static LLMultiFloater* getFloaterHost() {return sHostp; } + + static LLFloater* getFloaterByHandle(LLViewHandle handle); + +protected: + // Don't call this directly. You probably want to call close(). JC + void destroy(); + virtual void bringToFront(S32 x, S32 y); + virtual void setVisibleAndFrontmost(BOOL take_focus=TRUE); + void setForeground(BOOL b); // called only by floaterview + void cleanupHandles(); // remove handles to dead floaters + void createMinimizeButton(); + void updateButtons(); + void buildButtons(); + +protected: +// static LLViewerImage* sBackgroundImage; +// static LLViewerImage* sShadowImage; + + LLDragHandle* mDragHandle; + LLResizeBar* mResizeBar[4]; + LLResizeHandle* mResizeHandle[4]; + LLButton *mMinimizeButton; + BOOL mCanTearOff; + BOOL mMinimized; + LLRect mPreviousRect; + BOOL mForeground; + LLViewHandle mDependeeHandle; + + BOOL mFirstLook; // TRUE if the _next_ time this floater is visible will be the first time in the session that it is visible. + + BOOL mResizable; + S32 mMinWidth; + S32 mMinHeight; + + BOOL mAutoFocus; + BOOL mEditing; + + typedef std::set<LLViewHandle> handle_set_t; + typedef std::set<LLViewHandle>::iterator handle_set_iter_t; + handle_set_t mDependents; + bool mDragOnLeft; + + BOOL mButtonsEnabled[BUTTON_COUNT]; + LLButton* mButtons[BUTTON_COUNT]; + F32 mButtonScale; + + LLViewHandle mSnappedTo; + + LLViewHandle mHostHandle; + LLViewHandle mLastHostHandle; + + static BOOL sEditModeEnabled; + static LLMultiFloater* sHostp; + static LLString sButtonActiveImageNames[BUTTON_COUNT]; + static LLString sButtonInactiveImageNames[BUTTON_COUNT]; + static LLString sButtonPressedImageNames[BUTTON_COUNT]; + static LLString sButtonNames[BUTTON_COUNT]; + static LLString sButtonToolTips[BUTTON_COUNT]; + typedef void (*click_callback)(void *); + static click_callback sButtonCallbacks[BUTTON_COUNT]; + + typedef std::map<LLViewHandle, LLFloater*> handle_map_t; + typedef std::map<LLViewHandle, LLFloater*>::iterator handle_map_iter_t; + static handle_map_t sFloaterMap; + + std::vector<LLView*> mMinimizedHiddenChildren; +}; + + +///////////////////////////////////////////////////////////// +// LLFloaterView +// Parent of all floating panels + +class LLFloaterView : public LLUICtrl +{ +public: + LLFloaterView( const LLString& name, const LLRect& rect ); + + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent); + void reshape(S32 width, S32 height, BOOL called_from_parent, BOOL adjust_vertical); + + /*virtual*/ void draw(); + /*virtual*/ const LLRect getSnapRect() const; + void refresh(); + + void getNewFloaterPosition( S32* left, S32* top ); + void resetStartingFloaterPosition(); + LLRect findNeighboringPosition( LLFloater* reference_floater, LLFloater* neighbor ); + + // Given a child of gFloaterView, make sure this view can fit entirely onscreen. + void adjustToFitScreen(LLFloater* floater, BOOL allow_partial_outside); + + void getMinimizePosition( S32 *left, S32 *bottom); + void restoreAll(); // un-minimize all floaters + typedef std::set<LLView*> skip_list_t; + void LLFloaterView::pushVisibleAll(BOOL visible, const skip_list_t& skip_list = skip_list_t()); + void LLFloaterView::popVisibleAll(const skip_list_t& skip_list = skip_list_t()); + + void setCycleMode(BOOL mode); + BOOL getCycleMode(); + void bringToFront( LLFloater* child, BOOL give_focus = TRUE ); + void highlightFocusedFloater(); + void unhighlightFocusedFloater(); + void focusFrontFloater(); + void destroyAllChildren(); + // attempt to close all floaters + void closeAllChildren(bool app_quitting); + BOOL allChildrenClosed(); + + LLFloater* getFrontmost(); + LLFloater* getBackmost(); + LLFloater* getParentFloater(LLView* viewp); + LLFloater* getFocusedFloater(); + void syncFloaterTabOrder(); + + // Get a floater based the handle. If this returns NULL, it is up + // to the caller to discard the handle. + LLFloater* getFloaterByHandle(LLViewHandle handle); + + // Returns z order of child provided. 0 is closest, larger numbers + // are deeper in the screen. If there is no such child, the return + // value is not defined. + S32 getZOrder(LLFloater* child); + + void setSnapOffsetBottom(S32 offset) { mSnapOffsetBottom = offset; } + +private: + S32 mColumn; + S32 mNextLeft; + S32 mNextTop; + BOOL mFocusCycleMode; + S32 mSnapOffsetBottom; +}; + +class LLMultiFloater : public LLFloater +{ +public: + LLMultiFloater(); + LLMultiFloater(LLTabContainerCommon::TabPosition tab_pos); + LLMultiFloater(const LLString& name); + LLMultiFloater(const LLString& name, const LLRect& rect, LLTabContainer::TabPosition tab_pos = LLTabContainer::TOP, BOOL auto_resize = FALSE); + LLMultiFloater(const LLString& name, const LLString& rect_control, LLTabContainer::TabPosition tab_pos = LLTabContainer::TOP, BOOL auto_resize = FALSE); + virtual ~LLMultiFloater(); + + virtual void init(const LLString& title, BOOL resizable, + S32 min_width, S32 min_height, BOOL drag_on_left, + BOOL minimizable, BOOL close_btn); + + virtual BOOL postBuild(); + /*virtual*/ void open(); + /*virtual*/ void onClose(bool app_quitting); + /*virtual*/ void draw(); + /*virtual*/ void setVisible(BOOL visible); + /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent); + + /*virtual*/ EWidgetType getWidgetType() const; + /*virtual*/ LLString getWidgetTag() const; + + virtual void setCanResize(BOOL can_resize); + virtual void growToFit(LLFloater* floaterp, S32 width, S32 height); + virtual void addFloater(LLFloater* floaterp, BOOL select_added_floater, LLTabContainerCommon::eInsertionPoint insertion_point = LLTabContainerCommon::END); + + virtual void showFloater(LLFloater* floaterp); + virtual void removeFloater(LLFloater* floaterp); + + virtual void tabOpen(LLFloater* opened_floater, bool from_click); + virtual void tabClose(); + + virtual BOOL selectFloater(LLFloater* floaterp); + virtual void selectNextFloater(); + virtual void selectPrevFloater(); + + virtual LLFloater* getActiveFloater(); + virtual BOOL isFloaterFlashing(LLFloater* floaterp); + virtual S32 getFloaterCount(); + + virtual void setFloaterFlashing(LLFloater* floaterp, BOOL flashing); + virtual BOOL closeAllFloaters(); //Returns FALSE if the floater could not be closed due to pending confirmation dialogs + void setTabContainer(LLTabContainerCommon* tab_container) { if (!mTabContainer) mTabContainer = tab_container; } + static void onTabSelected(void* userdata, bool); + +protected: + struct LLFloaterData + { + S32 mWidth; + S32 mHeight; + BOOL mCanMinimize; + BOOL mCanResize; + }; + + LLTabContainerCommon* mTabContainer; + + typedef std::map<LLViewHandle, LLFloaterData> floater_data_map_t; + floater_data_map_t mFloaterDataMap; + + LLTabContainerCommon::TabPosition mTabPos; + BOOL mAutoResize; +}; + + +extern LLFloaterView* gFloaterView; + +#endif // LL_FLOATER_H + diff --git a/indra/llui/llfocusmgr.cpp b/indra/llui/llfocusmgr.cpp new file mode 100644 index 0000000000..030fbf0653 --- /dev/null +++ b/indra/llui/llfocusmgr.cpp @@ -0,0 +1,369 @@ +/** + * @file llfocusmgr.cpp + * @brief LLFocusMgr base class + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llfocusmgr.h" +#include "lluictrl.h" +#include "v4color.h" + +const F32 FOCUS_FADE_TIME = 0.3f; + +LLFocusMgr gFocusMgr; + +LLFocusMgr::LLFocusMgr() + : + mLockedView( NULL ), + mKeyboardLockedFocusLostCallback( NULL ), + mMouseCaptor( NULL ), + mMouseCaptureLostCallback( NULL ), + mKeyboardFocus( NULL ), + mDefaultKeyboardFocus( NULL ), + mKeyboardFocusLostCallback( NULL ), + mTopView( NULL ), + mTopViewLostCallback( NULL ), + mFocusWeight(0.f), + mAppHasFocus(TRUE) // Macs don't seem to notify us that we've gotten focus, so default to true + #ifdef _DEBUG + , mMouseCaptorName("none") + , mKeyboardFocusName("none") + , mTopViewName("none") + #endif +{ +} + +LLFocusMgr::~LLFocusMgr() +{ + mFocusHistory.clear(); +} + +void LLFocusMgr::releaseFocusIfNeeded( LLView* view ) +{ + if( childHasMouseCapture( view ) ) + { + setMouseCapture( NULL, NULL ); + } + + if( childHasKeyboardFocus( view )) + { + if (view == mLockedView) + { + mLockedView = NULL; + mKeyboardLockedFocusLostCallback = NULL; + setKeyboardFocus( NULL, NULL ); + } + else + { + setKeyboardFocus( mLockedView, mKeyboardLockedFocusLostCallback ); + } + } + + if( childIsTopView( view ) ) + { + setTopView( NULL, NULL ); + } +} + + +void LLFocusMgr::setKeyboardFocus(LLUICtrl* new_focus, FocusLostCallback on_focus_lost, BOOL lock) +{ + if (mLockedView && + (new_focus == NULL || + (new_focus != mLockedView && !new_focus->hasAncestor(mLockedView)))) + { + // don't allow focus to go to anything that is not the locked focus + // or one of its descendants + return; + } + FocusLostCallback old_callback = mKeyboardFocusLostCallback; + mKeyboardFocusLostCallback = on_focus_lost; + + //llinfos << "Keyboard focus handled by " << (new_focus ? new_focus->getName() : "nothing") << llendl; + + if( new_focus != mKeyboardFocus ) + { + LLUICtrl* old_focus = mKeyboardFocus; + mKeyboardFocus = new_focus; + + // clear out any existing flash + if (new_focus) + { + mFocusWeight = 0.f; + } + mFocusTimer.reset(); + + if( old_callback ) + { + old_callback( old_focus ); + } + + #ifdef _DEBUG + mKeyboardFocusName = new_focus ? new_focus->getName() : "none"; + #endif + + // If we've got a default keyboard focus, and the caller is + // releasing keyboard focus, move to the default. + if (mDefaultKeyboardFocus != NULL && new_focus == NULL) + { + mDefaultKeyboardFocus->setFocus(TRUE); + } + + LLView* focus_subtree = new_focus; + LLView* viewp = new_focus; + // find root-most focus root + while(viewp) + { + if (viewp->isFocusRoot()) + { + focus_subtree = viewp; + } + viewp = viewp->getParent(); + } + + + if (focus_subtree) + { + mFocusHistory[focus_subtree->mViewHandle] = new_focus ? new_focus->mViewHandle : LLViewHandle::sDeadHandle; + } + } + + if (lock) + { + mLockedView = new_focus; + mKeyboardLockedFocusLostCallback = on_focus_lost; + } +} + +void LLFocusMgr::setDefaultKeyboardFocus(LLUICtrl* default_focus) +{ + mDefaultKeyboardFocus = default_focus; +} + +// Returns TRUE is parent or any descedent of parent has keyboard focus. +BOOL LLFocusMgr::childHasKeyboardFocus(const LLView* parent ) const +{ + LLView* focus_view = mKeyboardFocus; + while( focus_view ) + { + if( focus_view == parent ) + { + return TRUE; + } + focus_view = focus_view->getParent(); + } + return FALSE; +} + +// Returns TRUE is parent or any descedent of parent is the mouse captor. +BOOL LLFocusMgr::childHasMouseCapture( LLView* parent ) +{ + if( mMouseCaptor && mMouseCaptor->isView() ) + { + LLView* captor_view = (LLView*)mMouseCaptor; + while( captor_view ) + { + if( captor_view == parent ) + { + return TRUE; + } + captor_view = captor_view->getParent(); + } + } + return FALSE; +} + +void LLFocusMgr::removeKeyboardFocusWithoutCallback( LLView* focus ) +{ + // should be ok to unlock here, as you have to know the locked view + // in order to unlock it + if (focus == mLockedView) + { + mLockedView = NULL; + mKeyboardLockedFocusLostCallback = NULL; + } + + if( mKeyboardFocus == focus ) + { + mKeyboardFocus = NULL; + mKeyboardFocusLostCallback = NULL; + #ifdef _DEBUG + mKeyboardFocusName = "none"; + #endif + } +} + + +void LLFocusMgr::setMouseCapture( LLMouseHandler* new_captor, void (*on_capture_lost)(LLMouseHandler* old_captor) ) +{ + //if (mFocusLocked) + //{ + // return; + //} + + void (*old_callback)(LLMouseHandler*) = mMouseCaptureLostCallback; + mMouseCaptureLostCallback = on_capture_lost; + + if( new_captor != mMouseCaptor ) + { + LLMouseHandler* old_captor = mMouseCaptor; + mMouseCaptor = new_captor; + /* + if (new_captor) + { + if ( new_captor->getName() == "Stickto") + { + llinfos << "New mouse captor: " << new_captor->getName() << llendl; + } + else + { + llinfos << "New mouse captor: " << new_captor->getName() << llendl; + } + } + else + { + llinfos << "New mouse captor: NULL" << llendl; + } + */ + + if( old_callback ) + { + old_callback( old_captor ); + } + + #ifdef _DEBUG + mMouseCaptorName = new_captor ? new_captor->getName() : "none"; + #endif + } +} + +void LLFocusMgr::removeMouseCaptureWithoutCallback( LLMouseHandler* captor ) +{ + //if (mFocusLocked) + //{ + // return; + //} + if( mMouseCaptor == captor ) + { + mMouseCaptor = NULL; + mMouseCaptureLostCallback = NULL; + #ifdef _DEBUG + mMouseCaptorName = "none"; + #endif + } +} + + +BOOL LLFocusMgr::childIsTopView( LLView* parent ) +{ + LLView* top_view = mTopView; + while( top_view ) + { + if( top_view == parent ) + { + return TRUE; + } + top_view = top_view->getParent(); + } + return FALSE; +} + + + +// set new_top = NULL to release top_view. +void LLFocusMgr::setTopView( LLView* new_top, void (*on_top_lost)(LLView* old_top) ) +{ + void (*old_callback)(LLView*) = mTopViewLostCallback; + mTopViewLostCallback = on_top_lost; + + if( new_top != mTopView ) + { + LLView* old_top = mTopView; + mTopView = new_top; + if( old_callback ) + { + old_callback( old_top ); + } + + mTopView = new_top; + + #ifdef _DEBUG + mTopViewName = new_top ? new_top->getName() : "none"; + #endif + } +} + +void LLFocusMgr::removeTopViewWithoutCallback( LLView* top_view ) +{ + if( mTopView == top_view ) + { + mTopView = NULL; + mTopViewLostCallback = NULL; + #ifdef _DEBUG + mTopViewName = "none"; + #endif + } +} + +void LLFocusMgr::unlockFocus() +{ + mLockedView = NULL; + mKeyboardLockedFocusLostCallback = NULL; +} + +F32 LLFocusMgr::getFocusFlashAmt() +{ + return clamp_rescale(getFocusTime(), 0.f, FOCUS_FADE_TIME, mFocusWeight, 0.f); +} + +LLColor4 LLFocusMgr::getFocusColor() +{ + LLColor4 focus_color = lerp(LLUI::sColorsGroup->getColor( "FocusColor" ), LLColor4::white, getFocusFlashAmt()); + // de-emphasize keyboard focus when app has lost focus (to avoid typing into wrong window problem) + if (!mAppHasFocus) + { + focus_color.mV[VALPHA] *= 0.4f; + } + return focus_color; +} + +void LLFocusMgr::triggerFocusFlash() +{ + mFocusTimer.reset(); + mFocusWeight = 1.f; +} + +void LLFocusMgr::setAppHasFocus(BOOL focus) +{ + if (!mAppHasFocus && focus) + { + triggerFocusFlash(); + } + mAppHasFocus = focus; +} + +LLUICtrl* LLFocusMgr::getLastFocusForGroup(LLView* subtree_root) +{ + if (subtree_root) + { + focus_history_map_t::iterator found_it = mFocusHistory.find(subtree_root->mViewHandle); + if (found_it != mFocusHistory.end()) + { + // found last focus for this subtree + return static_cast<LLUICtrl*>(LLView::getViewByHandle(found_it->second)); + } + } + return NULL; +} + +void LLFocusMgr::clearLastFocusForGroup(LLView* subtree_root) +{ + if (subtree_root) + { + mFocusHistory.erase(subtree_root->mViewHandle); + } +} diff --git a/indra/llui/llfocusmgr.h b/indra/llui/llfocusmgr.h new file mode 100644 index 0000000000..cb555fca91 --- /dev/null +++ b/indra/llui/llfocusmgr.h @@ -0,0 +1,102 @@ +/** + * @file llfocusmgr.h + * @brief LLFocusMgr base class + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// Singleton that manages keyboard and mouse focus + +#ifndef LL_LLFOCUSMGR_H +#define LL_LLFOCUSMGR_H + +#include "llstring.h" +#include "llframetimer.h" +#include "llview.h" + +class LLUICtrl; +class LLMouseHandler; + +class LLFocusMgr +{ +public: + typedef void (*FocusLostCallback)(LLUICtrl*); + + LLFocusMgr(); + ~LLFocusMgr(); + + // Mouse Captor + void setMouseCapture(LLMouseHandler* new_captor,void (*on_capture_lost)(LLMouseHandler* old_captor)); // new_captor = NULL to release the mouse. + LLMouseHandler* getMouseCapture() { return mMouseCaptor; } + void removeMouseCaptureWithoutCallback( LLMouseHandler* captor ); + BOOL childHasMouseCapture( LLView* parent ); + + // Keyboard Focus + void setKeyboardFocus(LLUICtrl* new_focus, FocusLostCallback on_focus_lost, BOOL lock = FALSE); // new_focus = NULL to release the focus. + LLUICtrl* getKeyboardFocus() const { return mKeyboardFocus; } + BOOL childHasKeyboardFocus( const LLView* parent ) const; + void removeKeyboardFocusWithoutCallback( LLView* focus ); + FocusLostCallback getFocusCallback() { return mKeyboardFocusLostCallback; } + F32 getFocusTime() const { return mFocusTimer.getElapsedTimeF32(); } + F32 getFocusFlashAmt(); + LLColor4 getFocusColor(); + void triggerFocusFlash(); + BOOL getAppHasFocus() { return mAppHasFocus; } + void setAppHasFocus(BOOL focus); + LLUICtrl* getLastFocusForGroup(LLView* subtree_root); + void clearLastFocusForGroup(LLView* subtree_root); + + // If setKeyboardFocus(NULL) is called, and there is a non-NULL default + // keyboard focus view, focus goes there. JC + void setDefaultKeyboardFocus(LLUICtrl* default_focus); + LLUICtrl* getDefaultKeyboardFocus() const { return mDefaultKeyboardFocus; } + + + // Top View + void setTopView(LLView* new_top, void (*on_top_lost)(LLView* old_top)); + LLView* getTopView() const { return mTopView; } + void removeTopViewWithoutCallback( LLView* top_view ); + BOOL childIsTopView( LLView* parent ); + + // All Three + void releaseFocusIfNeeded( LLView* top_view ); + void unlockFocus(); + BOOL focusLocked() { return mLockedView != NULL; } + +protected: + LLUICtrl* mLockedView; + FocusLostCallback mKeyboardLockedFocusLostCallback; + + // Mouse Captor + LLMouseHandler* mMouseCaptor; // Mouse events are premptively routed to this object + void (*mMouseCaptureLostCallback)(LLMouseHandler*); // The object to which mouse events are routed is called before another object takes its place + + // Keyboard Focus + LLUICtrl* mKeyboardFocus; // Keyboard events are preemptively routed to this object + LLUICtrl* mDefaultKeyboardFocus; + FocusLostCallback mKeyboardFocusLostCallback; // The object to which keyboard events are routed is called before another object takes its place + + // Top View + LLView* mTopView; + void (*mTopViewLostCallback)(LLView*); + + LLFrameTimer mFocusTimer; + F32 mFocusWeight; + + BOOL mAppHasFocus; + + typedef std::map<LLViewHandle, LLViewHandle> focus_history_map_t; + focus_history_map_t mFocusHistory; + + #ifdef _DEBUG + LLString mMouseCaptorName; + LLString mKeyboardFocusName; + LLString mTopViewName; + #endif +}; + +extern LLFocusMgr gFocusMgr; + +#endif // LL_LLFOCUSMGR_H + diff --git a/indra/llui/lliconctrl.cpp b/indra/llui/lliconctrl.cpp new file mode 100644 index 0000000000..006334fa4e --- /dev/null +++ b/indra/llui/lliconctrl.cpp @@ -0,0 +1,139 @@ +/** + * @file lliconctrl.cpp + * @brief LLIconCtrl base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "lliconctrl.h" + +// Linden library includes + +// Project includes +#include "llcontrol.h" +#include "llui.h" +#include "lluictrlfactory.h" + +const F32 RESOLUTION_BUMP = 1.f; + +LLIconCtrl::LLIconCtrl(const LLString& name, const LLRect &rect, const LLUUID &image_id) +: LLUICtrl(name, + rect, + FALSE, // mouse opaque + NULL, NULL, + FOLLOWS_LEFT | FOLLOWS_TOP), + mColor( LLColor4::white ), + mImageName("") +{ + setImage( image_id ); + setTabStop(FALSE); +} + +LLIconCtrl::LLIconCtrl(const LLString& name, const LLRect &rect, const LLString &image_name) +: LLUICtrl(name, + rect, + FALSE, // mouse opaque + NULL, NULL, + FOLLOWS_LEFT | FOLLOWS_TOP), + mColor( LLColor4::white ), + mImageName(image_name) +{ + LLUUID image_id; + image_id.set(LLUI::sAssetsGroup->getString( image_name )); + setImage( image_id ); + setTabStop(FALSE); +} + + +LLIconCtrl::~LLIconCtrl() +{ + mImagep = NULL; +} + + +void LLIconCtrl::setImage(const LLUUID &image_id) +{ + mImageID = image_id; + mImagep = LLUI::sImageProvider->getUIImageByID(image_id); +} + + +void LLIconCtrl::draw() +{ + if( getVisible() ) + { + // Border + BOOL has_image = !mImageID.isNull(); + + if( has_image ) + { + if( mImagep.notNull() ) + { + gl_draw_scaled_image(0, 0, + mRect.getWidth(), mRect.getHeight(), + mImagep, + mColor ); + } + } + + LLUICtrl::draw(); + } +} + +// virtual +void LLIconCtrl::setValue(const LLSD& value ) +{ + setImage(value.asUUID()); +} + +// virtual +LLSD LLIconCtrl::getValue() const +{ + LLSD ret = getImage(); + return ret; +} + +// virtual +LLXMLNodePtr LLIconCtrl::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLUICtrl::getXML(); + + if (mImageName != "") + { + node->createChild("image_name", TRUE)->setStringValue(mImageName); + } + + node->createChild("color", TRUE)->setFloatValue(4, mColor.mV); + + return node; +} + +LLView* LLIconCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("icon"); + node->getAttributeString("name", name); + + LLRect rect; + createRect(node, rect, parent, LLRect()); + + LLUUID image_id; + if (node->hasAttribute("image_name")) + { + LLString image_name; + node->getAttributeString("image_name", image_name); + image_id.set(LLUI::sAssetsGroup->getString( image_name )); + } + + LLColor4 color(LLColor4::white); + LLUICtrlFactory::getAttributeColor(node,"color", color); + + LLIconCtrl* icon = new LLIconCtrl(name, rect, image_id); + icon->setColor(color); + + icon->initFromXML(node, parent); + + return icon; +} diff --git a/indra/llui/lliconctrl.h b/indra/llui/lliconctrl.h new file mode 100644 index 0000000000..ea762982a2 --- /dev/null +++ b/indra/llui/lliconctrl.h @@ -0,0 +1,55 @@ +/** + * @file lliconctrl.h + * @brief LLIconCtrl base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLICONCTRL_H +#define LL_LLICONCTRL_H + +#include "lluuid.h" +#include "v4color.h" +#include "lluictrl.h" +#include "stdenums.h" +#include "llimagegl.h" + +class LLTextBox; + +// +// Classes +// +class LLIconCtrl +: public LLUICtrl +{ +public: + LLIconCtrl(const LLString& name, const LLRect &rect, const LLUUID &image_id); + LLIconCtrl(const LLString& name, const LLRect &rect, const LLString &image_name); + virtual ~LLIconCtrl(); + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_ICON; } + virtual LLString getWidgetTag() const { return LL_ICON_CTRL_TAG; } + + // llview overrides + virtual void draw(); + + void setImage(const LLUUID &image_id); + const LLUUID &getImage() const { return mImageID; } + + // Takes a UUID, wraps get/setImage + virtual void setValue(const LLSD& value ); + virtual LLSD getValue() const; + + void setColor(const LLColor4& color) { mColor = color; } + + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + +protected: + LLColor4 mColor; + LLString mImageName; + LLUUID mImageID; + LLPointer<LLImageGL> mImagep; +}; + +#endif diff --git a/indra/llui/llkeywords.cpp b/indra/llui/llkeywords.cpp new file mode 100644 index 0000000000..e8628c9374 --- /dev/null +++ b/indra/llui/llkeywords.cpp @@ -0,0 +1,502 @@ +/** + * @file llkeywords.cpp + * @brief Keyword list for LSL + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include <iostream> +#include <fstream> + +#include "llkeywords.h" +#include "lltexteditor.h" +#include "llstl.h" +#include <boost/tokenizer.hpp> + +const U32 KEYWORD_FILE_CURRENT_VERSION = 2; + +inline BOOL LLKeywordToken::isHead(const llwchar* s) +{ + // strncmp is much faster than string compare + BOOL res = TRUE; + const llwchar* t = mToken.c_str(); + S32 len = mToken.size(); + for (S32 i=0; i<len; i++) + { + if (s[i] != t[i]) + { + res = FALSE; + break; + } + } + return res; +} + +LLKeywords::LLKeywords() : mLoaded(FALSE) +{ +} + +LLKeywords::~LLKeywords() +{ + std::for_each(mWordTokenMap.begin(), mWordTokenMap.end(), DeletePairedPointer()); + std::for_each(mLineTokenList.begin(), mLineTokenList.end(), DeletePointer()); + std::for_each(mDelimiterTokenList.begin(), mDelimiterTokenList.end(), DeletePointer()); +} + +BOOL LLKeywords::loadFromFile( const LLString& filename ) +{ + mLoaded = FALSE; + + //////////////////////////////////////////////////////////// + // File header + + const S32 BUFFER_SIZE = 1024; + char buffer[BUFFER_SIZE]; + + llifstream file; + file.open(filename.c_str()); + if( file.fail() ) + { + llinfos << "LLKeywords::loadFromFile() Unable to open file: " << filename << llendl; + return mLoaded; + } + + // Identifying string + file >> buffer; + if( strcmp( buffer, "llkeywords" ) ) + { + llinfos << filename << " does not appear to be a keyword file" << llendl; + return mLoaded; + } + + // Check file version + file >> buffer; + U32 version_num; + file >> version_num; + if( strcmp(buffer, "version") || version_num != (U32)KEYWORD_FILE_CURRENT_VERSION ) + { + llinfos << filename << " does not appear to be a version " << KEYWORD_FILE_CURRENT_VERSION << " keyword file" << llendl; + return mLoaded; + } + + // start of line (SOL) + const char SOL_COMMENT[] = "#"; + const char SOL_WORD[] = "[word "; + const char SOL_LINE[] = "[line "; + const char SOL_ONE_SIDED_DELIMITER[] = "[one_sided_delimiter "; + const char SOL_TWO_SIDED_DELIMITER[] = "[two_sided_delimiter "; + + LLColor3 cur_color( 1, 0, 0 ); + LLKeywordToken::TOKEN_TYPE cur_type = LLKeywordToken::WORD; + + while (!file.eof()) + { + file.getline( buffer, BUFFER_SIZE ); + if( !strncmp( buffer, SOL_COMMENT, strlen(SOL_COMMENT) ) ) + { + continue; + } + else + if( !strncmp( buffer, SOL_WORD, strlen(SOL_WORD) ) ) + { + cur_color = readColor( buffer + strlen(SOL_WORD) ); + cur_type = LLKeywordToken::WORD; + continue; + } + else + if( !strncmp( buffer, SOL_LINE, strlen(SOL_LINE) ) ) + { + cur_color = readColor( buffer + strlen(SOL_LINE) ); + cur_type = LLKeywordToken::LINE; + continue; + } + else + if( !strncmp( buffer, SOL_TWO_SIDED_DELIMITER, strlen(SOL_TWO_SIDED_DELIMITER) ) ) + { + cur_color = readColor( buffer + strlen(SOL_TWO_SIDED_DELIMITER) ); + cur_type = LLKeywordToken::TWO_SIDED_DELIMITER; + continue; + } + if( !strncmp( buffer, SOL_ONE_SIDED_DELIMITER, strlen(SOL_ONE_SIDED_DELIMITER) ) ) + { + cur_color = readColor( buffer + strlen(SOL_ONE_SIDED_DELIMITER) ); + cur_type = LLKeywordToken::ONE_SIDED_DELIMITER; + continue; + } + + LLString token_buffer( buffer ); + LLString::trim(token_buffer); + + typedef boost::tokenizer<boost::char_separator<char> > tokenizer; + boost::char_separator<char> sep_word("", " \t"); + tokenizer word_tokens(token_buffer, sep_word); + tokenizer::iterator token_word_iter = word_tokens.begin(); + + if( !token_buffer.empty() && token_word_iter != word_tokens.end() ) + { + // first word is keyword + LLString keyword = (*token_word_iter); + LLString::trim(keyword); + + // following words are tooltip + LLString tool_tip; + while (++token_word_iter != word_tokens.end()) + { + tool_tip += (*token_word_iter); + } + LLString::trim(tool_tip); + + if( !tool_tip.empty() ) + { + // Replace : with \n for multi-line tool tips. + LLString::replaceChar( tool_tip, ':', '\n' ); + addToken(cur_type, keyword, cur_color, tool_tip ); + } + else + { + addToken(cur_type, keyword, cur_color, NULL ); + } + } + } + + file.close(); + + mLoaded = TRUE; + return mLoaded; +} + +// Add the token as described +void LLKeywords::addToken(LLKeywordToken::TOKEN_TYPE type, + const LLString& key_in, + const LLColor3& color, + const LLString& tool_tip_in ) +{ + LLWString key = utf8str_to_wstring(key_in); + LLWString tool_tip = utf8str_to_wstring(tool_tip_in); + switch(type) + { + case LLKeywordToken::WORD: + mWordTokenMap[key] = new LLKeywordToken(type, color, key, tool_tip); + break; + + case LLKeywordToken::LINE: + mLineTokenList.push_front(new LLKeywordToken(type, color, key, tool_tip)); + break; + + case LLKeywordToken::TWO_SIDED_DELIMITER: + case LLKeywordToken::ONE_SIDED_DELIMITER: + mDelimiterTokenList.push_front(new LLKeywordToken(type, color, key, tool_tip)); + break; + + default: + llassert(0); + } +} + +LLColor3 LLKeywords::readColor( const LLString& s ) +{ + F32 r, g, b; + r = g = b = 0.0f; + S32 read = sscanf(s.c_str(), "%f, %f, %f]", &r, &g, &b ); + if( read != 3 ) + { + llinfos << " poorly formed color in keyword file" << llendl; + } + return LLColor3( r, g, b ); +} + +// Walk through a string, applying the rules specified by the keyword token list and +// create a list of color segments. +void LLKeywords::findSegments(std::vector<LLTextSegment *>* seg_list, const LLWString& wtext) +{ + std::for_each(seg_list->begin(), seg_list->end(), DeletePointer()); + seg_list->clear(); + + if( wtext.empty() ) + { + return; + } + + S32 text_len = wtext.size(); + + seg_list->push_back( new LLTextSegment( LLColor3(0,0,0), 0, text_len ) ); + + const llwchar* base = wtext.c_str(); + const llwchar* cur = base; + const llwchar* line = NULL; + + while( *cur ) + { + if( *cur == '\n' || cur == base ) + { + if( *cur == '\n' ) + { + cur++; + if( !*cur || *cur == '\n' ) + { + continue; + } + } + + // Start of a new line + line = cur; + + // Skip white space + while( *cur && isspace(*cur) && (*cur != '\n') ) + { + cur++; + } + if( !*cur || *cur == '\n' ) + { + continue; + } + + // cur is now at the first non-whitespace character of a new line + + // Line start tokens + { + BOOL line_done = FALSE; + for (token_list_t::iterator iter = mLineTokenList.begin(); + iter != mLineTokenList.end(); ++iter) + { + LLKeywordToken* cur_token = *iter; + if( cur_token->isHead( cur ) ) + { + S32 seg_start = cur - base; + while( *cur && *cur != '\n' ) + { + // skip the rest of the line + cur++; + } + S32 seg_end = cur - base; + + //llinfos << "Seg: [" << (char*)LLString( base, seg_start, seg_end-seg_start) << "]" << llendl; + LLTextSegment* text_segment = new LLTextSegment( cur_token->getColor(), seg_start, seg_end ); + text_segment->setToken( cur_token ); + insertSegment( seg_list, text_segment, text_len); + line_done = TRUE; // to break out of second loop. + break; + } + } + + if( line_done ) + { + continue; + } + } + } + + // Skip white space + while( *cur && isspace(*cur) && (*cur != '\n') ) + { + cur++; + } + + while( *cur && *cur != '\n' ) + { + // Check against delimiters + { + S32 seg_start = 0; + LLKeywordToken* cur_delimiter = NULL; + for (token_list_t::iterator iter = mDelimiterTokenList.begin(); + iter != mDelimiterTokenList.end(); ++iter) + { + LLKeywordToken* delimiter = *iter; + if( delimiter->isHead( cur ) ) + { + cur_delimiter = delimiter; + break; + } + } + + if( cur_delimiter ) + { + S32 between_delimiters = 0; + S32 seg_end = 0; + + seg_start = cur - base; + cur += cur_delimiter->getLength(); + + if( cur_delimiter->getType() == LLKeywordToken::TWO_SIDED_DELIMITER ) + { + while( *cur && !cur_delimiter->isHead(cur)) + { + // Check for an escape sequence. + if (*cur == '\\') + { + // Count the number of backslashes. + S32 num_backslashes = 0; + while (*cur == '\\') + { + num_backslashes++; + between_delimiters++; + cur++; + } + // Is the next character the end delimiter? + if (cur_delimiter->isHead(cur)) + { + // Is there was an odd number of backslashes, then this delimiter + // does not end the sequence. + if (num_backslashes % 2 == 1) + { + between_delimiters++; + cur++; + } + else + { + // This is an end delimiter. + break; + } + } + } + else + { + between_delimiters++; + cur++; + } + } + + if( *cur ) + { + cur += cur_delimiter->getLength(); + seg_end = seg_start + between_delimiters + 2 * cur_delimiter->getLength(); + } + else + { + // eof + seg_end = seg_start + between_delimiters + cur_delimiter->getLength(); + } + } + else + { + llassert( cur_delimiter->getType() == LLKeywordToken::ONE_SIDED_DELIMITER ); + // Left side is the delimiter. Right side is eol or eof. + while( *cur && ('\n' != *cur) ) + { + between_delimiters++; + cur++; + } + seg_end = seg_start + between_delimiters + cur_delimiter->getLength(); + } + + + //llinfos << "Seg: [" << (char*)LLString( base, seg_start, seg_end-seg_start ) << "]" << llendl; + LLTextSegment* text_segment = new LLTextSegment( cur_delimiter->getColor(), seg_start, seg_end ); + text_segment->setToken( cur_delimiter ); + insertSegment( seg_list, text_segment, text_len); + + // Note: we don't increment cur, since the end of one delimited seg may be immediately + // followed by the start of another one. + continue; + } + } + + // check against words + llwchar prev = cur > base ? *(cur-1) : 0; + if( !isalnum( prev ) && (prev != '_') ) + { + const llwchar* p = cur; + while( isalnum( *p ) || (*p == '_') ) + { + p++; + } + S32 seg_len = p - cur; + if( seg_len > 0 ) + { + LLWString word( cur, 0, seg_len ); + word_token_map_t::iterator map_iter = mWordTokenMap.find(word); + if( map_iter != mWordTokenMap.end() ) + { + LLKeywordToken* cur_token = map_iter->second; + S32 seg_start = cur - base; + S32 seg_end = seg_start + seg_len; + + // llinfos << "Seg: [" << word.c_str() << "]" << llendl; + + + LLTextSegment* text_segment = new LLTextSegment( cur_token->getColor(), seg_start, seg_end ); + text_segment->setToken( cur_token ); + insertSegment( seg_list, text_segment, text_len); + } + cur += seg_len; + continue; + } + } + + if( *cur && *cur != '\n' ) + { + cur++; + } + } + } +} + +void LLKeywords::insertSegment(std::vector<LLTextSegment*>* seg_list, LLTextSegment* new_segment, S32 text_len ) +{ + LLTextSegment* last = seg_list->back(); + S32 new_seg_end = new_segment->getEnd(); + + if( new_segment->getStart() == last->getStart() ) + { + *last = *new_segment; + delete new_segment; + } + else + { + last->setEnd( new_segment->getStart() ); + seg_list->push_back( new_segment ); + } + + if( new_seg_end < text_len ) + { + seg_list->push_back( new LLTextSegment( LLColor3(0,0,0), new_seg_end, text_len ) ); + } +} + +#ifdef _DEBUG +void LLKeywords::dump() +{ + llinfos << "LLKeywords" << llendl; + + + llinfos << "LLKeywords::sWordTokenMap" << llendl; + word_token_map_t::iterator word_token_iter = mWordTokenMap.begin(); + while( word_token_iter != mWordTokenMap.end() ) + { + LLKeywordToken* word_token = word_token_iter->second; + word_token->dump(); + ++word_token_iter; + } + + llinfos << "LLKeywords::sLineTokenList" << llendl; + for (token_list_t::iterator iter = mLineTokenList.begin(); + iter != mLineTokenList.end(); ++iter) + { + LLKeywordToken* line_token = *iter; + line_token->dump(); + } + + + llinfos << "LLKeywords::sDelimiterTokenList" << llendl; + for (token_list_t::iterator iter = mDelimiterTokenList.begin(); + iter != mDelimiterTokenList.end(); ++iter) + { + LLKeywordToken* delimiter_token = *iter; + delimiter_token->dump(); + } +} + +void LLKeywordToken::dump() +{ + llinfos << "[" << + mColor.mV[VX] << ", " << + mColor.mV[VY] << ", " << + mColor.mV[VZ] << "] [" << + mToken.c_str() << "]" << + llendl; +} + +#endif // DEBUG diff --git a/indra/llui/llkeywords.h b/indra/llui/llkeywords.h new file mode 100644 index 0000000000..fcf70b77b1 --- /dev/null +++ b/indra/llui/llkeywords.h @@ -0,0 +1,91 @@ +/** + * @file llkeywords.h + * @brief Keyword list for LSL + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLKEYWORDS_H +#define LL_LLKEYWORDS_H + + +#include "llstring.h" +#include "v3color.h" +#include <map> +#include <list> +#include <deque> + +class LLTextSegment; + + +class LLKeywordToken +{ +public: + enum TOKEN_TYPE { WORD, LINE, TWO_SIDED_DELIMITER, ONE_SIDED_DELIMITER }; + + LLKeywordToken( TOKEN_TYPE type, const LLColor3& color, const LLWString& token, const LLWString& tool_tip ) + : + mType( type ), + mToken( token ), + mColor( color ), + mToolTip( tool_tip ) + { + } + + S32 getLength() { return mToken.size(); } + BOOL isHead(const llwchar* s); + const LLColor3& getColor() { return mColor; } + TOKEN_TYPE getType() { return mType; } + const LLWString& getToolTip() { return mToolTip; } + +#ifdef _DEBUG + void dump(); +#endif + +private: + TOKEN_TYPE mType; +public: + LLWString mToken; + LLColor3 mColor; +private: + LLWString mToolTip; +}; + +class LLKeywords +{ +public: + LLKeywords(); + ~LLKeywords(); + + BOOL loadFromFile(const LLString& filename); + BOOL isLoaded() { return mLoaded; } + + void findSegments(std::vector<LLTextSegment *> *seg_list, const LLWString& text ); + +#ifdef _DEBUG + void dump(); +#endif + + // Add the token as described + void addToken(LLKeywordToken::TOKEN_TYPE type, + const LLString& key, + const LLColor3& color, + const LLString& tool_tip = LLString::null); + +private: + LLColor3 readColor(const LLString& s); + void insertSegment(std::vector<LLTextSegment *> *seg_list, LLTextSegment* new_segment, S32 text_len); + +private: + BOOL mLoaded; +public: + typedef std::map<LLWString, LLKeywordToken*> word_token_map_t; + word_token_map_t mWordTokenMap; +private: + typedef std::deque<LLKeywordToken*> token_list_t; + token_list_t mLineTokenList; + token_list_t mDelimiterTokenList; +}; + +#endif // LL_LLKEYWORDS_H diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp new file mode 100644 index 0000000000..41049fdf1f --- /dev/null +++ b/indra/llui/lllineeditor.cpp @@ -0,0 +1,2301 @@ +/** + * @file lllineeditor.cpp + * @brief LLLineEditor base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// Text editor widget to let users enter a single line. + +#include "linden_common.h" + +#include "lllineeditor.h" + +#include "audioengine.h" +#include "llmath.h" +#include "llfontgl.h" +#include "llgl.h" +#include "sound_ids.h" +#include "lltimer.h" + +//#include "llclipboard.h" +#include "llcontrol.h" +#include "llbutton.h" +#include "llfocusmgr.h" +#include "llkeyboard.h" +#include "llrect.h" +#include "llresmgr.h" +#include "llstring.h" +#include "llwindow.h" +#include "llui.h" +#include "lluictrlfactory.h" +#include "llclipboard.h" + +// +// Imported globals +// + +// +// Constants +// + +const S32 UI_LINEEDITOR_CURSOR_THICKNESS = 2; +const S32 UI_LINEEDITOR_H_PAD = 2; +const S32 UI_LINEEDITOR_V_PAD = 1; +const F32 CURSOR_FLASH_DELAY = 1.0f; // in seconds +const S32 SCROLL_INCREMENT_ADD = 0; // make space for typing +const S32 SCROLL_INCREMENT_DEL = 4; // make space for baskspacing +const F32 AUTO_SCROLL_TIME = 0.05f; +const F32 LABEL_HPAD = 5.f; + +// This is a friend class of and is only used by LLLineEditor +class LLLineEditorRollback +{ +public: + LLLineEditorRollback( LLLineEditor* ed ) + : + mCursorPos( ed->mCursorPos ), + mScrollHPos( ed->mScrollHPos ), + mIsSelecting( ed->mIsSelecting ), + mSelectionStart( ed->mSelectionStart ), + mSelectionEnd( ed->mSelectionEnd ) + { + mText = ed->getText(); + } + + void doRollback( LLLineEditor* ed ) + { + ed->mCursorPos = mCursorPos; + ed->mScrollHPos = mScrollHPos; + ed->mIsSelecting = mIsSelecting; + ed->mSelectionStart = mSelectionStart; + ed->mSelectionEnd = mSelectionEnd; + ed->mText = mText; + } + + LLString getText() { return mText; } + +private: + LLString mText; + S32 mCursorPos; + S32 mScrollHPos; + BOOL mIsSelecting; + S32 mSelectionStart; + S32 mSelectionEnd; +}; + + +// +// Member functions +// + +LLLineEditor::LLLineEditor(const LLString& name, const LLRect& rect, + const LLString& default_text, const LLFontGL* font, + S32 max_length_bytes, + void (*commit_callback)(LLUICtrl* caller, void* user_data ), + void (*keystroke_callback)(LLLineEditor* caller, void* user_data ), + void (*focus_lost_callback)(LLLineEditor* caller, void* user_data ), + void* userdata, + LLLinePrevalidateFunc prevalidate_func, + LLViewBorder::EBevel border_bevel, + LLViewBorder::EStyle border_style, + S32 border_thickness) + : + LLUICtrl( name, rect, TRUE, commit_callback, userdata, FOLLOWS_TOP | FOLLOWS_LEFT ), + mMaxLengthChars(max_length_bytes), + mMaxLengthBytes(max_length_bytes), + mCursorPos( 0 ), + mScrollHPos( 0 ), + mBorderLeft(0), + mBorderRight(0), + mCommitOnFocusLost( TRUE ), + mKeystrokeCallback( keystroke_callback ), + mFocusLostCallback( focus_lost_callback ), + mIsSelecting( FALSE ), + mSelectionStart( 0 ), + mSelectionEnd( 0 ), + mLastSelectionX(-1), + mLastSelectionY(-1), + mPrevalidateFunc( prevalidate_func ), + mCursorColor( LLUI::sColorsGroup->getColor( "TextCursorColor" ) ), + mFgColor( LLUI::sColorsGroup->getColor( "TextFgColor" ) ), + mReadOnlyFgColor( LLUI::sColorsGroup->getColor( "TextFgReadOnlyColor" ) ), + mTentativeFgColor( LLUI::sColorsGroup->getColor( "TextFgTentativeColor" ) ), + mWriteableBgColor( LLUI::sColorsGroup->getColor( "TextBgWriteableColor" ) ), + mReadOnlyBgColor( LLUI::sColorsGroup->getColor( "TextBgReadOnlyColor" ) ), + mFocusBgColor( LLUI::sColorsGroup->getColor( "TextBgFocusColor" ) ), + mBorderThickness( border_thickness ), + mIgnoreArrowKeys( FALSE ), + mIgnoreTab( TRUE ), + mDrawAsterixes( FALSE ), + mHandleEditKeysDirectly( FALSE ), + mSelectAllonFocusReceived( FALSE ), + mPassDelete(FALSE), + mReadOnly(FALSE) +{ + llassert( max_length_bytes > 0 ); + + if (font) + { + mGLFont = font; + } + else + { + mGLFont = LLFontGL::sSansSerifSmall; + } + + mMinHPixels = mBorderThickness + UI_LINEEDITOR_H_PAD + mBorderLeft; + mMaxHPixels = mRect.getWidth() - mMinHPixels - mBorderThickness - mBorderRight; + + mScrollTimer.reset(); + + setText(default_text); + + setCursor(mText.length()); + + // Scalable UI somehow made these rectangles off-by-one. + // I don't know why. JC + LLRect border_rect(0, mRect.getHeight()-1, mRect.getWidth()-1, 0); + mBorder = new LLViewBorder( "line ed border", border_rect, border_bevel, border_style, mBorderThickness ); + addChild( mBorder ); + mBorder->setFollows(FOLLOWS_LEFT|FOLLOWS_RIGHT|FOLLOWS_TOP|FOLLOWS_BOTTOM); +} + + +LLLineEditor::~LLLineEditor() +{ + mFocusLostCallback = NULL; + mCommitOnFocusLost = FALSE; + + gFocusMgr.releaseFocusIfNeeded( this ); + + if( gEditMenuHandler == this ) + { + gEditMenuHandler = NULL; + } +} + +//virtual +EWidgetType LLLineEditor::getWidgetType() const +{ + return WIDGET_TYPE_LINE_EDITOR; +} + +//virtual +LLString LLLineEditor::getWidgetTag() const +{ + return LL_LINE_EDITOR_TAG; +} + +void LLLineEditor::onFocusLost() +{ + if( mFocusLostCallback ) + { + mFocusLostCallback( this, mCallbackUserData ); + } + + if( mCommitOnFocusLost ) + { + onCommit(); + } + + if( gEditMenuHandler == this ) + { + gEditMenuHandler = NULL; + } + + getWindow()->showCursorFromMouseMove(); +} + +void LLLineEditor::onCommit() +{ + LLUICtrl::onCommit(); + selectAll(); +} + +void LLLineEditor::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + LLUICtrl::reshape(width, height, called_from_parent ); + + mMaxHPixels = mRect.getWidth() - 2 * (mBorderThickness + UI_LINEEDITOR_H_PAD) + 1 - mBorderRight; +} + + +void LLLineEditor::setEnabled(BOOL enabled) +{ + mReadOnly = !enabled; + setTabStop(!mReadOnly); +} + + +void LLLineEditor::setMaxTextLength(S32 max_text_length) +{ + S32 max_len = llmax(0, max_text_length); + mMaxLengthBytes = max_len; + mMaxLengthChars = max_len; +} + +void LLLineEditor::setBorderWidth(S32 left, S32 right) +{ + mBorderLeft = llclamp(left, 0, mRect.getWidth()); + mBorderRight = llclamp(right, 0, mRect.getWidth()); + mMinHPixels = mBorderThickness + UI_LINEEDITOR_H_PAD + mBorderLeft; + mMaxHPixels = mRect.getWidth() - mMinHPixels - mBorderThickness - mBorderRight; +} + +void LLLineEditor::setLabel(const LLString &new_label) +{ + mLabel = new_label; +} + +void LLLineEditor::setText(const LLString &new_text) +{ + // If new text is identical, don't copy and don't move insertion point + if (mText.getString() == new_text) + { + return; + } + + // Check to see if entire field is selected. + S32 len = mText.length(); + BOOL allSelected = (len > 0) && (( mSelectionStart == 0 && mSelectionEnd == len ) + || ( mSelectionStart == len && mSelectionEnd == 0 )); + + LLString truncated_utf8 = new_text; + if (truncated_utf8.size() > (U32)mMaxLengthBytes) + { + utf8str_truncate(truncated_utf8, mMaxLengthBytes); + } + mText.assign(truncated_utf8); + mText.truncate(mMaxLengthChars); + + if (allSelected) + { + // ...keep whole thing selected + selectAll(); + } + else + { + // try to preserve insertion point, but deselect text + deselect(); + } + setCursor(llmin((S32)mText.length(), getCursor())); +} + + +// Picks a new cursor position based on the actual screen size of text being drawn. +void LLLineEditor::setCursorAtLocalPos( S32 local_mouse_x ) +{ + const llwchar* wtext = mText.getWString().c_str(); + LLWString asterix_text; + if (mDrawAsterixes) + { + for (S32 i = 0; i < mText.length(); i++) + { + asterix_text += '*'; + } + wtext = asterix_text.c_str(); + } + + S32 cursor_pos = + mScrollHPos + + mGLFont->charFromPixelOffset( + wtext, mScrollHPos, + (F32)(local_mouse_x - mMinHPixels), + (F32)(mMaxHPixels - mMinHPixels + 1)); // min-max range is inclusive + setCursor(cursor_pos); +} + +void LLLineEditor::setCursor( S32 pos ) +{ + S32 old_cursor_pos = getCursor(); + mCursorPos = llclamp( pos, 0, mText.length()); + + S32 pixels_after_scroll = findPixelNearestPos(); + if( pixels_after_scroll > mMaxHPixels ) + { + S32 width_chars_to_left = mGLFont->getWidth(mText.getWString().c_str(), 0, mScrollHPos); + S32 last_visible_char = mGLFont->maxDrawableChars(mText.getWString().c_str(), llmax(0.f, (F32)(mMaxHPixels - mMinHPixels + width_chars_to_left))); + S32 min_scroll = mGLFont->firstDrawableChar(mText.getWString().c_str(), (F32)(mMaxHPixels - mMinHPixels), mText.length(), getCursor()); + if (old_cursor_pos == last_visible_char) + { + mScrollHPos = llmin(mText.length(), llmax(min_scroll, mScrollHPos + SCROLL_INCREMENT_ADD)); + } + else + { + mScrollHPos = min_scroll; + } + } + else if (getCursor() < mScrollHPos) + { + if (old_cursor_pos == mScrollHPos) + { + mScrollHPos = llmax(0, llmin(getCursor(), mScrollHPos - SCROLL_INCREMENT_DEL)); + } + else + { + mScrollHPos = getCursor(); + } + } +} + + +void LLLineEditor::setCursorToEnd() +{ + setCursor(mText.length()); + deselect(); +} + +BOOL LLLineEditor::canDeselect() +{ + return hasSelection(); +} + + +void LLLineEditor::deselect() +{ + mSelectionStart = 0; + mSelectionEnd = 0; + mIsSelecting = FALSE; +} + + +void LLLineEditor::startSelection() +{ + mIsSelecting = TRUE; + mSelectionStart = getCursor(); + mSelectionEnd = getCursor(); +} + +void LLLineEditor::endSelection() +{ + if( mIsSelecting ) + { + mIsSelecting = FALSE; + mSelectionEnd = getCursor(); + } +} + +BOOL LLLineEditor::canSelectAll() +{ + return TRUE; +} + +void LLLineEditor::selectAll() +{ + mSelectionStart = mText.length(); + mSelectionEnd = 0; + setCursor(mSelectionEnd); + //mScrollHPos = 0; + mIsSelecting = TRUE; +} + + +BOOL LLLineEditor::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + setFocus( TRUE ); + + if (mSelectionEnd == 0 && mSelectionStart == mText.length()) + { + // if everything is selected, handle this as a normal click to change insertion point + handleMouseDown(x, y, mask); + } + else + { + // otherwise select everything + selectAll(); + } + + // We don't want handleMouseUp() to "finish" the selection (and thereby + // set mSelectionEnd to where the mouse is), so we finish the selection + // here. + mIsSelecting = FALSE; + + // delay cursor flashing + mKeystrokeTimer.reset(); + + return TRUE; +} + +BOOL LLLineEditor::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if (x < mBorderLeft || x > (mRect.getWidth() - mBorderRight)) + { + return LLUICtrl::handleMouseDown(x, y, mask); + } + if (mSelectAllonFocusReceived + && gFocusMgr.getKeyboardFocus() != this) + { + setFocus( TRUE ); + } + else + { + setFocus( TRUE ); + + if (mask & MASK_SHIFT) + { + // Handle selection extension + S32 old_cursor_pos = getCursor(); + setCursorAtLocalPos(x); + + if (hasSelection()) + { + /* Mac-like behavior - extend selection towards the cursor + if (getCursor() < mSelectionStart + && getCursor() < mSelectionEnd) + { + // ...left of selection + mSelectionStart = llmax(mSelectionStart, mSelectionEnd); + mSelectionEnd = getCursor(); + } + else if (getCursor() > mSelectionStart + && getCursor() > mSelectionEnd) + { + // ...right of selection + mSelectionStart = llmin(mSelectionStart, mSelectionEnd); + mSelectionEnd = getCursor(); + } + else + { + mSelectionEnd = getCursor(); + } + */ + // Windows behavior + mSelectionEnd = getCursor(); + } + else + { + mSelectionStart = old_cursor_pos; + mSelectionEnd = getCursor(); + } + // assume we're starting a drag select + mIsSelecting = TRUE; + } + else + { + // Move cursor and deselect for regular click + setCursorAtLocalPos( x ); + deselect(); + startSelection(); + } + + gFocusMgr.setMouseCapture( this, &LLLineEditor::onMouseCaptureLost ); + } + + // delay cursor flashing + mKeystrokeTimer.reset(); + + return TRUE; +} + + +BOOL LLLineEditor::handleHover(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + if (gFocusMgr.getMouseCapture() != this && (x < mBorderLeft || x > (mRect.getWidth() - mBorderRight))) + { + return LLUICtrl::handleHover(x, y, mask); + } + + if( getVisible() ) + { + if( (gFocusMgr.getMouseCapture() == this) && mIsSelecting ) + { + if (x != mLastSelectionX || y != mLastSelectionY) + { + mLastSelectionX = x; + mLastSelectionY = y; + } + // Scroll if mouse cursor outside of bounds + if (mScrollTimer.hasExpired()) + { + S32 increment = llround(mScrollTimer.getElapsedTimeF32() / AUTO_SCROLL_TIME); + mScrollTimer.reset(); + mScrollTimer.setTimerExpirySec(AUTO_SCROLL_TIME); + if( (x < mMinHPixels) && (mScrollHPos > 0 ) ) + { + // Scroll to the left + mScrollHPos = llclamp(mScrollHPos - increment, 0, mText.length()); + } + else + if( (x > mMaxHPixels) && (mCursorPos < (S32)mText.length()) ) + { + // If scrolling one pixel would make a difference... + S32 pixels_after_scrolling_one_char = findPixelNearestPos(1); + if( pixels_after_scrolling_one_char >= mMaxHPixels ) + { + // ...scroll to the right + mScrollHPos = llclamp(mScrollHPos + increment, 0, mText.length()); + } + } + } + + setCursorAtLocalPos( x ); + mSelectionEnd = getCursor(); + + // delay cursor flashing + mKeystrokeTimer.reset(); + + getWindow()->setCursor(UI_CURSOR_IBEAM); + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" << llendl; + handled = TRUE; + } + + if( !handled ) + { + getWindow()->setCursor(UI_CURSOR_IBEAM); + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (inactive)" << llendl; + handled = TRUE; + } + } + + return handled; +} + + +BOOL LLLineEditor::handleMouseUp(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + if( gFocusMgr.getMouseCapture() == this ) + { + gFocusMgr.setMouseCapture( NULL, NULL ); + handled = TRUE; + } + + if (!handled && (x < mBorderLeft || x > (mRect.getWidth() - mBorderRight))) + { + return LLUICtrl::handleMouseUp(x, y, mask); + } + + if( mIsSelecting ) + { + setCursorAtLocalPos( x ); + mSelectionEnd = getCursor(); + + handled = TRUE; + } + + if( handled ) + { + // delay cursor flashing + mKeystrokeTimer.reset(); + } + + return handled; +} + + +// Remove a single character from the text +void LLLineEditor::removeChar() +{ + if( getCursor() > 0 ) + { + mText.erase(getCursor() - 1, 1); + + setCursor(getCursor() - 1); + } + else + { + reportBadKeystroke(); + } +} + + +void LLLineEditor::addChar(const llwchar uni_char) +{ + llwchar new_c = uni_char; + if (hasSelection()) + { + deleteSelection(); + } + else if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) + { + mText.erase(getCursor(), 1); + } + + S32 length_chars = mText.length(); + S32 cur_bytes = mText.getString().size();; + S32 new_bytes = wchar_utf8_length(new_c); + + BOOL allow_char = TRUE; + + // Inserting character + if (length_chars == mMaxLengthChars) + { + allow_char = FALSE; + } + if ((new_bytes + cur_bytes) > mMaxLengthBytes) + { + allow_char = FALSE; + } + + if (allow_char) + { + // Will we need to scroll? + LLWString w_buf; + w_buf.assign(1, new_c); + + mText.insert(getCursor(), w_buf); + setCursor(getCursor() + 1); + } + else + { + reportBadKeystroke(); + } + + getWindow()->hideCursorUntilMouseMove(); +} + +// Extends the selection box to the new cursor position +void LLLineEditor::extendSelection( S32 new_cursor_pos ) +{ + if( !mIsSelecting ) + { + startSelection(); + } + + setCursor(new_cursor_pos); + mSelectionEnd = getCursor(); +} + + +void LLLineEditor::setSelection(S32 start, S32 end) +{ + S32 len = mText.length(); + + mIsSelecting = TRUE; + + // JC, yes, this seems odd, but I think you have to presume a + // selection dragged from the end towards the start. + mSelectionStart = llclamp(end, 0, len); + mSelectionEnd = llclamp(start, 0, len); + setCursor(start); +} + +S32 LLLineEditor::prevWordPos(S32 cursorPos) const +{ + const LLWString& wtext = mText.getWString(); + while( (cursorPos > 0) && (wtext[cursorPos-1] == ' ') ) + { + cursorPos--; + } + while( (cursorPos > 0) && isPartOfWord( wtext[cursorPos-1] ) ) + { + cursorPos--; + } + return cursorPos; +} + +S32 LLLineEditor::nextWordPos(S32 cursorPos) const +{ + const LLWString& wtext = mText.getWString(); + while( (cursorPos < getLength()) && isPartOfWord( wtext[cursorPos+1] ) ) + { + cursorPos++; + } + while( (cursorPos < getLength()) && (wtext[cursorPos+1] == ' ') ) + { + cursorPos++; + } + return cursorPos; +} + + +BOOL LLLineEditor::handleSelectionKey(KEY key, MASK mask) +{ + BOOL handled = FALSE; + + if( mask & MASK_SHIFT ) + { + handled = TRUE; + + switch( key ) + { + case KEY_LEFT: + if (mIgnoreArrowKeys) + { + handled = FALSE; + break; + } + if( 0 < getCursor() ) + { + S32 cursorPos = getCursor() - 1; + if( mask & MASK_CONTROL ) + { + cursorPos = prevWordPos(cursorPos); + } + extendSelection( cursorPos ); + } + else + { + reportBadKeystroke(); + } + break; + + case KEY_RIGHT: + if (mIgnoreArrowKeys) + { + handled = FALSE; + break; + } + if( getCursor() < mText.length()) + { + S32 cursorPos = getCursor() + 1; + if( mask & MASK_CONTROL ) + { + cursorPos = nextWordPos(cursorPos); + } + extendSelection( cursorPos ); + } + else + { + reportBadKeystroke(); + } + break; + + case KEY_PAGE_UP: + case KEY_HOME: + if (mIgnoreArrowKeys) + { + handled = FALSE; + break; + } + extendSelection( 0 ); + break; + + case KEY_PAGE_DOWN: + case KEY_END: + { + if (mIgnoreArrowKeys) + { + handled = FALSE; + break; + } + S32 len = mText.length(); + if( len ) + { + extendSelection( len ); + } + break; + } + + default: + handled = FALSE; + break; + } + } + + if (!handled && mHandleEditKeysDirectly) + { + if( (MASK_CONTROL & mask) && ('A' == key) ) + { + if( canSelectAll() ) + { + selectAll(); + } + else + { + reportBadKeystroke(); + } + handled = TRUE; + } + } + + + return handled; +} + +void LLLineEditor::deleteSelection() +{ + if( !mReadOnly && hasSelection() ) + { + S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); + S32 selection_length = abs( mSelectionStart - mSelectionEnd ); + + mText.erase(left_pos, selection_length); + deselect(); + setCursor(left_pos); + } +} + +BOOL LLLineEditor::canCut() +{ + return !mReadOnly && !mDrawAsterixes && hasSelection(); +} + +// cut selection to clipboard +void LLLineEditor::cut() +{ + if( canCut() ) + { + // Prepare for possible rollback + LLLineEditorRollback rollback( this ); + + + S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); + S32 length = abs( mSelectionStart - mSelectionEnd ); + gClipboard.copyFromSubstring( mText.getWString(), left_pos, length ); + deleteSelection(); + + // Validate new string and rollback the if needed. + BOOL need_to_rollback = ( mPrevalidateFunc && !mPrevalidateFunc( mText.getWString() ) ); + if( need_to_rollback ) + { + rollback.doRollback( this ); + reportBadKeystroke(); + } + else + if( mKeystrokeCallback ) + { + mKeystrokeCallback( this, mCallbackUserData ); + } + } +} + +BOOL LLLineEditor::canCopy() +{ + return !mDrawAsterixes && hasSelection(); +} + + +// copy selection to clipboard +void LLLineEditor::copy() +{ + if( canCopy() ) + { + S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); + S32 length = abs( mSelectionStart - mSelectionEnd ); + gClipboard.copyFromSubstring( mText.getWString(), left_pos, length ); + } +} + +BOOL LLLineEditor::canPaste() +{ + return !mReadOnly && gClipboard.canPasteString(); +} + + +// paste from clipboard +void LLLineEditor::paste() +{ + if (canPaste()) + { + LLWString paste = gClipboard.getPasteWString(); + if (!paste.empty()) + { + // Prepare for possible rollback + LLLineEditorRollback rollback(this); + + // Delete any selected characters + if (hasSelection()) + { + deleteSelection(); + } + + // Clean up string (replace tabs and returns and remove characters that our fonts don't support.) + LLWString clean_string(paste); + LLWString::replaceTabsWithSpaces(clean_string, 1); + //clean_string = wstring_detabify(paste, 1); + LLWString::replaceChar(clean_string, '\n', ' '); + + // Insert the string + + //check to see that the size isn't going to be larger than the + //max number of characters or bytes + U32 available_bytes = mMaxLengthBytes - wstring_utf8_length(mText); + size_t available_chars = mMaxLengthChars - mText.length(); + + if ( available_bytes < (U32) wstring_utf8_length(clean_string) ) + { + llwchar current_symbol = clean_string[0]; + U32 wchars_that_fit = 0; + U32 total_bytes = wchar_utf8_length(current_symbol); + + //loop over the "wide" characters (symbols) + //and check to see how large (in bytes) each symbol is. + while ( total_bytes <= available_bytes ) + { + //while we still have available bytes + //"accept" the current symbol and check the size + //of the next one + current_symbol = clean_string[++wchars_that_fit]; + total_bytes += wchar_utf8_length(current_symbol); + } + + clean_string = clean_string.substr(0, wchars_that_fit); + reportBadKeystroke(); + } + else if (available_chars < clean_string.length()) + { + // We can't insert all the characters. Insert as many as possible + // but make a noise to alert the user. JC + clean_string = clean_string.substr(0, available_chars); + reportBadKeystroke(); + } + + mText.insert(getCursor(), clean_string); + setCursor(llmin(mMaxLengthChars, getCursor() + (S32)clean_string.length())); + deselect(); + + // Validate new string and rollback the if needed. + BOOL need_to_rollback = ( mPrevalidateFunc && !mPrevalidateFunc( mText.getWString() ) ); + if( need_to_rollback ) + { + rollback.doRollback( this ); + reportBadKeystroke(); + } + else + if( mKeystrokeCallback ) + { + mKeystrokeCallback( this, mCallbackUserData ); + } + } + } +} + + +BOOL LLLineEditor::handleSpecialKey(KEY key, MASK mask) +{ + BOOL handled = FALSE; + + switch( key ) + { + case KEY_INSERT: + if (mask == MASK_NONE) + { + gKeyboard->toggleInsertMode(); + } + + handled = TRUE; + break; + + case KEY_BACKSPACE: + if (!mReadOnly) + { + //llinfos << "Handling backspace" << llendl; + if( hasSelection() ) + { + deleteSelection(); + } + else + if( 0 < getCursor() ) + { + removeChar(); + } + else + { + reportBadKeystroke(); + } + } + handled = TRUE; + break; + + case KEY_PAGE_UP: + case KEY_HOME: + if (!mIgnoreArrowKeys) + { + setCursor(0); + handled = TRUE; + } + break; + + case KEY_PAGE_DOWN: + case KEY_END: + if (!mIgnoreArrowKeys) + { + S32 len = mText.length(); + if( len ) + { + setCursor(len); + } + handled = TRUE; + } + break; + + case KEY_LEFT: + if (!mIgnoreArrowKeys) + { + if( hasSelection() ) + { + setCursor(llmin( getCursor() - 1, mSelectionStart, mSelectionEnd )); + } + else + if( 0 < getCursor() ) + { + S32 cursorPos = getCursor() - 1; + if( mask & MASK_CONTROL ) + { + cursorPos = prevWordPos(cursorPos); + } + setCursor(cursorPos); + } + else + { + reportBadKeystroke(); + } + handled = TRUE; + } + break; + + case KEY_RIGHT: + if (!mIgnoreArrowKeys) + { + if (hasSelection()) + { + setCursor(llmax(getCursor() + 1, mSelectionStart, mSelectionEnd)); + } + else + if (getCursor() < mText.length()) + { + S32 cursorPos = getCursor() + 1; + if( mask & MASK_CONTROL ) + { + cursorPos = nextWordPos(cursorPos); + } + setCursor(cursorPos); + } + else + { + reportBadKeystroke(); + } + handled = TRUE; + } + break; + + default: + break; + } + + if( !handled && mHandleEditKeysDirectly ) + { + // Standard edit keys (Ctrl-X, Delete, etc,) are handled here instead of routed by the menu system. + if( KEY_DELETE == key ) + { + if( canDoDelete() ) + { + doDelete(); + } + else + { + reportBadKeystroke(); + } + handled = TRUE; + } + else + if( MASK_CONTROL & mask ) + { + if( 'C' == key ) + { + if( canCopy() ) + { + copy(); + } + else + { + reportBadKeystroke(); + } + handled = TRUE; + } + else + if( 'V' == key ) + { + if( canPaste() ) + { + paste(); + } + else + { + reportBadKeystroke(); + } + handled = TRUE; + } + else + if( 'X' == key ) + { + if( canCut() ) + { + cut(); + } + else + { + reportBadKeystroke(); + } + handled = TRUE; + } + } + } + return handled; +} + + +BOOL LLLineEditor::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent ) +{ + BOOL handled = FALSE; + BOOL selection_modified = FALSE; + + if ( (gFocusMgr.getKeyboardFocus() == this) && getVisible()) + { + LLLineEditorRollback rollback( this ); + + if( !handled ) + { + handled = handleSelectionKey( key, mask ); + selection_modified = handled; + } + + // Handle most keys only if the text editor is writeable. + if ( !mReadOnly ) + { + if( !handled ) + { + handled = handleSpecialKey( key, mask ); + } + } + + if( handled ) + { + mKeystrokeTimer.reset(); + + // Most keystrokes will make the selection box go away, but not all will. + if( !selection_modified && + KEY_SHIFT != key && + KEY_CONTROL != key && + KEY_ALT != key && + KEY_CAPSLOCK ) + { + deselect(); + } + + BOOL need_to_rollback = FALSE; + + // If read-only, don't allow changes + need_to_rollback |= (mReadOnly && (mText.getString() == rollback.getText())); + + // Validate new string and rollback the keystroke if needed. + need_to_rollback |= (mPrevalidateFunc && !mPrevalidateFunc(mText.getWString())); + + if (need_to_rollback) + { + rollback.doRollback(this); + + reportBadKeystroke(); + } + + // Notify owner if requested + if (!need_to_rollback && handled) + { + if (mKeystrokeCallback) + { + mKeystrokeCallback(this, mCallbackUserData); + } + } + } + } + + return handled; +} + + +BOOL LLLineEditor::handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent) +{ + if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL + { + return FALSE; + } + + BOOL handled = FALSE; + + if ( (gFocusMgr.getKeyboardFocus() == this) && getVisible() && !mReadOnly) + { + handled = TRUE; + + LLLineEditorRollback rollback( this ); + + addChar(uni_char); + + mKeystrokeTimer.reset(); + + deselect(); + + BOOL need_to_rollback = FALSE; + + // Validate new string and rollback the keystroke if needed. + need_to_rollback |= ( mPrevalidateFunc && !mPrevalidateFunc( mText.getWString() ) ); + + if( need_to_rollback ) + { + rollback.doRollback( this ); + + reportBadKeystroke(); + } + + // Notify owner if requested + if( !need_to_rollback && handled ) + { + if( mKeystrokeCallback ) + { + // HACK! The only usage of this callback doesn't do anything with the character. + // We'll have to do something about this if something ever changes! - Doug + mKeystrokeCallback( this, mCallbackUserData ); + } + } + } + return handled; +} + + +BOOL LLLineEditor::canDoDelete() +{ + return ( !mReadOnly && (!mPassDelete || (hasSelection() || (getCursor() < mText.length()))) ); +} + +void LLLineEditor::doDelete() +{ + if (canDoDelete()) + { + // Prepare for possible rollback + LLLineEditorRollback rollback( this ); + + if (hasSelection()) + { + deleteSelection(); + } + else if ( getCursor() < mText.length()) + { + setCursor(getCursor() + 1); + removeChar(); + } + + // Validate new string and rollback the if needed. + BOOL need_to_rollback = ( mPrevalidateFunc && !mPrevalidateFunc( mText.getWString() ) ); + if( need_to_rollback ) + { + rollback.doRollback( this ); + reportBadKeystroke(); + } + else + { + if( mKeystrokeCallback ) + { + mKeystrokeCallback( this, mCallbackUserData ); + } + } + } +} + + +void LLLineEditor::draw() +{ + if( !getVisible() ) + { + return; + } + + S32 text_len = mText.length(); + + LLString saved_text; + if (mDrawAsterixes) + { + saved_text = mText.getString(); + LLString text; + for (S32 i = 0; i < mText.length(); i++) + { + text += '*'; + } + mText = text; + } + + // draw rectangle for the background + LLRect background( 0, mRect.getHeight(), mRect.getWidth(), 0 ); + background.stretch( -mBorderThickness ); + + LLColor4 bg_color = mReadOnlyBgColor; + + // drawing solids requires texturing be disabled + { + LLGLSNoTexture no_texture; + // draw background for text + if( !mReadOnly ) + { + if( gFocusMgr.getKeyboardFocus() == this ) + { + bg_color = mFocusBgColor; + } + else + { + bg_color = mWriteableBgColor; + } + } + gl_rect_2d(background, bg_color); + } + + // draw text + + S32 cursor_bottom = background.mBottom + 1; + S32 cursor_top = background.mTop - 1; + + LLColor4 text_color; + if (!mReadOnly) + { + if (!mTentative) + { + text_color = mFgColor; + } + else + { + text_color = mTentativeFgColor; + } + } + else + { + text_color = mReadOnlyFgColor; + } + LLColor4 label_color = mTentativeFgColor; + + S32 rendered_text = 0; + F32 rendered_pixels_right = (F32)mMinHPixels; + F32 text_bottom = (F32)background.mBottom + (F32)UI_LINEEDITOR_V_PAD; + + if( (gFocusMgr.getKeyboardFocus() == this) && hasSelection() ) + { + S32 select_left; + S32 select_right; + if( mSelectionStart < getCursor() ) + { + select_left = mSelectionStart; + select_right = getCursor(); + } + else + { + select_left = getCursor(); + select_right = mSelectionStart; + } + + if( select_left > mScrollHPos ) + { + // unselected, left side + rendered_text = mGLFont->render( + mText, mScrollHPos, + rendered_pixels_right, text_bottom, + text_color, + LLFontGL::LEFT, LLFontGL::BOTTOM, + LLFontGL::NORMAL, + select_left - mScrollHPos, + mMaxHPixels - llround(rendered_pixels_right), + &rendered_pixels_right); + } + + if( (rendered_pixels_right < (F32)mMaxHPixels) && (rendered_text < text_len) ) + { + LLColor4 color(1.f - bg_color.mV[0], 1.f - bg_color.mV[1], 1.f - bg_color.mV[2], 1.f); + // selected middle + S32 width = mGLFont->getWidth(mText.getWString().c_str(), mScrollHPos + rendered_text, select_right - mScrollHPos - rendered_text); + width = llmin(width, mMaxHPixels - llround(rendered_pixels_right)); + gl_rect_2d(llround(rendered_pixels_right), cursor_top, llround(rendered_pixels_right)+width, cursor_bottom, color); + + rendered_text += mGLFont->render( + mText, mScrollHPos + rendered_text, + rendered_pixels_right, text_bottom, + LLColor4( 1.f - text_color.mV[0], 1.f - text_color.mV[1], 1.f - text_color.mV[2], 1 ), + LLFontGL::LEFT, LLFontGL::BOTTOM, + LLFontGL::NORMAL, + select_right - mScrollHPos - rendered_text, + mMaxHPixels - llround(rendered_pixels_right), + &rendered_pixels_right); + } + + if( (rendered_pixels_right < (F32)mMaxHPixels) && (rendered_text < text_len) ) + { + // unselected, right side + mGLFont->render( + mText, mScrollHPos + rendered_text, + rendered_pixels_right, text_bottom, + text_color, + LLFontGL::LEFT, LLFontGL::BOTTOM, + LLFontGL::NORMAL, + S32_MAX, + mMaxHPixels - llround(rendered_pixels_right), + &rendered_pixels_right); + } + } + else + { + mGLFont->render( + mText, mScrollHPos, + rendered_pixels_right, text_bottom, + text_color, + LLFontGL::LEFT, LLFontGL::BOTTOM, + LLFontGL::NORMAL, + S32_MAX, + mMaxHPixels - llround(rendered_pixels_right), + &rendered_pixels_right); + } + + // If we're editing... + if( gFocusMgr.getKeyboardFocus() == this) + { + // (Flash the cursor every half second) + if (gShowTextEditCursor && !mReadOnly) + { + F32 elapsed = mKeystrokeTimer.getElapsedTimeF32(); + if( (elapsed < CURSOR_FLASH_DELAY ) || (S32(elapsed * 2) & 1) ) + { + S32 cursor_left = findPixelNearestPos(); + cursor_left -= UI_LINEEDITOR_CURSOR_THICKNESS / 2; + S32 cursor_right = cursor_left + UI_LINEEDITOR_CURSOR_THICKNESS; + if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection()) + { + const LLWString space(utf8str_to_wstring(LLString(" "))); + S32 wswidth = mGLFont->getWidth(space.c_str()); + S32 width = mGLFont->getWidth(mText.getWString().c_str(), getCursor(), 1) + 1; + cursor_right = cursor_left + llmax(wswidth, width); + } + // Use same color as text for the Cursor + gl_rect_2d(cursor_left, cursor_top, + cursor_right, cursor_bottom, text_color); + if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection()) + { + mGLFont->render(mText, getCursor(), (F32)(cursor_left + UI_LINEEDITOR_CURSOR_THICKNESS / 2), text_bottom, + LLColor4( 1.f - text_color.mV[0], 1.f - text_color.mV[1], 1.f - text_color.mV[2], 1 ), + LLFontGL::LEFT, LLFontGL::BOTTOM, + LLFontGL::NORMAL, + 1); + } + } + } + + // Draw children (border) + //mBorder->setVisible(TRUE); + mBorder->setKeyboardFocusHighlight( TRUE ); + LLView::draw(); + mBorder->setKeyboardFocusHighlight( FALSE ); + //mBorder->setVisible(FALSE); + } + else // does not have keyboard input + { + // draw label if no text provided + if (0 == mText.length()) + { + mGLFont->render(mLabel.getWString(), 0, + LABEL_HPAD, (F32)text_bottom, + label_color, + LLFontGL::LEFT, LLFontGL::BOTTOM, + LLFontGL::NORMAL, + S32_MAX, + mMaxHPixels - llround(rendered_pixels_right), + &rendered_pixels_right, FALSE); + } + // Draw children (border) + LLView::draw(); + } + + if (mDrawAsterixes) + { + mText = saved_text; + } +} + + +// Returns the local screen space X coordinate associated with the text cursor position. +S32 LLLineEditor::findPixelNearestPos(const S32 cursor_offset) +{ + S32 dpos = getCursor() - mScrollHPos + cursor_offset; + S32 result = mGLFont->getWidth(mText.getWString().c_str(), mScrollHPos, dpos) + mMinHPixels; + return result; +} + +void LLLineEditor::reportBadKeystroke() +{ + make_ui_sound("UISndBadKeystroke"); +} + +//virtual +void LLLineEditor::clear() +{ + mText.clear(); + setCursor(0); +} + +//virtual +void LLLineEditor::onTabInto() +{ + selectAll(); +} + +//virtual +BOOL LLLineEditor::acceptsTextInput() const +{ + return TRUE; +} + +// Start or stop the editor from accepting text-editing keystrokes +void LLLineEditor::setFocus( BOOL new_state ) +{ + BOOL old_state = hasFocus(); + + // getting focus when we didn't have it before, and we want to select all + if (!old_state && new_state && mSelectAllonFocusReceived) + { + selectAll(); + // We don't want handleMouseUp() to "finish" the selection (and thereby + // set mSelectionEnd to where the mouse is), so we finish the selection + // here. + mIsSelecting = FALSE; + } + + if( new_state ) + { + gEditMenuHandler = this; + + // Don't start the cursor flashing right away + mKeystrokeTimer.reset(); + } + else + { + // Not really needed, since loss of keyboard focus should take care of this, + // but limited paranoia is ok. + if( gEditMenuHandler == this ) + { + gEditMenuHandler = NULL; + } + + endSelection(); + } + + LLUICtrl::setFocus( new_state ); +} + +//virtual +void LLLineEditor::setRect(const LLRect& rect) +{ + LLUICtrl::setRect(rect); + if (mBorder) + { + LLRect border_rect = mBorder->getRect(); + // Scalable UI somehow made these rectangles off-by-one. + // I don't know why. JC + border_rect.setOriginAndSize(border_rect.mLeft, border_rect.mBottom, + rect.getWidth()-1, rect.getHeight()-1); + mBorder->setRect(border_rect); + } +} + +// Limits what characters can be used to [1234567890.-] with [-] only valid in the first position. +// Does NOT ensure that the string is a well-formed number--that's the job of post-validation--for +// the simple reasons that intermediate states may be invalid even if the final result is valid. +// +// static +BOOL LLLineEditor::prevalidateFloat(const LLWString &str) +{ + LLLocale locale(LLLocale::USER_LOCALE); + + BOOL success = TRUE; + LLWString trimmed = str; + LLWString::trim(trimmed); + S32 len = trimmed.length(); + if( 0 < len ) + { + // May be a comma or period, depending on the locale + char decimal_point = gResMgr->getDecimalPoint(); + + S32 i = 0; + + // First character can be a negative sign + if( '-' == trimmed[0] ) + { + i++; + } + + for( ; i < len; i++ ) + { + if( (decimal_point != trimmed[i] ) && !isdigit( trimmed[i] ) ) + { + success = FALSE; + break; + } + } + } + + return success; +} + +//static +BOOL LLLineEditor::isPartOfWord(llwchar c) { return (c == '_') || isalnum(c); } + +// static +BOOL LLLineEditor::postvalidateFloat(const LLString &str) +{ + LLLocale locale(LLLocale::USER_LOCALE); + + BOOL success = TRUE; + BOOL has_decimal = FALSE; + BOOL has_digit = FALSE; + + LLWString trimmed = utf8str_to_wstring(str); + LLWString::trim(trimmed); + S32 len = trimmed.length(); + if( 0 < len ) + { + S32 i = 0; + + // First character can be a negative sign + if( '-' == trimmed[0] ) + { + i++; + } + + // May be a comma or period, depending on the locale + char decimal_point = gResMgr->getDecimalPoint(); + + for( ; i < len; i++ ) + { + if( decimal_point == trimmed[i] ) + { + if( has_decimal ) + { + // can't have two + success = FALSE; + break; + } + else + { + has_decimal = TRUE; + } + } + else + if( isdigit( trimmed[i] ) ) + { + has_digit = TRUE; + } + else + { + success = FALSE; + break; + } + } + } + + // Gotta have at least one + success = has_digit; + + return success; +} + +// Limits what characters can be used to [1234567890-] with [-] only valid in the first position. +// Does NOT ensure that the string is a well-formed number--that's the job of post-validation--for +// the simple reasons that intermediate states may be invalid even if the final result is valid. +// +// static +BOOL LLLineEditor::prevalidateInt(const LLWString &str) +{ + LLLocale locale(LLLocale::USER_LOCALE); + + BOOL success = TRUE; + LLWString trimmed = str; + LLWString::trim(trimmed); + S32 len = trimmed.length(); + if( 0 < len ) + { + S32 i = 0; + + // First character can be a negative sign + if( '-' == trimmed[0] ) + { + i++; + } + + for( ; i < len; i++ ) + { + if( !isdigit( trimmed[i] ) ) + { + success = FALSE; + break; + } + } + } + + return success; +} + +// static +BOOL LLLineEditor::prevalidatePositiveS32(const LLWString &str) +{ + LLLocale locale(LLLocale::USER_LOCALE); + + LLWString trimmed = str; + LLWString::trim(trimmed); + S32 len = trimmed.length(); + BOOL success = TRUE; + if(0 < len) + { + if(('-' == trimmed[0]) || ('0' == trimmed[0])) + { + success = FALSE; + } + S32 i = 0; + while(success && (i < len)) + { + if(!isdigit(trimmed[i++])) + { + success = FALSE; + } + } + } + if (success) + { + S32 val = strtol(wstring_to_utf8str(trimmed).c_str(), NULL, 10); + if (val <= 0) + { + success = FALSE; + } + } + return success; +} + +BOOL LLLineEditor::prevalidateNonNegativeS32(const LLWString &str) +{ + LLLocale locale(LLLocale::USER_LOCALE); + + LLWString trimmed = str; + LLWString::trim(trimmed); + S32 len = trimmed.length(); + BOOL success = TRUE; + if(0 < len) + { + if('-' == trimmed[0]) + { + success = FALSE; + } + S32 i = 0; + while(success && (i < len)) + { + if(!isdigit(trimmed[i++])) + { + success = FALSE; + } + } + } + if (success) + { + S32 val = strtol(wstring_to_utf8str(trimmed).c_str(), NULL, 10); + if (val < 0) + { + success = FALSE; + } + } + return success; +} + +BOOL LLLineEditor::prevalidateAlphaNum(const LLWString &str) +{ + LLLocale locale(LLLocale::USER_LOCALE); + + BOOL rv = TRUE; + S32 len = str.length(); + if(len == 0) return rv; + while(len--) + { + if( !isalnum(str[len]) ) + { + rv = FALSE; + break; + } + } + return rv; +} + +// static +BOOL LLLineEditor::prevalidateAlphaNumSpace(const LLWString &str) +{ + LLLocale locale(LLLocale::USER_LOCALE); + + BOOL rv = TRUE; + S32 len = str.length(); + if(len == 0) return rv; + while(len--) + { + if(!(isalnum(str[len]) || (' ' == str[len]))) + { + rv = FALSE; + break; + } + } + return rv; +} + +// static +BOOL LLLineEditor::prevalidatePrintableNotPipe(const LLWString &str) +{ + BOOL rv = TRUE; + S32 len = str.length(); + if(len == 0) return rv; + while(len--) + { + if('|' == str[len]) + { + rv = FALSE; + break; + } + if(!((' ' == str[len]) || isalnum(str[len]) || ispunct(str[len]))) + { + rv = FALSE; + break; + } + } + return rv; +} + + +// static +BOOL LLLineEditor::prevalidatePrintableNoSpace(const LLWString &str) +{ + BOOL rv = TRUE; + S32 len = str.length(); + if(len == 0) return rv; + while(len--) + { + if(iswspace(str[len])) + { + rv = FALSE; + break; + } + if( !(isalnum(str[len]) || ispunct(str[len]) ) ) + { + rv = FALSE; + break; + } + } + return rv; +} + +// static +BOOL LLLineEditor::prevalidateASCII(const LLWString &str) +{ + BOOL rv = TRUE; + S32 len = str.length(); + while(len--) + { + if (str[len] < 0x20 || str[len] > 0x7f) + { + rv = FALSE; + break; + } + } + return rv; +} + +//static +void LLLineEditor::onMouseCaptureLost( LLMouseHandler* old_captor ) +{ + LLLineEditor* self = (LLLineEditor*) old_captor; + self->endSelection(); +} + + +void LLLineEditor::setSelectAllonFocusReceived(BOOL b) +{ + mSelectAllonFocusReceived = b; +} + + +void LLLineEditor::setKeystrokeCallback(void (*keystroke_callback)(LLLineEditor* caller, void* user_data)) +{ + mKeystrokeCallback = keystroke_callback; +} + +void LLLineEditor::setFocusLostCallback(void (*keystroke_callback)(LLLineEditor* caller, void* user_data)) +{ + mFocusLostCallback = keystroke_callback; +} + +// virtual +LLXMLNodePtr LLLineEditor::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLUICtrl::getXML(); + + node->createChild("max_length", TRUE)->setIntValue(mMaxLengthBytes); + + node->createChild("font", TRUE)->setStringValue(LLFontGL::nameFromFont(mGLFont)); + + if (mBorder) + { + LLString bevel; + switch(mBorder->getBevel()) + { + default: + case LLViewBorder::BEVEL_NONE: bevel = "none"; break; + case LLViewBorder::BEVEL_IN: bevel = "in"; break; + case LLViewBorder::BEVEL_OUT: bevel = "out"; break; + case LLViewBorder::BEVEL_BRIGHT:bevel = "bright"; break; + } + node->createChild("bevel_style", TRUE)->setStringValue(bevel); + + LLString style; + switch(mBorder->getStyle()) + { + default: + case LLViewBorder::STYLE_LINE: style = "line"; break; + case LLViewBorder::STYLE_TEXTURE: style = "texture"; break; + } + node->createChild("border_style", TRUE)->setStringValue(style); + + node->createChild("border_thickness", TRUE)->setIntValue(mBorder->getBorderWidth()); + } + + if (!mLabel.empty()) + { + node->createChild("label", TRUE)->setStringValue(mLabel.getString()); + } + + node->createChild("select_all_on_focus_received", TRUE)->setBoolValue(mSelectAllonFocusReceived); + + node->createChild("handle_edit_keys_directly", TRUE)->setBoolValue(mHandleEditKeysDirectly ); + + addColorXML(node, mCursorColor, "cursor_color", "TextCursorColor"); + addColorXML(node, mFgColor, "text_color", "TextFgColor"); + addColorXML(node, mReadOnlyFgColor, "text_readonly_color", "TextFgReadOnlyColor"); + addColorXML(node, mTentativeFgColor, "text_tentative_color", "TextFgTentativeColor"); + addColorXML(node, mReadOnlyBgColor, "bg_readonly_color", "TextBgReadOnlyColor"); + addColorXML(node, mWriteableBgColor, "bg_writeable_color", "TextBgWriteableColor"); + addColorXML(node, mFocusBgColor, "bg_focus_color", "TextBgFocusColor"); + + node->createChild("select_on_focus", TRUE)->setBoolValue(mSelectAllonFocusReceived ); + + return node; +} + +// static +LLView* LLLineEditor::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("line_editor"); + node->getAttributeString("name", name); + + LLRect rect; + createRect(node, rect, parent, LLRect()); + + S32 max_text_length = 128; + node->getAttributeS32("max_length", max_text_length); + + LLFontGL* font = LLView::selectFont(node); + + LLString text = node->getTextContents().substr(0, max_text_length - 1); + + LLViewBorder::EBevel bevel_style = LLViewBorder::BEVEL_IN; + LLViewBorder::getBevelFromAttribute(node, bevel_style); + + LLViewBorder::EStyle border_style = LLViewBorder::STYLE_LINE; + LLString border_string; + node->getAttributeString("border_style", border_string); + LLString::toLower(border_string); + + if (border_string == "texture") + { + border_style = LLViewBorder::STYLE_TEXTURE; + } + + S32 border_thickness = 1; + node->getAttributeS32("border_thickness", border_thickness); + + LLUICtrlCallback commit_callback = NULL; + + LLLineEditor* line_editor = new LLLineEditor(name, + rect, + text, + font, + max_text_length, + commit_callback, + NULL, + NULL, + NULL, + NULL, + bevel_style, + border_style, + border_thickness); + + LLString label; + if(node->getAttributeString("label", label)) + { + line_editor->setLabel(label); + } + BOOL select_all_on_focus_received = FALSE; + if (node->getAttributeBOOL("select_all_on_focus_received", select_all_on_focus_received)) + { + line_editor->setSelectAllonFocusReceived(select_all_on_focus_received); + } + BOOL handle_edit_keys_directly = FALSE; + if (node->getAttributeBOOL("handle_edit_keys_directly", handle_edit_keys_directly)) + { + line_editor->setHandleEditKeysDirectly(handle_edit_keys_directly); + } + + line_editor->setColorParameters(node); + + if(node->hasAttribute("select_on_focus")) + { + BOOL selectall = FALSE; + node->getAttributeBOOL("select_on_focus", selectall); + line_editor->setSelectAllonFocusReceived(selectall); + } + + LLString prevalidate; + if(node->getAttributeString("prevalidate", prevalidate)) + { + LLString::toLower(prevalidate); + + if ("ascii" == prevalidate) + { + line_editor->setPrevalidate( LLLineEditor::prevalidateASCII ); + } + else if ("float" == prevalidate) + { + line_editor->setPrevalidate( LLLineEditor::prevalidateFloat ); + } + else if ("int" == prevalidate) + { + line_editor->setPrevalidate( LLLineEditor::prevalidateInt ); + } + else if ("positive_s32" == prevalidate) + { + line_editor->setPrevalidate( LLLineEditor::prevalidatePositiveS32 ); + } + else if ("non_negative_s32" == prevalidate) + { + line_editor->setPrevalidate( LLLineEditor::prevalidateNonNegativeS32 ); + } + else if ("alpha_num" == prevalidate) + { + line_editor->setPrevalidate( LLLineEditor::prevalidateAlphaNum ); + } + else if ("alpha_num_space" == prevalidate) + { + line_editor->setPrevalidate( LLLineEditor::prevalidateAlphaNumSpace ); + } + else if ("printable_not_pipe" == prevalidate) + { + line_editor->setPrevalidate( LLLineEditor::prevalidatePrintableNotPipe ); + } + else if ("printable_no_space" == prevalidate) + { + line_editor->setPrevalidate( LLLineEditor::prevalidatePrintableNoSpace ); + } + } + + line_editor->initFromXML(node, parent); + + return line_editor; +} + +void LLLineEditor::setColorParameters(LLXMLNodePtr node) +{ + LLColor4 color; + if (LLUICtrlFactory::getAttributeColor(node,"cursor_color", color)) + { + setCursorColor(color); + } + if(node->hasAttribute("text_color")) + { + LLUICtrlFactory::getAttributeColor(node,"text_color", color); + setFgColor(color); + } + if(node->hasAttribute("text_readonly_color")) + { + LLUICtrlFactory::getAttributeColor(node,"text_readonly_color", color); + setReadOnlyFgColor(color); + } + if (LLUICtrlFactory::getAttributeColor(node,"text_tentative_color", color)) + { + setTentativeFgColor(color); + } + if(node->hasAttribute("bg_readonly_color")) + { + LLUICtrlFactory::getAttributeColor(node,"bg_readonly_color", color); + setReadOnlyBgColor(color); + } + if(node->hasAttribute("bg_writeable_color")) + { + LLUICtrlFactory::getAttributeColor(node,"bg_writeable_color", color); + setWriteableBgColor(color); + } +} + +void LLLineEditor::setValue(const LLSD& value ) +{ + setText(value.asString()); +} + +LLSD LLLineEditor::getValue() const +{ + LLString str = getText(); + LLSD ret(str); + return ret; +} + +BOOL LLLineEditor::setTextArg( const LLString& key, const LLString& text ) +{ + mText.setArg(key, text); + return TRUE; +} + +BOOL LLLineEditor::setLabelArg( const LLString& key, const LLString& text ) +{ + mLabel.setArg(key, text); + return TRUE; +} + +LLSearchEditor::LLSearchEditor(const LLString& name, + const LLRect& rect, + S32 max_length_bytes, + void (*search_callback)(const LLString& search_string, void* user_data), + void* userdata) + : + LLUICtrl(name, rect, TRUE, NULL, userdata), + mSearchCallback(search_callback) +{ + LLRect search_edit_rect(0, mRect.getHeight(), mRect.getWidth(), 0); + mSearchEdit = new LLLineEditor("search edit", + search_edit_rect, + LLString::null, + NULL, + max_length_bytes, + NULL, + onSearchEdit, + NULL, + this); + // TODO: this should be translatable + mSearchEdit->setLabel("Type here to search"); + mSearchEdit->setFollowsAll(); + mSearchEdit->setSelectAllonFocusReceived(TRUE); + + addChild(mSearchEdit); + + S32 btn_width = rect.getHeight(); // button is square, and as tall as search editor + LLRect clear_btn_rect(rect.getWidth() - btn_width, rect.getHeight(), rect.getWidth(), 0); + mClearSearchButton = new LLButton("clear search", + clear_btn_rect, + "closebox.tga", + "UIImgBtnCloseInactiveUUID", + LLString::null, + onClearSearch, + this, + NULL, + LLString::null); + mClearSearchButton->setFollowsRight(); + mClearSearchButton->setFollowsTop(); + mClearSearchButton->setImageColor(LLUI::sColorsGroup->getColor("TextFgTentativeColor")); + mClearSearchButton->setTabStop(FALSE); + mSearchEdit->addChild(mClearSearchButton); + + mSearchEdit->setBorderWidth(0, btn_width); +} + +LLSearchEditor::~LLSearchEditor() +{ +} + +//virtual +EWidgetType LLSearchEditor::getWidgetType() const +{ + return WIDGET_TYPE_SEARCH_EDITOR; +} + +//virtual +LLString LLSearchEditor::getWidgetTag() const +{ + return LL_SEARCH_EDITOR_TAG; +} + +//virtual +void LLSearchEditor::setValue(const LLSD& value ) +{ + mSearchEdit->setValue(value); +} + +//virtual +LLSD LLSearchEditor::getValue() const +{ + return mSearchEdit->getValue(); +} + +//virtual +BOOL LLSearchEditor::setTextArg( const LLString& key, const LLString& text ) +{ + return mSearchEdit->setTextArg(key, text); +} + +//virtual +BOOL LLSearchEditor::setLabelArg( const LLString& key, const LLString& text ) +{ + return mSearchEdit->setLabelArg(key, text); +} + +//virtual +void LLSearchEditor::clear() +{ + if (mSearchEdit) + { + mSearchEdit->clear(); + } +} + + +void LLSearchEditor::draw() +{ + mClearSearchButton->setVisible(!mSearchEdit->getWText().empty()); + + LLUICtrl::draw(); +} + +void LLSearchEditor::setText(const LLString &new_text) +{ + mSearchEdit->setText(new_text); +} + +//static +void LLSearchEditor::onSearchEdit(LLLineEditor* caller, void* user_data ) +{ + LLSearchEditor* search_editor = (LLSearchEditor*)user_data; + if (search_editor->mSearchCallback) + { + search_editor->mSearchCallback(caller->getText(), search_editor->mCallbackUserData); + } +} + +//static +void LLSearchEditor::onClearSearch(void* user_data) +{ + LLSearchEditor* search_editor = (LLSearchEditor*)user_data; + + search_editor->setText(LLString::null); + if (search_editor->mSearchCallback) + { + search_editor->mSearchCallback(LLString::null, search_editor->mCallbackUserData); + } +} + +// static +LLView* LLSearchEditor::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("search_editor"); + node->getAttributeString("name", name); + + LLRect rect; + createRect(node, rect, parent, LLRect()); + + S32 max_text_length = 128; + node->getAttributeS32("max_length", max_text_length); + + LLString text = node->getValue().substr(0, max_text_length - 1); + + LLSearchEditor* search_editor = new LLSearchEditor(name, + rect, + max_text_length, + NULL, NULL); + + search_editor->setText(text); + + search_editor->initFromXML(node, parent); + + return search_editor; +} diff --git a/indra/llui/lllineeditor.h b/indra/llui/lllineeditor.h new file mode 100644 index 0000000000..1df5dd88f7 --- /dev/null +++ b/indra/llui/lllineeditor.h @@ -0,0 +1,298 @@ +/** + * @file lllineeditor.h + * @brief LLLineEditor base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// Text editor widget to let users enter/edit a single line. +// +// +// Features: +// Text entry of a single line (text, delete, left and right arrow, insert, return). +// Callbacks either on every keystroke or just on the return key. +// Focus (allow multiple text entry widgets) +// Clipboard (cut, copy, and paste) +// Horizontal scrolling to allow strings longer than widget size allows +// Pre-validation (limit which keys can be used) + + +#ifndef LL_LLLINEEDITOR_H +#define LL_LLLINEEDITOR_H + +#include "v4color.h" +#include "llframetimer.h" + +#include "lleditmenuhandler.h" +#include "lluictrl.h" +#include "lluistring.h" +#include "llviewborder.h" + +class LLFontGL; +class LLLineEditorRollback; +class LLButton; + +typedef BOOL (*LLLinePrevalidateFunc)(const LLWString &wstr); + +// +// Classes +// +class LLLineEditor +: public LLUICtrl, public LLEditMenuHandler +{ + friend class LLLineEditorRollback; + +public: + LLLineEditor(const LLString& name, + const LLRect& rect, + const LLString& default_text = LLString::null, + const LLFontGL* glfont = NULL, + S32 max_length_bytes = 254, + void (*commit_callback)(LLUICtrl* caller, void* user_data) = NULL, + void (*keystroke_callback)(LLLineEditor* caller, void* user_data) = NULL, + void (*focus_lost_callback)(LLLineEditor* caller, void* user_data) = NULL, + void* userdata = NULL, + LLLinePrevalidateFunc prevalidate_func = NULL, + LLViewBorder::EBevel border_bevel = LLViewBorder::BEVEL_IN, + LLViewBorder::EStyle border_style = LLViewBorder::STYLE_LINE, + S32 border_thickness = 1); + + virtual ~LLLineEditor(); + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + virtual LLXMLNodePtr getXML(bool save_children = true) const; + void setColorParameters(LLXMLNodePtr node); + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + + // mousehandler overrides + /*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleMouseUp(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleHover(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleDoubleClick(S32 x,S32 y,MASK mask); + /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent ); + /*virtual*/ BOOL handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent); + + // LLEditMenuHandler overrides + virtual void cut(); + virtual BOOL canCut(); + + virtual void copy(); + virtual BOOL canCopy(); + + virtual void paste(); + virtual BOOL canPaste(); + + virtual void doDelete(); + virtual BOOL canDoDelete(); + + virtual void selectAll(); + virtual BOOL canSelectAll(); + + virtual void deselect(); + virtual BOOL canDeselect(); + + // view overrides + virtual void draw(); + virtual void reshape(S32 width,S32 height,BOOL called_from_parent=TRUE); + virtual void onFocusLost(); + virtual void setEnabled(BOOL enabled); + + // UI control overrides + virtual void clear(); + virtual void onTabInto(); + virtual void setFocus( BOOL b ); + virtual void setRect(const LLRect& rect); + virtual BOOL acceptsTextInput() const; + virtual void onCommit(); + + // assumes UTF8 text + virtual void setValue(const LLSD& value ); + virtual LLSD getValue() const; + virtual BOOL setTextArg( const LLString& key, const LLString& text ); + virtual BOOL setLabelArg( const LLString& key, const LLString& text ); + + void setLabel(const LLString &new_label); + void setText(const LLString &new_text); + + const LLString& getText() const { return mText.getString(); } + const LLWString& getWText() const { return mText.getWString(); } + S32 getLength() const { return mText.length(); } + + S32 getCursor() { return mCursorPos; } + void setCursor( S32 pos ); + void setCursorToEnd(); + + // Selects characters 'start' to 'end'. + void setSelection(S32 start, S32 end); + + void setCommitOnFocusLost( BOOL b ) { mCommitOnFocusLost = b; } + + void setCursorColor(const LLColor4& c) { mCursorColor = c; } + const LLColor4& getCursorColor() const { return mCursorColor; } + + void setFgColor( const LLColor4& c ) { mFgColor = c; } + void setReadOnlyFgColor( const LLColor4& c ) { mReadOnlyFgColor = c; } + void setTentativeFgColor(const LLColor4& c) { mTentativeFgColor = c; } + void setWriteableBgColor( const LLColor4& c ) { mWriteableBgColor = c; } + void setReadOnlyBgColor( const LLColor4& c ) { mReadOnlyBgColor = c; } + void setFocusBgColor(const LLColor4& c) { mFocusBgColor = c; } + + const LLColor4& getFgColor() const { return mFgColor; } + const LLColor4& getReadOnlyFgColor() const { return mReadOnlyFgColor; } + const LLColor4& getTentativeFgColor() const { return mTentativeFgColor; } + const LLColor4& getWriteableBgColor() const { return mWriteableBgColor; } + const LLColor4& getReadOnlyBgColor() const { return mReadOnlyBgColor; } + const LLColor4& getFocusBgColor() const { return mFocusBgColor; } + + void setIgnoreArrowKeys(BOOL b) { mIgnoreArrowKeys = b; } + void setIgnoreTab(BOOL b) { mIgnoreTab = b; } + void setPassDelete(BOOL b) { mPassDelete = b; } + + void setDrawAsterixes(BOOL b) { mDrawAsterixes = b; } + + // get the cursor position of the beginning/end of the prev/next word in the text + S32 prevWordPos(S32 cursorPos) const; + S32 nextWordPos(S32 cursorPos) const; + + BOOL hasSelection() { return (mSelectionStart != mSelectionEnd); } + void startSelection(); + void endSelection(); + void extendSelection(S32 new_cursor_pos); + void deleteSelection(); + + void setHandleEditKeysDirectly( BOOL b ) { mHandleEditKeysDirectly = b; } + void setSelectAllonFocusReceived(BOOL b); + + void setKeystrokeCallback(void (*keystroke_callback)(LLLineEditor* caller, void* user_data)); + void setFocusLostCallback(void (*keystroke_callback)(LLLineEditor* caller, void* user_data)); + + void setMaxTextLength(S32 max_text_length); + void setBorderWidth(S32 left, S32 right); + + static BOOL isPartOfWord(llwchar c); + // Prevalidation controls which keystrokes can affect the editor + void setPrevalidate( BOOL (*func)(const LLWString &) ) { mPrevalidateFunc = func; } + static BOOL prevalidateFloat(const LLWString &str ); + static BOOL prevalidateInt(const LLWString &str ); + static BOOL prevalidatePositiveS32(const LLWString &str); + static BOOL prevalidateNonNegativeS32(const LLWString &str); + static BOOL prevalidateAlphaNum(const LLWString &str ); + static BOOL prevalidateAlphaNumSpace(const LLWString &str ); + static BOOL prevalidatePrintableNotPipe(const LLWString &str); + static BOOL prevalidatePrintableNoSpace(const LLWString &str); + static BOOL prevalidateASCII(const LLWString &str); + + static BOOL postvalidateFloat(const LLString &str); + + static void onMouseCaptureLost( LLMouseHandler* old_captor ); + +protected: + void removeChar(); + void addChar(const llwchar c); + void setCursorAtLocalPos(S32 local_mouse_x); + + S32 findPixelNearestPos(S32 cursor_offset = 0); + void reportBadKeystroke(); + + BOOL handleSpecialKey(KEY key, MASK mask); + BOOL handleSelectionKey(KEY key, MASK mask); + BOOL handleControlKey(KEY key, MASK mask); + S32 handleCommitKey(KEY key, MASK mask); + +protected: + LLUIString mText; // The string being edited. + LLUIString mLabel; // text label that is visible when no user text provided + + LLViewBorder* mBorder; + const LLFontGL* mGLFont; + S32 mMaxLengthChars; // Max number of characters + S32 mMaxLengthBytes; // Max length of the UTF8 string. + S32 mCursorPos; // I-beam is just after the mCursorPos-th character. + S32 mScrollHPos; // Horizontal offset from the start of mText. Used for scrolling. + LLFrameTimer mScrollTimer; + S32 mMinHPixels; + S32 mMaxHPixels; + S32 mBorderLeft; + S32 mBorderRight; + + BOOL mCommitOnFocusLost; + + void (*mKeystrokeCallback)( LLLineEditor* caller, void* userdata ); + void (*mFocusLostCallback)( LLLineEditor* caller, void* userdata ); + + BOOL mIsSelecting; // Selection for clipboard operations + S32 mSelectionStart; + S32 mSelectionEnd; + S32 mLastSelectionX; + S32 mLastSelectionY; + + S32 (*mPrevalidateFunc)(const LLWString &str); + + LLFrameTimer mKeystrokeTimer; + + LLColor4 mCursorColor; + + LLColor4 mFgColor; + LLColor4 mReadOnlyFgColor; + LLColor4 mTentativeFgColor; + LLColor4 mWriteableBgColor; + LLColor4 mReadOnlyBgColor; + LLColor4 mFocusBgColor; + + S32 mBorderThickness; + + BOOL mIgnoreArrowKeys; + BOOL mIgnoreTab; + BOOL mDrawAsterixes; + + BOOL mHandleEditKeysDirectly; // If true, the standard edit keys (Ctrl-X, Delete, etc,) are handled here instead of routed by the menu system + BOOL mSelectAllonFocusReceived; + BOOL mPassDelete; + + BOOL mReadOnly; +}; + + +class LLSearchEditor : public LLUICtrl +{ +friend class LLLineEditorRollback; + +public: + LLSearchEditor(const LLString& name, + const LLRect& rect, + S32 max_length_bytes, + void (*search_callback)(const LLString& search_string, void* user_data), + void* userdata); + + virtual ~LLSearchEditor(); + + /*virtual*/ void draw(); + + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + + void setText(const LLString &new_text); + + void setSearchCallback(void (*search_callback)(const LLString& search_string, void* user_data), void* data) { mSearchCallback = search_callback; mCallbackUserData = data; } + + // LLUICtrl interface + virtual void setValue(const LLSD& value ); + virtual LLSD getValue() const; + virtual BOOL setTextArg( const LLString& key, const LLString& text ); + virtual BOOL setLabelArg( const LLString& key, const LLString& text ); + virtual void clear(); + + +protected: + LLLineEditor* mSearchEdit; + LLButton* mClearSearchButton; + + void (*mSearchCallback)(const LLString& search_string, void* user_data); + + static void onSearchEdit(LLLineEditor* caller, void* user_data ); + static void onClearSearch(void* user_data); +}; + +#endif // LL_LINEEDITOR_ diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp new file mode 100644 index 0000000000..de06c34c44 --- /dev/null +++ b/indra/llui/llmenugl.cpp @@ -0,0 +1,4341 @@ +/** + * @file llmenugl.cpp + * @brief LLMenuItemGL base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//***************************************************************************** +// +// 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 "llmath.h" +#include "llgl.h" +#include "llfocusmgr.h" +#include "llfont.h" +#include "llcoord.h" +#include "llwindow.h" +#include "llcriticaldamp.h" +#include "lluictrlfactory.h" + +#include "llfontgl.h" +#include "llresmgr.h" +#include "llui.h" + +#include "llglheaders.h" +#include "llstl.h" + +#include "v2math.h" +#include <set> +#include <boost/tokenizer.hpp> + +// static +LLView *LLMenuGL::sDefaultMenuContainer = NULL; + +S32 MENU_BAR_HEIGHT = 0; +S32 MENU_BAR_WIDTH = 0; + +///============================================================================ +/// Local function declarations, constants, enums, and typedefs +///============================================================================ + +const LLString SEPARATOR_NAME("separator"); +const LLString TEAROFF_SEPARATOR_LABEL( "~~~~~~~~~~~" ); +const LLString SEPARATOR_LABEL( "-----------" ); +const LLString VERTICAL_SEPARATOR_LABEL( "|" ); + +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 LLString BOOLEAN_TRUE_PREFIX( "X" ); +const LLString BRANCH_SUFFIX( ">" ); +const LLString ARROW_UP ("^^^^^^^"); +const LLString 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 ); +BOOL LLMenuItemGL::sDropShadowText = TRUE; +LLColor4 LLMenuGL::sDefaultBackgroundColor( 0.25f, 0.25f, 0.25f, 0.75f ); + +LLViewHandle 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 LLString& name, const LLString& label, KEY key, MASK mask ) : + LLView( name, TRUE ), + mJumpKey(KEY_NONE), + mAcceleratorKey( key ), + mAcceleratorMask( mask ), + mAllowKeyRepeat(FALSE), + mHighlight( FALSE ), + mGotHover( FALSE ), + mBriefItem( FALSE ), + mFont( LLFontGL::sSansSerif ), + mStyle(LLFontGL::NORMAL), + mDrawTextDisabled( FALSE ) +{ + setLabel( label ); +} + +// virtual +LLXMLNodePtr LLMenuItemGL::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLView::getXML(); + + 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()); + } + + return node; +} + +BOOL LLMenuItemGL::handleKey(KEY key, MASK mask, BOOL called_from_parent) +{ + // modified from LLView::handleKey + // ignore visibility, as keyboard accelerators should still trigger menu items + // even when they are not visible + // also, ignore enabled flag for self, as that can change based on menu callbacks + BOOL handled = FALSE; + + if( called_from_parent ) + { + // Downward traversal + if (mEnabled) + { + handled = childrenHandleKey( key, mask ) != NULL; + } + } + + if( !handled ) + { + handled = handleKeyHere( key, mask, called_from_parent ); + } + + return handled; +} + +BOOL LLMenuItemGL::handleAcceleratorKey(KEY key, MASK mask) +{ + if( mEnabled && (!gKeyboard->getKeyRepeated(key) || mAllowKeyRepeat) && (key == mAcceleratorKey) && (mask == mAcceleratorMask) ) + { + doIt(); + return TRUE; + } + return FALSE; +} + +BOOL LLMenuItemGL::handleHover(S32 x, S32 y, MASK mask) +{ + mGotHover = TRUE; + getWindow()->setCursor(UI_CURSOR_ARROW); + return TRUE; +} + +void LLMenuItemGL::setBriefItem(BOOL b) +{ + mBriefItem = b; +} + +// 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 <LLKeyBinding*> *listp) +{ + LLKeyBinding *accelerator = NULL; + + if (mAcceleratorKey != KEY_NONE) + { + std::list<LLKeyBinding*>::iterator list_it; + for (list_it = listp->begin(); list_it != listp->end(); ++list_it) + { + accelerator = *list_it; + if ((accelerator->mKey == mAcceleratorKey) && (accelerator->mMask == mAcceleratorMask)) + { + //FIXME: get calling code to throw up warning or route warning messages back to app-provided output + // LLString warning; + // warning.append("Duplicate key binding <"); + // appendAcceleratorString( warning ); + // warning.append("> for menu items:\n "); + // warning.append(accelerator->mName); + // warning.append("\n "); + // warning.append(mLabel); + + // llwarns << warning << llendl; + // LLAlertDialog::modalAlert(warning); + return FALSE; + } + } + if (!accelerator) + { + accelerator = new LLKeyBinding; + if (accelerator) + { + accelerator->mKey = mAcceleratorKey; + accelerator->mMask = mAcceleratorMask; +// 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( LLString& st ) +{ + // 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 ) + st.append( "Cmd-" ); // Symbol would be "\xE2\x8C\x98" + if( mAcceleratorMask & MASK_ALT ) + st.append( "Opt-" ); // Symbol would be "\xE2\x8C\xA5" + if( mAcceleratorMask & MASK_SHIFT ) + st.append( "Shift-" ); // Symbol would be "\xE2\x8C\xA7" +#else + if( mAcceleratorMask & MASK_CONTROL ) + st.append( "Ctrl-" ); + if( mAcceleratorMask & MASK_ALT ) + st.append( "Alt-" ); + if( mAcceleratorMask & MASK_SHIFT ) + st.append( "Shift-" ); +#endif + + LLString keystr = LLKeyboard::stringFromKey( mAcceleratorKey ); + if ((mAcceleratorMask & (MASK_CONTROL|MASK_ALT|MASK_SHIFT)) && + (keystr[0] == '-' || keystr[0] == '=')) + { + st.append( " " ); + } + st.append( keystr ); +} + +void LLMenuItemGL::setJumpKey(KEY key) +{ + mJumpKey = LLStringOps::toUpper((char)key); +} + +KEY LLMenuItemGL::getJumpKey() +{ + return mJumpKey; +} + + +// set the font used by all of the menu objects +void LLMenuItemGL::setFont(LLFontGL* font) +{ + mFont = font; +} + +// returns the height in pixels for the current font. +U32 LLMenuItemGL::getNominalHeight( void ) +{ + return llround(mFont->getLineHeight()) + MENU_ITEM_PADDING; +} + +// functions to control the color scheme +void LLMenuItemGL::setEnabledColor( const LLColor4& color ) +{ + sEnabledColor = color; +} + +void LLMenuItemGL::setDisabledColor( const LLColor4& color ) +{ + sDisabledColor = color; +} + +void LLMenuItemGL::setHighlightBGColor( const LLColor4& color ) +{ + sHighlightBackground = color; +} + +void LLMenuItemGL::setHighlightFGColor( const LLColor4& color ) +{ + sHighlightForeground = color; +} + + +// change the label +void LLMenuItemGL::setLabel( const LLString& label ) +{ + mLabel = label; +} + +// Get the parent menu for this item +LLMenuGL* LLMenuItemGL::getMenu() +{ + 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 ) +{ + U32 width; + + if (mBriefItem) + { + width = BRIEF_PAD_PIXELS; + } + else + { + width = PLAIN_PAD_PIXELS; + } + + if( KEY_NONE != mAcceleratorKey ) + { + width += ACCEL_PAD_PIXELS; + LLString 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(); + LLString st = mDrawAccelLabel.getString(); + appendAcceleratorString( st ); + mDrawAccelLabel = st; +} + +// set the hover status (called by it's menu) + void LLMenuItemGL::setHighlight( BOOL highlight ) +{ + mHighlight = highlight; +} + +// determine if this object is active +BOOL LLMenuItemGL::isActive( void ) const +{ + return FALSE; +} + +BOOL LLMenuItemGL::handleKeyHere( KEY key, MASK mask, BOOL called_from_parent ) +{ + if (mHighlight && + getMenu()->getVisible() && + (!getMenu()->getTornOff() || ((LLFloater*)getMenu()->getParent())->hasFocus())) + { + if (key == KEY_UP) + { + getMenu()->highlightPrevItem(this); + return TRUE; + } + else if (key == KEY_DOWN) + { + getMenu()->highlightNextItem(this); + return TRUE; + } + else if (key == KEY_RETURN && mask == MASK_NONE) + { + doIt(); + if (!getMenu()->getTornOff()) + { + ((LLMenuHolderGL*)getMenu()->getParent())->hideMenus(); + } + return TRUE; + } + } + + return FALSE; +} + +BOOL LLMenuItemGL::handleMouseUp( S32 x, S32 y, MASK ) +{ + //llinfos << mLabel.c_str() << " handleMouseUp " << x << "," << y + // << llendl; + if (mEnabled) + { + doIt(); + mHighlight = FALSE; + make_ui_sound("UISndClickRelease"); + return TRUE; + } + else + { + return FALSE; + } +} + +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 + if( mHighlight && !mBriefItem) + { + glColor4fv( sHighlightBackground.mV ); + gl_rect_2d( 0, mRect.getHeight(), mRect.getWidth(), 0 ); + } + + LLColor4 color; + + U8 font_style = mStyle; + if (LLMenuItemGL::sDropShadowText && getEnabled() && !mDrawTextDisabled ) + { + font_style |= LLFontGL::DROP_SHADOW; + } + + if ( mHighlight ) + { + color = sHighlightForeground; + } + else if( getEnabled() && !mDrawTextDisabled ) + { + color = sEnabledColor; + } + else + { + color = sDisabledColor; + } + + // Draw the text on top. + if (mBriefItem) + { + mFont->render( mLabel, 0, BRIEF_PAD_PIXELS / 2, 0, color, + LLFontGL::LEFT, LLFontGL::BOTTOM, font_style ); + } + 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, font_style, 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, font_style, S32_MAX, S32_MAX, NULL, FALSE ); + if( !mDrawAccelLabel.empty() ) + { + mFont->render( mDrawAccelLabel.getWString(), 0, (F32)mRect.mRight - (F32)RIGHT_PLAIN_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f) + 1.f, color, + LLFontGL::RIGHT, LLFontGL::BOTTOM, font_style, S32_MAX, S32_MAX, NULL, FALSE ); + } + if( !mDrawBranchLabel.empty() ) + { + mFont->render( mDrawBranchLabel.getWString(), 0, (F32)mRect.mRight - (F32)RIGHT_PAD_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f) + 1.f, color, + LLFontGL::RIGHT, LLFontGL::BOTTOM, font_style, S32_MAX, S32_MAX, NULL, FALSE ); + } + } + + // underline navigation key + BOOL draw_jump_key = gKeyboard->currentMask(FALSE) == MASK_ALT && + (!getMenu()->getHighlightedItem() || !getMenu()->getHighlightedItem()->isActive()) && + (!getMenu()->getTornOff()); + if (draw_jump_key) + { + LLString upper_case_label = mLabel.getString(); + LLString::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 + mGotHover = FALSE; +} + +BOOL LLMenuItemGL::setLabelArg( const LLString& key, const LLString& text ) +{ + mLabel.setArg(key, text); + return TRUE; +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemSeparatorGL +// +// This class represents a separator. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMenuItemSeparatorGL : public LLMenuItemGL +{ +public: + LLMenuItemSeparatorGL( const LLString &name = SEPARATOR_NAME ); + + virtual LLString getType() const { return "separator"; } + + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_MENU_ITEM_SEPARATOR; } + virtual LLString getWidgetTag() const { return LL_MENU_ITEM_SEPARATOR_GL_TAG; } + + // doIt() - do the primary funcationality of the menu item. + virtual void doIt( void ) {} + + virtual void draw( void ); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); + virtual BOOL handleHover(S32 x, S32 y, MASK mask); + + virtual U32 getNominalHeight( void ) { return SEPARATOR_HEIGHT_PIXELS; } +}; + +LLMenuItemSeparatorGL::LLMenuItemSeparatorGL( const LLString &name ) : + LLMenuItemGL( SEPARATOR_NAME, SEPARATOR_LABEL ) +{ +} + +void LLMenuItemSeparatorGL::draw( void ) +{ + glColor4fv( sDisabledColor.mV ); + const S32 y = mRect.getHeight() / 2; + const S32 PAD = 6; + gl_line_2d( PAD, y, mRect.getWidth() - PAD, y ); +} + +BOOL LLMenuItemSeparatorGL::handleMouseDown(S32 x, S32 y, MASK mask) +{ + LLMenuGL* parent_menu = getMenu(); + if (y > mRect.getHeight() / 2) + { + return parent_menu->handleMouseDown(x + mRect.mLeft, mRect.mTop + 1, mask); + } + else + { + return parent_menu->handleMouseDown(x + mRect.mLeft, mRect.mBottom - 1, mask); + } +} + +BOOL LLMenuItemSeparatorGL::handleMouseUp(S32 x, S32 y, MASK mask) +{ + LLMenuGL* parent_menu = getMenu(); + if (y > mRect.getHeight() / 2) + { + return parent_menu->handleMouseUp(x + mRect.mLeft, mRect.mTop + 1, mask); + } + else + { + return parent_menu->handleMouseUp(x + mRect.mLeft, mRect.mBottom - 1, mask); + } +} + +BOOL LLMenuItemSeparatorGL::handleHover(S32 x, S32 y, MASK mask) +{ + LLMenuGL* parent_menu = getMenu(); + if (y > mRect.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 EWidgetType getWidgetType() const { return WIDGET_TYPE_MENU_SEPARATOR_VERTICAL; } + virtual LLString getWidgetTag() const { return LL_MENU_ITEM_VERTICAL_SEPARATOR_GL_TAG; } + + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask) { return FALSE; } +}; + +LLMenuItemVerticalSeparatorGL::LLMenuItemVerticalSeparatorGL( void ) +{ + setLabel( VERTICAL_SEPARATOR_LABEL ); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemTearOffGL +// +// This class represents a separator. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +LLMenuItemTearOffGL::LLMenuItemTearOffGL(LLViewHandle parent_floater_handle) : + LLMenuItemGL("tear off", TEAROFF_SEPARATOR_LABEL), + mParentHandle(parent_floater_handle) +{ +} + +EWidgetType LLMenuItemTearOffGL::getWidgetType() const +{ + return WIDGET_TYPE_TEAROFF_MENU; +} + +LLString LLMenuItemTearOffGL::getWidgetTag() const +{ + return LL_MENU_ITEM_TEAR_OFF_GL_TAG; +} + +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); + } + + // grab menu holder before this menu is parented to a floater + LLMenuHolderGL* menu_holder = ((LLMenuHolderGL*)getMenu()->getParent()); + getMenu()->arrange(); + + LLFloater* parent_floater = LLFloater::getFloaterByHandle(mParentHandle); + LLFloater* tear_off_menu = LLTearOffMenu::create(getMenu()); + if (parent_floater && tear_off_menu) + { + parent_floater->addDependentFloater(tear_off_menu, FALSE); + } + + // hide menus + // only do it if the menu is open, not being triggered via accelerator + if (getMenu()->getVisible()) + { + menu_holder->hideMenus(); + } + + // give focus to torn off menu because it will have been taken away + // when parent menu closes + tear_off_menu->setFocus(TRUE); + } +} + +void LLMenuItemTearOffGL::draw() +{ + if( mHighlight && !mBriefItem) + { + glColor4fv( sHighlightBackground.mV ); + gl_rect_2d( 0, mRect.getHeight(), mRect.getWidth(), 0 ); + } + + if (mEnabled) + { + glColor4fv( sEnabledColor.mV ); + } + else + { + glColor4fv( sDisabledColor.mV ); + } + const S32 y = mRect.getHeight() / 3; + const S32 PAD = 6; + gl_line_2d( PAD, y, mRect.getWidth() - PAD, y ); + gl_line_2d( PAD, y * 2, mRect.getWidth() - PAD, y * 2 ); +} + +U32 LLMenuItemTearOffGL::getNominalHeight( void ) { return TEAROFF_SEPARATOR_HEIGHT_PIXELS; } + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemBlankGL +// +// This class represents a blank, non-functioning item. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMenuItemBlankGL : public LLMenuItemGL +{ +public: + LLMenuItemBlankGL( void ); + + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_MENU_ITEM_BLANK; } + virtual LLString getWidgetTag() const { return LL_MENU_ITEM_BLANK_GL_TAG; } + + // doIt() - do the primary funcationality of the menu item. + virtual void doIt( void ) {} + + virtual void draw( void ) {} +}; + +LLMenuItemBlankGL::LLMenuItemBlankGL( void ) +: LLMenuItemGL( "", "" ) +{ + mEnabled = FALSE; +} + +///============================================================================ +/// Class LLMenuItemCallGL +///============================================================================ + +LLMenuItemCallGL::LLMenuItemCallGL( const LLString& name, + const LLString& 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 LLString& 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 LLString& name, + const LLString& 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 LLString& 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(LLString enabled_control, LLView *context) +{ + // Register new listener + if (!enabled_control.empty()) + { + LLControlBase *control = context->findControl(enabled_control); + if (control) + { + LLSD state = control->registerListener(this, "ENABLED"); + setEnabled(state); + } + else + { + context->addBoolControl(enabled_control, mEnabled); + control = context->findControl(enabled_control); + control->registerListener(this, "ENABLED"); + } + } +} + +void LLMenuItemCallGL::setVisibleControl(LLString enabled_control, LLView *context) +{ + // Register new listener + if (!enabled_control.empty()) + { + LLControlBase *control = context->findControl(enabled_control); + if (control) + { + LLSD state = control->registerListener(this, "VISIBLE"); + setVisible(state); + } + else + { + context->addBoolControl(enabled_control, mEnabled); + control = context->findControl(enabled_control); + control->registerListener(this, "VISIBLE"); + } + } +} + +// virtual +bool LLMenuItemCallGL::handleEvent(LLPointer<LLEvent> event, const LLSD& userdata) +{ + if (userdata.asString() == "ENABLED" && event->desc() == "value_changed") + { + LLSD state = event->getValue(); + setEnabled(state); + return TRUE; + } + if (userdata.asString() == "VISIBLE" && event->desc() == "value_changed") + { + LLSD state = event->getValue(); + setVisible(state); + return TRUE; + } + return LLMenuItemGL::handleEvent(event, userdata); +} + +// virtual +LLXMLNodePtr LLMenuItemCallGL::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLMenuItemGL::getXML(); + + // Contents + + std::vector<LLListenerEntry> listeners = mDispatcher->getListeners(); + std::vector<LLListenerEntry>::iterator itor; + for (itor = listeners.begin(); itor != listeners.end(); ++itor) + { + LLString listener_name = findEventListener((LLSimpleListener*)itor->listener); + if (!listener_name.empty()) + { + 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<LLEvent> fired_event = new LLEvent(this); + fireEvent(fired_event, "on_click"); +} + +EWidgetType LLMenuItemCallGL::getWidgetType() const +{ + return WIDGET_TYPE_MENU_ITEM_CALL; +} + +LLString LLMenuItemCallGL::getWidgetTag() const +{ + return LL_MENU_ITEM_CALL_GL_TAG; +} + +void LLMenuItemCallGL::buildDrawLabel( void ) +{ + LLPointer<LLEvent> fired_event = new LLEvent(this); + fireEvent(fired_event, "on_build"); + if( mEnabledCallback ) + { + setEnabled( mEnabledCallback( mUserData ) ); + } + if(mLabelCallback) + { + LLString label; + mLabelCallback(label, mUserData); + mLabel = label; + } + LLMenuItemGL::buildDrawLabel(); +} + +BOOL LLMenuItemCallGL::handleAcceleratorKey( KEY key, MASK mask ) +{ + if( (!gKeyboard->getKeyRepeated(key) || mAllowKeyRepeat) && (key == mAcceleratorKey) && (mask == mAcceleratorMask) ) + { + LLPointer<LLEvent> fired_event = new LLEvent(this); + fireEvent(fired_event, "on_build"); + if( mEnabledCallback ) + { + setEnabled( mEnabledCallback( mUserData ) ); + } + if( !mEnabled ) + { + if( mOnDisabledCallback ) + { + mOnDisabledCallback( mUserData ); + } + } + } + return LLMenuItemGL::handleAcceleratorKey(key, mask); +} + +///============================================================================ +/// Class LLMenuItemCheckGL +///============================================================================ + +LLMenuItemCheckGL::LLMenuItemCheckGL ( const LLString& name, + const LLString& 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 LLString& 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 LLString& name, + const LLString& label, + menu_callback clicked_cb, + enabled_callback enabled_cb, + LLString 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); +} + +void LLMenuItemCheckGL::setCheckedControl(LLString checked_control, LLView *context) +{ + // Register new listener + if (!checked_control.empty()) + { + LLControlBase *control = context->findControl(checked_control); + if (control) + { + LLSD state = control->registerListener(this, "CHECKED"); + mChecked = state; + } + else + { + context->addBoolControl(checked_control, mChecked); + control = context->findControl(checked_control); + control->registerListener(this, "CHECKED"); + } + } +} + +// virtual +bool LLMenuItemCheckGL::handleEvent(LLPointer<LLEvent> event, const LLSD& userdata) +{ + if (userdata.asString() == "CHECKED" && event->desc() == "value_changed") + { + LLSD state = event->getValue(); + mChecked = state; + if(mChecked) + { + mDrawBoolLabel = BOOLEAN_TRUE_PREFIX; + } + else + { + mDrawBoolLabel.clear(); + } + return TRUE; + } + return LLMenuItemCallGL::handleEvent(event, userdata); +} + +// virtual +LLXMLNodePtr LLMenuItemCheckGL::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLMenuItemCallGL::getXML(); + return node; +} + +EWidgetType LLMenuItemCheckGL::getWidgetType() const +{ + return WIDGET_TYPE_MENU_ITEM_CHECK; +} + +LLString LLMenuItemCheckGL::getWidgetTag() const +{ + return LL_MENU_ITEM_CHECK_GL_TAG; +} + +// called to rebuild the draw label +void LLMenuItemCheckGL::buildDrawLabel( void ) +{ + if(mChecked || (mCheckCallback && mCheckCallback( mUserData ) ) ) + { + mDrawBoolLabel = BOOLEAN_TRUE_PREFIX; + } + else + { + mDrawBoolLabel.clear(); + } + LLMenuItemCallGL::buildDrawLabel(); +} + + +///============================================================================ +/// Class LLMenuItemToggleGL +///============================================================================ + +LLMenuItemToggleGL::LLMenuItemToggleGL( const LLString& name, const LLString& label, BOOL* toggle, + KEY key, MASK mask ) : + LLMenuItemGL( name, label, key, mask ), + mToggle( toggle ) +{ +} + +LLMenuItemToggleGL::LLMenuItemToggleGL( const LLString& 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 = BOOLEAN_TRUE_PREFIX; + } + else + { + mDrawBoolLabel.clear(); + } + mDrawAccelLabel.clear(); + LLString 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(); +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemBranchGL +// +// The LLMenuItemBranchGL represents a menu item that has a +// sub-menu. This is used to make cascading menus. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMenuItemBranchGL : public LLMenuItemGL +{ +protected: + LLMenuGL* mBranch; + +public: + LLMenuItemBranchGL( const LLString& name, const LLString& label, LLMenuGL* branch, + KEY key = KEY_NONE, MASK mask = MASK_NONE ); + virtual LLXMLNodePtr getXML(bool save_children = true) const; + + virtual LLView* getChildByName(const LLString& name, BOOL recurse) const; + + virtual LLString getType() const { return "menu"; } + + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); + + virtual BOOL handleAcceleratorKey(KEY key, MASK mask); + + // check if we've used these accelerators already + virtual BOOL addToAcceleratorList(std::list <LLKeyBinding*> *listp); + + // called to rebuild the draw label + virtual void buildDrawLabel( void ); + + // doIt() - do the primary funcationality of the menu item. + virtual void doIt( void ); + + virtual BOOL handleKey(KEY key, MASK mask, BOOL called_from_parent); + + // 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 handleKeyHere(KEY key, MASK mask, BOOL called_from_parent); + + virtual BOOL isActive() const { return !mBranch->getTornOff() && mBranch->getVisible(); } + + LLMenuGL *getBranch() const { return mBranch; } + + virtual void updateBranchParent( LLView* parentp ); + + // LLView Functionality + virtual void onVisibilityChange( BOOL curVisibilityIn ); + + virtual void draw(); + + virtual void setEnabledSubMenus(BOOL enabled); +}; + +LLMenuItemBranchGL::LLMenuItemBranchGL( const LLString& name, const LLString& label, LLMenuGL* branch, + KEY key, MASK mask ) : + LLMenuItemGL( name, label, key, mask ), + mBranch( branch ) +{ + mBranch->setVisible( FALSE ); + mBranch->setParentMenuItem(this); +} + +// virtual +LLView* LLMenuItemBranchGL::getChildByName(const LLString& name, BOOL recurse) const +{ + if (mBranch->getName() == name) + { + return mBranch; + } + // Always recurse on branches + return mBranch->getChildByName(name, recurse); +} + +EWidgetType LLMenuItemBranchGL::getWidgetType() const +{ + return WIDGET_TYPE_MENU_ITEM_BRANCH; +} + +LLString LLMenuItemBranchGL::getWidgetTag() const +{ + return LL_MENU_ITEM_BRANCH_GL_TAG; +} + +// virtual +BOOL LLMenuItemBranchGL::handleMouseUp(S32 x, S32 y, MASK mask) +{ + if (mEnabled) + { + doIt(); + make_ui_sound("UISndClickRelease"); + } + return FALSE; +} + +BOOL LLMenuItemBranchGL::handleAcceleratorKey(KEY key, MASK mask) +{ + return mBranch->handleAcceleratorKey(key, mask); +} + +// virtual +LLXMLNodePtr LLMenuItemBranchGL::getXML(bool save_children) const +{ + if (mBranch) + { + return mBranch->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<LLKeyBinding*> *listp) +{ + U32 item_count = mBranch->getItemCount(); + LLMenuItemGL *item; + + while (item_count--) + { + if ((item = mBranch->getItem(item_count))) + { + return item->addToAcceleratorList(listp); + } + } + return FALSE; +} + + +// called to rebuild the draw label +void LLMenuItemBranchGL::buildDrawLabel( void ) +{ + mDrawAccelLabel.clear(); + LLString st = mDrawAccelLabel; + appendAcceleratorString( st ); + mDrawAccelLabel = st; + mDrawBranchLabel = BRANCH_SUFFIX; +} + +// doIt() - do the primary functionality of the menu item. +void LLMenuItemBranchGL::doIt( void ) +{ + if (mBranch->getTornOff()) + { + gFloaterView->bringToFront((LLFloater*)mBranch->getParent()); + // this might not be necessary, as torn off branches don't get focus and hence no highligth + mBranch->highlightNextItem(NULL); + } + else if( !mBranch->getVisible() ) + { + mBranch->arrange(); + + LLRect rect = mBranch->getRect(); + // calculate root-view relative position for branch menu + S32 left = mRect.mRight; + S32 top = mRect.mTop - mRect.mBottom; + + localPointToOtherView(left, top, &left, &top, mBranch->getParent()); + + rect.setLeftTopAndSize( left, top, + rect.getWidth(), rect.getHeight() ); + + if (mBranch->getCanTearOff()) + { + rect.translate(0, TEAROFF_SEPARATOR_HEIGHT_PIXELS); + } + mBranch->setRect( rect ); + S32 x = 0; + S32 y = 0; + mBranch->localPointToOtherView( 0, 0, &x, &y, mBranch->getParent() ); + S32 delta_x = 0; + S32 delta_y = 0; + if( y < 0 ) + { + delta_y = -y; + } + + S32 window_width = mBranch->getParent()->getRect().getWidth(); + if( x > window_width - rect.getWidth() ) + { + // move sub-menu over to left side + delta_x = llmax(-x, (-1 * (rect.getWidth() + mRect.getWidth()))); + } + mBranch->translate( delta_x, delta_y ); + mBranch->setVisible( TRUE ); + } +} + +BOOL LLMenuItemBranchGL::handleKey(KEY key, MASK mask, BOOL called_from_parent) +{ + BOOL handled = FALSE; + if (called_from_parent) + { + handled = mBranch->handleKey(key, mask, called_from_parent); + } + + if (!handled) + { + handled = LLMenuItemGL::handleKey(key, mask, called_from_parent); + } + + return handled; +} + +// set the hover status (called by it's menu) +void LLMenuItemBranchGL::setHighlight( BOOL highlight ) +{ + BOOL auto_open = mEnabled && (!mBranch->getVisible() || mBranch->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 (mBranch->getTornOff()) + { + auto_open = FALSE; + } + + mHighlight = highlight; + if( highlight ) + { + if(auto_open) + { + doIt(); + } + } + else + { + if (mBranch->getTornOff()) + { + ((LLFloater*)mBranch->getParent())->setFocus(FALSE); + mBranch->clearHoverItem(); + } + else + { + mBranch->setVisible( FALSE ); + } + } +} + +void LLMenuItemBranchGL::setEnabledSubMenus(BOOL enabled) +{ + mBranch->setEnabledSubMenus(enabled); +} + +void LLMenuItemBranchGL::draw() +{ + LLMenuItemGL::draw(); + if (mBranch->getVisible() && !mBranch->getTornOff()) + { + mHighlight = TRUE; + } +} + +void LLMenuItemBranchGL::updateBranchParent(LLView* parentp) +{ + if (mBranch->getParent() == NULL) + { + // make the branch menu a sibling of my parent menu + mBranch->updateParent(parentp); + } +} + +void LLMenuItemBranchGL::onVisibilityChange( BOOL curVisibilityIn ) +{ + if (curVisibilityIn == FALSE && mBranch->getVisible() && !mBranch->getTornOff()) + { + mBranch->setVisible(FALSE); + } +} + +BOOL LLMenuItemBranchGL::handleKeyHere( KEY key, MASK mask, BOOL called_from_parent ) +{ + if (getMenu()->getVisible() && mBranch->getVisible() && key == KEY_LEFT) + { + BOOL handled = mBranch->clearHoverItem(); + if (handled && getMenu()->getTornOff()) + { + ((LLFloater*)getMenu()->getParent())->setFocus(TRUE); + } + return handled; + } + + if (mHighlight && + getMenu()->getVisible() && + // ignore keystrokes on background torn-off menus + (!getMenu()->getTornOff() || ((LLFloater*)getMenu()->getParent())->hasFocus()) && + key == KEY_RIGHT && !mBranch->getHighlightedItem()) + { + LLMenuItemGL* itemp = mBranch->highlightNextItem(NULL); + if (itemp) + { + return TRUE; + } + } + + return LLMenuItemGL::handleKeyHere(key, mask, called_from_parent); +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// 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 LLString& name, const LLString& label, LLMenuGL* branch, + KEY key = KEY_NONE, MASK mask = MASK_NONE ); + + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_MENU_ITEM_BRANCH_DOWN; } + virtual LLString getWidgetTag() const { return LL_MENU_ITEM_BRANCH_DOWN_GL_TAG; } + + virtual LLString 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 ); + + // called to rebuild the draw label + virtual void buildDrawLabel( void ); + + // doIt() - do the primary funcationality of the menu item. + virtual void doIt( 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 ); + + // determine if this object is active + 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 ) {return FALSE; } + virtual void draw( void ); + virtual BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent); + + virtual BOOL handleAcceleratorKey(KEY key, MASK mask); +}; + +LLMenuItemBranchDownGL::LLMenuItemBranchDownGL( const LLString& name, + const LLString& label, + LLMenuGL* 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 ) +{ + U32 width = LEFT_PAD_PIXELS + LEFT_WIDTH_PIXELS + RIGHT_PAD_PIXELS; + width += mFont->getWidth( mLabel.getWString().c_str() ); + return width; +} + +// called to rebuild the draw label +void LLMenuItemBranchDownGL::buildDrawLabel( void ) +{ + mDrawAccelLabel.clear(); + LLString st = mDrawAccelLabel; + appendAcceleratorString( st ); + mDrawAccelLabel = st; +} + +// doIt() - do the primary funcationality of the menu item. +void LLMenuItemBranchDownGL::doIt( void ) +{ + if( mBranch->getVisible() && !mBranch->getTornOff() ) + { + mBranch->setVisible( FALSE ); + } + else + { + if (mBranch->getTornOff()) + { + gFloaterView->bringToFront((LLFloater*)mBranch->getParent()); + } + else + { + // We're showing the drop-down menu, so patch up its labels/rects + mBranch->arrange(); + + LLRect rect = mBranch->getRect(); + S32 left = 0; + S32 top = mRect.mBottom; + localPointToOtherView(left, top, &left, &top, mBranch->getParent()); + + rect.setLeftTopAndSize( left, top, + rect.getWidth(), rect.getHeight() ); + mBranch->setRect( rect ); + S32 x = 0; + S32 y = 0; + mBranch->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; + } + mBranch->translate( delta_x, 0 ); + + //FIXME: get menuholder lookup working more generically + // hide existing menus + if (!mBranch->getTornOff()) + { + ((LLMenuHolderGL*)mBranch->getParent())->hideMenus(); + } + + mBranch->setVisible( TRUE ); + } + } +} + +// set the hover status (called by it's menu) +void LLMenuItemBranchDownGL::setHighlight( BOOL highlight ) +{ + mHighlight = highlight; + if( !highlight) + { + if (mBranch->getTornOff()) + { + ((LLFloater*)mBranch->getParent())->setFocus(FALSE); + mBranch->clearHoverItem(); + } + else + { + mBranch->setVisible( FALSE ); + } + } +} + +// determine if this object is active +// which, for branching menus, means the branch is open and has "focus" +BOOL LLMenuItemBranchDownGL::isActive( void ) const +{ + if (mBranch->getTornOff()) + { + return ((LLFloater*)mBranch->getParent())->hasFocus(); + } + else + { + return mBranch->getVisible(); + } +} + +BOOL LLMenuItemBranchDownGL::handleMouseDown( S32 x, S32 y, MASK mask ) +{ + doIt(); + make_ui_sound("UISndClick"); + return TRUE; +} + + +BOOL LLMenuItemBranchDownGL::handleAcceleratorKey(KEY key, MASK mask) +{ + BOOL branch_visible = mBranch->getVisible(); + BOOL handled = mBranch->handleAcceleratorKey(key, mask); + if (handled && !branch_visible) + { + // flash this menu entry because we triggered an invisible menu item + LLMenuHolderGL::setActivatedItem(this); + } + + return handled; +} + +BOOL LLMenuItemBranchDownGL::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent) +{ + if (mHighlight && getMenu()->getVisible() && mBranch->getVisible()) + { + if (key == KEY_LEFT) + { + LLMenuItemGL* itemp = getMenu()->highlightPrevItem(this); + if (itemp) + { + itemp->doIt(); + } + + return TRUE; + } + else if (key == KEY_RIGHT) + { + LLMenuItemGL* itemp = getMenu()->highlightNextItem(this); + if (itemp) + { + itemp->doIt(); + } + + return TRUE; + } + else if (key == KEY_DOWN) + { + if (!mBranch->getTornOff()) + { + mBranch->setVisible(TRUE); + } + mBranch->highlightNextItem(NULL); + return TRUE; + } + else if (key == KEY_UP) + { + if (!mBranch->getTornOff()) + { + mBranch->setVisible(TRUE); + } + mBranch->highlightPrevItem(NULL); + return TRUE; + } + } + + return FALSE; +} + +void LLMenuItemBranchDownGL::draw( void ) +{ + if( mHighlight ) + { + glColor4fv( sHighlightBackground.mV ); + gl_rect_2d( 0, mRect.getHeight(), mRect.getWidth(), 0 ); + } + + U8 font_style = mStyle; + if (LLMenuItemGL::sDropShadowText && getEnabled() && !mDrawTextDisabled ) + { + font_style |= LLFontGL::DROP_SHADOW; + } + + LLColor4 color; + if (mHighlight) + { + color = sHighlightForeground; + } + else if( mEnabled ) + { + color = sEnabledColor; + } + else + { + color = sDisabledColor; + } + mFont->render( mLabel.getWString(), 0, (F32)mRect.getWidth() / 2.f, (F32)LABEL_BOTTOM_PAD_PIXELS, color, + LLFontGL::HCENTER, LLFontGL::BOTTOM, font_style ); + // if branching menu is closed clear out highlight + if (mHighlight && ((!mBranch->getVisible() /*|| mBranch->getTornOff()*/) && !mGotHover)) + { + setHighlight(FALSE); + } + + // underline navigation key + BOOL draw_jump_key = gKeyboard->currentMask(FALSE) == MASK_ALT && + (!getMenu()->getHighlightedItem() || !getMenu()->getHighlightedItem()->isActive()) && + (!getMenu()->getTornOff()); // torn off menus don't use jump keys, too complicated + + if (draw_jump_key) + { + LLString upper_case_label = mLabel.getString(); + LLString::toUpper(upper_case_label); + std::string::size_type offset = upper_case_label.find(mJumpKey); + if (offset != std::string::npos) + { + S32 x_offset = llround((F32)mRect.getWidth() / 2.f - mFont->getWidthF32(mLabel.getString(), 0, S32_MAX) / 2.f); + S32 x_begin = x_offset + mFont->getWidth(mLabel, 0, offset); + S32 x_end = x_offset + mFont->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 + mGotHover = FALSE; +} + +///============================================================================ +/// Class LLMenuGL +///============================================================================ + +// Default constructor +LLMenuGL::LLMenuGL( const LLString& name, const LLString& label, LLViewHandle parent_floater_handle ) +: LLUICtrl( name, LLRect(), FALSE, NULL, NULL ), + mBackgroundColor( sDefaultBackgroundColor ), + mBgVisible( TRUE ), + mParentMenuItem( NULL ), + mLabel( label ), + mDropShadowed( TRUE ), + mHorizontalLayout( FALSE ), + mKeepFixedSize( FALSE ), + mLastMouseX(0), + mLastMouseY(0), + mMouseVelX(0), + mMouseVelY(0), + mTornOff(FALSE), + mTearOffItem(NULL), + mSpilloverBranch(NULL), + mSpilloverMenu(NULL), + mParentFloaterHandle(parent_floater_handle), + mJumpKey(KEY_NONE) +{ + mFadeTimer.stop(); + setCanTearOff(TRUE, parent_floater_handle); + setTabStop(FALSE); +} + +LLMenuGL::LLMenuGL( const LLString& label, LLViewHandle parent_floater_handle ) +: LLUICtrl( label, LLRect(), FALSE, NULL, NULL ), + mBackgroundColor( sDefaultBackgroundColor ), + mBgVisible( TRUE ), + mParentMenuItem( NULL ), + mLabel( label ), + mDropShadowed( TRUE ), + mHorizontalLayout( FALSE ), + mKeepFixedSize( FALSE ), + mLastMouseX(0), + mLastMouseY(0), + mMouseVelX(0), + mMouseVelY(0), + mTornOff(FALSE), + mTearOffItem(NULL), + mSpilloverBranch(NULL), + mSpilloverMenu(NULL), + mParentFloaterHandle(parent_floater_handle), + mJumpKey(KEY_NONE) +{ + mFadeTimer.stop(); + setCanTearOff(TRUE, parent_floater_handle); + 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, LLViewHandle parent_floater_handle ) +{ + if (tear_off && mTearOffItem == NULL) + { + mTearOffItem = new LLMenuItemTearOffGL(parent_floater_handle); + mItems.insert(mItems.begin(), mTearOffItem); + addChildAtEnd(mTearOffItem); + arrange(); + } + else if (!tear_off && mTearOffItem != NULL) + { + mItems.remove(mTearOffItem); + removeChild(mTearOffItem); + delete mTearOffItem; + mTearOffItem = NULL; + arrange(); + } +} + +// virtual +LLXMLNodePtr LLMenuGL::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLView::getXML(); + + // 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) +{ + if (child->hasName(LL_MENU_GL_TAG)) + { + // SUBMENU + LLMenuGL *submenu = (LLMenuGL*)LLMenuGL::fromXML(child, parent, factory); + appendMenu(submenu); + if (LLMenuGL::sDefaultMenuContainer != NULL) + { + submenu->updateParent(LLMenuGL::sDefaultMenuContainer); + } + else + { + submenu->updateParent(parent); + } + } + else if (child->hasName(LL_MENU_ITEM_CALL_GL_TAG) || + child->hasName(LL_MENU_ITEM_CHECK_GL_TAG) || + child->hasName(LL_MENU_ITEM_SEPARATOR_GL_TAG)) + { + LLMenuItemGL *item = NULL; + + LLString type; + LLString item_name; + LLString source_label; + LLString 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<boost::char_separator<char> > tokenizer; + boost::char_separator<char> 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; + } + + + if (child->hasName(LL_MENU_ITEM_SEPARATOR_GL_TAG)) + { + appendSeparator(item_name); + } + else + { + // ITEM + if (child->hasName(LL_MENU_ITEM_CALL_GL_TAG) || + child->hasName(LL_MENU_ITEM_CHECK_GL_TAG)) + { + MASK mask = 0; + LLString shortcut; + child->getAttributeString("shortcut", shortcut); + if (shortcut.find("control") != shortcut.npos) + { + 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("|"); + LLString 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)) + { + LLString 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")) + { + LLString callback_name; + LLString control_name = ""; + if (call_child->hasAttribute("function")) + { + call_child->getAttributeString("function", callback_name); + + control_name = callback_name; + + LLString 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) continue; + + new_item->addListener(callback, "on_build", userdata); + } + else if (call_child->hasAttribute("control")) + { + call_child->getAttributeString("control", control_name); + } + else + { + continue; + } + LLControlBase *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")) + { + LLString callback_name; + call_child->getAttributeString("function", callback_name); + + LLString callback_data = item_name; + if (call_child->hasAttribute("userdata")) + { + call_child->getAttributeString("userdata", callback_data); + } + + LLSimpleListener* callback = parent->getListenerByName(callback_name); + + if (!callback) continue; + + new_item->addListener(callback, "on_click", callback_data); + } + if (call_child->hasName("on_enable")) + { + LLString callback_name; + LLString control_name = ""; + if (call_child->hasAttribute("function")) + { + call_child->getAttributeString("function", callback_name); + + control_name = callback_name; + + LLString 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) 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")) + { + LLString callback_name; + LLString control_name = ""; + if (call_child->hasAttribute("function")) + { + call_child->getAttributeString("function", callback_name); + + control_name = callback_name; + + LLString 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) 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); + } + } + item = new_item; + item->setLabel(item_label); + item->setJumpKey(jump_key); + } + + if (item != NULL) + { + append(item); + } + } + } +} + +// static +LLView* LLMenuGL::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("menu"); + node->getAttributeString("name", name); + + LLString label = name; + node->getAttributeString("label", label); + + // parse jump key out of label + LLString new_menu_label; + + typedef boost::tokenizer<boost::char_separator<char> > tokenizer; + boost::char_separator<char> 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 = FALSE; + 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; +} + +// control the color scheme +void LLMenuGL::setDefaultBackgroundColor( const LLColor4& color ) +{ + sDefaultBackgroundColor = color; +} + +void LLMenuGL::setBackgroundColor( const LLColor4& color ) +{ + mBackgroundColor = color; +} + +// 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. + LLRect initial_rect = mRect; + + U32 width = 0, height = MENU_ITEM_PADDING; + + cleanupSpilloverBranch(); + + if( mItems.size() ) + { + U32 max_width = (getParent() != NULL) ? getParent()->getRect().getWidth() : U32_MAX; + U32 max_height = (getParent() != NULL) ? getParent()->getRect().getHeight() : U32_MAX; + //FIXME: create the item first and then ask for its dimensions? + S32 spillover_item_width = PLAIN_PAD_PIXELS + LLFontGL::sSansSerif->getWidth( "More" ); + S32 spillover_item_height = llround(LLFontGL::sSansSerif->getLineHeight()) + MENU_ITEM_PADDING; + + if (mHorizontalLayout) + { + item_list_t::iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + if ((*item_iter)->getVisible()) + { + if (!getTornOff() && width + (*item_iter)->getNominalWidth() > max_width - spillover_item_width) + { + // no room for any more items + createSpilloverBranch(); + + item_list_t::iterator spillover_iter; + for (spillover_iter = item_iter; spillover_iter != mItems.end(); ++spillover_iter) + { + LLMenuItemGL* itemp = (*spillover_iter); + removeChild(itemp); + mSpilloverMenu->append(itemp); + } + mItems.erase(item_iter, mItems.end()); + + mItems.push_back(mSpilloverBranch); + 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) + { + if ((*item_iter)->getVisible()) + { + if (!getTornOff() && height + (*item_iter)->getNominalHeight() > max_height - spillover_item_height) + { + // no room for any more items + createSpilloverBranch(); + + item_list_t::iterator spillover_iter; + for (spillover_iter= item_iter; spillover_iter != mItems.end(); ++spillover_iter) + { + LLMenuItemGL* itemp = (*spillover_iter); + removeChild(itemp); + mSpilloverMenu->append(itemp); + } + mItems.erase(item_iter, mItems.end()); + mItems.push_back(mSpilloverBranch); + 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() ); + } + } + } + } + + mRect.mRight = mRect.mLeft + width; + mRect.mTop = mRect.mBottom + height; + + S32 cur_height = (S32)llmin(max_height, height); + S32 cur_width = 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, cur_height, width, (*item_iter)->getNominalHeight()); + cur_height -= (*item_iter)->getNominalHeight(); + } + (*item_iter)->setRect( rect ); + (*item_iter)->buildDrawLabel(); + } + } + } + if (mKeepFixedSize) + { + reshape(initial_rect.getWidth(), initial_rect.getHeight()); + } +} + +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("More", "More", mParentFloaterHandle); + mSpilloverMenu->updateParent(getParent()); + // Inherit colors + mSpilloverMenu->setBackgroundColor( mBackgroundColor ); + mSpilloverMenu->setCanTearOff(FALSE); + + mSpilloverBranch = new LLMenuItemBranchGL("More", "More", mSpilloverMenu); + mSpilloverBranch->setFontStyle(LLFontGL::ITALIC); + } +} + +void LLMenuGL::cleanupSpilloverBranch() +{ + if (mSpilloverBranch && mSpilloverBranch->getParent() == this) + { + // head-recursion to propagate items back up to root menu + mSpilloverMenu->cleanupSpilloverBranch(); + + removeChild(mSpilloverBranch); + + item_list_t::iterator found_iter = std::find(mItems.begin(), mItems.end(), mSpilloverBranch); + if (found_iter != mItems.end()) + { + mItems.erase(found_iter); + } + + // pop off spillover items + while (mSpilloverMenu->getItemCount()) + { + LLMenuItemGL* itemp = mSpilloverMenu->getItem(0); + mSpilloverMenu->removeChild(itemp); + mSpilloverMenu->mItems.erase(mSpilloverMenu->mItems.begin()); + // put them at the end of our own list + mItems.push_back(itemp); + addChild(itemp); + } + } +} + +void LLMenuGL::createJumpKeys() +{ + mJumpKeys.clear(); + + std::set<LLString> unique_words; + std::set<LLString> shared_words; + + item_list_t::iterator item_it; + typedef boost::tokenizer<boost::char_separator<char> > tokenizer; + boost::char_separator<char> sep(" "); + + for(item_it = mItems.begin(); item_it != mItems.end(); ++item_it) + { + LLString uppercase_label = (*item_it)->getLabel(); + LLString::toUpper(uppercase_label); + + tokenizer tokens(uppercase_label, sep); + tokenizer::iterator token_iter; + for( token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) + { + if (unique_words.find(*token_iter) != unique_words.end()) + { + // this word exists in more than one menu instance + shared_words.insert(*token_iter); + } + else + { + // we have a new word, keep track of it + unique_words.insert(*token_iter); + } + } + } + + // pre-assign specified jump keys + for(item_it = mItems.begin(); item_it != mItems.end(); ++item_it) + { + KEY jump_key = (*item_it)->getJumpKey(); + if(jump_key != KEY_NONE) + { + if (mJumpKeys.find(jump_key) == mJumpKeys.end()) + { + mJumpKeys.insert(std::pair<KEY, LLMenuItemGL*>(jump_key, (*item_it))); + } + else + { + // this key is already spoken for, + // so we need to reassign it below + (*item_it)->setJumpKey(KEY_NONE); + } + } + } + + for(item_it = mItems.begin(); item_it != mItems.end(); ++item_it) + { + // skip over items that already have assigned jump keys + if ((*item_it)->getJumpKey() != KEY_NONE) + { + continue; + } + LLString uppercase_label = (*item_it)->getLabel(); + LLString::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) + { + LLString uppercase_word = *token_iter; + + // this word is not shared with other menu entries... + if (shared_words.find(*token_iter) == shared_words.end()) + { + S32 i; + for(i = 0; i < (S32)uppercase_word.size(); i++) + { + char jump_key = uppercase_word[i]; + + if (LLStringOps::isDigit(jump_key) || LLStringOps::isUpper(jump_key) && + mJumpKeys.find(jump_key) == mJumpKeys.end()) + { + mJumpKeys.insert(std::pair<KEY, LLMenuItemGL*>(jump_key, (*item_it))); + (*item_it)->setJumpKey(jump_key); + found_key = TRUE; + break; + } + } + } + if (found_key) + { + break; + } + } + } +} + +// remove all items on the menu +void LLMenuGL::empty( void ) +{ + mItems.clear(); + + deleteAllChildren(); + +} + +// Adjust rectangle of the menu +void LLMenuGL::setLeftAndBottom(S32 left, S32 bottom) +{ + mRect.mLeft = left; + mRect.mBottom = bottom; + arrange(); +} + +void LLMenuGL::handleJumpKey(KEY key) +{ + navigation_key_map_t::iterator found_it = mJumpKeys.find(key); + if(found_it != mJumpKeys.end() && found_it->second->getEnabled()) + { + clearHoverItem(); + // force highlight to close old menus and open and sub-menus + found_it->second->setHighlight(TRUE); + found_it->second->doIt(); + if (!found_it->second->isActive() && !getTornOff()) + { + // parent is a menu holder, because this is not a menu bar + ((LLMenuHolderGL*)getParent())->hideMenus(); + } + } +} + + +// Add the menu item to this menu. +BOOL LLMenuGL::append( LLMenuItemGL* item ) +{ + mItems.push_back( item ); + addChild( item ); + arrange(); + return TRUE; +} + +// add a separator to this menu +BOOL LLMenuGL::appendSeparator( const LLString &separator_name ) +{ + LLMenuItemGL* separator = new LLMenuItemSeparatorGL(separator_name); + return append( separator ); +} + +// add a menu - this will create a cascading menu +BOOL LLMenuGL::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 LLMenuItemBranchGL( menu->getName(), menu->getLabel(), menu ); + branch->setJumpKey(menu->getJumpKey()); + success &= append( branch ); + + // Inherit colors + menu->setBackgroundColor( mBackgroundColor ); + + 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 LLString& 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 LLString& 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 ); + 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) +{ + // highlighting first item on a torn off menu is the + // same as giving focus to it + if (!cur_item && getTornOff()) + { + ((LLFloater*)getParent())->setFocus(TRUE); + } + + item_list_t::iterator cur_item_iter; + for (cur_item_iter = mItems.begin(); cur_item_iter != mItems.end(); ++cur_item_iter) + { + if( (*cur_item_iter) == cur_item) + { + break; + } + } + + 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 items + if ((*next_item_iter)->getEnabled() && (*next_item_iter)->getName() != SEPARATOR_NAME) + { + 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) +{ + // highlighting first item on a torn off menu is the + // same as giving focus to it + if (!cur_item && getTornOff()) + { + ((LLFloater*)getParent())->setFocus(TRUE); + } + + item_list_t::reverse_iterator cur_item_iter; + for (cur_item_iter = mItems.rbegin(); cur_item_iter != mItems.rend(); ++cur_item_iter) + { + if( (*cur_item_iter) == cur_item) + { + break; + } + } + + 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 items + if ((*prev_item_iter)->getEnabled() && (*prev_item_iter)->getName() != SEPARATOR_NAME) + { + if (cur_item) + { + cur_item->setHighlight(FALSE); + } + (*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); + } + parentp->addChild(this); + item_list_t::iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + (*item_iter)->updateBranchParent(parentp); + } +} + +// LLView functionality +BOOL LLMenuGL::handleKey( KEY key, MASK mask, BOOL called_from_parent ) +{ + BOOL handled = FALSE; + + // Pass down even if not visible + if( mEnabled && called_from_parent ) + { + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + if (viewp->handleKey(key, mask, TRUE)) + { + handled = TRUE; + break; + } + } + } + + if( !handled ) + { + handled = handleKeyHere( key, mask, called_from_parent ); + if (handled && LLView::sDebugKeys) + { + llinfos << "Key handled by " << getName() << llendl; + } + } + + return handled; +} + +BOOL LLMenuGL::handleAcceleratorKey(KEY key, MASK mask) +{ + // 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::handleKeyHere( KEY key, MASK mask, BOOL called_from_parent ) +{ + if (key < KEY_SPECIAL && getVisible() && getEnabled() && mask == MASK_ALT) + { + if (getTornOff()) + { + // torn off menus do not handle jump keys (for now, the interaction is complex) + return FALSE; + } + handleJumpKey(key); + return TRUE; + } + return FALSE; +} + +BOOL LLMenuGL::handleHover( S32 x, S32 y, MASK mask ) +{ + // leave submenu in place if slope of mouse < MAX_MOUSE_SLOPE_SUB_MENU + S32 mouse_delta_x = x - mLastMouseX; + S32 mouse_delta_y = 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 ((gKeyboard->currentMask(FALSE) != MASK_ALT || + 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() && + 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); + } + mHasSelection = TRUE; + } + } + } + getWindow()->setCursor(UI_CURSOR_ARROW); + return TRUE; +} + +BOOL LLMenuGL::handleMouseUp( S32 x, S32 y, MASK mask ) +{ + if( LLView::childrenHandleMouseUp( x, y, mask ) ) + { + if (!getTornOff()) + { + ((LLMenuHolderGL*)getParent())->hideMenus(); + } + } + + return TRUE; +} + +void LLMenuGL::draw( void ) +{ + if (mDropShadowed && !mTornOff) + { + gl_drop_shadow(0, mRect.getHeight(), mRect.getWidth(), 0, + LLUI::sColorsGroup->getColor("ColorDropShadow"), + LLUI::sConfigGroup->getS32("DropShadowFloater") ); + } + + LLColor4 bg_color = mBackgroundColor; + + if( mBgVisible ) + { + gl_rect_2d( 0, mRect.getHeight(), mRect.getWidth(), 0, mBackgroundColor ); + } + LLView::draw(); +} + +void LLMenuGL::drawBackground(LLMenuItemGL* itemp, LLColor4& color) +{ + glColor4fv( color.mV ); + LLRect item_rect = itemp->getRect(); + gl_rect_2d( 0, item_rect.getHeight(), item_rect.getWidth(), 0); +} + +void LLMenuGL::setVisible(BOOL visible) +{ + if (visible != getVisible()) + { + if (!visible) + { + mFadeTimer.start(); + clearHoverItem(); + } + else + { + mHasSelection = FALSE; + mFadeTimer.stop(); + } + + //gViewerWindow->finishFastFrame(); + LLView::setVisible(visible); + } +} + +LLMenuGL* LLMenuGL::getChildMenuByName(const LLString& name, BOOL recurse) const +{ + LLView* view = getChildByName(name, recurse); + if (view) + { + if (view->getWidgetType() == WIDGET_TYPE_MENU_ITEM_BRANCH) + { + LLMenuItemBranchGL *branch = (LLMenuItemBranchGL *)view; + return branch->getBranch(); + } + if (view->getWidgetType() == WIDGET_TYPE_MENU || view->getWidgetType() == WIDGET_TYPE_PIE_MENU) + { + return (LLMenuGL*)view; + } + } + llwarns << "Child Menu " << name << " not found in menu " << mName << llendl; + return NULL; +} + +BOOL LLMenuGL::clearHoverItem(BOOL include_active) +{ + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLMenuItemGL* itemp = (LLMenuItemGL*)*child_it; + if (itemp->getHighlight() && (include_active || !itemp->isActive())) + { + itemp->setHighlight(FALSE); + return TRUE; + } + } + return FALSE; +} + +void hide_top_view( LLView* view ) +{ + if( view ) view->setVisible( FALSE ); +} + + +// static +void LLMenuGL::showPopup(LLView* spawning_view, LLMenuGL* menu, S32 x, S32 y) +{ + const S32 HPAD = 2; + LLRect rect = menu->getRect(); + //LLView* cur_view = spawning_view; + S32 left = x + HPAD; + S32 top = y; + spawning_view->localPointToOtherView(left, top, &left, &top, menu->getParent()); + rect.setLeftTopAndSize( left, top, + rect.getWidth(), rect.getHeight() ); + + + //rect.setLeftTopAndSize(x + HPAD, y, rect.getWidth(), rect.getHeight()); + menu->setRect( rect ); + + S32 bottom; + left = rect.mLeft; + bottom = rect.mBottom; + //menu->getParent()->localPointToScreen( rect.mLeft, rect.mBottom, + // &left, &bottom ); + S32 delta_x = 0; + S32 delta_y = 0; + if( bottom < 0 ) + { + delta_y = -bottom; + } + + S32 parent_width = menu->getParent()->getRect().getWidth(); + if( left > parent_width - rect.getWidth() ) + { + // At this point, we need to move the context menu to the + // other side of the mouse. + //delta_x = (window_width - rect.getWidth()) - x; + delta_x = -(rect.getWidth() + 2 * HPAD); + } + menu->translate( delta_x, delta_y ); + menu->setVisible( TRUE ); +} + +//----------------------------------------------------------------------------- +// class LLPieMenuBranch +// A branch to another pie menu +//----------------------------------------------------------------------------- +class LLPieMenuBranch : public LLMenuItemGL +{ +public: + LLPieMenuBranch(const LLString& name, const LLString& label, LLPieMenu* branch, + enabled_callback ecb, void* user_data); + + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_PIE_MENU_BRANCH; } + virtual LLString getWidgetTag() const { return LL_PIE_MENU_BRANCH_TAG; } + + // called to rebuild the draw label + virtual void buildDrawLabel( void ); + + // doIt() - do the primary funcationality of the menu item. + virtual void doIt( void ); + + LLPieMenu* getBranch() { return mBranch; } + +protected: + LLPieMenu* mBranch; + enabled_callback mEnabledCallback; + void* mUserData; +}; + +LLPieMenuBranch::LLPieMenuBranch(const LLString& name, + const LLString& label, + LLPieMenu* branch, + enabled_callback ecb, + void* user_data) +: LLMenuItemGL( name, label, KEY_NONE, MASK_NONE ), + mBranch( branch ), + mEnabledCallback( ecb ), + mUserData(user_data) +{ + mBranch->hide(FALSE); + mBranch->setParentMenuItem(this); +} + +// called to rebuild the draw label +void LLPieMenuBranch::buildDrawLabel( void ) +{ + if(mEnabledCallback) + { + setEnabled(mEnabledCallback(mUserData)); + mDrawTextDisabled = FALSE; + } + else + { + // 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; + } + } + mDrawTextDisabled = !any_enabled; + setEnabled(TRUE); + } + + mDrawAccelLabel.clear(); + LLString 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(TRUE); + mBranch->show( center_x, center_y, FALSE ); +} + +//----------------------------------------------------------------------------- +// class LLPieMenu +// A circular menu of items, icons, etc. +//----------------------------------------------------------------------------- +LLPieMenu::LLPieMenu(const LLString& name, const LLString& label) +: LLMenuGL(name, label), + mFirstMouseDown(FALSE), + mUseInfiniteRadius(FALSE), + mHoverItem(NULL), + mHoverThisFrame(FALSE), + mOuterRingAlpha(1.f), + mCurRadius(0.f), + mRightMouseDown(FALSE) +{ + LLMenuGL::setVisible(FALSE); + setCanTearOff(FALSE); +} + +LLPieMenu::LLPieMenu(const LLString& name) +: LLMenuGL(name, name), + mFirstMouseDown(FALSE), + mUseInfiniteRadius(FALSE), + mHoverItem(NULL), + mHoverThisFrame(FALSE), + mOuterRingAlpha(1.f), + mCurRadius(0.f), + mRightMouseDown(FALSE) +{ + LLMenuGL::setVisible(FALSE); + setCanTearOff(FALSE); +} + +// virtual +LLPieMenu::~LLPieMenu() +{ } + + +EWidgetType LLPieMenu::getWidgetType() const +{ + return WIDGET_TYPE_PIE_MENU; +} + +LLString LLPieMenu::getWidgetTag() const +{ + return LL_PIE_MENU_TAG; +} + +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 + LLString name("menu"); + child->getAttributeString("name", name); + LLString label(name); + child->getAttributeString("label", label); + + LLPieMenu *submenu = new LLPieMenu(name, label); + appendMenu(submenu); + submenu->initXML(child, context, factory); + } + else + { + parseChildXML(child, context, factory); + } + } +} + +// 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 (gFocusMgr.getMouseCapture() == this && + !mRightMouseDown && + mShrinkBorderTimer.getStarted() && + mShrinkBorderTimer.getElapsedTimeF32() >= PIE_SHRINK_TIME) + { + gFocusMgr.setMouseCapture(NULL, 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) + { + BOOL active = FALSE; + if (mHoverItem) + { + active = mHoverItem->isActive(); + 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; + } + } + } + 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 ); + } + + // always handle mouse down as mouse up will close open menus + return handled; +} + +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, NULL); + 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, NULL); + } + + BOOL result = handleMouseUp( x, y, mask ); + mRightMouseDown = 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); + } + } + + if (handled) + { + make_ui_sound("UISndClickRelease"); + } + + if (!handled && !mUseInfiniteRadius) + { + // call hidemenus to make sure transient selections get cleared + ((LLMenuHolderGL*)getParent())->hideMenus(); + } + + if (mFirstMouseDown) + { + make_ui_sound("UISndPieMenuAppear"); + mFirstMouseDown = FALSE; + } + + //FIXME: 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) mRect.getWidth(); + F32 height = (F32) mRect.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; + + glPushMatrix(); + { + glTranslatef(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 + glColor4fv( line_color.mV ); + gl_circle_2d( 0, 0, (F32)PIE_CENTER_SIZE, steps, FALSE ); + + // outer circle + glColor4fv( outer_color.mV ); + gl_circle_2d( 0, 0, mCurRadius, steps, FALSE ); + + LLUI::setLineWidth(1.0f); + } + glPopMatrix(); + + mHoverThisFrame = FALSE; + + LLView::draw(); +} + +void LLPieMenu::drawBackground(LLMenuItemGL* itemp, LLColor4& color) +{ + F32 width = (F32) mRect.getWidth(); + F32 height = (F32) mRect.getHeight(); + F32 center_x = width/2; + F32 center_y = height/2; + S32 steps = 100; + + glColor4fv( color.mV ); + glPushMatrix(); + { + glTranslatef(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++; + } + } + glPopMatrix(); +} + +// virtual +BOOL LLPieMenu::append(LLMenuItemGL *item) +{ + item->setBriefItem(TRUE); + item->setFont( LLFontGL::sSansSerifSmall ); + return LLMenuGL::append(item); +} + +// virtual +BOOL LLPieMenu::appendSeparator(const LLString &separator_name) +{ + LLMenuItemGL* separator = new LLMenuItemBlankGL(); + separator->setFont( LLFontGL::sSansSerifSmall ); + return append( separator ); +} + + +// virtual +BOOL LLPieMenu::appendMenu(LLPieMenu *menu, + enabled_callback enabled_cb, + void* user_data) +{ + if (menu == this) + { + llerrs << "Can't attach a pie menu to itself" << llendl; + } + LLPieMenuBranch *item; + item = new LLPieMenuBranch(menu->getName(), menu->getLabel(), menu, enabled_cb, user_data); + getParent()->addChild(item->getBranch()); + item->setFont( LLFontGL::sSansSerifSmall ); + return append( item ); +} + +// virtual +void LLPieMenu::arrange() +{ + const S32 rect_height = 180; + const S32 rect_width = 180; + + // 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 }; + + LLRect rect; + + 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 + + mRect.setOriginAndSize(mRect.mLeft, mRect.mBottom, rect_width, rect_height ); + + // 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( rect_width/2, rect_height/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 - mRect.getWidth() / 2; + S32 delta_y = y - mRect.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( mRect.getWidth()/2, mRect.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) + { + return (*item_iter); + } + 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 - mRect.getWidth() / 2; + S32 delta_y = y - mRect.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 = mRect.getWidth(); + S32 height = mRect.getHeight(); + + LLView* parent_view = getParent(); + S32 menu_region_width = parent_view->getRect().getWidth(); + S32 menu_region_height = parent_view->getRect().getHeight(); + + BOOL moved = FALSE; + + S32 local_x, local_y; + parent_view->screenPointToLocal(x, y, &local_x, &local_y); + + mRect.setCenterAndSize(local_x, local_y, width, height); + arrange(); + + // Adjust the pie rectangle to keep it on screen + if (mRect.mLeft < 0) + { + //mShiftHoriz = 0 - mRect.mLeft; + //mRect.translate( mShiftHoriz, 0 ); + mRect.translate( 0 - mRect.mLeft, 0 ); + moved = TRUE; + } + + if (mRect.mRight > menu_region_width) + { + //mShiftHoriz = menu_region_width - mRect.mRight; + //mRect.translate( mShiftHoriz, 0); + mRect.translate( menu_region_width - mRect.mRight, 0 ); + moved = TRUE; + } + + if (mRect.mBottom < 0) + { + //mShiftVert = -mRect.mBottom; + //mRect.translate( 0, mShiftVert ); + mRect.translate( 0, 0 - mRect.mBottom ); + moved = TRUE; + } + + + if (mRect.mTop > menu_region_height) + { + //mShiftVert = menu_region_height - mRect.mTop; + //mRect.translate( 0, mShiftVert ); + mRect.translate( 0, menu_region_height - mRect.mTop ); + moved = TRUE; + } + + // If we had to relocate the pie menu, put the cursor in the + // center of its rectangle + if (moved) + { + LLCoordGL center; + center.mX = (mRect.mLeft + mRect.mRight) / 2; + center.mY = (mRect.mTop + mRect.mBottom) / 2; + + LLUI::setCursorPositionLocal(getParent(), center.mX, center.mY); + } + + // FIXME: what happens when mouse buttons reversed? + mRightMouseDown = mouse_down; + mFirstMouseDown = mouse_down; + mUseInfiniteRadius = TRUE; + 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, NULL); + + 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; + + LLView::setVisible(FALSE); + + gFocusMgr.setMouseCapture(NULL, NULL); +} + +///============================================================================ +/// Class LLMenuBarGL +///============================================================================ + +// Default constructor +LLMenuBarGL::LLMenuBarGL( const LLString& name ) : LLMenuGL ( name, name ) +{ + mHorizontalLayout = TRUE; + setCanTearOff(FALSE); + mKeepFixedSize = TRUE; +} + +// 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(); + + 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) +{ + LLString name("menu"); + node->getAttributeString("name", name); + + BOOL opaque = FALSE; + node->getAttributeBOOL("opaque", opaque); + + LLMenuBarGL *menubar = new LLMenuBarGL(name); + + LLViewHandle parent_handle = LLViewHandle::sDeadHandle; + if (parent->getWidgetType() == WIDGET_TYPE_FLOATER) + { + parent_handle = ((LLFloater*)parent)->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); + // because of lazy initialization, have to disable tear off functionality + // and then re-enable with proper parent handle + if (menu->getCanTearOff()) + { + menu->setCanTearOff(FALSE); + menu->setCanTearOff(TRUE, parent_handle); + } + menubar->appendMenu(menu); + if (LLMenuGL::sDefaultMenuContainer != NULL) + { + menu->updateParent(LLMenuGL::sDefaultMenuContainer); + } + 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; +} + +void LLMenuBarGL::handleJumpKey(KEY key) +{ + navigation_key_map_t::iterator found_it = mJumpKeys.find(key); + if(found_it != mJumpKeys.end() && found_it->second->getEnabled()) + { + clearHoverItem(); + found_it->second->setHighlight(TRUE); + found_it->second->doIt(); + } +} + +// rearrange the child rects so they fit the shape of the menu bar. +void LLMenuBarGL::arrange( void ) +{ + U32 pos = 0; + LLRect rect( 0, mRect.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; + rect.mLeft = pos; + pos += item->getNominalWidth(); + rect.mRight = pos; + item->setRect( rect ); + item->buildDrawLabel(); + } +} + + +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::appendSeparator( const LLString &separator_name ) +{ + 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 ); + success &= branch->addToAcceleratorList(&mAccelerators); + success &= append( branch ); + branch->setJumpKey(branch->getJumpKey()); + return success; +} + +BOOL LLMenuBarGL::handleHover( S32 x, S32 y, MASK mask ) +{ + BOOL handled = FALSE; + LLView* active_menu = NULL; + + S32 mouse_delta_x = x - mLastMouseX; + S32 mouse_delta_y = 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() || 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)->isActive()) + { + 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(); + } + } + } + + 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 +///============================================================================ +LLMenuHolderGL::LLMenuHolderGL() +: LLPanel("Menu Holder") +{ + setMouseOpaque(FALSE); + sItemActivationTimer.stop(); + mCanHide = TRUE; +} + +LLMenuHolderGL::LLMenuHolderGL(const LLString& name, const LLRect& rect, BOOL mouse_opaque, U32 follows) +: LLPanel(name, rect, FALSE) +{ + setMouseOpaque(mouse_opaque); + sItemActivationTimer.stop(); + mCanHide = TRUE; +} + +LLMenuHolderGL::~LLMenuHolderGL() +{ +} + +EWidgetType LLMenuHolderGL::getWidgetType() const +{ + return WIDGET_TYPE_MENU_HOLDER; +} + +LLString LLMenuHolderGL::getWidgetTag() const +{ + return LL_MENU_HOLDER_GL_TAG; +} + +void LLMenuHolderGL::draw() +{ + LLView::draw(); + // now draw last selected item as overlay + LLMenuItemGL* selecteditem = (LLMenuItemGL*)LLView::getViewByHandle(sItemLastSelectedHandle); + if (selecteditem && 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; +} + +void LLMenuHolderGL::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + if (width != mRect.getWidth() || height != mRect.getHeight()) + { + hideMenus(); + } + LLView::reshape(width, height, called_from_parent); +} + +BOOL LLMenuHolderGL::hasVisibleMenu() +{ + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + if (viewp->getVisible()) + { + return TRUE; + } + } + return FALSE; +} + +BOOL LLMenuHolderGL::hideMenus() +{ + if (!mCanHide) + { + return FALSE; + } + BOOL menu_visible = hasVisibleMenu(); + if (menu_visible) + { + // clicked off of menu, hide them all + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + if (viewp->getVisible()) + { + viewp->setVisible(FALSE); + } + } + } + //if (gFocusMgr.childHasKeyboardFocus(this)) + //{ + // gFocusMgr.setKeyboardFocus(NULL, NULL); + //} + + return menu_visible; +} + +void LLMenuHolderGL::setActivatedItem(LLMenuItemGL* item) +{ + sItemLastSelectedHandle = item->mViewHandle; + 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) +{ + LLRect rect; + menup->localRectToOtherView(LLRect(-1, menup->getRect().getHeight(), menup->getRect().getWidth() + 3, 0), &rect, gFloaterView); + mTargetHeight = (F32)(rect.getHeight() + LLFLOATER_HEADER_SIZE + 5); + reshape(rect.getWidth(), rect.getHeight()); + setRect(rect); + mOldParent = menup->getParent(); + mOldParent->removeChild(menup); + + menup->setFollowsAll(); + addChild(menup); + menup->setVisible(TRUE); + menup->translate(-menup->getRect().mLeft + 1, -menup->getRect().mBottom + 1); + + menup->setTornOff(TRUE); + menup->setDropShadowed(FALSE); + + mMenu = menup; + + // highlight first item (tear off item will be disabled) + mMenu->highlightNextItem(NULL); +} + +LLTearOffMenu::~LLTearOffMenu() +{ +} + +void LLTearOffMenu::draw() +{ + if (hasFocus()) + { + 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; + } + } + } + + mMenu->setBackgroundVisible(mBgOpaque); + mMenu->arrange(); + + if (mRect.getHeight() != mTargetHeight) + { + // animate towards target height + reshape(mRect.getWidth(), llceil(lerp((F32)mRect.getHeight(), mTargetHeight, LLCriticalDamp::getInterpolant(0.05f)))); + } + else + { + // when in stasis, remain big enough to hold menu contents + mTargetHeight = (F32)(mMenu->getRect().getHeight() + LLFLOATER_HEADER_SIZE + 4); + reshape(mMenu->getRect().getWidth() + 3, mMenu->getRect().getHeight() + LLFLOATER_HEADER_SIZE + 5); + } + LLFloater::draw(); +} + +void LLTearOffMenu::onFocusReceived() +{ + // if nothing is highlighted, just highlight first item + if (!mMenu->getHighlightedItem()) + { + mMenu->highlightNextItem(NULL); + } +} + +void LLTearOffMenu::onFocusLost() +{ + // remove highlight from parent item and our own menu + mMenu->clearHoverItem(); +} + +//static +LLTearOffMenu* LLTearOffMenu::create(LLMenuGL* menup) +{ + LLTearOffMenu* tearoffp = new LLTearOffMenu(menup); + // keep onscreen + gFloaterView->adjustToFitScreen(tearoffp, FALSE); + tearoffp->open(); + 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(); +} + +///============================================================================ +/// Class LLEditMenuHandlerMgr +///============================================================================ +LLEditMenuHandlerMgr& LLEditMenuHandlerMgr::getInstance() +{ + static LLEditMenuHandlerMgr instance; + return instance; +} + +LLEditMenuHandlerMgr::LLEditMenuHandlerMgr() +{ +} + +LLEditMenuHandlerMgr::~LLEditMenuHandlerMgr() +{ +} + +///============================================================================ +/// Local function definitions +///============================================================================ diff --git a/indra/llui/llmenugl.h b/indra/llui/llmenugl.h new file mode 100644 index 0000000000..84cbf13b69 --- /dev/null +++ b/indra/llui/llmenugl.h @@ -0,0 +1,728 @@ +/** + * @file llmenugl.h + * @brief Declaration of the opengl based menu system. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLMENUGL_H +#define LL_LLMENUGL_H + +#include <list> + +#include "llstring.h" +#include "v4color.h" +#include "llframetimer.h" +#include "llevent.h" + +#include "llkeyboard.h" +#include "llfloater.h" +#include "lluistring.h" +#include "llview.h" + +class LLMenuItemGL; + +extern S32 MENU_BAR_HEIGHT; +extern S32 MENU_BAR_WIDTH; + +// These callbacks are used by the LLMenuItemCallGL and LLMenuItemCheckGL +// classes during their work. +typedef void (*menu_callback)(void*); + +// These callbacks are used by the LLMenuItemCallGL +// classes during their work. +typedef void (*on_disabled_callback)(void*); + +// This callback is used by the LLMenuItemCallGL and LLMenuItemCheckGL +// to determine if the current menu is enabled. +typedef BOOL (*enabled_callback)(void*); + +// This callback is used by LLMenuItemCheckGL to determine it's +// 'checked' state. +typedef BOOL (*check_callback)(void*); + +// This callback is potentially used by LLMenuItemCallGL. If provided, +// this function is called whenever it's time to determine the label's +// contents. Put the contents of the label in the provided parameter. +typedef void (*label_callback)(LLString&,void*); + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemGL +// +// The LLMenuItemGL represents a single menu item in a menu. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLFontGL; +class LLMenuGL; + + +class LLMenuItemGL : public LLView +{ +public: + LLMenuItemGL( const LLString& name, const LLString& label, KEY key = KEY_NONE, MASK = MASK_NONE ); + + virtual void setValue(const LLSD& value) { setLabel(value.asString()); } + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_MENU_ITEM; } + virtual LLString getWidgetTag() const { return LL_MENU_ITEM_TAG; } + + virtual LLXMLNodePtr getXML(bool save_children = true) const; + + virtual LLString getType() const { return "item"; } + + virtual BOOL handleKey(KEY key, MASK mask, BOOL called_from_parent); + virtual BOOL handleHover(S32 x, S32 y, MASK mask); + + virtual BOOL handleAcceleratorKey(KEY key, MASK mask); + + BOOL getHighlight() const { return mHighlight; } + + void setJumpKey(KEY key); + KEY getJumpKey(); + + // set the font used by this item. + void setFont(LLFontGL* font); + void setFontStyle(U8 style) { mStyle = style; } + + // returns the height in pixels for the current font. + virtual U32 getNominalHeight( void ); + + // functions to control the color scheme + static void setEnabledColor( const LLColor4& color ); + static void setDisabledColor( const LLColor4& color ); + static void setHighlightBGColor( const LLColor4& color ); + static void setHighlightFGColor( const LLColor4& color ); + + // Marks item as not needing space for check marks or accelerator keys + virtual void setBriefItem(BOOL brief); + + virtual BOOL addToAcceleratorList(std::list<LLKeyBinding*> *listp); + void setAllowKeyRepeat(BOOL allow) { mAllowKeyRepeat = allow; } + + // return the name label + LLString getLabel( void ) const { return mLabel.getString(); } + + // change the label + void setLabel( const LLString& label ); + virtual BOOL setLabelArg( const LLString& key, const LLString& text ); + + // Get the parent menu for this item + virtual LLMenuGL* getMenu(); + + // 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 ); + + // buildDrawLabel() - constructs the string used during the draw() + // function. This reduces the overall string manipulation, but can + // lead to visual errors if the state of the object changes + // without the knowledge of the menu item. For example, if a + // boolean being watched is changed outside of the menu item's + // doIt() function, the draw buffer will not be updated and will + // reflect the wrong value. If this ever becomes an issue, there + // are ways to fix this. + // Returns the enabled state of the item. + virtual void buildDrawLabel( void ); + + // for branching menu items, bring sub menus up to root level of menu hierarchy + virtual void updateBranchParent( LLView* parentp ){}; + + // doIt() - do the primary funcationality of the menu item. + virtual void doIt( void ) = 0; + + // set the hover status (called by it's menu) + virtual void setHighlight( BOOL highlight ); + + // determine if this object is active + virtual BOOL isActive( void ) const; + + virtual void setEnabledSubMenus(BOOL enable){}; + + // LLView Functionality + virtual BOOL handleKeyHere( KEY key, MASK mask, BOOL called_from_parent ); + virtual BOOL handleMouseUp( S32 x, S32 y, MASK mask ); + virtual void draw( void ); + + BOOL getDrawTextDisabled() const { return mDrawTextDisabled; } + +protected: + // This function appends the character string representation of + // the current accelerator key and mask to the provided string. + void appendAcceleratorString( LLString& st ); + +public: + static LLColor4 sEnabledColor; + static LLColor4 sDisabledColor; + static LLColor4 sHighlightBackground; + static LLColor4 sHighlightForeground; + +protected: + static BOOL sDropShadowText; + + // mLabel contains the actual label specified by the user. + LLUIString mLabel; + + // The draw labels contain some of the labels that we draw during + // the draw() routine. This optimizes away some of the string + // manipulation. + LLUIString mDrawBoolLabel; + LLUIString mDrawAccelLabel; + LLUIString mDrawBranchLabel; + + // Keyboard and mouse variables + KEY mJumpKey; + KEY mAcceleratorKey; + MASK mAcceleratorMask; + BOOL mAllowKeyRepeat; + BOOL mHighlight; + BOOL mGotHover; + + // If true, suppress normal space for check marks on the left and accelerator + // keys on the right. + BOOL mBriefItem; + + // Font for this item + LLFontGL* mFont; + + U8 mStyle; + BOOL mDrawTextDisabled; +}; + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemCallGL +// +// The LLMenuItemCallerGL represents a single menu item in a menu that +// calls a user defined callback. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMenuItemCallGL : public LLMenuItemGL +{ +protected: + menu_callback mCallback; + // mEnabledCallback should return TRUE if the item should be enabled + enabled_callback mEnabledCallback; + label_callback mLabelCallback; + void* mUserData; + on_disabled_callback mOnDisabledCallback; + +public: + + + void setMenuCallback(menu_callback callback, void* data) { mCallback = callback; mUserData = data; }; + void setEnabledCallback(enabled_callback callback) { mEnabledCallback = callback; }; + + // normal constructor + LLMenuItemCallGL( const LLString& name, + menu_callback clicked_cb, + enabled_callback enabled_cb = NULL, + void* user_data = NULL, + KEY key = KEY_NONE, MASK mask = MASK_NONE, + BOOL enabled = TRUE, + on_disabled_callback on_disabled_cb = NULL); + LLMenuItemCallGL( const LLString& name, + const LLString& label, + menu_callback clicked_cb, + enabled_callback enabled_cb = NULL, + void* user_data = NULL, + KEY key = KEY_NONE, MASK mask = MASK_NONE, + BOOL enabled = TRUE, + on_disabled_callback on_disabled_cb = NULL); + + // constructor for when you want to trap the arrange method. + LLMenuItemCallGL( const LLString& name, + const LLString& label, + menu_callback clicked_cb, + enabled_callback enabled_cb, + label_callback label_cb, + void* user_data, + KEY key = KEY_NONE, MASK mask = MASK_NONE, + BOOL enabled = TRUE, + on_disabled_callback on_disabled_c = NULL); + LLMenuItemCallGL( const LLString& name, + menu_callback clicked_cb, + enabled_callback enabled_cb, + label_callback label_cb, + void* user_data, + KEY key = KEY_NONE, MASK mask = MASK_NONE, + BOOL enabled = TRUE, + on_disabled_callback on_disabled_c = NULL); + virtual LLXMLNodePtr getXML(bool save_children = true) const; + + virtual LLString getType() const { return "call"; } + + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + void setEnabledControl(LLString enabled_control, LLView *context); + void setVisibleControl(LLString enabled_control, LLView *context); + + virtual void setUserData(void *userdata) { mUserData = userdata; } + + // called to rebuild the draw label + virtual void buildDrawLabel( void ); + + // doIt() - do the primary funcationality of the menu item. + virtual void doIt( void ); + + virtual BOOL handleAcceleratorKey(KEY key, MASK mask); + + //virtual void draw(); + + virtual bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata); +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemCheckGL +// +// The LLMenuItemCheckGL is an extension of the LLMenuItemCallGL +// class, by allowing another method to be specified which determines +// if the menu item should consider itself checked as true or not. Be +// careful that the check callback provided - it needs to be VERY +// FUCKING EFFICIENT, because it may need to be checked a lot. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMenuItemCheckGL +: public LLMenuItemCallGL +{ +protected: + check_callback mCheckCallback; + BOOL mChecked; + +public: + LLMenuItemCheckGL( const LLString& name, + const LLString& label, + menu_callback callback, + enabled_callback enabled_cb, + check_callback check, + void* user_data, + KEY key = KEY_NONE, MASK mask = MASK_NONE ); + LLMenuItemCheckGL( const LLString& name, + menu_callback callback, + enabled_callback enabled_cb, + check_callback check, + void* user_data, + KEY key = KEY_NONE, MASK mask = MASK_NONE ); + LLMenuItemCheckGL( const LLString& name, + const LLString& label, + menu_callback callback, + enabled_callback enabled_cb, + LLString control_name, + LLView *context, + void* user_data, + KEY key = KEY_NONE, MASK mask = MASK_NONE ); + virtual LLXMLNodePtr getXML(bool save_children = true) const; + + void setCheckedControl(LLString checked_control, LLView *context); + + virtual void setValue(const LLSD& value) { mChecked = value.asBoolean(); } + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + virtual LLString getType() const { return "check"; } + + // called to rebuild the draw label + virtual void buildDrawLabel( void ); + + virtual bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata); + + // LLView Functionality + //virtual void draw( void ); +}; + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemToggleGL +// +// The LLMenuItemToggleGL is a menu item that wraps around a user +// specified and controlled boolean. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMenuItemToggleGL : public LLMenuItemGL +{ +protected: + BOOL* mToggle; + +public: + LLMenuItemToggleGL( const LLString& name, const LLString& label, + BOOL* toggle, + KEY key = KEY_NONE, MASK mask = MASK_NONE ); + + LLMenuItemToggleGL( const LLString& name, + BOOL* toggle, + KEY key = KEY_NONE, MASK mask = MASK_NONE ); + + virtual LLString getType() const { return "toggle"; } + + // called to rebuild the draw label + virtual void buildDrawLabel( void ); + + // doIt() - do the primary funcationality of the menu item. + virtual void doIt( void ); + + // LLView Functionality + //virtual void draw( void ); +}; + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuGL +// +// The Menu class represents a normal rectangular menu somewhere on +// screen. A Menu can have menu items (described above) or sub-menus +// attached to it. Sub-menus are implemented via a specialized +// menu-item type known as a branch. Since it's easy to do wrong, I've +// taken the branch functionality out of public view, and encapsulate +// it in the appendMenu() method. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMenuArrowGL; +class LLMenuItemBranchGL; +class LLMenuItemTearOffGL; + +class LLMenuGL +: public LLUICtrl +{ +public: + LLMenuGL( const LLString& name, const LLString& label, LLViewHandle parent_floater = LLViewHandle::sDeadHandle ); + LLMenuGL( const LLString& label, LLViewHandle parent_floater = LLViewHandle::sDeadHandle ); + virtual ~LLMenuGL( void ); + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + + void parseChildXML(LLXMLNodePtr child, LLView *parent, LLUICtrlFactory *factory); + + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_MENU; } + virtual LLString getWidgetTag() const { return LL_MENU_GL_TAG; } + + // LLView Functionality + virtual BOOL handleKey( KEY key, MASK mask, BOOL called_from_parent ); + virtual BOOL handleKeyHere( KEY key, MASK mask, BOOL called_from_parent ); + virtual BOOL handleHover( S32 x, S32 y, MASK mask ); + virtual BOOL handleMouseUp( S32 x, S32 y, MASK mask ); + virtual void draw( void ); + virtual void drawBackground(LLMenuItemGL* itemp, LLColor4& color); + virtual void setVisible(BOOL visible); + + virtual BOOL LLMenuGL::handleAcceleratorKey(KEY key, MASK mask); + + LLMenuGL* getChildMenuByName(const LLString& name, BOOL recurse) const; + + BOOL clearHoverItem(BOOL include_active = TRUE); + + // return the name label + const LLString& getLabel( void ) const { return mLabel.getString(); } + void setLabel(const LLString& label) { mLabel = label; } + + static void setDefaultBackgroundColor( const LLColor4& color ); + void setBackgroundColor( const LLColor4& color ); + void setBackgroundVisible( BOOL b ) { mBgVisible = b; } + void setCanTearOff(BOOL tear_off, LLViewHandle parent_floater_handle = LLViewHandle::sDeadHandle); + + // Add the menu item to this menu. + virtual BOOL append( LLMenuItemGL* item ); + + // add a separator to this menu + virtual BOOL appendSeparator( const LLString &separator_name = "separator" ); + + // add a menu - this will create a cascading menu + virtual BOOL appendMenu( LLMenuGL* menu ); + + // for branching menu items, bring sub menus up to root level of menu hierarchy + virtual void updateParent( LLView* parentp ); + + // setItemEnabled() - pass the name and the enable flag for a + // menu item. TRUE will make sure it's enabled, FALSE will disable + // it. + void setItemEnabled( const LLString& name, BOOL enable ); + + // propagate message to submenus + void setEnabledSubMenus(BOOL enable); + + void setItemVisible( const LLString& name, BOOL visible); + + // sets the left,bottom corner of menu, useful for popups + void setLeftAndBottom(S32 left, S32 bottom); + + virtual void handleJumpKey(KEY key); + + // Shape this menu to fit the current state of the children, and + // adjust the child rects to fit. This is called automatically + // when you add items. *FIX: We may need to deal with visibility + // arrangement. + virtual void arrange( void ); + + // remove all items on the menu + void empty( void ); + + // Rearrange the components, and do the right thing if the menu doesn't + // fit in the bounds. + // virtual void arrangeWithBounds(LLRect bounds); + + void setItemLastSelected(LLMenuItemGL* item); // must be in menu + U32 getItemCount(); // number of menu items + LLMenuItemGL* getItem(S32 number); // 0 = first item + LLMenuItemGL* getHighlightedItem(); + + LLMenuItemGL* highlightNextItem(LLMenuItemGL* cur_item, BOOL skip_disabled = TRUE); + LLMenuItemGL* highlightPrevItem(LLMenuItemGL* cur_item, BOOL skip_disabled = TRUE); + + void buildDrawLabels(); + void createJumpKeys(); + + // Show popup in global screen space based on last mouse location. + static void showPopup(LLMenuGL* menu); + + // Show popup at a specific location. + static void showPopup(LLView* spawning_view, LLMenuGL* menu, S32 x, S32 y); + + // Whether to drop shadow menu bar + void setDropShadowed( const BOOL shadowed ); + + void setParentMenuItem( LLMenuItemGL* parent_menu_item ) { mParentMenuItem = parent_menu_item; } + LLMenuItemGL* getParentMenuItem() { return mParentMenuItem; } + + void setTornOff(BOOL torn_off); + BOOL getTornOff() { return mTornOff; } + + BOOL getCanTearOff() { return mTearOffItem != NULL; } + + KEY getJumpKey() { return mJumpKey; } + void setJumpKey(KEY key) { mJumpKey = key; } + + static void onFocusLost(LLView* old_focus); + + static LLView *sDefaultMenuContainer; + +protected: + void createSpilloverBranch(); + void cleanupSpilloverBranch(); + +protected: + static LLColor4 sDefaultBackgroundColor; + + LLColor4 mBackgroundColor; + BOOL mBgVisible; + typedef std::list< LLMenuItemGL* > item_list_t; + item_list_t mItems; + typedef std::map<KEY, LLMenuItemGL*> navigation_key_map_t; + navigation_key_map_t mJumpKeys; + LLMenuItemGL* mParentMenuItem; + LLUIString mLabel; + BOOL mDropShadowed; // Whether to drop shadow + BOOL mHorizontalLayout; + BOOL mKeepFixedSize; + BOOL mHasSelection; + LLFrameTimer mFadeTimer; + S32 mLastMouseX; + S32 mLastMouseY; + S32 mMouseVelX; + S32 mMouseVelY; + BOOL mTornOff; + LLMenuItemTearOffGL* mTearOffItem; + LLMenuItemBranchGL* mSpilloverBranch; + LLMenuGL* mSpilloverMenu; + LLViewHandle mParentFloaterHandle; + KEY mJumpKey; +}; + +//----------------------------------------------------------------------------- +// class LLPieMenu +// A circular menu of items, icons, etc. +//----------------------------------------------------------------------------- + +class LLPieMenu +: public LLMenuGL +{ +public: + LLPieMenu(const LLString& name, const LLString& label); + LLPieMenu(const LLString& name); + virtual ~LLPieMenu(); + + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + void initXML(LLXMLNodePtr node, LLView *context, LLUICtrlFactory *factory); + + // LLView Functionality + // can't set visibility directly, must call show or hide + virtual void setVisible(BOOL visible); + + //virtual BOOL handleKey( KEY key, MASK mask, BOOL called_from_parent ); + virtual BOOL handleHover( S32 x, S32 y, MASK mask ); + virtual BOOL handleMouseDown( S32 x, S32 y, MASK mask ); + virtual BOOL handleRightMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleRightMouseUp( S32 x, S32 y, MASK mask ); + virtual BOOL handleMouseUp( S32 x, S32 y, MASK mask ); + virtual void draw(); + virtual void drawBackground(LLMenuItemGL* itemp, LLColor4& color); + + virtual BOOL append(LLMenuItemGL* item); + virtual BOOL appendSeparator( const LLString &separator_name = "separator" ); + + // the enabled callback is meant for the submenu. The api works + // this way because the menu branch item responsible for the pie + // submenu is constructed here. + virtual BOOL appendMenu(LLPieMenu *menu, + enabled_callback enabled_cb = NULL, + void* user_data = NULL ); + virtual void arrange( void ); + + // Display the menu centered on this point on the screen. + void show(S32 x, S32 y, BOOL mouse_down); + void hide(BOOL item_selected); + +protected: + LLMenuItemGL *pieItemFromXY(S32 x, S32 y); + S32 pieItemIndexFromXY(S32 x, S32 y); + +private: + // These cause menu items to be spuriously selected by right-clicks + // near the window edge at low frame rates. I don't think they are + // needed unless you shift the menu position in the draw() function. JC + //S32 mShiftHoriz; // non-zero if menu had to shift this frame + //S32 mShiftVert; // non-zero if menu had to shift this frame + BOOL mFirstMouseDown; // true from show until mouse up + BOOL mUseInfiniteRadius; // allow picking pie menu items anywhere outside of center circle + LLMenuItemGL* mHoverItem; + BOOL mHoverThisFrame; + LLFrameTimer mShrinkBorderTimer; + F32 mOuterRingAlpha; // for rendering pie menus as both bounded and unbounded + F32 mCurRadius; + BOOL mRightMouseDown; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuBarGL +// +// A menu bar displays menus horizontally. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMenuBarGL : public LLMenuGL +{ +protected: + std::list <LLKeyBinding*> mAccelerators; + +public: + LLMenuBarGL( const LLString& name ); + virtual ~LLMenuBarGL(); + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_MENU_BAR; } + virtual LLString getWidgetTag() const { return LL_MENU_BAR_GL_TAG; } + + // rearrange the child rects so they fit the shape of the menu + // bar. + virtual void handleJumpKey(KEY key); + virtual void arrange( void ); + + // add a vertical separator to this menu + virtual BOOL appendSeparator( const LLString &separator_name = "separator" ); + + // add a menu - this will create a drop down menu. + virtual BOOL appendMenu( LLMenuGL* menu ); + + // LLView Functionality + virtual BOOL handleHover( S32 x, S32 y, MASK mask ); + + // Returns x position of rightmost child, usually Help menu + S32 getRightmostMenuEdge(); +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuHolderGL +// +// High level view that serves as parent for all menus +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLMenuHolderGL : public LLPanel +{ +public: + LLMenuHolderGL(); + LLMenuHolderGL(const LLString& name, const LLRect& rect, BOOL mouse_opaque, U32 follows = FOLLOWS_NONE); + virtual ~LLMenuHolderGL(); + + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + virtual BOOL hideMenus(); + void reshape(S32 width, S32 height, BOOL called_from_parent); + void setCanHide(BOOL can_hide) { mCanHide = can_hide; } + + // LLView functionality + virtual void draw(); + virtual BOOL handleMouseDown( S32 x, S32 y, MASK mask ); + virtual BOOL handleRightMouseDown( S32 x, S32 y, MASK mask ); + + static void setActivatedItem(LLMenuItemGL* item); +protected: + BOOL hasVisibleMenu(); + + static LLViewHandle sItemLastSelectedHandle; + static LLFrameTimer sItemActivationTimer; + + BOOL mCanHide; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLTearOffMenu +// +// Floater that hosts a menu +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLTearOffMenu : public LLFloater +{ +public: + static LLTearOffMenu* create(LLMenuGL* menup); + virtual ~LLTearOffMenu(); + virtual void onClose(bool app_quitting); + virtual void draw(void); + virtual void onFocusReceived(); + virtual void onFocusLost(); + +protected: + LLTearOffMenu(LLMenuGL* menup); + +protected: + LLView* mOldParent; + LLMenuGL* mMenu; + F32 mTargetHeight; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemTearOffGL +// +// This class represents a separator. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMenuItemTearOffGL : public LLMenuItemGL +{ +public: + LLMenuItemTearOffGL( LLViewHandle parent_floater_handle = (LLViewHandle)LLViewHandle::sDeadHandle ); + + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + virtual LLString getType() const { return "tearoff_menu"; } + + virtual void doIt(void); + virtual void draw(void); + virtual U32 getNominalHeight(); + +protected: + LLViewHandle mParentHandle; +}; + + +//FIXME: this is currently working, so finish implementation +class LLEditMenuHandlerMgr +{ +public: + LLEditMenuHandlerMgr& getInstance(); + virtual ~LLEditMenuHandlerMgr(); +protected: + LLEditMenuHandlerMgr(); + +}; + +#endif // LL_LLMENUGL_H diff --git a/indra/llui/llmodaldialog.cpp b/indra/llui/llmodaldialog.cpp new file mode 100644 index 0000000000..4eaf6b7559 --- /dev/null +++ b/indra/llui/llmodaldialog.cpp @@ -0,0 +1,288 @@ +/** + * @file llmodaldialog.cpp + * @brief LLModalDialog base class + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llmodaldialog.h" + +#include "llfocusmgr.h" +#include "v4color.h" +#include "v2math.h" +#include "llui.h" +#include "llwindow.h" +#include "llkeyboard.h" + +// static +std::list<LLModalDialog*> LLModalDialog::sModalStack; + +LLModalDialog::LLModalDialog( const LLString& title, S32 width, S32 height, BOOL modal ) + : LLFloater( "modal container", + LLRect( 0, height, width, 0 ), + title, + FALSE, // resizable + DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT, + FALSE, // drag_on_left + modal ? FALSE : TRUE, // minimizable + modal ? FALSE : TRUE, // close button + TRUE), // bordered + mModal( modal ) +{ + setVisible( FALSE ); + setBackgroundVisible(TRUE); + setBackgroundOpaque(TRUE); + centerOnScreen(); // default position +} + +LLModalDialog::~LLModalDialog() +{ + // don't unlock focus unless we have it + if (gFocusMgr.childHasKeyboardFocus(this)) + { + gFocusMgr.unlockFocus(); + } +} + +void LLModalDialog::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + LLFloater::reshape(width, height, called_from_parent); + centerOnScreen(); +} + +void LLModalDialog::startModal() +{ + if (mModal) + { + // If Modal, Hide the active modal dialog + if (!sModalStack.empty()) + { + LLModalDialog* front = sModalStack.front(); + front->setVisible(FALSE); + } + + // This is a modal dialog. It sucks up all mouse and keyboard operations. + gFocusMgr.setMouseCapture( this, NULL ); + gFocusMgr.setTopView( this, NULL ); + setFocus(TRUE); + + sModalStack.push_front( this ); + } + + setVisible( TRUE ); +} + +void LLModalDialog::stopModal() +{ + gFocusMgr.unlockFocus(); + gFocusMgr.releaseFocusIfNeeded( this ); + + if (mModal) + { + std::list<LLModalDialog*>::iterator iter = std::find(sModalStack.begin(), sModalStack.end(), this); + if (iter != sModalStack.end()) + { + sModalStack.erase(iter); + } + else + { + llwarns << "LLModalDialog::stopModal not in list!" << llendl; + } + } + if (!sModalStack.empty()) + { + LLModalDialog* front = sModalStack.front(); + front->setVisible(TRUE); + } +} + + +void LLModalDialog::setVisible( BOOL visible ) +{ + if (mModal) + { + if( visible ) + { + // This is a modal dialog. It sucks up all mouse and keyboard operations. + gFocusMgr.setMouseCapture( this, NULL ); + + // The dialog view is a root view + gFocusMgr.setTopView( this, NULL ); + setFocus( TRUE ); + } + else + { + gFocusMgr.releaseFocusIfNeeded( this ); + } + } + + LLFloater::setVisible( visible ); +} + +BOOL LLModalDialog::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if (!LLFloater::handleMouseDown(x, y, mask)) + { + if (mModal) + { + // Click was outside the panel + make_ui_sound("UISndInvalidOp"); + } + } + return TRUE; +} + +BOOL LLModalDialog::handleHover(S32 x, S32 y, MASK mask) +{ + if( childrenHandleHover(x, y, mask) == NULL ) + { + getWindow()->setCursor(UI_CURSOR_ARROW); + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << llendl; + } + return TRUE; +} + +BOOL LLModalDialog::handleMouseUp(S32 x, S32 y, MASK mask) +{ + childrenHandleMouseUp(x, y, mask); + return TRUE; +} + +BOOL LLModalDialog::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + childrenHandleScrollWheel(x, y, clicks); + return TRUE; +} + +BOOL LLModalDialog::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + if (!LLFloater::handleDoubleClick(x, y, mask)) + { + // Click outside the panel + make_ui_sound("UISndInvalidOp"); + } + return TRUE; +} + +BOOL LLModalDialog::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + childrenHandleRightMouseDown(x, y, mask); + return TRUE; +} + + +BOOL LLModalDialog::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent ) +{ + childrenHandleKey(key, mask); + + LLFloater::handleKeyHere(key, mask, called_from_parent ); + + if (mModal) + { + // Suck up all keystokes except CTRL-Q. + BOOL is_quit = ('Q' == key) && (MASK_CONTROL == mask); + return !is_quit; + } + else + { + // don't process escape key until message box has been on screen a minimal amount of time + // to avoid accidentally destroying the message box when user is hitting escape at the time it appears + BOOL enough_time_elapsed = mVisibleTime.getElapsedTimeF32() > 1.0f; + if (enough_time_elapsed && key == KEY_ESCAPE) + { + close(); + return TRUE; + } + return FALSE; + } +} + +void LLModalDialog::onClose(bool app_quitting) +{ + stopModal(); + LLFloater::onClose(app_quitting); +} + +// virtual +void LLModalDialog::draw() +{ + if (getVisible()) + { + LLColor4 shadow_color = LLUI::sColorsGroup->getColor("ColorDropShadow"); + S32 shadow_lines = LLUI::sConfigGroup->getS32("DropShadowFloater"); + + gl_drop_shadow( 0, mRect.getHeight(), mRect.getWidth(), 0, + shadow_color, shadow_lines); + + LLFloater::draw(); + + if (mModal) + { + // If we've lost focus to a non-child, get it back ASAP. + if( gFocusMgr.getTopView() != this ) + { + gFocusMgr.setTopView( this, NULL); + } + + if( !gFocusMgr.childHasKeyboardFocus( this ) ) + { + setFocus(TRUE); + } + + if( !gFocusMgr.childHasMouseCapture( this ) ) + { + gFocusMgr.setMouseCapture( this, NULL ); + } + } + } +} + +void LLModalDialog::centerOnScreen() +{ + LLVector2 window_size = LLUI::getWindowSize(); + + S32 dialog_left = (llround(window_size.mV[VX]) - mRect.getWidth()) / 2; + S32 dialog_bottom = (llround(window_size.mV[VY]) - mRect.getHeight()) / 2; + + translate( dialog_left - mRect.mLeft, dialog_bottom - mRect.mBottom ); +} + + +// static +void LLModalDialog::onAppFocusLost() +{ + if( !sModalStack.empty() ) + { + LLModalDialog* instance = LLModalDialog::sModalStack.front(); + if( gFocusMgr.childHasMouseCapture( instance ) ) + { + gFocusMgr.setMouseCapture( NULL, NULL ); + } + + if( gFocusMgr.childHasKeyboardFocus( instance ) ) + { + gFocusMgr.setKeyboardFocus( NULL, NULL ); + } + } +} + +// static +void LLModalDialog::onAppFocusGained() +{ + if( !sModalStack.empty() ) + { + LLModalDialog* instance = LLModalDialog::sModalStack.front(); + + // This is a modal dialog. It sucks up all mouse and keyboard operations. + gFocusMgr.setMouseCapture( instance, NULL ); + instance->setFocus(TRUE); + gFocusMgr.setTopView( instance, NULL ); + + instance->centerOnScreen(); + } +} + + diff --git a/indra/llui/llmodaldialog.h b/indra/llui/llmodaldialog.h new file mode 100644 index 0000000000..b97e95d12c --- /dev/null +++ b/indra/llui/llmodaldialog.h @@ -0,0 +1,60 @@ +/** + * @file llmodaldialog.h + * @brief LLModalDialog base class + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLMODALDIALOG_H +#define LL_LLMODALDIALOG_H + +#include "llfloater.h" +#include "llframetimer.h" + +class LLModalDialog; + +// By default, a ModalDialog is modal, i.e. no other window can have focus +// However, for the sake of code reuse and simplicity, if mModal == false, +// the dialog behaves like a normal floater + +class LLModalDialog : public LLFloater +{ +public: + LLModalDialog( const LLString& title, S32 width, S32 height, BOOL modal = true ); + /*virtual*/ ~LLModalDialog(); + + /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = 1); + + /*virtual*/ void startModal(); + /*virtual*/ void stopModal(); + + /*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleMouseUp(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleHover(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleScrollWheel(S32 x, S32 y, S32 clicks); + /*virtual*/ BOOL handleDoubleClick(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleRightMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent ); + + /*virtual*/ void onClose(bool app_quitting); + + /*virtual*/ void setVisible(BOOL visible); + /*virtual*/ void draw(); + + static void onAppFocusLost(); + static void onAppFocusGained(); + + static S32 activeCount() { return sModalStack.size(); } + +protected: + void centerOnScreen(); + +protected: + LLFrameTimer mVisibleTime; + BOOL mModal; // do not change this after creation! + + static std::list<LLModalDialog*> sModalStack; // Top of stack is currently being displayed +}; + +#endif // LL_LLMODALDIALOG_H diff --git a/indra/llui/llpanel.cpp b/indra/llui/llpanel.cpp new file mode 100644 index 0000000000..91bf6befe7 --- /dev/null +++ b/indra/llui/llpanel.cpp @@ -0,0 +1,1030 @@ +/** + * @file llpanel.cpp + * @brief LLPanel base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// Opaque view with a background and a border. Can contain LLUICtrls. + +#include "linden_common.h" + +#include "llpanel.h" + +#include "llalertdialog.h" +#include "llfocusmgr.h" +#include "llfontgl.h" +#include "llrect.h" +#include "llerror.h" +#include "lltimer.h" + +#include "llmenugl.h" +//#include "llstatusbar.h" +#include "llui.h" +#include "llkeyboard.h" +#include "lllineeditor.h" +#include "llcontrol.h" +#include "lltextbox.h" +#include "lluictrl.h" +#include "lluictrlfactory.h" +#include "llviewborder.h" +#include "llbutton.h" + +LLPanel::panel_map_t LLPanel::sPanelMap; +LLPanel::alert_queue_t LLPanel::sAlertQueue; + +void LLPanel::init() +{ + // mRectControl + mBgColorAlpha = LLUI::sColorsGroup->getColor( "DefaultBackgroundColor" ); + mBgColorOpaque = LLUI::sColorsGroup->getColor( "FocusBackgroundColor" ); + mDefaultBtnHighlight = LLUI::sColorsGroup->getColor( "DefaultHighlightLight" ); + mBgVisible = FALSE; + mBgOpaque = FALSE; + mBorder = NULL; + mDefaultBtn = NULL; + setIsChrome(FALSE); //is this a decorator to a live window or a form? + mLastTabGroup = 0; + + // add self to handle->panel map + sPanelMap[mViewHandle] = this; + setTabStop(FALSE); +} + +LLPanel::LLPanel() +: mRectControl() +{ + init(); +} + +LLPanel::LLPanel(const LLString& name) +: LLUICtrl(name, LLRect(0, 0, 0, 0), TRUE, NULL, NULL), + mRectControl() +{ + init(); +} + + +LLPanel::LLPanel(const LLString& name, const LLRect& rect, BOOL bordered) +: LLUICtrl(name, rect, TRUE, NULL, NULL), + mRectControl() +{ + init(); + if (bordered) + { + addBorder(); + } +} + + +LLPanel::LLPanel(const LLString& name, const LLString& rect_control, BOOL bordered) +: LLUICtrl(name, LLUI::sConfigGroup->getRect(rect_control), TRUE, NULL, NULL), + mRectControl( rect_control ) +{ + init(); + if (bordered) + { + addBorder(); + } +} + +void LLPanel::addBorder(LLViewBorder::EBevel border_bevel, + LLViewBorder::EStyle border_style, S32 border_thickness) +{ + mBorder = new LLViewBorder( "panel border", + LLRect(0, mRect.getHeight(), mRect.getWidth(), 0), + border_bevel, border_style, border_thickness ); + mBorder->setSaveToXML(false); + addChild( mBorder ); +} + + +LLPanel::~LLPanel() +{ + if( !mRectControl.empty() ) + { + LLUI::sConfigGroup->setRect( mRectControl, mRect ); + } + sPanelMap.erase(mViewHandle); +} + + +// virtual +EWidgetType LLPanel::getWidgetType() const +{ + return WIDGET_TYPE_PANEL; +} + +// virtual +LLString LLPanel::getWidgetTag() const +{ + return LL_PANEL_TAG; +} + +// virtual +BOOL LLPanel::isPanel() +{ + return TRUE; +} + +// virtual +BOOL LLPanel::postBuild() +{ + return TRUE; +} + +// virtual +void LLPanel::clearCtrls() +{ + LLView::ctrl_list_t ctrls = getCtrlList(); + for (LLView::ctrl_list_t::iterator ctrl_it = ctrls.begin(); ctrl_it != ctrls.end(); ++ctrl_it) + { + LLUICtrl* ctrl = *ctrl_it; + ctrl->setFocus( FALSE ); + ctrl->setEnabled( FALSE ); + ctrl->clear(); + } +} + +void LLPanel::setCtrlsEnabled( BOOL b ) +{ + LLView::ctrl_list_t ctrls = getCtrlList(); + for (LLView::ctrl_list_t::iterator ctrl_it = ctrls.begin(); ctrl_it != ctrls.end(); ++ctrl_it) + { + LLUICtrl* ctrl = *ctrl_it; + ctrl->setEnabled( b ); + } +} + +void LLPanel::draw() +{ + if( getVisible() ) + { + // draw background + if( mBgVisible ) + { + S32 left = LLPANEL_BORDER_WIDTH; + S32 top = mRect.getHeight() - LLPANEL_BORDER_WIDTH; + S32 right = mRect.getWidth() - LLPANEL_BORDER_WIDTH; + S32 bottom = LLPANEL_BORDER_WIDTH; + + if (mBgOpaque ) + { + gl_rect_2d( left, top, right, bottom, mBgColorOpaque ); + } + else + { + gl_rect_2d( left, top, right, bottom, mBgColorAlpha ); + } + } + + if( mDefaultBtn) + { + if (gFocusMgr.childHasKeyboardFocus( this ) && mDefaultBtn->getEnabled()) + { + LLUICtrl* focus_ctrl = gFocusMgr.getKeyboardFocus(); + BOOL focus_is_child_button = focus_ctrl->getWidgetType() == WIDGET_TYPE_BUTTON && static_cast<LLButton *>(focus_ctrl)->getCommitOnReturn(); + // only enable default button when current focus is not a return-capturing button + mDefaultBtn->setBorderEnabled(!focus_is_child_button); + } + else + { + mDefaultBtn->setBorderEnabled(FALSE); + } + } + + LLView::draw(); + } +} + +void LLPanel::refresh() +{ + // do nothing by default + // but is automatically called in setFocus(TRUE) +} + +void LLPanel::setDefaultBtn(LLButton* btn) +{ + if (mDefaultBtn && mDefaultBtn->getEnabled()) + { + mDefaultBtn->setBorderEnabled(FALSE); + } + mDefaultBtn = btn; + if (mDefaultBtn) + { + mDefaultBtn->setBorderEnabled(TRUE); + } +} + +void LLPanel::setDefaultBtn(const LLString& id) +{ + LLButton *button = LLUICtrlFactory::getButtonByName(this, id); + if (button) + { + setDefaultBtn(button); + } + else + { + setDefaultBtn(NULL); + } +} + +BOOL LLPanel::handleKey(KEY key, MASK mask, BOOL called_from_parent) +{ + BOOL handled = FALSE; + if (getVisible() && getEnabled()) + { + if( (mask == MASK_SHIFT) && (KEY_TAB == key)) + { + //SHIFT-TAB + LLView* cur_focus = gFocusMgr.getKeyboardFocus(); + if (cur_focus && gFocusMgr.childHasKeyboardFocus(this)) + { + LLView* focus_root = cur_focus; + while(cur_focus->getParent()) + { + cur_focus = cur_focus->getParent(); + if (cur_focus->isFocusRoot()) + { + // this is the root-most focus root found so far + focus_root = cur_focus; + } + } + handled = focus_root->focusPrevItem(FALSE); + } + else if (!cur_focus && mIsFocusRoot) + { + handled = focusLastItem(); + if (!handled) + { + setFocus(TRUE); + handled = TRUE; + } + } + } + else + if( (mask == MASK_NONE ) && (KEY_TAB == key)) + { + //TAB + LLView* cur_focus = gFocusMgr.getKeyboardFocus(); + if (cur_focus && gFocusMgr.childHasKeyboardFocus(this)) + { + LLView* focus_root = cur_focus; + while(cur_focus->getParent()) + { + cur_focus = cur_focus->getParent(); + if (cur_focus->isFocusRoot()) + { + focus_root = cur_focus; + } + } + handled = focus_root->focusNextItem(FALSE); + } + else if (!cur_focus && mIsFocusRoot) + { + handled = focusFirstItem(); + if (!handled) + { + setFocus(TRUE); + handled = TRUE; + } + } + } + } + + if (!handled) + { + handled = LLView::handleKey(key, mask, called_from_parent); + } + + return handled; +} + +void LLPanel::addCtrl( LLUICtrl* ctrl, S32 tab_group) +{ + mLastTabGroup = tab_group; + + LLView::addCtrl(ctrl, tab_group); + // propagate chrome to children only if they have not been flagged as chrome + if (!ctrl->getIsChrome()) + { + ctrl->setIsChrome(getIsChrome()); + } +} + +void LLPanel::addCtrlAtEnd( LLUICtrl* ctrl, S32 tab_group) +{ + mLastTabGroup = tab_group; + + LLView::addCtrlAtEnd(ctrl, tab_group); + if (!ctrl->getIsChrome()) + { + ctrl->setIsChrome(getIsChrome()); + } +} + +BOOL LLPanel::handleKeyHere( KEY key, MASK mask, BOOL called_from_parent ) +{ + BOOL handled = FALSE; + + if( getVisible() && getEnabled() && gFocusMgr.childHasKeyboardFocus(this) && KEY_ESCAPE == key ) + { + gFocusMgr.setKeyboardFocus(NULL, NULL); + return TRUE; + } + + if( getVisible() && getEnabled() && + gFocusMgr.childHasKeyboardFocus(this) && !called_from_parent ) + { + LLUICtrl* cur_focus = gFocusMgr.getKeyboardFocus(); + if (key == KEY_RETURN && mask == MASK_NONE) + { + // set keyboard focus to self to trigger commitOnFocusLost behavior on current ctrl + if (cur_focus && cur_focus->acceptsTextInput()) + { + cur_focus->onCommit(); + handled = TRUE; + } + } + + // If we have a default button, click it when + // return is pressed, unless current focus is a return-capturing button + // in which case *that* button will handle the return key + if (!(cur_focus->getWidgetType() == WIDGET_TYPE_BUTTON && static_cast<LLButton *>(cur_focus)->getCommitOnReturn())) + { + // RETURN key means hit default button in this case + if (key == KEY_RETURN && mask == MASK_NONE + && mDefaultBtn != NULL + && mDefaultBtn->getVisible() + && mDefaultBtn->getEnabled()) + { + mDefaultBtn->onCommit(); + handled = TRUE; + } + } + } + + return handled; +} + +void LLPanel::requires(LLString name, EWidgetType type) +{ + mRequirements[name] = type; +} + +BOOL LLPanel::checkRequirements() +{ + BOOL retval = TRUE; + LLString message; + + for (requirements_map_t::iterator i = mRequirements.begin(); i != mRequirements.end(); ++i) + { + if (!this->getCtrlByNameAndType(i->first, i->second)) + { + retval = FALSE; + message += i->first + " " + LLUICtrlFactory::getWidgetType(i->second) + "\n"; + } + } + + if (!retval) + { + LLString::format_map_t args; + args["[COMPONENTS]"] = message; + args["[FLOATER]"] = getName(); + + llwarns << getName() << " failed requirements check on: \n" + << message << llendl; + + alertXml("FailedRequirementsCheck", args); + } + + return retval; +} + +//static +void LLPanel::alertXml(LLString label, LLString::format_map_t args) +{ + sAlertQueue.push(LLAlertInfo(label,args)); +} + +//static +BOOL LLPanel::nextAlert(LLAlertInfo &alert) +{ + if (!sAlertQueue.empty()) + { + alert = sAlertQueue.front(); + sAlertQueue.pop(); + return TRUE; + } + + return FALSE; +} + +void LLPanel::setFocus(BOOL b) +{ + if( b ) + { + if (!gFocusMgr.childHasKeyboardFocus(this)) + { + //refresh(); + if (!focusFirstItem()) + { + LLUICtrl::setFocus(TRUE); + } + onFocusReceived(); + } + } + else + { + if( this == gFocusMgr.getKeyboardFocus() ) + { + gFocusMgr.setKeyboardFocus( NULL, NULL ); + } + else + { + //RN: why is this here? + LLView::ctrl_list_t ctrls = getCtrlList(); + for (LLView::ctrl_list_t::iterator ctrl_it = ctrls.begin(); ctrl_it != ctrls.end(); ++ctrl_it) + { + LLUICtrl* ctrl = *ctrl_it; + ctrl->setFocus( FALSE ); + } + } + } +} + +void LLPanel::setBackgroundColor(const LLColor4& color) +{ + mBgColorOpaque = color; +} + +void LLPanel::setTransparentColor(const LLColor4& color) +{ + mBgColorAlpha = color; +} + +void LLPanel::setBorderVisible(BOOL b) +{ + if (mBorder) + { + mBorder->setVisible( b ); + } +} + +LLView* LLPanel::getCtrlByNameAndType(const LLString& name, EWidgetType type) +{ + LLView* view = getChildByName(name, TRUE); + if (view) + { + if (type == WIDGET_TYPE_DONTCARE || view->getWidgetType() == type) + { + return view; + } + else + { + llwarns << "Widget " << name << " has improper type in panel " << mName << "\n" + << "Is: \t\t" << view->getWidgetType() << "\n" + << "Should be: \t" << type + << llendl; + } + } + else + { + childNotFound(name); + } + return NULL; +} + +// static +LLPanel* LLPanel::getPanelByHandle(LLViewHandle handle) +{ + if (!sPanelMap.count(handle)) + { + return NULL; + } + + return sPanelMap[handle]; +} + +// virtual +LLXMLNodePtr LLPanel::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLView::getXML(); + + if (mBorder && mBorder->getVisible()) + { + node->createChild("border", TRUE)->setBoolValue(TRUE); + } + + if (!mRectControl.empty()) + { + node->createChild("rect_control", TRUE)->setStringValue(mRectControl); + } + + if (!mLabel.empty()) + { + node->createChild("label", TRUE)->setStringValue(mLabel); + } + + if (save_children) + { + LLView::child_list_const_reverse_iter_t rit; + for (rit = getChildList()->rbegin(); rit != getChildList()->rend(); ++rit) + { + LLView* childp = *rit; + + if (childp->getSaveToXML()) + { + LLXMLNodePtr xml_node = childp->getXML(); + + node->addChild(xml_node); + } + } + } + + return node; +} + +LLView* LLPanel::fromXML(LLXMLNodePtr node, LLView* parentp, LLUICtrlFactory *factory) +{ + LLString name("panel"); + node->getAttributeString("name", name); + + LLPanel* panelp = factory->createFactoryPanel(name); + // Fall back on a default panel, if there was no special factory. + if (!panelp) + { + panelp = new LLPanel("tab panel"); + } + + panelp->initPanelXML(node, parentp, factory); + + return panelp; +} + +void LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("panel"); + node->getAttributeString("name", name); + setName(name); + + setPanelParameters(node, parent); + + LLXMLNodePtr child; + for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) + { + factory->createWidget(this, child); + } + + LLString xml_filename; + node->getAttributeString("filename", xml_filename); + if (!xml_filename.empty()) + { + factory->buildPanel(this, xml_filename, NULL); + } + + postBuild(); +} + +void LLPanel::setPanelParameters(LLXMLNodePtr node, LLView* parentp) +{ + /////// Rect, follows, tool_tip, enabled, visible attributes /////// + initFromXML(node, parentp); + + /////// Border attributes /////// + BOOL border = FALSE; + node->getAttributeBOOL("border", border); + if (border) + { + LLViewBorder::EBevel bevel_style = LLViewBorder::BEVEL_OUT; + LLViewBorder::getBevelFromAttribute(node, bevel_style); + + LLViewBorder::EStyle border_style = LLViewBorder::STYLE_LINE; + LLString border_string; + node->getAttributeString("border_style", border_string); + LLString::toLower(border_string); + + if (border_string == "texture") + { + border_style = LLViewBorder::STYLE_TEXTURE; + } + + S32 border_thickness = LLPANEL_BORDER_WIDTH; + node->getAttributeS32("border_thickness", border_thickness); + + addBorder(bevel_style, border_style, border_thickness); + } + + /////// Background attributes /////// + BOOL background_visible = FALSE; + node->getAttributeBOOL("background_visible", background_visible); + setBackgroundVisible(background_visible); + + BOOL background_opaque = FALSE; + node->getAttributeBOOL("background_opaque", background_opaque); + setBackgroundOpaque(background_opaque); + + LLColor4 color; + color = LLUI::sColorsGroup->getColor( "FocusBackgroundColor" ); + LLUICtrlFactory::getAttributeColor(node,"bg_opaque_color", color); + setBackgroundColor(color); + + color = LLUI::sColorsGroup->getColor( "DefaultBackgroundColor" ); + LLUICtrlFactory::getAttributeColor(node,"bg_alpha_color", color); + setTransparentColor(color); + + LLString label; + node->getAttributeString("label", label); + setLabel(label); +} + +void LLPanel::childSetVisible(const LLString& id, bool visible) +{ + LLView* child = getChildByName(id, true); + if (child) + { + child->setVisible(visible); + } +} + +bool LLPanel::childIsVisible(const LLString& id) const +{ + LLView* child = getChildByName(id, true); + if (child) + { + return (bool)child->getVisible(); + } + return false; +} + +void LLPanel::childSetEnabled(const LLString& id, bool enabled) +{ + LLView* child = getChildByName(id, true); + if (child) + { + child->setEnabled(enabled); + } +} + +void LLPanel::childSetTentative(const LLString& id, bool tentative) +{ + LLView* child = getChildByName(id, true); + if (child) + { + child->setTentative(tentative); + } +} + +bool LLPanel::childIsEnabled(const LLString& id) const +{ + LLView* child = getChildByName(id, true); + if (child) + { + return (bool)child->getEnabled(); + } + return false; +} + + +void LLPanel::childSetToolTip(const LLString& id, const LLString& msg) +{ + LLView* child = getChildByName(id, true); + if (child) + { + child->setToolTip(msg); + } +} + +void LLPanel::childSetRect(const LLString& id, const LLRect& rect) +{ + LLView* child = getChildByName(id, true); + if (child) + { + child->setRect(rect); + } +} + +bool LLPanel::childGetRect(const LLString& id, LLRect& rect) const +{ + LLView* child = getChildByName(id, true); + if (child) + { + rect = child->getRect(); + return true; + } + return false; +} + +void LLPanel::childSetFocus(const LLString& id, BOOL focus) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + child->setFocus(focus); + } +} + +BOOL LLPanel::childHasFocus(const LLString& id) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + return child->hasFocus(); + } + else + { + childNotFound(id); + return FALSE; + } +} + + +void LLPanel::childSetFocusChangedCallback(const LLString& id, void (*cb)(LLUICtrl*, void*)) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + child->setFocusChangedCallback(cb); + } +} + +void LLPanel::childSetCommitCallback(const LLString& id, void (*cb)(LLUICtrl*, void*), void *userdata ) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + child->setCommitCallback(cb); + child->setCallbackUserData(userdata); + } +} + +void LLPanel::childSetDoubleClickCallback(const LLString& id, void (*cb)(void*), void *userdata ) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + child->setDoubleClickCallback(cb); + if (userdata) + { + child->setCallbackUserData(userdata); + } + } +} + +void LLPanel::childSetValidate(const LLString& id, BOOL (*cb)(LLUICtrl*, void*)) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + child->setValidateBeforeCommit(cb); + } +} + +void LLPanel::childSetUserData(const LLString& id, void* userdata) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + child->setCallbackUserData(userdata); + } +} + +void LLPanel::childSetColor(const LLString& id, const LLColor4& color) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + child->setColor(color); + } +} + +LLCtrlSelectionInterface* LLPanel::childGetSelectionInterface(const LLString& id) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + return child->getSelectionInterface(); + } + return NULL; +} + +LLCtrlListInterface* LLPanel::childGetListInterface(const LLString& id) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + return child->getListInterface(); + } + return NULL; +} + +LLCtrlScrollInterface* LLPanel::childGetScrollInterface(const LLString& id) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + return child->getScrollInterface(); + } + return NULL; +} + +void LLPanel::childSetValue(const LLString& id, LLSD value) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + child->setValue(value); + } +} + +LLSD LLPanel::childGetValue(const LLString& id) const +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + return child->getValue(); + } + // Not found => return undefined + return LLSD(); +} + +BOOL LLPanel::childSetTextArg(const LLString& id, const LLString& key, const LLString& text) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + return child->setTextArg(key, text); + } + return FALSE; +} + +BOOL LLPanel::childSetLabelArg(const LLString& id, const LLString& key, const LLString& text) +{ + LLView* child = getChildByName(id, true); + if (child) + { + return child->setLabelArg(key, text); + } + return FALSE; +} + +void LLPanel::childSetMinValue(const LLString& id, LLSD min_value) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + child->setMinValue(min_value); + } +} + +void LLPanel::childSetMaxValue(const LLString& id, LLSD max_value) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + child->setMaxValue(max_value); + } +} + +void LLPanel::childShowTab(const LLString& id, const LLString& tabname, bool visible) +{ + LLTabContainerCommon* child = LLUICtrlFactory::getTabContainerByName(this, id); + if (child) + { + child->selectTabByName(tabname); + } +} + +LLPanel *LLPanel::childGetVisibleTab(const LLString& id) +{ + LLTabContainerCommon* child = LLUICtrlFactory::getTabContainerByName(this, id); + if (child) + { + return child->getCurrentPanel(); + } + return NULL; +} + +void LLPanel::childSetTabChangeCallback(const LLString& id, const LLString& tabname, void (*on_tab_clicked)(void*, bool), void *userdata) +{ + LLTabContainerCommon* child = LLUICtrlFactory::getTabContainerByName(this, id); + if (child) + { + LLPanel *panel = child->getPanelByName(tabname); + if (panel) + { + child->setTabChangeCallback(panel, on_tab_clicked); + child->setTabUserData(panel, userdata); + } + } +} + +void LLPanel::childSetText(const LLString& id, const LLString& text) +{ + childSetValue(id, LLSD(text)); +} + +void LLPanel::childSetKeystrokeCallback(const LLString& id, void (*keystroke_callback)(LLLineEditor* caller, void* user_data), void *user_data) +{ + LLLineEditor* child = LLUICtrlFactory::getLineEditorByName(this, id); + if (child) + { + child->setKeystrokeCallback(keystroke_callback); + if (user_data) + { + child->setCallbackUserData(user_data); + } + } +} + +void LLPanel::childSetPrevalidate(const LLString& id, BOOL (*func)(const LLWString &) ) +{ + LLLineEditor* child = LLUICtrlFactory::getLineEditorByName(this, id); + if (child) + { + child->setPrevalidate(func); + } +} + +LLString LLPanel::childGetText(const LLString& id) +{ + return childGetValue(id).asString(); +} + +void LLPanel::childSetWrappedText(const LLString& id, const LLString& text, bool visible) +{ + LLTextBox* child = (LLTextBox*)getCtrlByNameAndType(id, WIDGET_TYPE_TEXT_BOX); + if (child) + { + child->setVisible(visible); + child->setWrappedText(text); + } +} + +void LLPanel::childSetAction(const LLString& id, void(*function)(void*), void* value) +{ + LLButton* button = (LLButton*)getCtrlByNameAndType(id, WIDGET_TYPE_BUTTON); + if (button) + { + button->setClickedCallback(function, value); + } +} + +void LLPanel::childSetActionTextbox(const LLString& id, void(*function)(void*)) +{ + LLTextBox* textbox = (LLTextBox*)getCtrlByNameAndType(id, WIDGET_TYPE_TEXT_BOX); + if (textbox) + { + textbox->setClickedCallback(function); + } +} + +void LLPanel::childSetControlName(const LLString& id, const LLString& control_name) +{ + LLView* view = getChildByName(id, TRUE); + if (view) + { + view->setControlName(control_name, NULL); + } +} + +//virtual +LLView* LLPanel::getChildByName(const LLString& name, BOOL recurse) const +{ + LLView* view = LLUICtrl::getChildByName(name, recurse); + if (!view) + { + childNotFound(name); + } + return view; +} + +void LLPanel::childNotFound(const LLString& id) const +{ + if (mExpectedMembers.find(id) == mExpectedMembers.end()) + { + mNewExpectedMembers.insert(id); + } +} + +void LLPanel::childDisplayNotFound() +{ + if (mNewExpectedMembers.empty()) + { + return; + } + LLString msg; + expected_members_list_t::iterator itor; + for (itor=mNewExpectedMembers.begin(); itor!=mNewExpectedMembers.end(); ++itor) + { + msg.append(*itor); + msg.append("\n"); + mExpectedMembers.insert(*itor); + } + mNewExpectedMembers.clear(); + LLString::format_map_t args; + args["[CONTROLS]"] = msg; + LLAlertDialog::showXml("FloaterNotFound", args); +} + diff --git a/indra/llui/llpanel.h b/indra/llui/llpanel.h new file mode 100644 index 0000000000..9da27b6f38 --- /dev/null +++ b/indra/llui/llpanel.h @@ -0,0 +1,229 @@ +/** + * @file llpanel.h + * @author James Cook, Tom Yedwab + * @brief LLPanel base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLPANEL_H +#define LL_LLPANEL_H + +// Opaque view with a background and a border. Can contain LLUICtrls. + +#include "llcallbackmap.h" +#include "lluictrl.h" +#include "llviewborder.h" +#include "v4color.h" +#include <list> + +const S32 LLPANEL_BORDER_WIDTH = 1; +const BOOL BORDER_YES = TRUE; +const BOOL BORDER_NO = FALSE; + +class LLViewerImage; +class LLUUID; +class LLCheckBoxCtrl; +class LLComboBox; +class LLIconCtrl; +class LLLineEditor; +class LLRadioGroup; +class LLScrollListCtrl; +class LLSliderCtrl; +class LLSpinCtrl; +class LLTextBox; +class LLTextEditor; + +class LLAlertInfo +{ +public: + LLString mLabel; + LLString::format_map_t mArgs; + + LLAlertInfo(LLString label, LLString::format_map_t args) + : mLabel(label), mArgs(args) { } + + LLAlertInfo() { } +}; + +class LLPanel : public LLUICtrl +{ +public: + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + // defaults to TRUE + virtual BOOL isPanel(); + + // minimal constructor for data-driven initialization + LLPanel(); + LLPanel(const LLString& name); + + // Position and size not saved + LLPanel(const LLString& name, const LLRect& rect, BOOL bordered = TRUE); + + // Position and size are saved to rect_control + LLPanel(const LLString& name, const LLString& rect_control, BOOL bordered = TRUE); + + void addBorder( LLViewBorder::EBevel border_bevel = LLViewBorder::BEVEL_OUT, + LLViewBorder::EStyle border_style = LLViewBorder::STYLE_LINE, + S32 border_thickness = LLPANEL_BORDER_WIDTH ); + + virtual ~LLPanel(); + virtual void draw(); + virtual void refresh(); // called in setFocus() + virtual void setFocus( BOOL b ); + void setFocusRoot(BOOL b) { mIsFocusRoot = b; } + virtual BOOL handleKeyHere( KEY key, MASK mask, BOOL called_from_parent ); + virtual BOOL handleKey( KEY key, MASK mask, BOOL called_from_parent ); + virtual BOOL postBuild(); + + void requires(LLString name, EWidgetType type = WIDGET_TYPE_DONTCARE); + BOOL checkRequirements(); + + static void alertXml(LLString label, LLString::format_map_t args = LLString::format_map_t()); + static BOOL nextAlert(LLAlertInfo &alert); + + void setBackgroundColor( const LLColor4& color ); + void setTransparentColor(const LLColor4& color); + void setBackgroundVisible( BOOL b ) { mBgVisible = b; } + void setBackgroundOpaque(BOOL b) { mBgOpaque = b; } + void setDefaultBtn(LLButton* btn = NULL); + void setDefaultBtn(const LLString& id); + void setLabel(LLString label) { mLabel = label; } + LLString getLabel() const { return mLabel; } + + void setRectControl(const LLString& rect_control) { mRectControl.assign(rect_control); } + + void setBorderVisible( BOOL b ); + + void setCtrlsEnabled(BOOL b); + virtual void clearCtrls(); + + LLViewHandle getHandle() { return mViewHandle; } + + S32 getLastTabGroup() { return mLastTabGroup; } + + LLView* getCtrlByNameAndType(const LLString& name, EWidgetType type); + + static LLPanel* getPanelByHandle(LLViewHandle handle); + + virtual const LLCallbackMap::map_t& getFactoryMap() const { return mFactoryMap; } + + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + void initPanelXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + void setPanelParameters(LLXMLNodePtr node, LLView *parentp); + + // ** Wrappers for setting child properties by name ** -TomY + + // Override to set not found list + virtual LLView* getChildByName(const LLString& name, BOOL recurse = FALSE) const; + + // LLView + void childSetVisible(const LLString& name, bool visible); + void childShow(const LLString& name) { childSetVisible(name, true); } + void childHide(const LLString& name) { childSetVisible(name, false); } + bool childIsVisible(const LLString& id) const; + void childSetTentative(const LLString& name, bool tentative); + + void childSetEnabled(const LLString& name, bool enabled); + void childEnable(const LLString& name) { childSetEnabled(name, true); } + void childDisable(const LLString& name) { childSetEnabled(name, false); }; + bool childIsEnabled(const LLString& id) const; + + void childSetToolTip(const LLString& id, const LLString& msg); + void childSetRect(const LLString& id, const LLRect &rect); + bool childGetRect(const LLString& id, LLRect& rect) const; + + // LLUICtrl + void childSetFocus(const LLString& id, BOOL focus = TRUE); + BOOL childHasFocus(const LLString& id); + void childSetFocusChangedCallback(const LLString& id, void (*cb)(LLUICtrl*, void*)); + + void childSetCommitCallback(const LLString& id, void (*cb)(LLUICtrl*, void*), void* userdata = NULL ); + void childSetDoubleClickCallback(const LLString& id, void (*cb)(void*), void* userdata = NULL ); + void childSetValidate(const LLString& id, BOOL (*cb)(LLUICtrl*, void*) ); + void childSetUserData(const LLString& id, void* userdata); + + void childSetColor(const LLString& id, const LLColor4& color); + + LLCtrlSelectionInterface* childGetSelectionInterface(const LLString& id); + LLCtrlListInterface* childGetListInterface(const LLString& id); + LLCtrlScrollInterface* childGetScrollInterface(const LLString& id); + + // This is the magic bullet for data-driven UI + void childSetValue(const LLString& id, LLSD value); + LLSD childGetValue(const LLString& id) const; + + // For setting text / label replacement params, e.g. "Hello [NAME]" + // Not implemented for all types, defaults to noop, returns FALSE if not applicaple + BOOL childSetTextArg(const LLString& id, const LLString& key, const LLString& text); + BOOL childSetLabelArg(const LLString& id, const LLString& key, const LLString& text); + + // LLSlider / LLSpinCtrl + void childSetMinValue(const LLString& id, LLSD min_value); + void childSetMaxValue(const LLString& id, LLSD max_value); + + // LLTabContainer + void childShowTab(const LLString& id, const LLString& tabname, bool visible = true); + LLPanel *childGetVisibleTab(const LLString& id); + void childSetTabChangeCallback(const LLString& id, const LLString& tabname, void (*on_tab_clicked)(void*, bool), void *userdata); + + // LLTextBox + void childSetWrappedText(const LLString& id, const LLString& text, bool visible = true); + + // LLTextBox/LLTextEditor/LLLineEditor + void childSetText(const LLString& id, const LLString& text); + LLString childGetText(const LLString& id); + + // LLLineEditor + void childSetKeystrokeCallback(const LLString& id, void (*keystroke_callback)(LLLineEditor* caller, void* user_data), void *user_data); + void childSetPrevalidate(const LLString& id, BOOL (*func)(const LLWString &) ); + + // LLButton + void childSetAction(const LLString& id, void(*function)(void*), void* value); + void childSetActionTextbox(const LLString& id, void(*function)(void*)); + void childSetControlName(const LLString& id, const LLString& control_name); + + // Error reporting + void childNotFound(const LLString& id) const; + void childDisplayNotFound(); + + typedef std::queue<LLAlertInfo> alert_queue_t; + static alert_queue_t sAlertQueue; + +private: + // common constructor + void init(); + +protected: + virtual void addCtrl( LLUICtrl* ctrl, S32 tab_group ); + virtual void addCtrlAtEnd( LLUICtrl* ctrl, S32 tab_group); + + // Unified error reporting for the child* functions + typedef std::set<LLString> expected_members_list_t; + mutable expected_members_list_t mExpectedMembers; + mutable expected_members_list_t mNewExpectedMembers; + + LLString mRectControl; + LLColor4 mBgColorAlpha; + LLColor4 mBgColorOpaque; + LLColor4 mDefaultBtnHighlight; + BOOL mBgVisible; + BOOL mBgOpaque; + LLViewBorder* mBorder; + LLButton* mDefaultBtn; + LLCallbackMap::map_t mFactoryMap; + LLString mLabel; + S32 mLastTabGroup; + + typedef std::map<LLString, EWidgetType> requirements_map_t; + requirements_map_t mRequirements; + + typedef std::map<LLViewHandle, LLPanel*> panel_map_t; + static panel_map_t sPanelMap; +}; + +#endif diff --git a/indra/llui/llradiogroup.cpp b/indra/llui/llradiogroup.cpp new file mode 100644 index 0000000000..69c0da6933 --- /dev/null +++ b/indra/llui/llradiogroup.cpp @@ -0,0 +1,440 @@ +/** + * @file llradiogroup.cpp + * @brief LLRadioGroup base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// An invisible view containing multiple mutually exclusive toggling +// buttons (usually radio buttons). Automatically handles the mutex +// condition by highlighting only one button at a time. + +#include "linden_common.h" + +#include "llboost.h" + +#include "llradiogroup.h" +#include "indra_constants.h" + +#include "llviewborder.h" +#include "llcontrol.h" +#include "llui.h" +#include "llfocusmgr.h" + +LLRadioGroup::LLRadioGroup(const LLString& name, const LLRect& rect, + const LLString& control_name, + LLUICtrlCallback callback, + void* userdata, + BOOL border) +: LLUICtrl(name, rect, TRUE, callback, userdata, FOLLOWS_LEFT | FOLLOWS_TOP), + mSelectedIndex(0) +{ + setControlName(control_name, NULL); + init(border); +} + +LLRadioGroup::LLRadioGroup(const LLString& name, const LLRect& rect, + S32 initial_index, + LLUICtrlCallback callback, + void* userdata, + BOOL border) : + LLUICtrl(name, rect, TRUE, callback, userdata, FOLLOWS_LEFT | FOLLOWS_TOP), + mSelectedIndex(initial_index) +{ + init(border); +} + +void LLRadioGroup::init(BOOL border) +{ + if (border) + { + addChild( new LLViewBorder( "radio group border", + LLRect(0, mRect.getHeight(), mRect.getWidth(), 0), + LLViewBorder::BEVEL_NONE, + LLViewBorder::STYLE_LINE, + 1 ) ); + } + mHasBorder = border; +} + + + + +LLRadioGroup::~LLRadioGroup() +{ +} + + +// virtual +void LLRadioGroup::setEnabled(BOOL enabled) +{ + for (child_list_const_iter_t child_iter = getChildList()->begin(); + child_iter != getChildList()->end(); ++child_iter) + { + LLView *child = *child_iter; + child->setEnabled(enabled); + } + LLView::setEnabled(enabled); +} + +void LLRadioGroup::setIndexEnabled(S32 index, BOOL enabled) +{ + S32 count = 0; + for (button_list_t::iterator iter = mRadioButtons.begin(); + iter != mRadioButtons.end(); ++iter) + { + LLRadioCtrl* child = *iter; + if (count == index) + { + child->setEnabled(enabled); + if (index == mSelectedIndex && enabled == FALSE) + { + setSelectedIndex(-1); + } + break; + } + count++; + } + count = 0; + if (mSelectedIndex < 0) + { + // Set to highest enabled value < index, + // or lowest value above index if none lower are enabled + // or 0 if none are enabled + for (button_list_t::iterator iter = mRadioButtons.begin(); + iter != mRadioButtons.end(); ++iter) + { + LLRadioCtrl* child = *iter; + if (count >= index && mSelectedIndex >= 0) + { + break; + } + if (child->getEnabled()) + { + setSelectedIndex(count); + } + count++; + } + if (mSelectedIndex < 0) + { + setSelectedIndex(0); + } + } +} + +S32 LLRadioGroup::getSelectedIndex() const +{ + return mSelectedIndex; +} + +BOOL LLRadioGroup::setSelectedIndex(S32 index, BOOL from_event) +{ + if (index < 0 || index >= (S32)mRadioButtons.size()) + { + return FALSE; + } + + mSelectedIndex = index; + + if (!from_event) + { + setControlValue(getSelectedIndex()); + } + + return TRUE; +} + +BOOL LLRadioGroup::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent) +{ + BOOL handled = FALSE; + // do any of the tab buttons have keyboard focus? + if (getEnabled() && !called_from_parent) + { + switch(key) + { + case KEY_DOWN: + if (!setSelectedIndex((getSelectedIndex() + 1))) + { + make_ui_sound("UISndInvalidOp"); + } + else + { + onCommit(); + } + handled = TRUE; + break; + case KEY_UP: + if (!setSelectedIndex((getSelectedIndex() - 1))) + { + make_ui_sound("UISndInvalidOp"); + } + else + { + onCommit(); + } + handled = TRUE; + break; + case KEY_LEFT: + if (!setSelectedIndex((getSelectedIndex() - 1))) + { + make_ui_sound("UISndInvalidOp"); + } + else + { + onCommit(); + } + handled = TRUE; + break; + case KEY_RIGHT: + if (!setSelectedIndex((getSelectedIndex() + 1))) + { + make_ui_sound("UISndInvalidOp"); + } + else + { + onCommit(); + } + handled = TRUE; + break; + default: + break; + } + } + return handled; +} + +void LLRadioGroup::draw() +{ + S32 current_button = 0; + + BOOL take_focus = FALSE; + if (gFocusMgr.childHasKeyboardFocus(this)) + { + take_focus = TRUE; + } + + for (button_list_t::iterator iter = mRadioButtons.begin(); + iter != mRadioButtons.end(); ++iter) + { + LLRadioCtrl* radio = *iter; + BOOL selected = (current_button == mSelectedIndex); + radio->setValue( selected ); + if (take_focus && selected && !gFocusMgr.childHasKeyboardFocus(radio)) + { + radio->focusFirstItem(); + } + current_button++; + } + + LLView::draw(); +} + + +// When adding a button, we need to ensure that the radio +// group gets a message when the button is clicked. +LLRadioCtrl* LLRadioGroup::addRadioButton(const LLString& name, const LLString& label, const LLRect& rect, const LLFontGL* font ) +{ + // Highlight will get fixed in draw method above + LLRadioCtrl* radio = new LLRadioCtrl(name, rect, label, font, + onClickButton, this); + addChild(radio); + mRadioButtons.push_back(radio); + return radio; +} + +// Handle one button being clicked. All child buttons must have this +// function as their callback function. + +// static +void LLRadioGroup::onClickButton(LLUICtrl* ui_ctrl, void* userdata) +{ + // llinfos << "LLRadioGroup::onClickButton" << llendl; + + LLRadioCtrl* clickedRadio = (LLRadioCtrl*) ui_ctrl; + LLRadioGroup* self = (LLRadioGroup*) userdata; + + S32 counter = 0; + for (button_list_t::iterator iter = self->mRadioButtons.begin(); + iter != self->mRadioButtons.end(); ++iter) + { + LLRadioCtrl* radio = *iter; + if (radio == clickedRadio) + { + // llinfos << "clicked button " << counter << llendl; + self->setSelectedIndex(counter); + self->setControlValue(counter); + + // BUG: Calls click callback even if button didn't actually change + self->onCommit(); + + return; + } + + counter++; + } + + llwarns << "LLRadioGroup::onClickButton - clicked button that isn't a child" << llendl; +} + +void LLRadioGroup::setValue( const LLSD& value ) +{ + LLString value_name = value.asString(); + int idx = 0; + for (button_list_t::const_iterator iter = mRadioButtons.begin(); + iter != mRadioButtons.end(); ++iter) + { + LLRadioCtrl* radio = *iter; + if (radio->getName() == value_name) + { + setSelectedIndex(idx); + idx = -1; + break; + } + ++idx; + } + if (idx != -1) + { + // string not found, try integer + if (value.isInteger()) + { + setSelectedIndex((S32) value.asInteger(), TRUE); + } + else + { + llwarns << "LLRadioGroup::setValue: value not found: " << value_name << llendl; + } + } +} + +LLSD LLRadioGroup::getValue() const +{ + int index = getSelectedIndex(); + int idx = 0; + for (button_list_t::const_iterator iter = mRadioButtons.begin(); + iter != mRadioButtons.end(); ++iter) + { + if (idx == index) return LLSD((*iter)->getName()); + ++idx; + } + return LLSD(); +} + +// virtual +LLXMLNodePtr LLRadioGroup::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLUICtrl::getXML(); + + // Attributes + + node->createChild("draw_border", TRUE)->setBoolValue(mHasBorder); + + // Contents + + for (button_list_t::const_iterator iter = mRadioButtons.begin(); + iter != mRadioButtons.end(); ++iter) + { + LLRadioCtrl* radio = *iter; + + LLXMLNodePtr child_node = radio->LLView::getXML(); + child_node->setStringValue(radio->getLabel()); + child_node->setName("radio_item"); + + node->addChild(child_node); + } + + return node; +} + +// static +LLView* LLRadioGroup::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("radio_group"); + node->getAttributeString("name", name); + + U32 initial_value = 0; + node->getAttributeU32("initial_value", initial_value); + + BOOL draw_border = TRUE; + node->getAttributeBOOL("draw_border", draw_border); + + LLRect rect; + createRect(node, rect, parent, LLRect()); + + LLRadioGroup* radio_group = new LLRadioGroup(name, + rect, + initial_value, + NULL, + NULL, + draw_border); + + const LLString& contents = node->getValue(); + + LLRect group_rect = radio_group->getRect(); + + LLFontGL *font = LLView::selectFont(node); + + if (contents.find_first_not_of(" \n\t") != contents.npos) + { + // ...old school default vertical layout + typedef boost::tokenizer<boost::char_separator<char> > tokenizer; + boost::char_separator<char> sep("\t\n"); + tokenizer tokens(contents, sep); + tokenizer::iterator token_iter = tokens.begin(); + + const S32 HPAD = 4, VPAD = 4; + S32 cur_y = group_rect.getHeight() - VPAD; + + while(token_iter != tokens.end()) + { + const char* line = token_iter->c_str(); + LLRect rect(HPAD, cur_y, group_rect.getWidth() - (2 * HPAD), cur_y - 15); + cur_y -= VPAD + 15; + radio_group->addRadioButton("radio", line, rect, font); + ++token_iter; + } + llwarns << "Legacy radio group format used! Please convert to use <radio_item> tags!" << llendl; + } + else + { + // ...per pixel layout + LLXMLNodePtr child; + for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) + { + if (child->hasName("radio_item")) + { + LLRect item_rect; + createRect(child, item_rect, radio_group, rect); + + LLString radioname("radio"); + child->getAttributeString("name", radioname); + LLString item_label = child->getTextContents(); + LLRadioCtrl* radio = radio_group->addRadioButton(radioname, item_label.c_str(), item_rect, font); + + radio->initFromXML(child, radio_group); + } + } + } + + radio_group->initFromXML(node, parent); + + return radio_group; +} + + +LLRadioCtrl::LLRadioCtrl(const LLString& name, const LLRect& rect, const LLString& label, + const LLFontGL* font, void (*commit_callback)(LLUICtrl*, void*), void* callback_userdata) : + LLCheckBoxCtrl(name, rect, label, font, commit_callback, callback_userdata, FALSE, RADIO_STYLE) +{ + setTabStop(FALSE); +} + +LLRadioCtrl::~LLRadioCtrl() +{ +} + +void LLRadioCtrl::setValue(const LLSD& value) +{ + LLCheckBoxCtrl::setValue(value); + mButton->setTabStop(value.asBoolean()); +} diff --git a/indra/llui/llradiogroup.h b/indra/llui/llradiogroup.h new file mode 100644 index 0000000000..01b4a61b82 --- /dev/null +++ b/indra/llui/llradiogroup.h @@ -0,0 +1,102 @@ +/** + * @file llradiogroup.h + * @brief LLRadioGroup base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// An invisible view containing multiple mutually exclusive toggling +// buttons (usually radio buttons). Automatically handles the mutex +// condition by highlighting only one button at a time. + +#ifndef LL_LLRADIOGROUP_H +#define LL_LLRADIOGROUP_H + +#include "lluictrl.h" +#include "llcheckboxctrl.h" + +class LLFontGL; + +// Radio controls are checkbox controls with use_radio_style true +class LLRadioCtrl : public LLCheckBoxCtrl +{ +public: + LLRadioCtrl(const LLString& name, const LLRect& rect, const LLString& label, + const LLFontGL* font = NULL, + void (*commit_callback)(LLUICtrl*, void*) = NULL, + void* callback_userdata = NULL); + /*virtual*/ ~LLRadioCtrl(); + + /*virtual*/ void setValue(const LLSD& value); +}; + +class LLRadioGroup +: public LLUICtrl +{ +public: + // Build a radio group. The number (0...n-1) of the currently selected + // element will be stored in the named control. After the control is + // changed the callback will be called. + LLRadioGroup(const LLString& name, const LLRect& rect, + const LLString& control_name, + LLUICtrlCallback callback = NULL, + void* userdata = NULL, + BOOL border = TRUE); + + // Another radio group constructor, but this one doesn't rely on + // needing a control + LLRadioGroup(const LLString& name, const LLRect& rect, + S32 initial_index, + LLUICtrlCallback callback = NULL, + void* userdata = NULL, + BOOL border = TRUE); + + virtual ~LLRadioGroup(); + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_RADIO_GROUP; } + virtual LLString getWidgetTag() const { return LL_RADIO_GROUP_TAG; } + + virtual BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent); + + virtual void setEnabled(BOOL enabled); + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + void setIndexEnabled(S32 index, BOOL enabled); + + S32 getItemCount() { return mRadioButtons.size(); } + // return the index value of the selected item + S32 getSelectedIndex() const; + + // set the index value programatically + BOOL setSelectedIndex(S32 index, BOOL from_event = FALSE); + + // Accept and retrieve strings of the radio group control names + virtual void setValue(const LLSD& value ); + virtual LLSD getValue() const; + + // Draw the group, but also fix the highlighting based on the + // control. + void draw(); + + // You must use this method to add buttons to a radio group. + // Don't use addChild -- it won't set the callback function + // correctly. + LLRadioCtrl* addRadioButton(const LLString& name, const LLString& label, const LLRect& rect, const LLFontGL* font); + LLRadioCtrl* getRadioButton(const S32& index) { return mRadioButtons[index]; } + // Update the control as needed. Userdata must be a pointer to the + // button. + static void onClickButton(LLUICtrl* radio, void* userdata); + +protected: + // protected function shared by the two constructors. + void init(BOOL border); + + S32 mSelectedIndex; + typedef std::vector<LLRadioCtrl*> button_list_t; + button_list_t mRadioButtons; + + BOOL mHasBorder; +}; + + +#endif diff --git a/indra/llui/llresizebar.cpp b/indra/llui/llresizebar.cpp new file mode 100644 index 0000000000..0183c58c93 --- /dev/null +++ b/indra/llui/llresizebar.cpp @@ -0,0 +1,257 @@ +/** + * @file llresizebar.cpp + * @brief LLResizeBar base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llresizebar.h" + +//#include "llviewermenu.h" +//#include "llviewerimagelist.h" +#include "llmath.h" +#include "llui.h" +#include "llmenugl.h" +#include "llfocusmgr.h" +#include "llwindow.h" + +LLResizeBar::LLResizeBar( const LLString& name, const LLRect& rect, S32 min_width, S32 min_height, Side side ) + : + LLView( name, rect, TRUE ), + mDragStartScreenX( 0 ), + mDragStartScreenY( 0 ), + mLastMouseScreenX( 0 ), + mLastMouseScreenY( 0 ), + mMinWidth( min_width ), + mMinHeight( min_height ), + mSide( side ) +{ + // set up some generically good follow code. + switch( side ) + { + case LEFT: + setFollowsLeft(); + setFollowsTop(); + setFollowsBottom(); + break; + case TOP: + setFollowsTop(); + setFollowsLeft(); + setFollowsRight(); + break; + case RIGHT: + setFollowsRight(); + setFollowsTop(); + setFollowsBottom(); + break; + case BOTTOM: + setFollowsBottom(); + setFollowsLeft(); + setFollowsRight(); + break; + default: + break; + } +} + + +BOOL LLResizeBar::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if( mEnabled ) + { + // Route future Mouse messages here preemptively. (Release on mouse up.) + // No handler needed for focus lost since this clas has no state that depends on it. + gFocusMgr.setMouseCapture( this, NULL ); + + //localPointToScreen(x, y, &mDragStartScreenX, &mDragStartScreenX); + localPointToOtherView(x, y, &mDragStartScreenX, &mDragStartScreenY, getParent()->getParent()); + mLastMouseScreenX = mDragStartScreenX; + mLastMouseScreenY = mDragStartScreenY; + } + + return TRUE; +} + + +BOOL LLResizeBar::handleMouseUp(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + if( gFocusMgr.getMouseCapture() == this ) + { + // Release the mouse + gFocusMgr.setMouseCapture( NULL, NULL ); + handled = TRUE; + } + else + { + handled = TRUE; + } + return handled; +} + +EWidgetType LLResizeBar::getWidgetType() const +{ + return WIDGET_TYPE_RESIZE_BAR; +} + +LLString LLResizeBar::getWidgetTag() const +{ + return LL_RESIZE_BAR_TAG; +} + +BOOL LLResizeBar::handleHover(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + // We only handle the click if the click both started and ended within us + if( gFocusMgr.getMouseCapture() == this ) + { + //FIXME: this, of course, is fragile + LLView* floater_view = getParent()->getParent(); + S32 floater_view_x; + S32 floater_view_y; + localPointToOtherView(x, y, &floater_view_x, &floater_view_y, floater_view); + + S32 delta_x = floater_view_x - mDragStartScreenX; + S32 delta_y = floater_view_y - mDragStartScreenY; + + LLCoordGL mouse_dir; + // use hysteresis on mouse motion to preserve user intent when mouse stops moving + mouse_dir.mX = (floater_view_x == mLastMouseScreenX) ? mLastMouseDir.mX : floater_view_x - mLastMouseScreenX; + mouse_dir.mY = (floater_view_y == mLastMouseScreenY) ? mLastMouseDir.mY : floater_view_y - mLastMouseScreenY; + mLastMouseDir = mouse_dir; + mLastMouseScreenX = floater_view_x; + mLastMouseScreenY = floater_view_y; + + // Make sure the mouse in still over the application. We don't want to make the parent + // so big that we can't see the resize handle any more. + LLRect valid_rect = floater_view->getRect(); + LLView* parentView = getParent(); + if( valid_rect.localPointInRect( floater_view_x, floater_view_y ) && parentView ) + { + // Resize the parent + LLRect parent_rect = parentView->getRect(); + LLRect scaled_rect = parent_rect; + + S32 new_width = parent_rect.getWidth(); + S32 new_height = parent_rect.getHeight(); + + switch( mSide ) + { + case LEFT: + new_width = parent_rect.getWidth() - delta_x; + if( new_width < mMinWidth ) + { + new_width = mMinWidth; + delta_x = parent_rect.getWidth() - mMinWidth; + } + scaled_rect.translate(delta_x, 0); + break; + + case TOP: + new_height = parent_rect.getHeight() + delta_y; + if( new_height < mMinHeight ) + { + new_height = mMinHeight; + delta_y = mMinHeight - parent_rect.getHeight(); + } + break; + + case RIGHT: + new_width = parent_rect.getWidth() + delta_x; + if( new_width < mMinWidth ) + { + new_width = mMinWidth; + delta_x = mMinWidth - parent_rect.getWidth(); + } + break; + + case BOTTOM: + new_height = parent_rect.getHeight() - delta_y; + if( new_height < mMinHeight ) + { + new_height = mMinHeight; + delta_y = parent_rect.getHeight() - mMinHeight; + } + scaled_rect.translate(0, delta_y); + break; + } + + scaled_rect.mTop = scaled_rect.mBottom + new_height; + scaled_rect.mRight = scaled_rect.mLeft + new_width; + parentView->setRect(scaled_rect); + + S32 snap_delta_x = 0; + S32 snap_delta_y = 0; + + LLView* snap_view = NULL; + + switch( mSide ) + { + case LEFT: + snap_view = parentView->findSnapEdge(snap_delta_x, mouse_dir, SNAP_LEFT, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + snap_delta_x -= scaled_rect.mLeft; + scaled_rect.mLeft += snap_delta_x; + break; + case TOP: + snap_view = parentView->findSnapEdge(snap_delta_y, mouse_dir, SNAP_TOP, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + snap_delta_y -= scaled_rect.mTop; + scaled_rect.mTop += snap_delta_y; + break; + case RIGHT: + snap_view = parentView->findSnapEdge(snap_delta_x, mouse_dir, SNAP_RIGHT, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + snap_delta_x -= scaled_rect.mRight; + scaled_rect.mRight += snap_delta_x; + break; + case BOTTOM: + snap_view = parentView->findSnapEdge(snap_delta_y, mouse_dir, SNAP_BOTTOM, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + snap_delta_y -= scaled_rect.mBottom; + scaled_rect.mBottom += snap_delta_y; + break; + } + + parentView->snappedTo(snap_view); + + parentView->setRect(parent_rect); + + parentView->reshape(scaled_rect.getWidth(), scaled_rect.getHeight(), FALSE); + parentView->translate(scaled_rect.mLeft - parentView->getRect().mLeft, scaled_rect.mBottom - parentView->getRect().mBottom); + + floater_view_x = mDragStartScreenX + delta_x; + floater_view_y = mDragStartScreenY + delta_y; + mDragStartScreenX = floater_view_x + snap_delta_x; + mDragStartScreenY = floater_view_y + snap_delta_y; + } + + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" << llendl; + handled = TRUE; + } + else + { + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (inactive)" << llendl; + handled = TRUE; + } + + if( handled ) + { + switch( mSide ) + { + case LEFT: + case RIGHT: + getWindow()->setCursor(UI_CURSOR_SIZEWE); + break; + + case TOP: + case BOTTOM: + getWindow()->setCursor(UI_CURSOR_SIZENS); + break; + } + } + + return handled; +} + diff --git a/indra/llui/llresizebar.h b/indra/llui/llresizebar.h new file mode 100644 index 0000000000..c2a07fd3e3 --- /dev/null +++ b/indra/llui/llresizebar.h @@ -0,0 +1,47 @@ +/** + * @file llresizebar.h + * @brief LLResizeBar base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_RESIZEBAR_H +#define LL_RESIZEBAR_H + +#include "llview.h" +#include "llcoord.h" + +class LLResizeBar : public LLView +{ +public: + enum Side { LEFT, TOP, RIGHT, BOTTOM }; + + LLResizeBar(const LLString& name, const LLRect& rect, S32 min_width, S32 min_height, Side side ); + + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + +// virtual void draw(); No appearance + virtual BOOL handleHover(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); + + void setResizeLimits( S32 min_width, S32 min_height ) { mMinWidth = min_width; mMinHeight = min_height; } + +protected: + S32 mDragStartScreenX; + S32 mDragStartScreenY; + S32 mLastMouseScreenX; + S32 mLastMouseScreenY; + LLCoordGL mLastMouseDir; + S32 mMinWidth; + S32 mMinHeight; + Side mSide; +}; + +const S32 RESIZE_BAR_HEIGHT = 3; + +#endif // LL_RESIZEBAR_H + + diff --git a/indra/llui/llresizehandle.cpp b/indra/llui/llresizehandle.cpp new file mode 100644 index 0000000000..77101fa296 --- /dev/null +++ b/indra/llui/llresizehandle.cpp @@ -0,0 +1,321 @@ +/** + * @file llresizehandle.cpp + * @brief LLResizeHandle base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llresizehandle.h" + +#include "llfocusmgr.h" +#include "llmath.h" +#include "llui.h" +#include "llmenugl.h" +#include "llcontrol.h" +#include "llfloater.h" +#include "llwindow.h" + +const S32 RESIZE_BORDER_WIDTH = 3; + +LLResizeHandle::LLResizeHandle( const LLString& name, const LLRect& rect, S32 min_width, S32 min_height, ECorner corner ) + : + LLView( name, rect, TRUE ), + mDragStartScreenX( 0 ), + mDragStartScreenY( 0 ), + mLastMouseScreenX( 0 ), + mLastMouseScreenY( 0 ), + mImage( NULL ), + mMinWidth( min_width ), + mMinHeight( min_height ), + mCorner( corner ) +{ + setSaveToXML(false); + + if( RIGHT_BOTTOM == mCorner) + { + LLUUID image_id(LLUI::sConfigGroup->getString("UIImgResizeBottomRightUUID")); + mImage = LLUI::sImageProvider->getUIImageByID(image_id); + } + + switch( mCorner ) + { + case LEFT_TOP: setFollows( FOLLOWS_LEFT | FOLLOWS_TOP ); break; + case LEFT_BOTTOM: setFollows( FOLLOWS_LEFT | FOLLOWS_BOTTOM ); break; + case RIGHT_TOP: setFollows( FOLLOWS_RIGHT | FOLLOWS_TOP ); break; + case RIGHT_BOTTOM: setFollows( FOLLOWS_RIGHT | FOLLOWS_BOTTOM ); break; + } +} + +EWidgetType LLResizeHandle::getWidgetType() const +{ + return WIDGET_TYPE_RESIZE_HANDLE; +} + +LLString LLResizeHandle::getWidgetTag() const +{ + return LL_RESIZE_HANDLE_TAG; +} + +BOOL LLResizeHandle::handleMouseDown(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + if( getVisible() && pointInHandle(x, y) ) + { + handled = TRUE; + if( mEnabled ) + { + // Route future Mouse messages here preemptively. (Release on mouse up.) + // No handler needed for focus lost since this clas has no state that depends on it. + gFocusMgr.setMouseCapture( this, NULL ); + + localPointToScreen(x, y, &mDragStartScreenX, &mDragStartScreenY); + mLastMouseScreenX = mDragStartScreenX; + mLastMouseScreenY = mDragStartScreenY; + } + } + + return handled; +} + + +BOOL LLResizeHandle::handleMouseUp(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + if( gFocusMgr.getMouseCapture() == this ) + { + // Release the mouse + gFocusMgr.setMouseCapture( NULL, NULL ); + handled = TRUE; + } + else + if( getVisible() && pointInHandle(x, y) ) + { + handled = TRUE; + } + + return handled; +} + + +BOOL LLResizeHandle::handleHover(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + // We only handle the click if the click both started and ended within us + if( gFocusMgr.getMouseCapture() == this ) + { + // Make sure the mouse in still over the application. We don't want to make the parent + // so big that we can't see the resize handle any more. + + S32 screen_x; + S32 screen_y; + localPointToScreen(x, y, &screen_x, &screen_y); + const LLRect& valid_rect = gFloaterView->getRect(); // Assumes that the parent is a floater. + screen_x = llclamp( screen_x, valid_rect.mLeft, valid_rect.mRight ); + screen_y = llclamp( screen_y, valid_rect.mBottom, valid_rect.mTop ); + + LLView* parentView = getParent(); + if( parentView ) + { + // Resize the parent + LLRect parent_rect = parentView->getRect(); + LLRect scaled_rect = parent_rect; + S32 delta_x = screen_x - mDragStartScreenX; + S32 delta_y = screen_y - mDragStartScreenY; + LLCoordGL mouse_dir; + // use hysteresis on mouse motion to preserve user intent when mouse stops moving + mouse_dir.mX = (screen_x == mLastMouseScreenX) ? mLastMouseDir.mX : screen_x - mLastMouseScreenX; + mouse_dir.mY = (screen_y == mLastMouseScreenY) ? mLastMouseDir.mY : screen_y - mLastMouseScreenY; + mLastMouseScreenX = screen_x; + mLastMouseScreenY = screen_y; + mLastMouseDir = mouse_dir; + + S32 x_multiple = 1; + S32 y_multiple = 1; + switch( mCorner ) + { + case LEFT_TOP: + x_multiple = -1; + y_multiple = 1; + break; + case LEFT_BOTTOM: + x_multiple = -1; + y_multiple = -1; + break; + case RIGHT_TOP: + x_multiple = 1; + y_multiple = 1; + break; + case RIGHT_BOTTOM: + x_multiple = 1; + y_multiple = -1; + break; + } + + S32 new_width = parent_rect.getWidth() + x_multiple * delta_x; + if( new_width < mMinWidth ) + { + new_width = mMinWidth; + delta_x = x_multiple * (mMinWidth - parent_rect.getWidth()); + } + + S32 new_height = parent_rect.getHeight() + y_multiple * delta_y; + if( new_height < mMinHeight ) + { + new_height = mMinHeight; + delta_y = y_multiple * (mMinHeight - parent_rect.getHeight()); + } + + switch( mCorner ) + { + case LEFT_TOP: + scaled_rect.translate(delta_x, 0); + break; + case LEFT_BOTTOM: + scaled_rect.translate(delta_x, delta_y); + break; + case RIGHT_TOP: + break; + case RIGHT_BOTTOM: + scaled_rect.translate(0, delta_y); + break; + } + + // temporarily set new parent rect + scaled_rect.mRight = scaled_rect.mLeft + new_width; + scaled_rect.mTop = scaled_rect.mBottom + new_height; + parentView->setRect(scaled_rect); + + S32 snap_delta_x = 0; + S32 snap_delta_y = 0; + + LLView* snap_view = NULL; + LLView* test_view = NULL; + + // now do snapping + switch(mCorner) + { + case LEFT_TOP: + snap_view = parentView->findSnapEdge(snap_delta_x, mouse_dir, SNAP_LEFT, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + snap_delta_x -= scaled_rect.mLeft; + test_view = parentView->findSnapEdge(snap_delta_y, mouse_dir, SNAP_TOP, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + snap_delta_y -= scaled_rect.mTop; + if (!snap_view) + { + snap_view = test_view; + } + scaled_rect.mLeft += snap_delta_x; + scaled_rect.mTop += snap_delta_y; + break; + case LEFT_BOTTOM: + snap_view = parentView->findSnapEdge(snap_delta_x, mouse_dir, SNAP_LEFT, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + snap_delta_x -= scaled_rect.mLeft; + test_view = parentView->findSnapEdge(snap_delta_y, mouse_dir, SNAP_BOTTOM, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + snap_delta_y -= scaled_rect.mBottom; + if (!snap_view) + { + snap_view = test_view; + } + scaled_rect.mLeft += snap_delta_x; + scaled_rect.mBottom += snap_delta_y; + break; + case RIGHT_TOP: + snap_view = parentView->findSnapEdge(snap_delta_x, mouse_dir, SNAP_RIGHT, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + snap_delta_x -= scaled_rect.mRight; + test_view = parentView->findSnapEdge(snap_delta_y, mouse_dir, SNAP_TOP, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + snap_delta_y -= scaled_rect.mTop; + if (!snap_view) + { + snap_view = test_view; + } + scaled_rect.mRight += snap_delta_x; + scaled_rect.mTop += snap_delta_y; + break; + case RIGHT_BOTTOM: + snap_view = parentView->findSnapEdge(snap_delta_x, mouse_dir, SNAP_RIGHT, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + snap_delta_x -= scaled_rect.mRight; + test_view = parentView->findSnapEdge(snap_delta_y, mouse_dir, SNAP_BOTTOM, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + snap_delta_y -= scaled_rect.mBottom; + if (!snap_view) + { + snap_view = test_view; + } + scaled_rect.mRight += snap_delta_x; + scaled_rect.mBottom += snap_delta_y; + break; + } + + parentView->snappedTo(snap_view); + + // reset parent rect + parentView->setRect(parent_rect); + + // translate and scale to new shape + parentView->reshape(scaled_rect.getWidth(), scaled_rect.getHeight(), FALSE); + parentView->translate(scaled_rect.mLeft - parentView->getRect().mLeft, scaled_rect.mBottom - parentView->getRect().mBottom); + + screen_x = mDragStartScreenX + delta_x + snap_delta_x; + screen_y = mDragStartScreenY + delta_y + snap_delta_y; + mDragStartScreenX = screen_x; + mDragStartScreenY = screen_y; + } + + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active) " << llendl; + handled = TRUE; + } + else + if( getVisible() && pointInHandle( x, y ) ) + { + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (inactive) " << llendl; + handled = TRUE; + } + + if( handled ) + { + switch( mCorner ) + { + case RIGHT_BOTTOM: + case LEFT_TOP: + getWindow()->setCursor(UI_CURSOR_SIZENWSE); + break; + case LEFT_BOTTOM: + case RIGHT_TOP: + getWindow()->setCursor(UI_CURSOR_SIZENESW); + break; + } + } + + return handled; +} + +// assumes GL state is set for 2D +void LLResizeHandle::draw() +{ + if( mImage.notNull() && getVisible() && (RIGHT_BOTTOM == mCorner) ) + { + gl_draw_image( 0, 0, mImage ); + } +} + + +BOOL LLResizeHandle::pointInHandle( S32 x, S32 y ) +{ + if( pointInView(x, y) ) + { + const S32 TOP_BORDER = (mRect.getHeight() - RESIZE_BORDER_WIDTH); + const S32 RIGHT_BORDER = (mRect.getWidth() - RESIZE_BORDER_WIDTH); + + switch( mCorner ) + { + case LEFT_TOP: return (x <= RESIZE_BORDER_WIDTH) || (y >= TOP_BORDER); + case LEFT_BOTTOM: return (x <= RESIZE_BORDER_WIDTH) || (y <= RESIZE_BORDER_WIDTH); + case RIGHT_TOP: return (x >= RIGHT_BORDER) || (y >= TOP_BORDER); + case RIGHT_BOTTOM: return TRUE; + } + } + return FALSE; +} diff --git a/indra/llui/llresizehandle.h b/indra/llui/llresizehandle.h new file mode 100644 index 0000000000..1350d1af20 --- /dev/null +++ b/indra/llui/llresizehandle.h @@ -0,0 +1,56 @@ +/** + * @file llresizehandle.h + * @brief LLResizeHandle base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_RESIZEHANDLE_H +#define LL_RESIZEHANDLE_H + +#include "stdtypes.h" +#include "llview.h" +#include "llimagegl.h" +#include "llcoord.h" + + +class LLResizeHandle : public LLView +{ +public: + enum ECorner { LEFT_TOP, LEFT_BOTTOM, RIGHT_TOP, RIGHT_BOTTOM }; + + + LLResizeHandle(const LLString& name, const LLRect& rect, S32 min_width, S32 min_height, ECorner corner = RIGHT_BOTTOM ); + + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + virtual void draw(); + virtual BOOL handleHover(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); + + void setResizeLimits( S32 min_width, S32 min_height ) { mMinWidth = min_width; mMinHeight = min_height; } + +protected: + BOOL pointInHandle( S32 x, S32 y ); + +protected: + S32 mDragStartScreenX; + S32 mDragStartScreenY; + S32 mLastMouseScreenX; + S32 mLastMouseScreenY; + LLCoordGL mLastMouseDir; + LLPointer<LLImageGL> mImage; + S32 mMinWidth; + S32 mMinHeight; + ECorner mCorner; +}; + +const S32 RESIZE_HANDLE_HEIGHT = 16; +const S32 RESIZE_HANDLE_WIDTH = 16; + +#endif // LL_RESIZEHANDLE_H + + diff --git a/indra/llui/llresmgr.cpp b/indra/llui/llresmgr.cpp new file mode 100644 index 0000000000..67137d8bbb --- /dev/null +++ b/indra/llui/llresmgr.cpp @@ -0,0 +1,447 @@ +/** + * @file llresmgr.cpp + * @brief Localized resource manager + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// NOTE: this is a MINIMAL implementation. The interface will remain, but the implementation will +// (when the time is right) become dynamic and probably use external files. + +#include "linden_common.h" + +#include "llresmgr.h" +#include "llfontgl.h" +#include "llerror.h" +#include "llstring.h" + +// Global singleton +LLResMgr* gResMgr = NULL; + +LLResMgr::LLResMgr() +{ + U32 i; + + // Init values for each locale. + // Note: This is only the most bare-bones version. In the future, load these dynamically, on demand. + + ////////////////////////////////////////////////////////////////////////////// + // USA + // USA Fonts + for( i=0; i<LLFONT_COUNT; i++ ) + { + mUSAFonts[i] = NULL; + } + mUSAFonts[ LLFONT_OCRA ] = LLFontGL::sMonospace; + mUSAFonts[ LLFONT_SANSSERIF ] = LLFontGL::sSansSerif; + mUSAFonts[ LLFONT_SANSSERIF_SMALL ] = LLFontGL::sSansSerifSmall; + mUSAFonts[ LLFONT_SANSSERIF_BIG ] = LLFontGL::sSansSerifBig; + mUSAFonts[ LLFONT_SMALL ] = LLFontGL::sMonospace; +/* + // USA Strings + for( i=0; i<LLSTR_COUNT; i++ ) + { + mUSAStrings[i] = ""; + } + mUSAStrings[ LLSTR_HELLO ] = "hello"; + mUSAStrings[ LLSTR_GOODBYE ] = "goodbye"; + mUSAStrings[ LLSTR_CHAT_LABEL ] = "Chat"; + mUSAStrings[ LLSTR_STATUS_LABEL ] = "Properties"; + mUSAStrings[ LLSTR_X ] = "X"; + mUSAStrings[ LLSTR_Y ] = "Y"; + mUSAStrings[ LLSTR_Z ] = "Z"; + mUSAStrings[ LLSTR_POSITION ] = "Position (meters)"; + mUSAStrings[ LLSTR_SCALE ] = "Size (meters)"; + mUSAStrings[ LLSTR_ROTATION ] = "Rotation (degrees)"; + mUSAStrings[ LLSTR_HAS_PHYSICS ] = "Has Physics"; + mUSAStrings[ LLSTR_SCRIPT ] = "Script"; + mUSAStrings[ LLSTR_HELP ] = "Help"; + mUSAStrings[ LLSTR_REMOVE ] = "Remove"; + mUSAStrings[ LLSTR_CLEAR ] = "Clear"; + mUSAStrings[ LLSTR_APPLY ] = "Apply"; + mUSAStrings[ LLSTR_CANCEL ] = "Cancel"; + mUSAStrings[ LLSTR_MATERIAL ] = "Material"; + mUSAStrings[ LLSTR_FACE ] = "Face"; + mUSAStrings[ LLSTR_TEXTURE ] = "Texture"; + mUSAStrings[ LLSTR_TEXTURE_SIZE ] = "Repeats per Face"; + mUSAStrings[ LLSTR_TEXTURE_OFFSET ] = "Offset"; + mUSAStrings[ LLSTR_TEXTURE_ROTATION ] = "Rotation (degrees)"; + mUSAStrings[ LLSTR_U ] = "U"; + mUSAStrings[ LLSTR_V ] = "V"; + mUSAStrings[ LLSTR_OWNERSHIP ] = "Ownership"; + mUSAStrings[ LLSTR_PUBLIC ] = "Public"; + mUSAStrings[ LLSTR_PRIVATE ] = "Private"; + mUSAStrings[ LLSTR_REVERT ] = "Revert"; + mUSAStrings[ LLSTR_INSERT_SAMPLE ] = "Insert Sample"; + mUSAStrings[ LLSTR_SET_TEXTURE ] = "Set Texture"; + mUSAStrings[ LLSTR_EDIT_SCRIPT ] = "Edit Script..."; + mUSAStrings[ LLSTR_MOUSELOOK_INSTRUCTIONS ] = "Press ESC to leave Mouselook."; + mUSAStrings[ LLSTR_EDIT_FACE_INSTRUCTIONS ] = "Click on face to select part. Click and hold on a picture to look more like that. Press ESC to leave Face Edit Mode."; + mUSAStrings[ LLSTR_CLOSE ] = "Close"; + mUSAStrings[ LLSTR_MOVE ] = "Move"; + mUSAStrings[ LLSTR_ROTATE ] = "Rotate"; + mUSAStrings[ LLSTR_RESIZE ] = "Resize"; + mUSAStrings[ LLSTR_PLACE_BOX ] = "Place Box"; + mUSAStrings[ LLSTR_PLACE_PRISM ] = "Place Prism"; + mUSAStrings[ LLSTR_PLACE_PYRAMID ] = "Place Pyramid"; + mUSAStrings[ LLSTR_PLACE_TETRAHEDRON ] = "Place Tetrahedron"; + mUSAStrings[ LLSTR_PLACE_CYLINDER ] = "Place Cylinder"; + mUSAStrings[ LLSTR_PLACE_HALF_CYLINDER ] = "Place Half-Cylinder"; + mUSAStrings[ LLSTR_PLACE_CONE ] = "Place Cone"; + mUSAStrings[ LLSTR_PLACE_HALF_CONE ] = "Place Half-Cone"; + mUSAStrings[ LLSTR_PLACE_SPHERE ] = "Place Sphere"; + mUSAStrings[ LLSTR_PLACE_HALF_SPHERE ] = "Place Half-Sphere"; + mUSAStrings[ LLSTR_PLACE_BIRD ] = "Place Bird"; + mUSAStrings[ LLSTR_PLACE_SNAKE ] = "Place Silly Snake"; + mUSAStrings[ LLSTR_PLACE_ROCK ] = "Place Rock"; + mUSAStrings[ LLSTR_PLACE_TREE ] = "Place Tree"; + mUSAStrings[ LLSTR_PLACE_GRASS ] = "Place Grass"; + mUSAStrings[ LLSTR_MODIFY_LAND ] = "Modify Land"; +*/ + ////////////////////////////////////////////////////////////////////////////// + // UK + // The Brits are a lot like us Americans, so initially assume we're the same and only code the exceptions. + + // UK Fonts + for( i=0; i<LLFONT_COUNT; i++ ) + { + mUKFonts[i] = mUSAFonts[i]; + } +/* + // UK Strings + for( i=0; i<LLSTR_COUNT; i++ ) + { + mUKStrings[i] = mUSAStrings[i]; + } + mUKStrings[ LLSTR_HELLO ] = "hullo"; + mUKStrings[ LLSTR_GOODBYE ] = "cheerio"; +*/ + ////////////////////////////////////////////////////////////////////////////// + // Set default + setLocale( LLLOCALE_USA ); + +} + + +void LLResMgr::setLocale( LLLOCALE_ID locale_id ) +{ + mLocale = locale_id; + + //RN: for now, use normal 'C' locale for everything but specific UI input/output routines + switch( locale_id ) + { + case LLLOCALE_USA: +//#if LL_WINDOWS +// // Windows doesn't use ISO country codes. +// llinfos << "Setting locale to " << setlocale( LC_ALL, "english-usa" ) << llendl; +//#else +// // posix version should work everywhere else. +// llinfos << "Setting locale to " << setlocale( LC_ALL, "en_US" ) << llendl; +//#endif + +// mStrings = mUSAStrings; + mFonts = mUSAFonts; + break; + case LLLOCALE_UK: +//#if LL_WINDOWS +// // Windows doesn't use ISO country codes. +// llinfos << "Setting locale to " << setlocale( LC_ALL, "english-uk" ) << llendl; +//#else +// // posix version should work everywhere else. +// llinfos << "Setting locale to " << setlocale( LC_ALL, "en_GB" ) << llendl; +//#endif + +// mStrings = mUKStrings; + mFonts = mUKFonts; + break; + default: + llassert(0); + setLocale(LLLOCALE_USA); + break; + } +} + +char LLResMgr::getDecimalPoint() const +{ + char decimal = localeconv()->decimal_point[0]; + +#if LL_DARWIN + // On the Mac, locale support is broken before 10.4, which causes things to go all pear-shaped. + if(decimal == 0) + { + decimal = '.'; + } +#endif + + return decimal; +} + +char LLResMgr::getThousandsSeparator() const +{ + char separator = localeconv()->thousands_sep[0]; + +#if LL_DARWIN + // On the Mac, locale support is broken before 10.4, which causes things to go all pear-shaped. + if(separator == 0) + { + separator = ','; + } +#endif + + return separator; +} + +char LLResMgr::getMonetaryDecimalPoint() const +{ + char decimal = localeconv()->mon_decimal_point[0]; + +#if LL_DARWIN + // On the Mac, locale support is broken before 10.4, which causes things to go all pear-shaped. + if(decimal == 0) + { + decimal = '.'; + } +#endif + + return decimal; +} + +char LLResMgr::getMonetaryThousandsSeparator() const +{ + char separator = localeconv()->mon_thousands_sep[0]; + +#if LL_DARWIN + // On the Mac, locale support is broken before 10.4, which causes things to go all pear-shaped. + if(separator == 0) + { + separator = ','; + } +#endif + + return separator; +} + + +// Sets output to a string of integers with monetary separators inserted according to the locale. +void LLResMgr::getMonetaryString( LLString& output, S32 input ) const +{ + LLLocale locale(LLLocale::USER_LOCALE); + struct lconv *conv = localeconv(); + +#if LL_DARWIN + // On the Mac, locale support is broken before 10.4, which causes things to go all pear-shaped. + // Fake up a conv structure with some reasonable values for the fields this function uses. + struct lconv fakeconv; + if(conv->negative_sign[0] == 0) // Real locales all seem to have something here... + { + fakeconv = *conv; // start with what's there. + switch(mLocale) + { + default: // Unknown -- use the US defaults. + case LLLOCALE_USA: + case LLLOCALE_UK: // UK ends up being the same as US for the items used here. + fakeconv.negative_sign = "-"; + fakeconv.mon_grouping = "\x03\x03\x00"; // commas every 3 digits + fakeconv.n_sign_posn = 1; // negative sign before the string + break; + } + conv = &fakeconv; + } +#endif + + char* negative_sign = conv->negative_sign; + char separator = getMonetaryThousandsSeparator(); + char* grouping = conv->mon_grouping; + + // Note on mon_grouping: + // Specifies a string that defines the size of each group of digits in formatted monetary quantities. + // The operand for the mon_grouping keyword consists of a sequence of semicolon-separated integers. + // Each integer specifies the number of digits in a group. The initial integer defines the size of + // the group immediately to the left of the decimal delimiter. The following integers define succeeding + // groups to the left of the previous group. If the last integer is not -1, the size of the previous + // group (if any) is repeatedly used for the remainder of the digits. If the last integer is -1, no + // further grouping is performed. + + + // Note: we assume here that the currency symbol goes on the left. (Hey, it's Lindens! We can just decide.) + BOOL negative = (input < 0 ); + BOOL negative_before = negative && (conv->n_sign_posn != 2); + BOOL negative_after = negative && (conv->n_sign_posn == 2); + + LLString digits = llformat("%u", abs(input)); + if( !grouping || !grouping[0] ) + { + output.assign("L$"); + if( negative_before ) + { + output.append( negative_sign ); + } + output.append( digits ); + if( negative_after ) + { + output.append( negative_sign ); + } + return; + } + + S32 groupings[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + S32 cur_group; + for( cur_group = 0; grouping[ cur_group ]; cur_group++ ) + { + if( grouping[ cur_group ] != ';' ) + { + groupings[cur_group] = grouping[ cur_group ]; + } + cur_group++; + + if( groupings[cur_group] < 0 ) + { + break; + } + } + S32 group_count = cur_group; + + char reversed_output[20] = ""; + char forward_output[20] = ""; + S32 output_pos = 0; + + cur_group = 0; + S32 pos = digits.size()-1; + S32 count_within_group = 0; + while( (pos >= 0) && (groupings[cur_group] >= 0) ) + { + count_within_group++; + if( count_within_group > groupings[cur_group] ) + { + count_within_group = 1; + reversed_output[ output_pos++ ] = separator; + + if( (cur_group + 1) >= group_count ) + { + break; + } + else + if( groupings[cur_group + 1] > 0 ) + { + cur_group++; + } + } + reversed_output[ output_pos++ ] = digits[pos--]; + } + + while( pos >= 0 ) + { + reversed_output[ output_pos++ ] = digits[pos--]; + } + + + reversed_output[ output_pos ] = '\0'; + forward_output[ output_pos ] = '\0'; + + for( S32 i = 0; i < output_pos; i++ ) + { + forward_output[ output_pos - 1 - i ] = reversed_output[ i ]; + } + + output.assign("L$"); + if( negative_before ) + { + output.append( negative_sign ); + } + output.append( forward_output ); + if( negative_after ) + { + output.append( negative_sign ); + } +} + +void LLResMgr::getIntegerString( LLString& output, S32 input ) const +{ + S32 fraction = 0; + LLString fraction_string; + S32 remaining_count = input; + while(remaining_count > 0) + { + fraction = (remaining_count) % 1000; + + if (!output.empty()) + { + if (fraction == remaining_count) + { + fraction_string = llformat("%d%c", fraction, getThousandsSeparator()); + } + else + { + fraction_string = llformat("%3.3d%c", fraction, getThousandsSeparator()); + } + output = fraction_string + output; + } + else + { + if (fraction == remaining_count) + { + fraction_string = llformat("%d", fraction); + } + else + { + fraction_string = llformat("%3.3d", fraction); + } + output = fraction_string; + } + remaining_count /= 1000; + } +} + +const LLString LLFONT_ID_NAMES[] = +{ + LLString("OCRA"), + LLString("SANSSERIF"), + LLString("SANSSERIF_SMALL"), + LLString("SANSSERIF_BIG"), + LLString("SMALL"), +}; + +const LLFontGL* LLResMgr::getRes( LLString font_id ) const +{ + for (S32 i=0; i<LLFONT_COUNT; ++i) + { + if (LLFONT_ID_NAMES[i] == font_id) + { + return getRes((LLFONT_ID)i); + } + } + return NULL; +} + +#if LL_WINDOWS +const LLString LLLocale::USER_LOCALE("English_United States.1252");// = LLString::null; +const LLString LLLocale::SYSTEM_LOCALE("English_United States.1252"); +#elif LL_DARWIN +const LLString LLLocale::USER_LOCALE("en_US.iso8859-1");// = LLString::null; +const LLString LLLocale::SYSTEM_LOCALE("en_US.iso8859-1"); +#else // LL_LINUX likes this +const LLString LLLocale::USER_LOCALE("en_US.utf8");// = LLString::null; +const LLString LLLocale::SYSTEM_LOCALE("en_US.utf8"); +#endif + + +LLLocale::LLLocale(const LLString& locale_string) +{ + mPrevLocaleString = setlocale( LC_ALL, NULL ); + char* new_locale_string = setlocale( LC_ALL, locale_string.c_str()); + if ( new_locale_string == NULL) + { + llwarns << "Failed to set locale " << locale_string << llendl; + setlocale(LC_ALL, SYSTEM_LOCALE.c_str()); + } + //else + //{ + // llinfos << "Set locale to " << new_locale_string << llendl; + //} +} + +LLLocale::~LLLocale() +{ + setlocale( LC_ALL, mPrevLocaleString.c_str() ); +} diff --git a/indra/llui/llresmgr.h b/indra/llui/llresmgr.h new file mode 100644 index 0000000000..b79fa2021f --- /dev/null +++ b/indra/llui/llresmgr.h @@ -0,0 +1,147 @@ +/** + * @file llresmgr.h + * @brief Localized resource manager + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// NOTE: this is a MINIMAL implementation. The interface will remain, but the implementation will +// (when the time is right) become dynamic and probably use external files. + +#ifndef LL_LLRESMGR_H +#define LL_LLRESMGR_H + +#include "locale.h" +#include "stdtypes.h" +#include "llstring.h" + +enum LLLOCALE_ID +{ + LLLOCALE_USA, + LLLOCALE_UK, + LLLOCALE_COUNT // Number of values in this enum. Keep at end. +}; + +/* +enum LLSTR_ID +{ + LLSTR_HELLO, + LLSTR_GOODBYE, + LLSTR_CHAT_LABEL, + LLSTR_STATUS_LABEL, + LLSTR_X, + LLSTR_Y, + LLSTR_Z, + LLSTR_POSITION, + LLSTR_SCALE, + LLSTR_ROTATION, + LLSTR_HAS_PHYSICS, + LLSTR_SCRIPT, + LLSTR_HELP, + LLSTR_REMOVE, + LLSTR_CLEAR, + LLSTR_APPLY, + LLSTR_CANCEL, + LLSTR_MATERIAL, + LLSTR_FACE, + LLSTR_TEXTURE, + LLSTR_TEXTURE_SIZE, + LLSTR_TEXTURE_OFFSET, + LLSTR_TEXTURE_ROTATION, + LLSTR_U, + LLSTR_V, + LLSTR_OWNERSHIP, + LLSTR_PUBLIC, + LLSTR_PRIVATE, + LLSTR_REVERT, + LLSTR_INSERT_SAMPLE, + LLSTR_SET_TEXTURE, + LLSTR_EDIT_SCRIPT, + LLSTR_MOUSELOOK_INSTRUCTIONS, + LLSTR_EDIT_FACE_INSTRUCTIONS, + LLSTR_CLOSE, + LLSTR_MOVE, + LLSTR_ROTATE, + LLSTR_RESIZE, + LLSTR_PLACE_BOX, + LLSTR_PLACE_PRISM, + LLSTR_PLACE_PYRAMID, + LLSTR_PLACE_TETRAHEDRON, + LLSTR_PLACE_CYLINDER, + LLSTR_PLACE_HALF_CYLINDER, + LLSTR_PLACE_CONE, + LLSTR_PLACE_HALF_CONE, + LLSTR_PLACE_SPHERE, + LLSTR_PLACE_HALF_SPHERE, + LLSTR_PLACE_BIRD, + LLSTR_PLACE_SNAKE, + LLSTR_PLACE_ROCK, + LLSTR_PLACE_TREE, + LLSTR_PLACE_GRASS, + LLSTR_MODIFY_LAND, + LLSTR_COUNT // Number of values in this enum. Keep at end. +}; +*/ + +enum LLFONT_ID +{ + LLFONT_OCRA, + LLFONT_SANSSERIF, + LLFONT_SANSSERIF_SMALL, + LLFONT_SANSSERIF_BIG, + LLFONT_SMALL, + LLFONT_COUNT // Number of values in this enum. Keep at end. +}; + +class LLFontGL; + +class LLResMgr +{ +public: + LLResMgr(); + + void setLocale( LLLOCALE_ID locale_id ); + LLLOCALE_ID getLocale() const { return mLocale; } + + char getDecimalPoint() const; + char getThousandsSeparator() const; + + char getMonetaryDecimalPoint() const; + char getMonetaryThousandsSeparator() const; + void getMonetaryString( LLString& output, S32 input ) const; + void getIntegerString( LLString& output, S32 input ) const; + +// const char* getRes( LLSTR_ID string_id ) const { return mStrings[ string_id ]; } + const LLFontGL* getRes( LLFONT_ID font_id ) const { return mFonts[ font_id ]; } + const LLFontGL* getRes( LLString font_id ) const; + +private: + LLLOCALE_ID mLocale; +// const char** mStrings; + const LLFontGL** mFonts; + +// const char* mUSAStrings[LLSTR_COUNT]; + const LLFontGL* mUSAFonts[LLFONT_COUNT]; + +// const char* mUKStrings[LLSTR_COUNT]; + const LLFontGL* mUKFonts[LLFONT_COUNT]; +}; + +class LLLocale +{ +public: + LLLocale(const LLString& locale_string); + virtual ~LLLocale(); + +public: + static const LLString USER_LOCALE; + static const LLString SYSTEM_LOCALE; + +protected: + LLString mPrevLocaleString; +}; + +extern LLResMgr* gResMgr; + +#endif // LL_RESMGR_ diff --git a/indra/llui/llscrollbar.cpp b/indra/llui/llscrollbar.cpp new file mode 100644 index 0000000000..4a5ae1dadf --- /dev/null +++ b/indra/llui/llscrollbar.cpp @@ -0,0 +1,618 @@ +/** + * @file llscrollbar.cpp + * @brief Scrollbar UI widget + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llscrollbar.h" + +#include "llmath.h" +#include "lltimer.h" +#include "v3color.h" + +#include "llbutton.h" +#include "llcriticaldamp.h" +#include "llkeyboard.h" +#include "llui.h" +//#include "llviewerimagelist.h" +#include "llfocusmgr.h" +#include "llwindow.h" +#include "llglheaders.h" +#include "llcontrol.h" + +LLScrollbar::LLScrollbar( + const LLString& name, LLRect rect, + LLScrollbar::ORIENTATION orientation, + S32 doc_size, S32 doc_pos, S32 page_size, + void (*change_callback)( S32 new_pos, LLScrollbar* self, void* userdata ), + void* callback_user_data, + S32 step_size) +: LLUICtrl( name, rect, TRUE, NULL, NULL ), + + mChangeCallback( change_callback ), + mCallbackUserData( callback_user_data ), + mOrientation( orientation ), + mDocSize( doc_size ), + mDocPos( doc_pos ), + mPageSize( page_size ), + mStepSize( step_size ), + mDocChanged(FALSE), + mDragStartX( 0 ), + mDragStartY( 0 ), + mHoverGlowStrength(0.15f), + mCurGlowStrength(0.f), + mTrackColor( LLUI::sColorsGroup->getColor("ScrollbarTrackColor") ), + mThumbColor ( LLUI::sColorsGroup->getColor("ScrollbarThumbColor") ), + mHighlightColor ( LLUI::sColorsGroup->getColor("DefaultHighlightLight") ), + mShadowColor ( LLUI::sColorsGroup->getColor("DefaultShadowLight") ), + mOnScrollEndCallback( NULL ), + mOnScrollEndData( NULL ) +{ + //llassert( 0 <= mDocSize ); + //llassert( 0 <= mDocPos && mDocPos <= mDocSize ); + + setTabStop(FALSE); + updateThumbRect(); + + // Page up and page down buttons + LLRect line_up_rect; + LLString line_up_img; + LLString line_up_selected_img; + LLString line_down_img; + LLString line_down_selected_img; + + LLRect line_down_rect; + + if( LLScrollbar::VERTICAL == mOrientation ) + { + line_up_rect.setLeftTopAndSize( 0, mRect.getHeight(), SCROLLBAR_SIZE, SCROLLBAR_SIZE ); + line_up_img="UIImgBtnScrollUpOutUUID"; + line_up_selected_img="UIImgBtnScrollUpInUUID"; + + line_down_rect.setOriginAndSize( 0, 0, SCROLLBAR_SIZE, SCROLLBAR_SIZE ); + line_down_img="UIImgBtnScrollDownOutUUID"; + line_down_selected_img="UIImgBtnScrollDownInUUID"; + } + else + { + // Horizontal + line_up_rect.setOriginAndSize( 0, 0, SCROLLBAR_SIZE, SCROLLBAR_SIZE ); + line_up_img="UIImgBtnScrollLeftOutUUID"; + line_up_selected_img="UIImgBtnScrollLeftInUUID"; + + line_down_rect.setOriginAndSize( mRect.getWidth() - SCROLLBAR_SIZE, 0, SCROLLBAR_SIZE, SCROLLBAR_SIZE ); + line_down_img="UIImgBtnScrollRightOutUUID"; + line_down_selected_img="UIImgBtnScrollRightInUUID"; + } + + LLButton* line_up_btn = new LLButton( + "Line Up", line_up_rect, + line_up_img, line_up_selected_img, "", + &LLScrollbar::onLineUpBtnPressed, this, LLFontGL::sSansSerif ); + if( LLScrollbar::VERTICAL == mOrientation ) + { + line_up_btn->setFollowsRight(); + line_up_btn->setFollowsTop(); + } + else + { + // horizontal + line_up_btn->setFollowsLeft(); + line_up_btn->setFollowsBottom(); + } + line_up_btn->setHeldDownCallback( &LLScrollbar::onLineUpBtnPressed ); + line_up_btn->setTabStop(FALSE); + addChild(line_up_btn); + + LLButton* line_down_btn = new LLButton( + "Line Down", line_down_rect, + line_down_img, line_down_selected_img, "", + &LLScrollbar::onLineDownBtnPressed, this, LLFontGL::sSansSerif ); + line_down_btn->setFollowsRight(); + line_down_btn->setFollowsBottom(); + line_down_btn->setHeldDownCallback( &LLScrollbar::onLineDownBtnPressed ); + line_down_btn->setTabStop(FALSE); + addChild(line_down_btn); +} + + +LLScrollbar::~LLScrollbar() +{ + // Children buttons killed by parent class +} + +void LLScrollbar::setDocParams( S32 size, S32 pos ) +{ + mDocSize = size; + mDocPos = llclamp( pos, 0, getDocPosMax() ); + mDocChanged = TRUE; + + updateThumbRect(); +} + +void LLScrollbar::setDocPos(S32 pos) +{ + mDocPos = llclamp( pos, 0, getDocPosMax() ); + mDocChanged = TRUE; + + updateThumbRect(); +} + +void LLScrollbar::setDocSize(S32 size) +{ + mDocSize = size; + mDocPos = llclamp( mDocPos, 0, getDocPosMax() ); + mDocChanged = TRUE; + + updateThumbRect(); +} + +void LLScrollbar::setPageSize( S32 page_size ) +{ + mPageSize = page_size; + mDocPos = llclamp( mDocPos, 0, getDocPosMax() ); + mDocChanged = TRUE; + + updateThumbRect(); +} + +void LLScrollbar::updateThumbRect() +{ +// llassert( 0 <= mDocSize ); +// llassert( 0 <= mDocPos && mDocPos <= getDocPosMax() ); + + const S32 THUMB_MIN_LENGTH = 16; + + S32 window_length = (mOrientation == LLScrollbar::HORIZONTAL) ? mRect.getWidth() : mRect.getHeight(); + S32 thumb_bg_length = window_length - 2 * SCROLLBAR_SIZE; + S32 visible_lines = llmin( mDocSize, mPageSize ); + S32 thumb_length = mDocSize ? llmax( visible_lines * thumb_bg_length / mDocSize, THUMB_MIN_LENGTH ) : thumb_bg_length; + + S32 variable_lines = mDocSize - visible_lines; + + if( mOrientation == LLScrollbar::VERTICAL ) + { + S32 thumb_start_max = thumb_bg_length + SCROLLBAR_SIZE; + S32 thumb_start_min = SCROLLBAR_SIZE + THUMB_MIN_LENGTH; + S32 thumb_start = variable_lines ? llclamp( thumb_start_max - (mDocPos * (thumb_bg_length - thumb_length)) / variable_lines, thumb_start_min, thumb_start_max ) : thumb_start_max; + + mThumbRect.mLeft = 0; + mThumbRect.mTop = thumb_start; + mThumbRect.mRight = SCROLLBAR_SIZE; + mThumbRect.mBottom = thumb_start - thumb_length; + } + else + { + // Horizontal + S32 thumb_start_max = thumb_bg_length + SCROLLBAR_SIZE - thumb_length; + S32 thumb_start_min = SCROLLBAR_SIZE; + S32 thumb_start = variable_lines ? llclamp( thumb_start_min + (mDocPos * (thumb_bg_length - thumb_length)) / variable_lines, thumb_start_min, thumb_start_max ) : thumb_start_min; + + mThumbRect.mLeft = thumb_start; + mThumbRect.mTop = SCROLLBAR_SIZE; + mThumbRect.mRight = thumb_start + thumb_length; + mThumbRect.mBottom = 0; + } + + if (mOnScrollEndCallback && mOnScrollEndData && (mDocPos == getDocPosMax())) + { + mOnScrollEndCallback(mOnScrollEndData); + } +} + +BOOL LLScrollbar::handleMouseDown(S32 x, S32 y, MASK mask) +{ + // Check children first + BOOL handled_by_child = LLView::childrenHandleMouseDown(x, y, mask) != NULL; + if( !handled_by_child ) + { + if( mThumbRect.pointInRect(x,y) ) + { + // Start dragging the thumb + // No handler needed for focus lost since this clas has no state that depends on it. + gFocusMgr.setMouseCapture( this, NULL ); + mDragStartX = x; + mDragStartY = y; + mOrigRect.mTop = mThumbRect.mTop; + mOrigRect.mBottom = mThumbRect.mBottom; + mOrigRect.mLeft = mThumbRect.mLeft; + mOrigRect.mRight = mThumbRect.mRight; + mLastDelta = 0; + } + else + { + if( + ( (LLScrollbar::VERTICAL == mOrientation) && (mThumbRect.mTop < y) ) || + ( (LLScrollbar::HORIZONTAL == mOrientation) && (x < mThumbRect.mLeft) ) + ) + { + // Page up + pageUp(0); + } + else + if( + ( (LLScrollbar::VERTICAL == mOrientation) && (y < mThumbRect.mBottom) ) || + ( (LLScrollbar::HORIZONTAL == mOrientation) && (mThumbRect.mRight < x) ) + ) + { + // Page down + pageDown(0); + } + } + } + + return TRUE; +} + + +BOOL LLScrollbar::handleHover(S32 x, S32 y, MASK mask) +{ + // Note: we don't bother sending the event to the children (the arrow buttons) + // because they'll capture the mouse whenever they need hover events. + + BOOL handled = FALSE; + if( gFocusMgr.getMouseCapture() == this ) + { + S32 height = mRect.getHeight(); + S32 width = mRect.getWidth(); + + if( VERTICAL == mOrientation ) + { +// S32 old_pos = mThumbRect.mTop; + + S32 delta_pixels = y - mDragStartY; + if( mOrigRect.mBottom + delta_pixels < SCROLLBAR_SIZE ) + { + delta_pixels = SCROLLBAR_SIZE - mOrigRect.mBottom - 1; + } + else + if( mOrigRect.mTop + delta_pixels > height - SCROLLBAR_SIZE ) + { + delta_pixels = height - SCROLLBAR_SIZE - mOrigRect.mTop + 1; + } + + mThumbRect.mTop = mOrigRect.mTop + delta_pixels; + mThumbRect.mBottom = mOrigRect.mBottom + delta_pixels; + + S32 thumb_length = mThumbRect.getHeight(); + S32 thumb_track_length = height - 2 * SCROLLBAR_SIZE; + + + if( delta_pixels != mLastDelta || mDocChanged) + { + // Note: delta_pixels increases as you go up. mDocPos increases down (line 0 is at the top of the page). + S32 usable_track_length = thumb_track_length - thumb_length; + if( 0 < usable_track_length ) + { + S32 variable_lines = getDocPosMax(); + S32 pos = mThumbRect.mTop; + F32 ratio = F32(pos - SCROLLBAR_SIZE - thumb_length) / usable_track_length; + + S32 new_pos = llclamp( S32(variable_lines - ratio * variable_lines + 0.5f), 0, variable_lines ); + // Note: we do not call updateThumbRect() here. Instead we let the thumb and the document go slightly + // out of sync (less than a line's worth) to make the thumb feel responsive. + changeLine( new_pos - mDocPos, FALSE ); + } + } + + mLastDelta = delta_pixels; + + } + else + { + // Horizontal +// S32 old_pos = mThumbRect.mLeft; + + S32 delta_pixels = x - mDragStartX; + + if( mOrigRect.mLeft + delta_pixels < SCROLLBAR_SIZE ) + { + delta_pixels = SCROLLBAR_SIZE - mOrigRect.mLeft - 1; + } + else + if( mOrigRect.mRight + delta_pixels > width - SCROLLBAR_SIZE ) + { + delta_pixels = width - SCROLLBAR_SIZE - mOrigRect.mRight + 1; + } + + mThumbRect.mLeft = mOrigRect.mLeft + delta_pixels; + mThumbRect.mRight = mOrigRect.mRight + delta_pixels; + + S32 thumb_length = mThumbRect.getWidth(); + S32 thumb_track_length = width - 2 * SCROLLBAR_SIZE; + + if( delta_pixels != mLastDelta || mDocChanged) + { + // Note: delta_pixels increases as you go up. mDocPos increases down (line 0 is at the top of the page). + S32 usable_track_length = thumb_track_length - thumb_length; + if( 0 < usable_track_length ) + { + S32 variable_lines = getDocPosMax(); + S32 pos = mThumbRect.mLeft; + F32 ratio = F32(pos - SCROLLBAR_SIZE) / usable_track_length; + + S32 new_pos = llclamp( S32(ratio * variable_lines + 0.5f), 0, variable_lines); + + // Note: we do not call updateThumbRect() here. Instead we let the thumb and the document go slightly + // out of sync (less than a line's worth) to make the thumb feel responsive. + changeLine( new_pos - mDocPos, FALSE ); + } + } + + mLastDelta = delta_pixels; + } + + getWindow()->setCursor(UI_CURSOR_ARROW); + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" << llendl; + handled = TRUE; + } + else + { + handled = childrenHandleMouseUp( x, y, mask ) != NULL; + } + + // Opaque + if( !handled ) + { + getWindow()->setCursor(UI_CURSOR_ARROW); + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (inactive)" << llendl; + handled = TRUE; + } + + mDocChanged = FALSE; + return handled; +} + + +BOOL LLScrollbar::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + BOOL handled = FALSE; + if( getVisible() && mRect.localPointInRect( x, y ) ) + { + if( getEnabled() ) + { + changeLine( clicks * mStepSize, TRUE ); + } + handled = TRUE; + } + + return handled; +} + +BOOL LLScrollbar::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, void *carge_data, EAcceptance *accept, LLString &tooltip_msg) +{ + if (!drop) + { + //TODO: refactor this + S32 variable_lines = getDocPosMax(); + S32 pos = (VERTICAL == mOrientation) ? y : x; + S32 thumb_length = (VERTICAL == mOrientation) ? mThumbRect.getHeight() : mThumbRect.getWidth(); + S32 thumb_track_length = (VERTICAL == mOrientation) ? (mRect.getHeight() - 2 * SCROLLBAR_SIZE) : (mRect.getWidth() - 2 * SCROLLBAR_SIZE); + S32 usable_track_length = thumb_track_length - thumb_length; + F32 ratio = (VERTICAL == mOrientation) ? F32(pos - SCROLLBAR_SIZE - thumb_length) / usable_track_length + : F32(pos - SCROLLBAR_SIZE) / usable_track_length; + S32 new_pos = (VERTICAL == mOrientation) ? llclamp( S32(variable_lines - ratio * variable_lines + 0.5f), 0, variable_lines ) + : llclamp( S32(ratio * variable_lines + 0.5f), 0, variable_lines ); + changeLine( new_pos - mDocPos, TRUE ); + } + return TRUE; +} + +BOOL LLScrollbar::handleMouseUp(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + if( gFocusMgr.getMouseCapture() == this ) + { + gFocusMgr.setMouseCapture( NULL, NULL ); + handled = TRUE; + } + else + { + // Opaque, so don't just check children + handled = LLView::handleMouseUp( x, y, mask ); + } + + return handled; +} + +void LLScrollbar::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + LLView::reshape( width, height, called_from_parent ); + updateThumbRect(); +} + + +void LLScrollbar::draw() +{ + if( getVisible() ) + { + S32 local_mouse_x; + S32 local_mouse_y; + LLCoordWindow cursor_pos_window; + getWindow()->getCursorPosition(&cursor_pos_window); + LLCoordGL cursor_pos_gl; + getWindow()->convertCoords(cursor_pos_window, &cursor_pos_gl); + + screenPointToLocal(cursor_pos_gl.mX, cursor_pos_gl.mY, &local_mouse_x, &local_mouse_y); + BOOL other_captor = gFocusMgr.getMouseCapture() && gFocusMgr.getMouseCapture() != this; + BOOL hovered = mEnabled && !other_captor && (gFocusMgr.getMouseCapture() == this || mThumbRect.pointInRect(local_mouse_x, local_mouse_y)); + if (hovered) + { + mCurGlowStrength = lerp(mCurGlowStrength, mHoverGlowStrength, LLCriticalDamp::getInterpolant(0.05f)); + } + else + { + mCurGlowStrength = lerp(mCurGlowStrength, 0.f, LLCriticalDamp::getInterpolant(0.05f)); + } + + + // Draw background and thumb. + LLUUID rounded_rect_image_id; + rounded_rect_image_id.set(LLUI::sAssetsGroup->getString("rounded_square.tga")); + LLImageGL* rounded_rect_imagep = LLUI::sImageProvider->getUIImageByID(rounded_rect_image_id); + + if (!rounded_rect_imagep) + { + gl_rect_2d(mOrientation == HORIZONTAL ? SCROLLBAR_SIZE : 0, + mOrientation == VERTICAL ? mRect.getHeight() - 2 * SCROLLBAR_SIZE : mRect.getHeight(), + mOrientation == HORIZONTAL ? mRect.getWidth() - 2 * SCROLLBAR_SIZE : mRect.getWidth(), + mOrientation == VERTICAL ? SCROLLBAR_SIZE : 0, mTrackColor, TRUE); + + gl_rect_2d(mThumbRect, mThumbColor, TRUE); + + } + else + { + // Background + gl_draw_scaled_image_with_border(mOrientation == HORIZONTAL ? SCROLLBAR_SIZE : 0, + mOrientation == VERTICAL ? SCROLLBAR_SIZE : 0, + 16, + 16, + mOrientation == HORIZONTAL ? mRect.getWidth() - 2 * SCROLLBAR_SIZE : mRect.getWidth(), + mOrientation == VERTICAL ? mRect.getHeight() - 2 * SCROLLBAR_SIZE : mRect.getHeight(), + rounded_rect_imagep, + mTrackColor, + TRUE); + + // Thumb + LLRect outline_rect = mThumbRect; + outline_rect.stretch(2); + + if (gFocusMgr.getKeyboardFocus() == this) + { + gl_draw_scaled_image_with_border(outline_rect.mLeft, outline_rect.mBottom, 16, 16, outline_rect.getWidth(), outline_rect.getHeight(), + rounded_rect_imagep, gFocusMgr.getFocusColor() ); + } + + gl_draw_scaled_image_with_border(mThumbRect.mLeft, mThumbRect.mBottom, 16, 16, mThumbRect.getWidth(), mThumbRect.getHeight(), + rounded_rect_imagep, mThumbColor ); + if (mCurGlowStrength > 0.01f) + { + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + gl_draw_scaled_image_with_border(mThumbRect.mLeft, mThumbRect.mBottom, 16, 16, mThumbRect.getWidth(), mThumbRect.getHeight(), + rounded_rect_imagep, LLColor4(1.f, 1.f, 1.f, mCurGlowStrength), TRUE); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + } + + BOOL was_scrolled_to_bottom = (getDocPos() == getDocPosMax()); + if (mOnScrollEndCallback && was_scrolled_to_bottom) + { + mOnScrollEndCallback(mOnScrollEndData); + } + // Draw children + LLView::draw(); + } +} + +void LLScrollbar::changeLine( S32 delta, BOOL update_thumb ) +{ + S32 new_pos = llclamp( mDocPos + delta, 0, getDocPosMax() ); + if( new_pos != mDocPos ) + { + mDocPos = new_pos; + } + + if( mChangeCallback ) + { + mChangeCallback( mDocPos, this, mCallbackUserData ); + } + + if( update_thumb ) + { + updateThumbRect(); + } +} + +void LLScrollbar::setValue(const LLSD& value) +{ + setDocPos((S32) value.asInteger()); +} + +EWidgetType LLScrollbar::getWidgetType() const +{ + return WIDGET_TYPE_SCROLLBAR; +} + +LLString LLScrollbar::getWidgetTag() const +{ + return LL_SCROLLBAR_TAG; +} + +BOOL LLScrollbar::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent) +{ + BOOL handled = FALSE; + + if( getVisible() && mEnabled && !called_from_parent ) + { + switch( key ) + { + case KEY_HOME: + changeLine( -mDocPos, TRUE ); + handled = TRUE; + break; + + case KEY_END: + changeLine( getDocPosMax() - mDocPos, TRUE ); + handled = TRUE; + break; + + case KEY_DOWN: + changeLine( mStepSize, TRUE ); + handled = TRUE; + break; + + case KEY_UP: + changeLine( - mStepSize, TRUE ); + handled = TRUE; + break; + + case KEY_PAGE_DOWN: + pageDown(1); + break; + + case KEY_PAGE_UP: + pageUp(1); + break; + } + } + + return handled; +} + +void LLScrollbar::pageUp(S32 overlap) +{ + if (mDocSize > mPageSize) + { + changeLine( -(mPageSize - overlap), TRUE ); + } +} + +void LLScrollbar::pageDown(S32 overlap) +{ + if (mDocSize > mPageSize) + { + changeLine( mPageSize - overlap, TRUE ); + } +} + +// static +void LLScrollbar::onLineUpBtnPressed( void* userdata ) +{ + LLScrollbar* self = (LLScrollbar*) userdata; + + self->changeLine( - self->mStepSize, TRUE ); +} + +// static +void LLScrollbar::onLineDownBtnPressed( void* userdata ) +{ + LLScrollbar* self = (LLScrollbar*) userdata; + self->changeLine( self->mStepSize, TRUE ); +} + diff --git a/indra/llui/llscrollbar.h b/indra/llui/llscrollbar.h new file mode 100644 index 0000000000..f479707499 --- /dev/null +++ b/indra/llui/llscrollbar.h @@ -0,0 +1,123 @@ +/** + * @file llscrollbar.h + * @brief Scrollbar UI widget + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_SCROLLBAR_H +#define LL_SCROLLBAR_H + +#include "stdtypes.h" +#include "lluictrl.h" +#include "v4color.h" + +// +// Constants +// +const S32 SCROLLBAR_SIZE = 16; + + +// +// Classes +// +class LLScrollbar +: public LLUICtrl +{ +public: + enum ORIENTATION { HORIZONTAL, VERTICAL }; + + LLScrollbar(const LLString& name, LLRect rect, + ORIENTATION orientation, + S32 doc_size, S32 doc_pos, S32 page_size, + void(*change_callback)( S32 new_pos, LLScrollbar* self, void* userdata ), + void* callback_user_data = NULL, + S32 step_size = 1); + + virtual ~LLScrollbar(); + + virtual void setValue(const LLSD& value); + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + // Overrides from LLView + virtual BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); + virtual BOOL handleHover(S32 x, S32 y, MASK mask); + virtual BOOL handleScrollWheel(S32 x, S32 y, S32 clicks); + virtual BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, void *carge_data, EAcceptance *accept, LLString &tooltip_msg); + + virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); + + virtual void draw(); + + void setDocParams( S32 size, S32 pos ); + + // How long the "document" is. + void setDocSize( S32 size ); + S32 getDocSize() { return mDocSize; } + + // How many "lines" the "document" has scrolled. + // 0 <= DocPos <= DocSize - DocVisibile + void setDocPos( S32 pos ); + S32 getDocPos() { return mDocPos; } + + // How many "lines" of the "document" is can appear on a page. + void setPageSize( S32 page_size ); + S32 getPageSize() { return mPageSize; } + + // The farthest the document can be scrolled (top of the last page). + S32 getDocPosMax() { return llmax( 0, mDocSize - mPageSize); } + + void pageUp(S32 overlap); + void pageDown(S32 overlap); + + static void onLineUpBtnPressed(void* userdata); + static void onLineDownBtnPressed(void* userdata); + + void setTrackColor( const LLColor4& color ) { mTrackColor = color; } + void setThumbColor( const LLColor4& color ) { mThumbColor = color; } + void setHighlightColor( const LLColor4& color ) { mHighlightColor = color; } + void setShadowColor( const LLColor4& color ) { mShadowColor = color; } + + void setOnScrollEndCallback(void (*callback)(void*), void* userdata) { mOnScrollEndCallback = callback; mOnScrollEndData = userdata;} +protected: + void updateThumbRect(); + void changeLine(S32 delta, BOOL update_thumb ); + +protected: + void (*mChangeCallback)( S32 new_pos, LLScrollbar* self, void* userdata ); + void* mCallbackUserData; + + ORIENTATION mOrientation; + S32 mDocSize; // Size of the document that the scrollbar is modeling. Units depend on the user. 0 <= mDocSize. + S32 mDocPos; // Position within the doc that the scrollbar is modeling, in "lines" (user size) + S32 mPageSize; // Maximum number of lines that can be seen at one time. + S32 mStepSize; + BOOL mDocChanged; + + LLRect mThumbRect; + S32 mDragStartX; + S32 mDragStartY; + F32 mHoverGlowStrength; + F32 mCurGlowStrength; + + LLRect mOrigRect; + S32 mLastDelta; + + LLColor4 mTrackColor; + LLColor4 mThumbColor; + LLColor4 mFocusColor; + LLColor4 mHighlightColor; + LLColor4 mShadowColor; + + void (*mOnScrollEndCallback)(void*); + void *mOnScrollEndData; +}; + + + +#endif // LL_SCROLLBAR_H diff --git a/indra/llui/llscrollcontainer.cpp b/indra/llui/llscrollcontainer.cpp new file mode 100644 index 0000000000..15bb8e3f24 --- /dev/null +++ b/indra/llui/llscrollcontainer.cpp @@ -0,0 +1,772 @@ +/** + * @file llscrollcontainer.cpp + * @brief LLScrollableContainerView base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//***************************************************************************** +// +// A view meant to encapsulate a clipped region which is +// scrollable. It automatically takes care of pixel perfect scrolling +// and cliipping, as well as turning the scrollbars on or off based on +// the width and height of the view you're scrolling. +// +//***************************************************************************** + +#include "linden_common.h" + +#include "llgl.h" + +#include "llscrollcontainer.h" +#include "llscrollbar.h" +#include "llui.h" +#include "llkeyboard.h" +#include "llviewborder.h" +#include "llfocusmgr.h" +#include "llframetimer.h" +#include "lluictrlfactory.h" +#include "llfontgl.h" + +#include "llglheaders.h" + +///---------------------------------------------------------------------------- +/// Local function declarations, constants, enums, and typedefs +///---------------------------------------------------------------------------- + +static const S32 HORIZONTAL_MULTIPLE = 8; +static const S32 VERTICAL_MULTIPLE = 16; +static const F32 MIN_AUTO_SCROLL_RATE = 120.f; +static const F32 MAX_AUTO_SCROLL_RATE = 500.f; +static const F32 AUTO_SCROLL_RATE_ACCEL = 120.f; + +///---------------------------------------------------------------------------- +/// Class LLScrollableContainerView +///---------------------------------------------------------------------------- + +// Default constructor +LLScrollableContainerView::LLScrollableContainerView( const LLString& name, + const LLRect& rect, + LLView* scrolled_view, + BOOL is_opaque, + const LLColor4& bg_color ) : + LLUICtrl( name, rect, FALSE, NULL, NULL ), + mScrolledView( scrolled_view ), + mIsOpaque( is_opaque ), + mBackgroundColor( bg_color ), + mReserveScrollCorner( FALSE ), + mAutoScrolling( FALSE ), + mAutoScrollRate( 0.f ) +{ + if( mScrolledView ) + { + addChild( mScrolledView ); + } + + init(); +} + +// LLUICtrl constructor +LLScrollableContainerView::LLScrollableContainerView( const LLString& name, const LLRect& rect, + LLUICtrl* scrolled_ctrl, BOOL is_opaque, + const LLColor4& bg_color) : + LLUICtrl( name, rect, FALSE, NULL, NULL ), + mScrolledView( scrolled_ctrl ), + mIsOpaque( is_opaque ), + mBackgroundColor( bg_color ), + mReserveScrollCorner( FALSE ), + mAutoScrolling( FALSE ), + mAutoScrollRate( 0.f ) +{ + if( scrolled_ctrl ) + { + addChild( scrolled_ctrl ); + } + + init(); +} + +void LLScrollableContainerView::init() +{ + LLRect border_rect( 0, mRect.getHeight(), mRect.getWidth(), 0 ); + mBorder = new LLViewBorder( "scroll border", border_rect, LLViewBorder::BEVEL_IN ); + addChild( mBorder ); + + mInnerRect.set( 0, mRect.getHeight(), mRect.getWidth(), 0 ); + mInnerRect.stretch( -mBorder->getBorderWidth() ); + + LLRect vertical_scroll_rect = mInnerRect; + vertical_scroll_rect.mLeft = vertical_scroll_rect.mRight - SCROLLBAR_SIZE; + mScrollbar[VERTICAL] = new LLScrollbar( "scrollable vertical", + vertical_scroll_rect, + LLScrollbar::VERTICAL, + mInnerRect.getHeight(), + 0, + mInnerRect.getHeight(), + NULL, this, + VERTICAL_MULTIPLE); + addChild( mScrollbar[VERTICAL] ); + mScrollbar[VERTICAL]->setVisible( FALSE ); + mScrollbar[VERTICAL]->setFollowsRight(); + mScrollbar[VERTICAL]->setFollowsTop(); + mScrollbar[VERTICAL]->setFollowsBottom(); + + LLRect horizontal_scroll_rect = mInnerRect; + horizontal_scroll_rect.mTop = horizontal_scroll_rect.mBottom + SCROLLBAR_SIZE; + mScrollbar[HORIZONTAL] = new LLScrollbar( "scrollable horizontal", + horizontal_scroll_rect, + LLScrollbar::HORIZONTAL, + mInnerRect.getWidth(), + 0, + mInnerRect.getWidth(), + NULL, this, + HORIZONTAL_MULTIPLE); + addChild( mScrollbar[HORIZONTAL] ); + mScrollbar[HORIZONTAL]->setVisible( FALSE ); + mScrollbar[HORIZONTAL]->setFollowsLeft(); + mScrollbar[HORIZONTAL]->setFollowsRight(); + + setTabStop(FALSE); +} + +// Destroys the object +LLScrollableContainerView::~LLScrollableContainerView( void ) +{ + // mScrolledView and mScrollbar are child views, so the LLView + // destructor takes care of memory deallocation. + for( S32 i = 0; i < SCROLLBAR_COUNT; i++ ) + { + mScrollbar[i] = NULL; + } + mScrolledView = NULL; +} + +/* +// scrollbar handlers +void LLScrollableContainerView::horizontalChange( S32 new_pos, + LLScrollbar* sb, + void* user_data ) +{ + LLScrollableContainerView* cont = reinterpret_cast<LLScrollableContainerView*>(user_data); +// cont->scrollHorizontal( new_pos ); +} + + +void LLScrollableContainerView::verticalChange( S32 new_pos, LLScrollbar* sb, + void* user_data ) +{ + LLScrollableContainerView* cont = reinterpret_cast<LLScrollableContainerView*>(user_data); +// cont->scrollVertical( new_pos ); +} +*/ + +// internal scrollbar handlers +// virtual +void LLScrollableContainerView::scrollHorizontal( S32 new_pos ) +{ + //llinfos << "LLScrollableContainerView::scrollHorizontal()" << llendl; + if( mScrolledView ) + { + LLRect doc_rect = mScrolledView->getRect(); + S32 old_pos = -(doc_rect.mLeft - mInnerRect.mLeft); + mScrolledView->translate( -(new_pos - old_pos), 0 ); + } +} + +// virtual +void LLScrollableContainerView::scrollVertical( S32 new_pos ) +{ + // llinfos << "LLScrollableContainerView::scrollVertical() " << new_pos << llendl; + if( mScrolledView ) + { + LLRect doc_rect = mScrolledView->getRect(); + S32 old_pos = doc_rect.mTop - mInnerRect.mTop; + mScrolledView->translate( 0, new_pos - old_pos ); + } +} + +// LLView functionality +void LLScrollableContainerView::reshape(S32 width, S32 height, + BOOL called_from_parent) +{ + LLUICtrl::reshape( width, height, called_from_parent ); + + mInnerRect.set( 0, mRect.getHeight(), mRect.getWidth(), 0 ); + mInnerRect.stretch( -mBorder->getBorderWidth() ); + + if (mScrolledView) + { + const LLRect& scrolled_rect = mScrolledView->getRect(); + + S32 visible_width = 0; + S32 visible_height = 0; + BOOL show_v_scrollbar = FALSE; + BOOL show_h_scrollbar = FALSE; + calcVisibleSize( scrolled_rect, &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar ); + + mScrollbar[VERTICAL]->setDocSize( scrolled_rect.getHeight() ); + mScrollbar[VERTICAL]->setPageSize( visible_height ); + + mScrollbar[HORIZONTAL]->setDocSize( scrolled_rect.getWidth() ); + mScrollbar[HORIZONTAL]->setPageSize( visible_width ); + } +} + +BOOL LLScrollableContainerView::handleKey( KEY key, MASK mask, BOOL called_from_parent ) +{ + if( getVisible() && mEnabled ) + { + if( called_from_parent ) + { + // Downward traversal + + // Don't pass keys to scrollbars on downward. + + // Handle 'child' view. + if( mScrolledView && mScrolledView->handleKey(key, mask, TRUE) ) + { + return TRUE; + } + } + else + { + // Upward traversal + + for( S32 i = 0; i < SCROLLBAR_COUNT; i++ ) + { + // Note: the scrollbar _is_ actually being called from it's parent. Here + // we're delgating LLScrollableContainerView's upward traversal to the scrollbars + if( mScrollbar[i]->handleKey(key, mask, TRUE) ) + { + return TRUE; + } + } + + if (getParent()) + { + return getParent()->handleKey( key, mask, FALSE ); + } + } + } + + return FALSE; +} + +BOOL LLScrollableContainerView::handleScrollWheel( S32 x, S32 y, S32 clicks ) +{ + if( mEnabled ) + { + for( S32 i = 0; i < SCROLLBAR_COUNT; i++ ) + { + // Note: tries vertical and then horizontal + + // Pretend the mouse is over the scrollbar + if( mScrollbar[i]->handleScrollWheel( 0, 0, clicks ) ) + { + return TRUE; + } + } + } + + // Opaque + return TRUE; +} + +BOOL LLScrollableContainerView::handleDragAndDrop(S32 x, S32 y, MASK mask, + BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + LLString& tooltip_msg) +{ + // Scroll folder view if needed. Never accepts a drag or drop. + *accept = ACCEPT_NO; + BOOL handled = FALSE; + if( mScrollbar[HORIZONTAL]->getVisible() || mScrollbar[VERTICAL]->getVisible() ) + { + const S32 AUTOSCROLL_SIZE = 10; + S32 auto_scroll_speed = llround(mAutoScrollRate * LLFrameTimer::getFrameDeltaTimeF32()); + + LLRect inner_rect_local( 0, mInnerRect.getHeight(), mInnerRect.getWidth(), 0 ); + if( mScrollbar[HORIZONTAL]->getVisible() ) + { + inner_rect_local.mBottom += SCROLLBAR_SIZE; + } + if( mScrollbar[VERTICAL]->getVisible() ) + { + inner_rect_local.mRight -= SCROLLBAR_SIZE; + } + + if( mScrollbar[HORIZONTAL]->getVisible() ) + { + LLRect left_scroll_rect = inner_rect_local; + left_scroll_rect.mRight = AUTOSCROLL_SIZE; + if( left_scroll_rect.pointInRect( x, y ) && (mScrollbar[HORIZONTAL]->getDocPos() > 0) ) + { + mScrollbar[HORIZONTAL]->setDocPos( mScrollbar[HORIZONTAL]->getDocPos() - auto_scroll_speed ); + mAutoScrolling = TRUE; + handled = TRUE; + } + + LLRect right_scroll_rect = inner_rect_local; + right_scroll_rect.mLeft = inner_rect_local.mRight - AUTOSCROLL_SIZE; + if( right_scroll_rect.pointInRect( x, y ) && (mScrollbar[HORIZONTAL]->getDocPos() < mScrollbar[HORIZONTAL]->getDocPosMax()) ) + { + mScrollbar[HORIZONTAL]->setDocPos( mScrollbar[HORIZONTAL]->getDocPos() + auto_scroll_speed ); + mAutoScrolling = TRUE; + handled = TRUE; + } + } + if( mScrollbar[VERTICAL]->getVisible() ) + { + LLRect bottom_scroll_rect = inner_rect_local; + bottom_scroll_rect.mTop = AUTOSCROLL_SIZE + bottom_scroll_rect.mBottom; + if( bottom_scroll_rect.pointInRect( x, y ) && (mScrollbar[VERTICAL]->getDocPos() < mScrollbar[VERTICAL]->getDocPosMax()) ) + { + mScrollbar[VERTICAL]->setDocPos( mScrollbar[VERTICAL]->getDocPos() + auto_scroll_speed ); + mAutoScrolling = TRUE; + handled = TRUE; + } + + LLRect top_scroll_rect = inner_rect_local; + top_scroll_rect.mBottom = inner_rect_local.mTop - AUTOSCROLL_SIZE; + if( top_scroll_rect.pointInRect( x, y ) && (mScrollbar[VERTICAL]->getDocPos() > 0) ) + { + mScrollbar[VERTICAL]->setDocPos( mScrollbar[VERTICAL]->getDocPos() - auto_scroll_speed ); + mAutoScrolling = TRUE; + handled = TRUE; + } + } + } + + if( !handled ) + { + handled = childrenHandleDragAndDrop(x, y, mask, drop, cargo_type, + cargo_data, accept, tooltip_msg) != NULL; + } + + return TRUE; +} + + +BOOL LLScrollableContainerView::handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect) +{ + if( getVisible() && pointInView(x,y) ) + { + S32 local_x, local_y; + for( S32 i = 0; i < SCROLLBAR_COUNT; i++ ) + { + local_x = x - mScrollbar[i]->getRect().mLeft; + local_y = y - mScrollbar[i]->getRect().mBottom; + if( mScrollbar[i]->handleToolTip(local_x, local_y, msg, sticky_rect) ) + { + return TRUE; + } + } + // Handle 'child' view. + if( mScrolledView ) + { + local_x = x - mScrolledView->getRect().mLeft; + local_y = y - mScrolledView->getRect().mBottom; + if( mScrolledView->handleToolTip(local_x, local_y, msg, sticky_rect) ) + { + return TRUE; + } + } + + // Opaque + return TRUE; + } + return FALSE; +} + +void LLScrollableContainerView::calcVisibleSize( S32 *visible_width, S32 *visible_height, BOOL* show_h_scrollbar, BOOL* show_v_scrollbar ) +{ + const LLRect& rect = mScrolledView->getRect(); + calcVisibleSize(rect, visible_width, visible_height, show_h_scrollbar, show_v_scrollbar); +} + +void LLScrollableContainerView::calcVisibleSize( const LLRect& doc_rect, S32 *visible_width, S32 *visible_height, BOOL* show_h_scrollbar, BOOL* show_v_scrollbar ) +{ + S32 doc_width = doc_rect.getWidth(); + S32 doc_height = doc_rect.getHeight(); + + *visible_width = mRect.getWidth() - 2 * mBorder->getBorderWidth(); + *visible_height = mRect.getHeight() - 2 * mBorder->getBorderWidth(); + + *show_v_scrollbar = FALSE; + if( *visible_height < doc_height ) + { + *show_v_scrollbar = TRUE; + *visible_width -= SCROLLBAR_SIZE; + } + + *show_h_scrollbar = FALSE; + if( *visible_width < doc_width ) + { + *show_h_scrollbar = TRUE; + *visible_height -= SCROLLBAR_SIZE; + + // Must retest now that visible_height has changed + if( !*show_v_scrollbar && (*visible_height < doc_height) ) + { + *show_v_scrollbar = TRUE; + *visible_width -= SCROLLBAR_SIZE; + } + } +} + +void LLScrollableContainerView::draw() +{ + if (mAutoScrolling) + { + // add acceleration to autoscroll + mAutoScrollRate = llmin(mAutoScrollRate + (LLFrameTimer::getFrameDeltaTimeF32() * AUTO_SCROLL_RATE_ACCEL), MAX_AUTO_SCROLL_RATE); + } + else + { + // reset to minimum + mAutoScrollRate = MIN_AUTO_SCROLL_RATE; + } + // clear this flag to be set on next call to handleDragAndDrop + mAutoScrolling = FALSE; + + if( getVisible() ) + { + // auto-focus when scrollbar active + // this allows us to capture user intent (i.e. stop automatically scrolling the view/etc) + if (!gFocusMgr.childHasKeyboardFocus(this) && + (gFocusMgr.getMouseCapture() == mScrollbar[VERTICAL] || gFocusMgr.getMouseCapture() == mScrollbar[HORIZONTAL])) + { + focusFirstItem(); + } + + // Draw background + if( mIsOpaque ) + { + LLGLSNoTexture no_texture; + glColor4fv( mBackgroundColor.mV ); + gl_rect_2d( mInnerRect ); + } + + // Draw mScrolledViews and update scroll bars. + // get a scissor region ready, and draw the scrolling view. The + // scissor region ensures that we don't draw outside of the bounds + // of the rectangle. + if( mScrolledView ) + { + updateScroll(); + + // Draw the scrolled area. + { + S32 visible_width = 0; + S32 visible_height = 0; + BOOL show_v_scrollbar = FALSE; + BOOL show_h_scrollbar = FALSE; + calcVisibleSize( mScrolledView->getRect(), &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar ); + + LLGLEnable scissor_test(GL_SCISSOR_TEST); + LLUI::setScissorRegionLocal(LLRect(mInnerRect.mLeft, + mInnerRect.mBottom + (show_h_scrollbar ? SCROLLBAR_SIZE : 0) + visible_height, + visible_width, + mInnerRect.mBottom + (show_h_scrollbar ? SCROLLBAR_SIZE : 0) + )); + drawChild(mScrolledView); + } + } + + // Highlight border if a child of this container has keyboard focus + if( mBorder->getVisible() ) + { + mBorder->setKeyboardFocusHighlight( gFocusMgr.childHasKeyboardFocus(this) ); + } + + // Draw all children except mScrolledView + // Note: scrollbars have been adjusted by above drawing code + for (child_list_const_reverse_iter_t child_iter = getChildList()->rbegin(); + child_iter != getChildList()->rend(); ++child_iter) + { + LLView *viewp = *child_iter; + if( sDebugRects ) + { + sDepth++; + } + if( (viewp != mScrolledView) && viewp->getVisible() ) + { + drawChild(viewp); + } + if( sDebugRects ) + { + sDepth--; + } + } + + if (sDebugRects) + { + drawDebugRect(); + } + } +} + +void LLScrollableContainerView::updateScroll() +{ + LLRect doc_rect = mScrolledView->getRect(); + S32 doc_width = doc_rect.getWidth(); + S32 doc_height = doc_rect.getHeight(); + S32 visible_width = 0; + S32 visible_height = 0; + BOOL show_v_scrollbar = FALSE; + BOOL show_h_scrollbar = FALSE; + calcVisibleSize( doc_rect, &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar ); + + S32 border_width = mBorder->getBorderWidth(); + if( show_v_scrollbar ) + { + if( doc_rect.mTop < mRect.getHeight() - border_width ) + { + mScrolledView->translate( 0, mRect.getHeight() - border_width - doc_rect.mTop ); + } + + scrollVertical( mScrollbar[VERTICAL]->getDocPos() ); + mScrollbar[VERTICAL]->setVisible( TRUE ); + + S32 v_scrollbar_height = visible_height; + if( !show_h_scrollbar && mReserveScrollCorner ) + { + v_scrollbar_height -= SCROLLBAR_SIZE; + } + mScrollbar[VERTICAL]->reshape( SCROLLBAR_SIZE, v_scrollbar_height, TRUE ); + + // Make room for the horizontal scrollbar (or not) + S32 v_scrollbar_offset = 0; + if( show_h_scrollbar || mReserveScrollCorner ) + { + v_scrollbar_offset = SCROLLBAR_SIZE; + } + LLRect r = mScrollbar[VERTICAL]->getRect(); + r.translate( 0, mInnerRect.mBottom - r.mBottom + v_scrollbar_offset ); + mScrollbar[VERTICAL]->setRect( r ); + } + else + { + mScrolledView->translate( 0, mRect.getHeight() - border_width - doc_rect.mTop ); + + mScrollbar[VERTICAL]->setVisible( FALSE ); + mScrollbar[VERTICAL]->setDocPos( 0 ); + } + + if( show_h_scrollbar ) + { + if( doc_rect.mLeft > border_width ) + { + mScrolledView->translate( border_width - doc_rect.mLeft, 0 ); + mScrollbar[HORIZONTAL]->setDocPos( 0 ); + } + else + { + scrollHorizontal( mScrollbar[HORIZONTAL]->getDocPos() ); + } + + mScrollbar[HORIZONTAL]->setVisible( TRUE ); + S32 h_scrollbar_width = visible_width; + if( !show_v_scrollbar && mReserveScrollCorner ) + { + h_scrollbar_width -= SCROLLBAR_SIZE; + } + mScrollbar[HORIZONTAL]->reshape( h_scrollbar_width, SCROLLBAR_SIZE, TRUE ); + } + else + { + mScrolledView->translate( border_width - doc_rect.mLeft, 0 ); + + mScrollbar[HORIZONTAL]->setVisible( FALSE ); + mScrollbar[HORIZONTAL]->setDocPos( 0 ); + } + + mScrollbar[HORIZONTAL]->setDocSize( doc_width ); + mScrollbar[HORIZONTAL]->setPageSize( visible_width ); + + mScrollbar[VERTICAL]->setDocSize( doc_height ); + mScrollbar[VERTICAL]->setPageSize( visible_height ); +} + +void LLScrollableContainerView::setBorderVisible(BOOL b) +{ + mBorder->setVisible( b ); +} + +// Scroll so that as much of rect as possible is showing (where rect is defined in the space of scroller view, not scrolled) +void LLScrollableContainerView::scrollToShowRect(const LLRect& rect, const LLCoordGL& desired_offset) +{ + if (!mScrolledView) + { + llwarns << "LLScrollableContainerView::scrollToShowRect with no view!" << llendl; + return; + } + + S32 visible_width = 0; + S32 visible_height = 0; + BOOL show_v_scrollbar = FALSE; + BOOL show_h_scrollbar = FALSE; + const LLRect& scrolled_rect = mScrolledView->getRect(); + calcVisibleSize( scrolled_rect, &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar ); + + // can't be so far left that right side of rect goes off screen, or so far right that left side does + S32 horiz_offset = llclamp(desired_offset.mX, llmin(0, -visible_width + rect.getWidth()), 0); + // can't be so high that bottom of rect goes off screen, or so low that top does + S32 vert_offset = llclamp(desired_offset.mY, 0, llmax(0, visible_height - rect.getHeight())); + + // Vertical + // 1. First make sure the top is visible + // 2. Then, if possible without hiding the top, make the bottom visible. + S32 vert_pos = mScrollbar[VERTICAL]->getDocPos(); + + // find scrollbar position to get top of rect on screen (scrolling up) + S32 top_offset = scrolled_rect.mTop - rect.mTop - vert_offset; + // find scrollbar position to get bottom of rect on screen (scrolling down) + S32 bottom_offset = vert_offset == 0 ? scrolled_rect.mTop - rect.mBottom - visible_height : top_offset; + // scroll up far enough to see top or scroll down just enough if item is bigger than visual area + if( vert_pos >= top_offset || visible_height < rect.getHeight()) + { + vert_pos = top_offset; + } + // else scroll down far enough to see bottom + else + if( vert_pos <= bottom_offset ) + { + vert_pos = bottom_offset; + } + + mScrollbar[VERTICAL]->setDocSize( scrolled_rect.getHeight() ); + mScrollbar[VERTICAL]->setPageSize( visible_height ); + mScrollbar[VERTICAL]->setDocPos( vert_pos ); + + // Horizontal + // 1. First make sure left side is visible + // 2. Then, if possible without hiding the left side, make the right side visible. + S32 horiz_pos = mScrollbar[HORIZONTAL]->getDocPos(); + S32 left_offset = rect.mLeft - scrolled_rect.mLeft + horiz_offset; + S32 right_offset = horiz_offset == 0 ? rect.mRight - scrolled_rect.mLeft - visible_width : left_offset; + + if( horiz_pos >= left_offset || visible_width < rect.getWidth() ) + { + horiz_pos = left_offset; + } + else if( horiz_pos <= right_offset ) + { + horiz_pos = right_offset; + } + + mScrollbar[HORIZONTAL]->setDocSize( scrolled_rect.getWidth() ); + mScrollbar[HORIZONTAL]->setPageSize( visible_width ); + mScrollbar[HORIZONTAL]->setDocPos( horiz_pos ); + + // propagate scroll to document + updateScroll(); +} + +void LLScrollableContainerView::pageUp(S32 overlap) +{ + mScrollbar[VERTICAL]->pageUp(overlap); +} + +void LLScrollableContainerView::pageDown(S32 overlap) +{ + mScrollbar[VERTICAL]->pageDown(overlap); +} + +void LLScrollableContainerView::goToTop() +{ + mScrollbar[VERTICAL]->setDocPos(0); +} + +void LLScrollableContainerView::goToBottom() +{ + mScrollbar[VERTICAL]->setDocPos(mScrollbar[VERTICAL]->getDocSize()); +} + +S32 LLScrollableContainerView::getBorderWidth() +{ + if (mBorder) + { + return mBorder->getBorderWidth(); + } + + return 0; +} + +// virtual +LLXMLNodePtr LLScrollableContainerView::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLView::getXML(); + + // Attributes + + node->createChild("opaque", TRUE)->setBoolValue(mIsOpaque); + + if (mIsOpaque) + { + node->createChild("color", TRUE)->setFloatValue(4, mBackgroundColor.mV); + } + + // Contents + + LLXMLNodePtr child_node = mScrolledView->getXML(); + + node->addChild(child_node); + + return node; +} + +LLView* LLScrollableContainerView::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("scroll_container"); + node->getAttributeString("name", name); + + LLRect rect; + createRect(node, rect, parent, LLRect()); + + BOOL opaque = FALSE; + node->getAttributeBOOL("opaque", opaque); + + LLColor4 color(0,0,0,0); + LLUICtrlFactory::getAttributeColor(node,"color", color); + + // Create the scroll view + LLScrollableContainerView *ret = new LLScrollableContainerView(name, rect, (LLPanel*)NULL, opaque, color); + + LLPanel* panelp = NULL; + + // Find a child panel to add + LLXMLNodePtr child; + for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) + { + LLView *control = factory->createCtrlWidget(panelp, child); + if (control && control->isPanel()) + { + if (panelp) + { + llinfos << "Warning! Attempting to put multiple panels into a scrollable container view!" << llendl; + delete control; + } + else + { + panelp = (LLPanel*)control; + } + } + } + + if (panelp == NULL) + { + panelp = new LLPanel("dummy", LLRect::null, FALSE); + } + + ret->mScrolledView = panelp; + + return ret; +} + +///---------------------------------------------------------------------------- +/// Local function definitions +///---------------------------------------------------------------------------- diff --git a/indra/llui/llscrollcontainer.h b/indra/llui/llscrollcontainer.h new file mode 100644 index 0000000000..5f23be4628 --- /dev/null +++ b/indra/llui/llscrollcontainer.h @@ -0,0 +1,109 @@ +/** + * @file llscrollcontainer.h + * @brief LLScrollableContainerView class header file. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSCROLLCONTAINER_H +#define LL_LLSCROLLCONTAINER_H + +#include "lluictrl.h" +#ifndef LL_V4COLOR_H +#include "v4color.h" +#endif +#include "stdenums.h" +#include "llcoord.h" + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLScrollableContainerView +// +// A view meant to encapsulate a clipped region which is +// scrollable. It automatically takes care of pixel perfect scrolling +// and cliipping, as well as turning the scrollbars on or off based on +// the width and height of the view you're scrolling. +// +// This class is a decorator class. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLScrollbar; +class LLViewBorder; + + +class LLScrollableContainerView : public LLUICtrl +{ +public: + LLScrollableContainerView( const LLString& name, const LLRect& rect, + LLView* scrolled_view, BOOL is_opaque = FALSE, + const LLColor4& bg_color = LLColor4(0,0,0,0) ); + LLScrollableContainerView( const LLString& name, const LLRect& rect, + LLUICtrl* scrolled_ctrl, BOOL is_opaque = FALSE, + const LLColor4& bg_color = LLColor4(0,0,0,0) ); + virtual ~LLScrollableContainerView( void ); + + void init(); + + void setScrolledView(LLView* view) { mScrolledView = view; } + + virtual void setValue(const LLSD& value) { mInnerRect.setValue(value); } + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_SCROLL_CONTAINER; } + virtual LLString getWidgetTag() const { return LL_SCROLLABLE_CONTAINER_VIEW_TAG; } + + // scrollbar handlers + static void horizontalChange( S32 new_pos, LLScrollbar* sb, void* user_data ); + static void verticalChange( S32 new_pos, LLScrollbar* sb, void* user_data ); + + void calcVisibleSize( S32 *visible_width, S32 *visible_height, BOOL* show_h_scrollbar, BOOL* show_v_scrollbar ); + void calcVisibleSize( const LLRect& doc_rect, S32 *visible_width, S32 *visible_height, BOOL* show_h_scrollbar, BOOL* show_v_scrollbar ); + void setBorderVisible( BOOL b ); + + void scrollToShowRect( const LLRect& rect, const LLCoordGL& desired_offset ); + void setReserveScrollCorner( BOOL b ) { mReserveScrollCorner = b; } + const LLRect& getScrolledViewRect() { return mScrolledView->getRect(); } + void pageUp(S32 overlap = 0); + void pageDown(S32 overlap = 0); + void goToTop(); + void goToBottom(); + S32 getBorderWidth(); + + // LLView functionality + virtual void reshape(S32 width, S32 height, BOOL called_from_parent); + virtual BOOL handleKey(KEY key, MASK mask, BOOL called_from_parent); + virtual BOOL handleScrollWheel( S32 x, S32 y, S32 clicks ); + virtual BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + LLString& tooltip_msg); + + virtual BOOL handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect); + virtual void draw(); + + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + +protected: + // internal scrollbar handlers + virtual void scrollHorizontal( S32 new_pos ); + virtual void scrollVertical( S32 new_pos ); + void updateScroll(); + + // Note: vertical comes before horizontal because vertical + // scrollbars have priority for mouse and keyboard events. + enum { VERTICAL, HORIZONTAL, SCROLLBAR_COUNT }; + + LLScrollbar* mScrollbar[SCROLLBAR_COUNT]; + LLView* mScrolledView; + S32 mSize; + BOOL mIsOpaque; + LLColor4 mBackgroundColor; + LLRect mInnerRect; + LLViewBorder* mBorder; + BOOL mReserveScrollCorner; + BOOL mAutoScrolling; + F32 mAutoScrollRate; +}; + + +#endif // LL_LLSCROLLCONTAINER_H diff --git a/indra/llui/llscrollingpanellist.cpp b/indra/llui/llscrollingpanellist.cpp new file mode 100644 index 0000000000..a4d20edfe9 --- /dev/null +++ b/indra/llui/llscrollingpanellist.cpp @@ -0,0 +1,150 @@ +/** + * @file llscrollingpanellist.cpp + * @brief + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llstl.h" + +#include "llscrollingpanellist.h" + +///////////////////////////////////////////////////////////////////// +// LLScrollingPanelList + +// This could probably be integrated with LLScrollContainer -SJB + +void LLScrollingPanelList::clearPanels() +{ + deleteAllChildren(); + mPanelList.clear(); + reshape( 1, 1, FALSE ); +} + +void LLScrollingPanelList::addPanel( LLScrollingPanel* panel ) +{ + addChildAtEnd( panel ); + mPanelList.push_front( panel ); + + const S32 GAP_BETWEEN_PANELS = 6; + + // Resize this view + S32 total_height = 0; + S32 max_width = 0; + S32 cur_gap = 0; + for (std::deque<LLScrollingPanel*>::iterator iter = mPanelList.begin(); + iter != mPanelList.end(); ++iter) + { + LLScrollingPanel *childp = *iter; + total_height += childp->getRect().getHeight() + cur_gap; + max_width = llmax( max_width, childp->getRect().getWidth() ); + cur_gap = GAP_BETWEEN_PANELS; + } + reshape( max_width, total_height, FALSE ); + + // Reposition each of the child views + S32 cur_y = total_height; + for (std::deque<LLScrollingPanel*>::iterator iter = mPanelList.begin(); + iter != mPanelList.end(); ++iter) + { + LLScrollingPanel *childp = *iter; + cur_y -= childp->getRect().getHeight(); + childp->translate( -childp->getRect().mLeft, cur_y - childp->getRect().mBottom); + cur_y -= GAP_BETWEEN_PANELS; + } +} + +void LLScrollingPanelList::updatePanels(BOOL allow_modify) +{ + for (std::deque<LLScrollingPanel*>::iterator iter = mPanelList.begin(); + iter != mPanelList.end(); ++iter) + { + LLScrollingPanel *childp = *iter; + childp->updatePanel(allow_modify); + } +} + +void LLScrollingPanelList::updatePanelVisiblilty() +{ + // Determine visibility of children. + S32 BORDER_WIDTH = 2; // HACK + + LLRect parent_local_rect = getParent()->getRect(); + parent_local_rect.stretch( -BORDER_WIDTH ); + + LLRect parent_screen_rect; + getParent()->localPointToScreen( + BORDER_WIDTH, 0, + &parent_screen_rect.mLeft, &parent_screen_rect.mBottom ); + getParent()->localPointToScreen( + parent_local_rect.getWidth() - BORDER_WIDTH, parent_local_rect.getHeight() - BORDER_WIDTH, + &parent_screen_rect.mRight, &parent_screen_rect.mTop ); + + for (std::deque<LLScrollingPanel*>::iterator iter = mPanelList.begin(); + iter != mPanelList.end(); ++iter) + { + LLScrollingPanel *childp = *iter; + const LLRect& local_rect = childp->getRect(); + LLRect screen_rect; + childp->localPointToScreen( + 0, 0, + &screen_rect.mLeft, &screen_rect.mBottom ); + childp->localPointToScreen( + local_rect.getWidth(), local_rect.getHeight(), + &screen_rect.mRight, &screen_rect.mTop ); + + BOOL intersects = + ( (screen_rect.mRight > parent_screen_rect.mLeft) && (screen_rect.mLeft < parent_screen_rect.mRight) ) && + ( (screen_rect.mTop > parent_screen_rect.mBottom) && (screen_rect.mBottom < parent_screen_rect.mTop) ); + + childp->setVisible( intersects ); + } +} + +void LLScrollingPanelList::setValue(const LLSD& value) +{ + +} + +EWidgetType LLScrollingPanelList::getWidgetType() const +{ + return WIDGET_TYPE_SCROLLING_PANEL_LIST; +} + +LLString LLScrollingPanelList::getWidgetTag() const +{ + return LL_SCROLLING_PANEL_LIST_TAG; +} + +void LLScrollingPanelList::draw() +{ + if( getVisible() ) + { + updatePanelVisiblilty(); + } + LLUICtrl::draw(); +} + + +// static +LLView* LLScrollingPanelList::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("scrolling_panel_list"); + node->getAttributeString("name", name); + + LLRect rect; + createRect(node, rect, parent, LLRect()); + + LLScrollingPanelList* scrolling_panel_list = new LLScrollingPanelList(name, rect); + scrolling_panel_list->initFromXML(node, parent); + return scrolling_panel_list; +} + +// virtual +LLXMLNodePtr LLScrollingPanelList::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLUICtrl::getXML(); + return node; +} diff --git a/indra/llui/llscrollingpanellist.h b/indra/llui/llscrollingpanellist.h new file mode 100644 index 0000000000..b5f20ce172 --- /dev/null +++ b/indra/llui/llscrollingpanellist.h @@ -0,0 +1,53 @@ +/** + * @file llscrollingpanellist.h + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include <vector> + +#include "llui.h" +#include "lluictrlfactory.h" +#include "llview.h" +#include "llpanel.h" + +// virtual class for scrolling panels +class LLScrollingPanel : public LLPanel +{ +public: + LLScrollingPanel(const LLString& name, const LLRect& rect) + : LLPanel(name, rect) + { + } + virtual void updatePanel(BOOL allow_modify) = 0; + +}; + +// A set of panels that are displayed in a vertical sequence inside a scroll container. +class LLScrollingPanelList : public LLUICtrl +{ +public: + LLScrollingPanelList(const LLString& name, const LLRect& rect) + : LLUICtrl(name, rect, TRUE, NULL, NULL, FOLLOWS_LEFT | FOLLOWS_BOTTOM ) {} + + virtual void setValue(const LLSD& value); + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + virtual LLXMLNodePtr getXML(bool save_children) const; + + virtual void draw(); + + void clearPanels(); + void addPanel( LLScrollingPanel* panel ); + void updatePanels(BOOL allow_modify); + + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + +protected: + void updatePanelVisiblilty(); + +protected: + std::deque<LLScrollingPanel*> mPanelList; +}; diff --git a/indra/llui/llscrolllistctrl.cpp b/indra/llui/llscrolllistctrl.cpp new file mode 100644 index 0000000000..5d11973b88 --- /dev/null +++ b/indra/llui/llscrolllistctrl.cpp @@ -0,0 +1,2673 @@ +/** + * @file llscrolllistctrl.cpp + * @brief LLScrollListCtrl base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include <algorithm> + +#include "linden_common.h" +#include "llstl.h" +#include "llboost.h" + +#include "llscrolllistctrl.h" + +#include "indra_constants.h" + +#include "llcheckboxctrl.h" +#include "llclipboard.h" +#include "llfocusmgr.h" +#include "llgl.h" +#include "llglheaders.h" +#include "llresmgr.h" +#include "llscrollbar.h" +#include "llstring.h" +#include "llui.h" +#include "lluictrlfactory.h" +#include "llwindow.h" +#include "llcontrol.h" +#include "llkeyboard.h" + +const S32 LIST_BORDER_PAD = 2; // white space inside the border and to the left of the scrollbar + +U32 LLScrollListCtrl::sSortColumn = 1; +BOOL LLScrollListCtrl::sSortAscending = TRUE; + +// local structures & classes. +struct SortScrollListItem +{ + SortScrollListItem(const S32 sort_col, BOOL sort_ascending) + { + mSortCol = sort_col; + sSortAscending = sort_ascending; + } + + bool operator()(const LLScrollListItem* i1, const LLScrollListItem* i2) + { + const LLScrollListCell *cell1; + const LLScrollListCell *cell2; + + cell1 = i1->getColumn(mSortCol); + cell2 = i2->getColumn(mSortCol); + + S32 order = 1; + if (!sSortAscending) + { + order = -1; + } + + BOOL retval = FALSE; + + if (cell1 && cell2) + { + retval = ((order * LLString::compareDict(cell1->getText(), cell2->getText())) < 0); + } + + return (retval ? TRUE : FALSE); + } + +protected: + S32 mSortCol; + S32 sSortAscending; +}; + + + +// +// LLScrollListIcon +// +LLScrollListIcon::LLScrollListIcon(LLImageGL* icon, S32 width, LLUUID image_id) : +mIcon(icon), mImageUUID(image_id.getString()) +{ + if (width) + { + mWidth = width; + } + else + { + mWidth = icon->getWidth(); + } +} + +LLScrollListIcon::~LLScrollListIcon() +{ +} + +// +// LLScrollListCheck +// +LLScrollListCheck::LLScrollListCheck(LLCheckBoxCtrl* check_box, S32 width) +{ + mCheckBox = check_box; + LLRect rect(mCheckBox->getRect()); + if (width) + { + + rect.mRight = rect.mLeft + width; + mCheckBox->setRect(rect); + mWidth = width; + } + else + { + mWidth = rect.getWidth(); //check_box->getWidth(); + } +} + +LLScrollListCheck::~LLScrollListCheck() +{ + delete mCheckBox; +} + +void LLScrollListCheck::drawToWidth(S32 width, const LLColor4& color, const LLColor4& highlight_color) const +{ + mCheckBox->draw(); + +} + +BOOL LLScrollListCheck::handleClick() +{ + if ( mCheckBox->getEnabled() ) + { + LLCheckBoxCtrl::onButtonPress(mCheckBox); + } + return TRUE; +} + +// +// LLScrollListText +// +U32 LLScrollListText::sCount = 0; + +LLScrollListText::LLScrollListText( const LLString& text, const LLFontGL* font, S32 width, U8 font_style, LLColor4& color, BOOL use_color, BOOL visible) +: mText( text ), + mFont( font ), + mFontStyle( font_style ), + mWidth( width ), + mVisible( visible ), + mHighlightChars( 0 ) +{ + if (use_color) + { + mColor = new LLColor4(); + mColor->setVec(color); + } + else + { + mColor = NULL; + } + + sCount++; + + // initialize rounded rect image + if (!mRoundedRectImage) + { + mRoundedRectImage = LLUI::sImageProvider->getUIImageByID(LLUUID(LLUI::sAssetsGroup->getString("rounded_square.tga"))); + } + + // Yes, that's four dots, because we want it to have a little + // padding, in proportion to the font size. + mEllipsisWidth = (S32)mFont->getWidth("...."); +} + +LLScrollListText::~LLScrollListText() +{ + sCount--; + delete mColor; +} + +void LLScrollListText::setText(const LLString& text) +{ + mText = text; +} + +void LLScrollListText::drawToWidth(S32 width, const LLColor4& color, const LLColor4& highlight_color) const +{ + // If the user has specified a small minimum width, use that. + if (mWidth > 0 && mWidth < width) + { + width = mWidth; + } + + const LLColor4* display_color; + if (mColor) + { + display_color = mColor; + } + else + { + display_color = &color; + } + + if (mHighlightChars > 0) + { + mRoundedRectImage->bind(); + glColor4fv(highlight_color.mV); + gl_segmented_rect_2d_tex(-2, + llround(mFont->getLineHeight()) + 1, + mFont->getWidth(mText.getString(), 0, mHighlightChars) + 1, + 1, + mRoundedRectImage->getWidth(), + mRoundedRectImage->getHeight(), + 16); + } + + // Try to draw the entire string + F32 right_x; + U32 string_chars = mText.length(); + U32 drawn_chars = mFont->render(mText.getWString(), 0, 0, 2, + *display_color, + LLFontGL::LEFT, + LLFontGL::BOTTOM, + mFontStyle, + string_chars, + width - mEllipsisWidth, + &right_x, FALSE); + + // If we didn't get the whole string, abbreviate + if (drawn_chars < string_chars && drawn_chars) + { + mFont->renderUTF8("...", 0, right_x, 0.f, color, LLFontGL::LEFT, LLFontGL::BOTTOM, mFontStyle, + S32_MAX, S32_MAX, NULL, FALSE); + } +} + + +LLScrollListItem::~LLScrollListItem() +{ + std::for_each(mColumns.begin(), mColumns.end(), DeletePointer()); +} + +BOOL LLScrollListItem::handleMouseDown(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + S32 left = 0; + S32 right = 0; + S32 width = 0; + + std::vector<LLScrollListCell *>::iterator iter = mColumns.begin(); + std::vector<LLScrollListCell *>::iterator end = mColumns.end(); + for ( ; iter != end; ++iter) + { + width = (*iter)->getWidth(); + right += width; + if (left <= x && x < right ) + { + handled = (*iter)->handleClick(); + break; + } + + left += width; + } + return handled; +} + +void LLScrollListItem::setNumColumns(S32 columns) +{ + S32 prev_columns = mColumns.size(); + if (columns < prev_columns) + { + std::for_each(mColumns.begin()+columns, mColumns.end(), DeletePointer()); + } + + mColumns.resize(columns); + + for (S32 col = prev_columns; col < columns; ++col) + { + mColumns[col] = NULL; + } +} + +void LLScrollListItem::setColumn( S32 column, LLScrollListCell *cell ) +{ + if (column < (S32)mColumns.size()) + { + delete mColumns[column]; + mColumns[column] = cell; + } + else + { + llerrs << "LLScrollListItem::setColumn: bad column: " << column << llendl; + } +} + +LLString LLScrollListItem::getContentsCSV() +{ + LLString ret; + + S32 count = getNumColumns(); + for (S32 i=0; i<count; ++i) + { + ret += getColumn(i)->getText(); + if (i < count-1) + { + ret += ", "; + } + } + + return ret; +} + +void LLScrollListItem::setEnabled(BOOL b) +{ + if (b != mEnabled) + { + std::vector<LLScrollListCell *>::iterator iter = mColumns.begin(); + std::vector<LLScrollListCell *>::iterator end = mColumns.end(); + for ( ; iter != end; ++iter) + { + (*iter)->setEnabled(b); + } + mEnabled = b; + } +} + +//--------------------------------------------------------------------------- +// LLScrollListCtrl +//--------------------------------------------------------------------------- + +LLScrollListCtrl::LLScrollListCtrl(const LLString& name, const LLRect& rect, + void (*commit_callback)(LLUICtrl* ctrl, void* userdata), + void* callback_user_data, + BOOL allow_multiple_selection, + BOOL show_border + ) + : LLUICtrl(name, rect, TRUE, commit_callback, callback_user_data), + mLineHeight(0), + mScrollLines(0), + mPageLines(0), + mHeadingHeight(20), + mMaxSelectable(0), + mHeadingFont(NULL), + mAllowMultipleSelection( allow_multiple_selection ), + mAllowKeyboardMovement(TRUE), + mCommitOnKeyboardMovement(TRUE), + mCommitOnSelectionChange(FALSE), + mSelectionChanged(FALSE), + mCanSelect(TRUE), + mDisplayColumnButtons(FALSE), + mCollapseEmptyColumns(FALSE), + mIsPopup(FALSE), + mMaxItemCount(INT_MAX), + //mItemCount(0), + mBackgroundVisible( TRUE ), + mDrawStripes(TRUE), + mBgWriteableColor( LLUI::sColorsGroup->getColor( "ScrollBgWriteableColor" ) ), + mBgReadOnlyColor( LLUI::sColorsGroup->getColor( "ScrollBgReadOnlyColor" ) ), + mBgSelectedColor( LLUI::sColorsGroup->getColor("ScrollSelectedBGColor") ), + mBgStripeColor( LLUI::sColorsGroup->getColor("ScrollBGStripeColor") ), + mFgSelectedColor( LLUI::sColorsGroup->getColor("ScrollSelectedFGColor") ), + mFgUnselectedColor( LLUI::sColorsGroup->getColor("ScrollUnselectedColor") ), + mFgDisabledColor( LLUI::sColorsGroup->getColor("ScrollDisabledColor") ), + mHighlightedColor( LLUI::sColorsGroup->getColor("ScrollHighlightedColor") ), + mBorderThickness( 2 ), + mOnDoubleClickCallback( NULL ), + mOnMaximumSelectCallback( NULL ), + mHighlightedItem(-1), + mBorder(NULL), + mDefaultColumn("SIMPLE"), + mSearchColumn(0), + mNumDynamicWidthColumns(0), + mTotalStaticColumnWidth(0), + mDrewSelected(FALSE) +{ + mItemListRect.setOriginAndSize( + mBorderThickness + LIST_BORDER_PAD, + mBorderThickness + LIST_BORDER_PAD, + mRect.getWidth() - 2*( mBorderThickness + LIST_BORDER_PAD ) - SCROLLBAR_SIZE, + mRect.getHeight() - 2*( mBorderThickness + LIST_BORDER_PAD ) ); + + updateLineHeight(); + + mPageLines = mLineHeight? (mItemListRect.getHeight()) / mLineHeight : 0; + + // Init the scrollbar + LLRect scroll_rect; + scroll_rect.setOriginAndSize( + mRect.getWidth() - mBorderThickness - SCROLLBAR_SIZE, + mItemListRect.mBottom, + SCROLLBAR_SIZE, + mItemListRect.getHeight()); + mScrollbar = new LLScrollbar( "Scrollbar", scroll_rect, + LLScrollbar::VERTICAL, + getItemCount(), + mScrollLines, + mPageLines, + &LLScrollListCtrl::onScrollChange, this ); + mScrollbar->setFollowsRight(); + mScrollbar->setFollowsTop(); + mScrollbar->setFollowsBottom(); + mScrollbar->setEnabled( TRUE ); + mScrollbar->setVisible( TRUE ); + addChild(mScrollbar); + + // Border + if (show_border) + { + LLRect border_rect( 0, mRect.getHeight(), mRect.getWidth(), 0 ); + mBorder = new LLViewBorder( "dlg border", border_rect, LLViewBorder::BEVEL_IN, LLViewBorder::STYLE_LINE, 1 ); + addChild(mBorder); + } + + mColumnPadding = 5; + + mLastSelected = NULL; +} + +LLScrollListCtrl::~LLScrollListCtrl() +{ + std::for_each(mItemList.begin(), mItemList.end(), DeletePointer()); + + if( gEditMenuHandler == this ) + { + gEditMenuHandler = NULL; + } +} + + +BOOL LLScrollListCtrl::setMaxItemCount(S32 max_count) +{ + if (max_count >= getItemCount()) + { + mMaxItemCount = max_count; + } + return (max_count == mMaxItemCount); +} + +S32 LLScrollListCtrl::isEmpty() const +{ + return mItemList.empty(); +} + +S32 LLScrollListCtrl::getItemCount() const +{ + return mItemList.size(); +} + +// virtual LLScrolListInterface function (was deleteAllItems) +void LLScrollListCtrl::clearRows() +{ + std::for_each(mItemList.begin(), mItemList.end(), DeletePointer()); + mItemList.clear(); + //mItemCount = 0; + + // Scroll the bar back up to the top. + mScrollbar->setDocParams(0, 0); + + mScrollLines = 0; + mLastSelected = NULL; +} + + +LLScrollListItem* LLScrollListCtrl::getFirstSelected() const +{ + item_list::const_iterator iter; + for(iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + if (item->getSelected()) + { + return item; + } + } + return NULL; +} + +std::vector<LLScrollListItem*> LLScrollListCtrl::getAllSelected() const +{ + std::vector<LLScrollListItem*> ret; + item_list::const_iterator iter; + for(iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + if (item->getSelected()) + { + ret.push_back(item); + } + } + return ret; +} + +S32 LLScrollListCtrl::getFirstSelectedIndex() +{ + S32 CurSelectedIndex = 0; + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + if (item->getSelected()) + { + return CurSelectedIndex; + } + CurSelectedIndex++; + } + + return -1; +} + + +LLScrollListItem* LLScrollListCtrl::getFirstData() const +{ + if (mItemList.size() == 0) + { + return NULL; + } + return mItemList[0]; +} + +std::vector<LLScrollListItem*> LLScrollListCtrl::getAllData() const +{ + std::vector<LLScrollListItem*> ret; + item_list::const_iterator iter; + for(iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + ret.push_back(item); + } + return ret; +} + + +void LLScrollListCtrl::reshape( S32 width, S32 height, BOOL called_from_parent ) +{ + LLUICtrl::reshape( width, height, called_from_parent ); + + S32 heading_size = (mDisplayColumnButtons ? mHeadingHeight : 0); + + mItemListRect.setOriginAndSize( + mBorderThickness + LIST_BORDER_PAD, + mBorderThickness + LIST_BORDER_PAD, + mRect.getWidth() - 2*( mBorderThickness + LIST_BORDER_PAD ) - SCROLLBAR_SIZE, + mRect.getHeight() - 2*( mBorderThickness + LIST_BORDER_PAD ) - heading_size ); + + mPageLines = mLineHeight? mItemListRect.getHeight() / mLineHeight : 0; + mScrollbar->setVisible(mPageLines < getItemCount()); + mScrollbar->setPageSize( mPageLines ); + + updateColumns(); + updateColumnButtons(); +} + + +// Attempt to size the control to show all items. +// Do not make larger than width or height. +void LLScrollListCtrl::arrange(S32 max_width, S32 max_height) +{ + S32 height = mLineHeight * (getItemCount() + 1); + height = llmin( height, max_height ); + + S32 width = mRect.getWidth(); + + reshape( width, height ); +} + + +LLRect LLScrollListCtrl::getRequiredRect() +{ + S32 height = mLineHeight * (getItemCount() + 1); + S32 width = mRect.getWidth(); + + return LLRect(0, height, width, 0); +} + + +BOOL LLScrollListCtrl::addItem( LLScrollListItem* item, EAddPosition pos ) +{ + BOOL not_too_big = getItemCount() < mMaxItemCount; + if (not_too_big) + { + switch( pos ) + { + case ADD_TOP: + mItemList.push_front(item); + break; + + case ADD_SORTED: + LLScrollListCtrl::sSortColumn = 0; + LLScrollListCtrl::sSortAscending = TRUE; + mItemList.push_back(item); + std::sort(mItemList.begin(), mItemList.end(), SortScrollListItem(sSortColumn, sSortAscending)); + break; + + case ADD_BOTTOM: + mItemList.push_back(item); + break; + + default: + llassert(0); + mItemList.push_back(item); + break; + } + + updateLineHeight(); + mPageLines = mLineHeight ? mItemListRect.getHeight() / mLineHeight : 0; + mScrollbar->setVisible(mPageLines < getItemCount()); + mScrollbar->setPageSize( mPageLines ); + + mScrollbar->setDocSize( getItemCount() ); + } + return not_too_big; +} + + +// Line height is the max height of all the cells in all the items. +void LLScrollListCtrl::updateLineHeight() +{ + const S32 ROW_PAD = 2; + + mLineHeight = 0; + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem *itemp = *iter; + S32 num_cols = itemp->getNumColumns(); + S32 i = 0; + for (const LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i)) + { + mLineHeight = llmax( mLineHeight, cell->getHeight() + ROW_PAD ); + } + } +} + +void LLScrollListCtrl::updateColumns() +{ + mColumnsIndexed.resize(mColumns.size()); + + std::map<LLString, LLScrollListColumn>::iterator column_itor; + for (column_itor = mColumns.begin(); column_itor != mColumns.end(); ++column_itor) + { + LLScrollListColumn *column = &column_itor->second; + if (column->mRelWidth >= 0) + { + column->mWidth = (S32)llround(column->mRelWidth*mItemListRect.getWidth()); + } + else if (column->mDynamicWidth) + { + column->mWidth = (mItemListRect.getWidth() - mTotalStaticColumnWidth) / mNumDynamicWidthColumns; + + } + mColumnsIndexed[column_itor->second.mIndex] = column; + } +} + +void LLScrollListCtrl::updateColumnButtons() +{ + std::map<LLString, LLScrollListColumn>::iterator column_itor; + for (column_itor = mColumns.begin(); column_itor != mColumns.end(); ++column_itor) + { + LLScrollListColumn* column = &column_itor->second; + LLButton *button = column->mButton; + + if (button) + { + mColumnsIndexed[column->mIndex] = column; + + S32 top = mItemListRect.mTop; + S32 left = mItemListRect.mLeft; + { + std::map<LLString, LLScrollListColumn>::iterator itor; + for (itor = mColumns.begin(); itor != mColumns.end(); ++itor) + { + if (itor->second.mIndex < column->mIndex && + itor->second.mWidth > 0) + { + left += itor->second.mWidth + mColumnPadding; + } + } + } + S32 right = left+column->mWidth; + if (column->mIndex != (S32)mColumns.size()-1) + { + right += mColumnPadding; + } + LLRect temp_rect = LLRect(left,top+mHeadingHeight,right,top); + button->setRect(temp_rect); + button->setFont(mHeadingFont); + button->setVisible(mDisplayColumnButtons); + } + } +} + +void LLScrollListCtrl::setDisplayHeading(BOOL display) +{ + mDisplayColumnButtons = display; + + updateColumns(); + + setHeadingHeight(mHeadingHeight); +} + +void LLScrollListCtrl::setHeadingHeight(S32 heading_height) +{ + mHeadingHeight = heading_height; + + reshape(mRect.getWidth(), mRect.getHeight()); + + // Resize + mScrollbar->reshape(SCROLLBAR_SIZE, mItemListRect.getHeight()); + + updateColumnButtons(); +} + +void LLScrollListCtrl::setHeadingFont(const LLFontGL* heading_font) +{ + mHeadingFont = heading_font; + updateColumnButtons(); +} + +void LLScrollListCtrl::setCollapseEmptyColumns(BOOL collapse) +{ + mCollapseEmptyColumns = collapse; +} + +BOOL LLScrollListCtrl::selectFirstItem() +{ + BOOL success = FALSE; + + // our $%&@#$()^%#$()*^ iterators don't let us check against the first item inside out iteration + BOOL first_item = TRUE; + + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem *itemp = *iter; + if( first_item && itemp->getEnabled() ) + { + if (!itemp->getSelected()) + { + selectItem(itemp); + } + success = TRUE; + } + else + { + deselectItem(itemp); + } + first_item = FALSE; + } + if (mCommitOnSelectionChange) + { + commitIfChanged(); + } + return success; +} + + +BOOL LLScrollListCtrl::selectNthItem( S32 target_index ) +{ + // Deselects all other items + BOOL success = FALSE; + S32 index = 0; + + target_index = llclamp(target_index, 0, (S32)mItemList.size() - 1); + + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem *itemp = *iter; + if( target_index == index ) + { + if( itemp->getEnabled() ) + { + selectItem(itemp); + success = TRUE; + } + } + else + { + deselectItem(itemp); + } + index++; + } + + if (mCommitOnSelectionChange) + { + commitIfChanged(); + } + + mSearchString.clear(); + + return success; +} + + +void LLScrollListCtrl::swapWithNext(S32 index) +{ + if (index >= ((S32)mItemList.size() - 1)) + { + // At end of list, doesn't do anything + return; + } + LLScrollListItem *cur_itemp = mItemList[index]; + mItemList[index] = mItemList[index + 1]; + mItemList[index + 1] = cur_itemp; +} + + +void LLScrollListCtrl::swapWithPrevious(S32 index) +{ + if (index <= 0) + { + // At beginning of list, don't do anything + } + + LLScrollListItem *cur_itemp = mItemList[index]; + mItemList[index] = mItemList[index - 1]; + mItemList[index - 1] = cur_itemp; +} + + +void LLScrollListCtrl::deleteSingleItem(S32 target_index) +{ + if (target_index >= (S32)mItemList.size()) + { + return; + } + + LLScrollListItem *itemp; + itemp = mItemList[target_index]; + if (itemp == mLastSelected) + { + mLastSelected = NULL; + } + delete itemp; + mItemList.erase(mItemList.begin() + target_index); +} + +void LLScrollListCtrl::deleteSelectedItems() +{ + item_list::iterator iter; + for (iter = mItemList.begin(); iter < mItemList.end(); ) + { + LLScrollListItem* itemp = *iter; + if (itemp->getSelected()) + { + delete itemp; + iter = mItemList.erase(iter); + } + else + { + iter++; + } + } + mLastSelected = NULL; +} + +void LLScrollListCtrl::highlightNthItem(S32 target_index) +{ + if (mHighlightedItem != target_index) + { + mHighlightedItem = target_index; + } +} + +S32 LLScrollListCtrl::getItemIndex( LLScrollListItem* target_item ) +{ + S32 index = 0; + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem *itemp = *iter; + if (target_item == itemp) + { + return index; + } + index++; + } + return -1; +} + +S32 LLScrollListCtrl::getItemIndex( LLUUID& target_id ) +{ + S32 index = 0; + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem *itemp = *iter; + if (target_id == itemp->getUUID()) + { + return index; + } + index++; + } + return -1; +} + +void LLScrollListCtrl::selectPrevItem( BOOL extend_selection) +{ + LLScrollListItem* prev_item = NULL; + + if (!getFirstSelected()) + { + selectFirstItem(); + } + else + { + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* cur_item = *iter; + + if (cur_item->getSelected()) + { + if (prev_item) + { + selectItem(prev_item, !extend_selection); + } + else + { + reportInvalidInput(); + } + break; + } + + prev_item = cur_item; + } + } + + if ((mCommitOnSelectionChange || mCommitOnKeyboardMovement)) + { + commitIfChanged(); + } + + mSearchString.clear(); +} + + +void LLScrollListCtrl::selectNextItem( BOOL extend_selection) +{ + if (!getFirstSelected()) + { + selectFirstItem(); + } + else + { + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + if (item->getSelected()) + { + if (++iter != mItemList.end()) + { + LLScrollListItem *next_item = *iter; + if (next_item) + { + selectItem(next_item, !extend_selection); + } + else + { + reportInvalidInput(); + } + } + break; + } + } + } + + if ((mCommitOnSelectionChange || mCommitOnKeyboardMovement)) + { + onCommit(); + } + + mSearchString.clear(); +} + + + +void LLScrollListCtrl::deselectAllItems(BOOL no_commit_on_change) +{ + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + deselectItem(item); + } + + if (mCommitOnSelectionChange && !no_commit_on_change) + { + commitIfChanged(); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// "Simple" interface: use this when you're creating a list that contains only unique strings, only +// one of which can be selected at a time. + +LLScrollListItem* LLScrollListCtrl::addSimpleItem(const LLString& item_text, EAddPosition pos, BOOL enabled) +{ + LLScrollListItem* item = NULL; + if (getItemCount() < mMaxItemCount) + { + // simple items have their LLSD data set to their label + item = new LLScrollListItem( LLSD(item_text) ); + item->setEnabled(enabled); + item->addColumn( item_text, gResMgr->getRes( LLFONT_SANSSERIF_SMALL ) ); + addItem( item, pos ); + } + return item; +} + + +// Selects first enabled item of the given name. +// Returns false if item not found. +BOOL LLScrollListCtrl::selectSimpleItem(const LLString& label, BOOL case_sensitive) +{ + //RN: assume no empty items + if (label.empty()) + { + return FALSE; + } + + LLString target_text = label; + if (!case_sensitive) + { + LLString::toLower(target_text); + } + + BOOL found = FALSE; + + item_list::iterator iter; + S32 index = 0; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + // Only select enabled items with matching names + LLString item_text = item->getColumn(0)->getText(); + if (!case_sensitive) + { + LLString::toLower(item_text); + } + BOOL select = !found && item->getEnabled() && item_text == target_text; + if (select) + { + selectItem(item); + } + found = found || select; + index++; + } + + if (mCommitOnSelectionChange) + { + commitIfChanged(); + } + + return found; +} + + +BOOL LLScrollListCtrl::selectSimpleItemByPrefix(const LLString& target, BOOL case_sensitive) +{ + return selectSimpleItemByPrefix(utf8str_to_wstring(target), case_sensitive); +} + +// Selects first enabled item that has a name where the name's first part matched the target string. +// Returns false if item not found. +BOOL LLScrollListCtrl::selectSimpleItemByPrefix(const LLWString& target, BOOL case_sensitive) +{ + BOOL found = FALSE; + + LLWString target_trimmed( target ); + S32 target_len = target_trimmed.size(); + + if( 0 == target_len ) + { + // Is "" a valid choice? + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + // Only select enabled items with matching names + LLScrollListCell* cellp = item->getColumn(mSearchColumn); + BOOL select = cellp ? item->getEnabled() && ('\0' == cellp->getText()[0]) : FALSE; + if (select) + { + selectItem(item); + found = TRUE; + break; + } + } + } + else + { + if (!case_sensitive) + { + // do comparisons in lower case + LLWString::toLower(target_trimmed); + } + + for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + + // Only select enabled items with matching names + LLScrollListCell* cellp = item->getColumn(mSearchColumn); + if (!cellp) + { + continue; + } + LLWString item_label = utf8str_to_wstring(cellp->getText()); + if (!case_sensitive) + { + LLWString::toLower(item_label); + } + + BOOL select = item->getEnabled() && !item_label.compare(0, target_len, target_trimmed); + + if (select) + { + selectItem(item); + found = TRUE; + break; + } + } + } + + if (mCommitOnSelectionChange) + { + commitIfChanged(); + } + + return found; +} + +const LLString& LLScrollListCtrl::getSimpleSelectedItem(S32 column) const +{ + LLScrollListItem* item; + + item = getFirstSelected(); + if (item) + { + return item->getColumn(column)->getText(); + } + + return LLString::null; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// "StringUUID" interface: use this when you're creating a list that contains non-unique strings each of which +// has an associated, unique UUID, and only one of which can be selected at a time. + +LLScrollListItem* LLScrollListCtrl::addStringUUIDItem(const LLString& item_text, const LLUUID& id, EAddPosition pos, BOOL enabled, S32 column_width) +{ + LLScrollListItem* item = NULL; + if (getItemCount() < mMaxItemCount) + { + item = new LLScrollListItem( enabled, NULL, id ); + item->addColumn(item_text, gResMgr->getRes(LLFONT_SANSSERIF_SMALL), column_width); + addItem( item, pos ); + } + return item; +} + +LLScrollListItem* LLScrollListCtrl::addSimpleItem(const LLString& item_text, LLSD sd, EAddPosition pos, BOOL enabled, S32 column_width) +{ + LLScrollListItem* item = NULL; + if (getItemCount() < mMaxItemCount) + { + item = new LLScrollListItem( sd ); + item->setEnabled(enabled); + item->addColumn(item_text, gResMgr->getRes(LLFONT_SANSSERIF_SMALL), column_width); + addItem( item, pos ); + } + return item; +} + + +// Select the line or lines that match this UUID +BOOL LLScrollListCtrl::selectByID( const LLUUID& id ) +{ + return selectByValue( LLSD(id) ); +} + +BOOL LLScrollListCtrl::setSelectedByValue(LLSD value, BOOL selected) +{ + BOOL found = FALSE; + + if (selected && !mAllowMultipleSelection) deselectAllItems(TRUE); + + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + if (item->getEnabled() && (item->getValue().asString() == value.asString())) + { + if (selected) + { + selectItem(item); + } + else + { + deselectItem(item); + } + found = TRUE; + break; + } + } + + if (mCommitOnSelectionChange) + { + commitIfChanged(); + } + + return found; +} + +BOOL LLScrollListCtrl::isSelected(LLSD value) +{ + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + if (item->getValue().asString() == value.asString()) + { + return item->getSelected(); + } + } + return FALSE; +} + +LLUUID LLScrollListCtrl::getStringUUIDSelectedItem() +{ + LLScrollListItem* item = getFirstSelected(); + + if (item) + { + return item->getUUID(); + } + + return LLUUID::null; +} + +LLSD LLScrollListCtrl::getSimpleSelectedValue() +{ + LLScrollListItem* item = getFirstSelected(); + + if (item) + { + return item->getValue(); + } + else + { + return LLSD(); + } +} + +void LLScrollListCtrl::drawItems() +{ + S32 x = mItemListRect.mLeft; + S32 y = mItemListRect.mTop - mLineHeight; + + S32 num_page_lines = mPageLines; + + LLRect item_rect; + + LLGLSUIDefault gls_ui; + + { + + S32 cur_x = x; + S32 cur_y = y; + + mDrewSelected = FALSE; + + S32 line = 0; + LLColor4 color; + S32 max_columns = 0; + + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + + item_rect.setOriginAndSize( + cur_x, + cur_y, + mScrollbar->getVisible() ? mItemListRect.getWidth() : mItemListRect.getWidth() + mScrollbar->getRect().getWidth(), + mLineHeight ); + + lldebugs << mItemListRect.getWidth() << llendl; + + if (item->getSelected()) + { + mDrewSelected = TRUE; + } + + max_columns = llmax(max_columns, item->getNumColumns()); + + LLRect bg_rect = item_rect; + // pad background rectangle to separate it from contents + bg_rect.stretch(LIST_BORDER_PAD, 0); + + if( mScrollLines <= line && line < mScrollLines + num_page_lines ) + { + if( item->getSelected() && mCanSelect) + { + // Draw background of selected item + LLGLSNoTexture no_texture; + glColor4fv(mBgSelectedColor.mV); + gl_rect_2d( bg_rect ); + + color = mFgSelectedColor; + } + else if (mHighlightedItem == line && mCanSelect) + { + LLGLSNoTexture no_texture; + glColor4fv(mHighlightedColor.mV); + gl_rect_2d( bg_rect ); + color = (item->getEnabled() ? mFgUnselectedColor : mFgDisabledColor); + } + else + { + color = (item->getEnabled() ? mFgUnselectedColor : mFgDisabledColor); + if (mDrawStripes && (line%2 == 0) && (max_columns > 1)) + { + LLGLSNoTexture no_texture; + glColor4fv(mBgStripeColor.mV); + gl_rect_2d( bg_rect ); + } + } + + S32 line_x = cur_x; + { + S32 num_cols = item->getNumColumns(); + S32 cur_col = 0; + S32 dynamic_width = 0; + S32 dynamic_remainder = 0; + if(mNumDynamicWidthColumns > 0) + { + dynamic_width = (mItemListRect.getWidth() - mTotalStaticColumnWidth) / mNumDynamicWidthColumns; + dynamic_remainder = (mItemListRect.getWidth() - mTotalStaticColumnWidth) % mNumDynamicWidthColumns; + } + for (LLScrollListCell* cell = item->getColumn(0); cur_col < num_cols; cell = item->getColumn(++cur_col)) + { + S32 cell_width = cell->getWidth(); + if(mColumnsIndexed.size() > (U32)cur_col && mColumnsIndexed[cur_col] && mColumnsIndexed[cur_col]->mDynamicWidth) + { + cell_width = dynamic_width + (--dynamic_remainder ? 1 : 0); + cell->setWidth(cell_width); + } + // Two ways a cell could be hidden + if (cell_width < 0 + || !cell->getVisible()) continue; + LLUI::pushMatrix(); + LLUI::translate((F32) cur_x, (F32) cur_y, 0.0f); + S32 space_left = mItemListRect.mRight - cur_x; + LLColor4 highlight_color = LLColor4::white; + F32 type_ahead_timeout = LLUI::sConfigGroup->getF32("TypeAheadTimeout"); + + highlight_color.mV[VALPHA] = clamp_rescale(mSearchTimer.getElapsedTimeF32(), type_ahead_timeout * 0.7f, type_ahead_timeout, 0.4f, 0.f); + cell->drawToWidth( space_left, color, highlight_color ); + LLUI::popMatrix(); + + cur_x += cell_width + mColumnPadding; + + } + } + cur_x = line_x; + cur_y -= mLineHeight; + } + line++; + } + } +} + + +void LLScrollListCtrl::draw() +{ + if( getVisible() ) + { + LLRect background(0, mRect.getHeight(), mRect.getWidth(), 0); + // Draw background + if (mBackgroundVisible) + { + LLGLSNoTexture no_texture; + glColor4fv( getEnabled() ? mBgWriteableColor.mV : mBgReadOnlyColor.mV ); + gl_rect_2d(background); + } + + drawItems(); + + if (mBorder) + { + mBorder->setKeyboardFocusHighlight(gFocusMgr.getKeyboardFocus() == this); + } + + LLUICtrl::draw(); + } +} + +void LLScrollListCtrl::setEnabled(BOOL enabled) +{ + mCanSelect = enabled; + setTabStop(enabled); + mScrollbar->setTabStop(!enabled && mScrollbar->getPageSize() < mScrollbar->getDocSize()); +} + +BOOL LLScrollListCtrl::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + BOOL handled = FALSE; + // Pretend the mouse is over the scrollbar + handled = mScrollbar->handleScrollWheel( 0, 0, clicks ); + return handled; +} + + +BOOL LLScrollListCtrl::handleMouseDown(S32 x, S32 y, MASK mask) +{ + BOOL handled = LLView::childrenHandleMouseDown(x, y, mask) != NULL; + + if( !handled && mCanSelect) + { + LLScrollListItem* hit_item = hitItem(x, y); + if( hit_item ) + { + if( mAllowMultipleSelection ) + { + if (mask & MASK_SHIFT) + { + if (mLastSelected == NULL) + { + selectItem(hit_item); + } + else + { + // Select everthing between mLastSelected and hit_item + bool selecting = false; + item_list::iterator itor; + // If we multiselect backwards, we'll stomp on mLastSelected, + // meaning that we never stop selecting until hitting max or + // the end of the list. + LLScrollListItem* lastSelected = mLastSelected; + for (itor = mItemList.begin(); itor != mItemList.end(); ++itor) + { + if(mMaxSelectable > 0 && getAllSelected().size() >= mMaxSelectable) + { + if(mOnMaximumSelectCallback) + { + mOnMaximumSelectCallback(mCallbackUserData); + } + break; + } + LLScrollListItem *item = *itor; + if (item == hit_item || item == lastSelected) + { + selectItem(item, FALSE); + selecting = !selecting; + } + if (selecting) + { + selectItem(item, FALSE); + } + } + } + } + else if (mask & MASK_CONTROL) + { + if (hit_item->getSelected()) + { + deselectItem(hit_item); + } + else + { + if(!(mMaxSelectable > 0 && getAllSelected().size() >= mMaxSelectable)) + { + selectItem(hit_item, FALSE); + } + else + { + if(mOnMaximumSelectCallback) + { + mOnMaximumSelectCallback(mCallbackUserData); + } + } + } + } + else + { + deselectAllItems(TRUE); + selectItem(hit_item); + } + } + else + { + selectItem(hit_item); + } + + hit_item->handleMouseDown(x - mBorderThickness - LIST_BORDER_PAD, + 1, mask); + // always commit on mousedown + onCommit(); + mSelectionChanged = FALSE; + + // clear search string on mouse operations + mSearchString.clear(); + } + else + { + mLastSelected = NULL; + } + } + + gFocusMgr.setKeyboardFocus(this, NULL); + + return TRUE; +} + +BOOL LLScrollListCtrl::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + //BOOL handled = FALSE; + if(getVisible()) + { + // Offer the click to the children, even if we aren't enabled + // so the scroll bars will work. + if (NULL == LLView::childrenHandleDoubleClick(x, y, mask)) + { + if( mCanSelect && mOnDoubleClickCallback ) + { + mOnDoubleClickCallback( mCallbackUserData ); + } + } + } + return TRUE; +} + +LLScrollListItem* LLScrollListCtrl::hitItem( S32 x, S32 y ) +{ + // Excludes disabled items. + LLScrollListItem* hit_item = NULL; + + LLRect item_rect; + item_rect.setLeftTopAndSize( + mItemListRect.mLeft, + mItemListRect.mTop, + mItemListRect.getWidth(), + mLineHeight ); + + int num_page_lines = mPageLines; + + S32 line = 0; + item_list::iterator iter; + for(iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + if( mScrollLines <= line && line < mScrollLines + num_page_lines ) + { + if( item->getEnabled() && item_rect.pointInRect( x, y ) ) + { + hit_item = item; + break; + } + + item_rect.translate(0, -mLineHeight); + } + line++; + } + + return hit_item; +} + + +BOOL LLScrollListCtrl::handleHover(S32 x,S32 y,MASK mask) +{ + BOOL handled = FALSE; + + if(getVisible()) + { + if (mCanSelect) + { + LLScrollListItem* item = hitItem(x, y); + if (item) + { + highlightNthItem(getItemIndex(item)); + } + else + { + highlightNthItem(-1); + } + } + + handled = LLView::handleHover( x, y, mask ); + + if( !handled ) + { + // Opaque + getWindow()->setCursor(UI_CURSOR_ARROW); + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << llendl; + handled = TRUE; + } + } + return handled; +} + + +BOOL LLScrollListCtrl::handleKeyHere(KEY key,MASK mask, BOOL called_from_parent ) +{ + BOOL handled = FALSE; + + // not called from parent means we have keyboard focus or a child does + if (mCanSelect && !called_from_parent) + { + // Ignore capslock + mask = mask; + + if (mask == MASK_NONE) + { + switch(key) + { + case KEY_UP: + if (mAllowKeyboardMovement || hasFocus()) + { + // commit implicit in call + selectPrevItem(FALSE); + scrollToShowSelected(); + handled = TRUE; + } + break; + case KEY_DOWN: + if (mAllowKeyboardMovement || hasFocus()) + { + // commit implicit in call + selectNextItem(FALSE); + scrollToShowSelected(); + handled = TRUE; + } + break; + case KEY_PAGE_UP: + if (mAllowKeyboardMovement || hasFocus()) + { + selectNthItem(getFirstSelectedIndex() - (mScrollbar->getPageSize() - 1)); + scrollToShowSelected(); + if (mCommitOnKeyboardMovement + && !mCommitOnSelectionChange) + { + onCommit(); + } + handled = TRUE; + } + break; + case KEY_PAGE_DOWN: + if (mAllowKeyboardMovement || hasFocus()) + { + selectNthItem(getFirstSelectedIndex() + (mScrollbar->getPageSize() - 1)); + scrollToShowSelected(); + if (mCommitOnKeyboardMovement + && !mCommitOnSelectionChange) + { + onCommit(); + } + handled = TRUE; + } + break; + case KEY_HOME: + if (mAllowKeyboardMovement || hasFocus()) + { + selectFirstItem(); + scrollToShowSelected(); + if (mCommitOnKeyboardMovement + && !mCommitOnSelectionChange) + { + onCommit(); + } + handled = TRUE; + } + break; + case KEY_END: + if (mAllowKeyboardMovement || hasFocus()) + { + selectNthItem(getItemCount() - 1); + scrollToShowSelected(); + if (mCommitOnKeyboardMovement + && !mCommitOnSelectionChange) + { + onCommit(); + } + handled = TRUE; + } + break; + case KEY_RETURN: + // JC - Special case: Only claim to have handled it + // if we're the special non-commit-on-move + // type. AND we are visible + if (!mCommitOnKeyboardMovement && mask == MASK_NONE && getVisible()) + { + onCommit(); + mSearchString.clear(); + handled = TRUE; + } + break; + case KEY_BACKSPACE: + mSearchTimer.reset(); + if (mSearchString.size()) + { + mSearchString.erase(mSearchString.size() - 1, 1); + } + if (mSearchString.empty()) + { + if (getFirstSelected()) + { + LLScrollListCell* cellp = getFirstSelected()->getColumn(mSearchColumn); + if (cellp) + { + cellp->highlightText(0); + } + } + } + else if (selectSimpleItemByPrefix(wstring_to_utf8str(mSearchString), FALSE)) + { + // update search string only on successful match + mSearchTimer.reset(); + + // highlight current search on matching item + LLScrollListCell* cellp = getFirstSelected()->getColumn(mSearchColumn); + if (cellp) + { + cellp->highlightText(mSearchString.size()); + } + + if (mCommitOnKeyboardMovement + && !mCommitOnSelectionChange) + { + onCommit(); + } + } + break; + default: + break; + } + } + // TODO: multiple: shift-up, shift-down, shift-home, shift-end, select all + } + + return handled; +} + +BOOL LLScrollListCtrl::handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent) +{ + if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL + { + return FALSE; + } + + // perform incremental search based on keyboard input + if (mSearchTimer.getElapsedTimeF32() > LLUI::sConfigGroup->getF32("TypeAheadTimeout")) + { + mSearchString.clear(); + } + + // type ahead search is case insensitive + uni_char = LLStringOps::toLower((llwchar)uni_char); + + if (selectSimpleItemByPrefix(wstring_to_utf8str(mSearchString + (llwchar)uni_char), FALSE)) + { + // update search string only on successful match + mSearchString += uni_char; + mSearchTimer.reset(); + + // highlight current search on matching item + LLScrollListCell* cellp = getFirstSelected()->getColumn(mSearchColumn); + if (cellp) + { + cellp->highlightText(mSearchString.size()); + } + + if (mCommitOnKeyboardMovement + && !mCommitOnSelectionChange) + { + onCommit(); + } + } + // handle iterating over same starting character + else if (isRepeatedChars(mSearchString + (llwchar)uni_char) && !mItemList.empty()) + { + // start from last selected item, in case we previously had a successful match against + // duplicated characters ('AA' matches 'Aaron') + item_list::iterator start_iter = mItemList.begin(); + S32 first_selected = getFirstSelectedIndex(); + + // if we have a selection (> -1) then point iterator at the selected item + if (first_selected > 0) + { + // point iterator to first selected item + start_iter += first_selected; + } + + // start search at first item after current selection + item_list::iterator iter = start_iter; + ++iter; + if (iter == mItemList.end()) + { + iter = mItemList.begin(); + } + + // loop around once, back to previous selection + while(iter != start_iter) + { + LLScrollListItem* item = *iter; + + LLScrollListCell* cellp = item->getColumn(mSearchColumn); + if (cellp) + { + // Only select enabled items with matching first characters + LLWString item_label = utf8str_to_wstring(cellp->getText()); + if (item->getEnabled() && LLStringOps::toLower(item_label[0]) == uni_char) + { + selectItem(item); + cellp->highlightText(1); + mSearchTimer.reset(); + + if (mCommitOnKeyboardMovement + && !mCommitOnSelectionChange) + { + onCommit(); + } + + break; + } + } + + ++iter; + if (iter == mItemList.end()) + { + iter = mItemList.begin(); + } + } + } + + // make sure selected item is on screen + scrollToShowSelected(); + return TRUE; +} + + +void LLScrollListCtrl::reportInvalidInput() +{ + make_ui_sound("UISndBadKeystroke"); +} + +BOOL LLScrollListCtrl::isRepeatedChars(const LLWString& string) const +{ + if (string.empty()) + { + return FALSE; + } + + llwchar first_char = string[0]; + + for (U32 i = 0; i < string.size(); i++) + { + if (string[i] != first_char) + { + return FALSE; + } + } + + return TRUE; +} + +void LLScrollListCtrl::selectItem(LLScrollListItem* itemp, BOOL select_single_item) +{ + if (!itemp) return; + + if (!itemp->getSelected()) + { + if (mLastSelected) + { + LLScrollListCell* cellp = mLastSelected->getColumn(mSearchColumn); + if (cellp) + { + cellp->highlightText(0); + } + } + if (select_single_item) + { + deselectAllItems(TRUE); + } + itemp->setSelected(TRUE); + mLastSelected = itemp; + mSelectionChanged = TRUE; + } +} + +void LLScrollListCtrl::deselectItem(LLScrollListItem* itemp) +{ + if (!itemp) return; + + if (itemp->getSelected()) + { + if (mLastSelected == itemp) + { + mLastSelected = NULL; + } + + itemp->setSelected(FALSE); + LLScrollListCell* cellp = itemp->getColumn(mSearchColumn); + if (cellp) + { + cellp->highlightText(0); + } + mSelectionChanged = TRUE; + } +} + +void LLScrollListCtrl::commitIfChanged() +{ + if (mSelectionChanged) + { + mSelectionChanged = FALSE; + onCommit(); + } +} + +// Called by scrollbar +//static +void LLScrollListCtrl::onScrollChange( S32 new_pos, LLScrollbar* scrollbar, void* userdata ) +{ + LLScrollListCtrl* self = (LLScrollListCtrl*) userdata; + self->mScrollLines = new_pos; +} + + +// First column is column 0 +void LLScrollListCtrl::sortByColumn(U32 column, BOOL ascending) +{ + LLScrollListCtrl::sSortColumn = column; + LLScrollListCtrl::sSortAscending = ascending; + std::sort(mItemList.begin(), mItemList.end(), SortScrollListItem(sSortColumn, sSortAscending)); +} + +void LLScrollListCtrl::sortByColumn(LLString name, BOOL ascending) +{ + std::map<LLString, LLScrollListColumn>::iterator itor = mColumns.find(name); + if (itor != mColumns.end()) + { + sortByColumn((*itor).second.mIndex, ascending); + } +} + +S32 LLScrollListCtrl::getScrollPos() +{ + return mScrollbar->getDocPos(); +} + + +void LLScrollListCtrl::setScrollPos( S32 pos ) +{ + mScrollbar->setDocPos( pos ); + + onScrollChange(mScrollbar->getDocPos(), mScrollbar, this); +} + + +void LLScrollListCtrl::scrollToShowSelected() +{ + S32 index = getFirstSelectedIndex(); + if (index < 0) + { + return; + } + + LLScrollListItem* item = mItemList[index]; + if (!item) + { + // I don't THINK this should ever happen. + return; + } + + S32 lowest = mScrollLines; + S32 highest = mScrollLines + mPageLines; + + if (index < lowest) + { + // need to scroll to show item + setScrollPos(index); + } + else if (highest <= index) + { + setScrollPos(index - mPageLines + 1); + } +} + +// virtual +LLXMLNodePtr LLScrollListCtrl::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLUICtrl::getXML(); + + // Attributes + + node->createChild("multi_select", TRUE)->setBoolValue(mAllowMultipleSelection); + + node->createChild("draw_border", TRUE)->setBoolValue((mBorder != NULL)); + + node->createChild("draw_heading", TRUE)->setBoolValue(mDisplayColumnButtons); + + node->createChild("background_visible", TRUE)->setBoolValue(mBackgroundVisible); + + node->createChild("draw_stripes", TRUE)->setBoolValue(mDrawStripes); + + node->createChild("column_padding", TRUE)->setIntValue(mColumnPadding); + + addColorXML(node, mBgWriteableColor, "bg_writeable_color", "ScrollBgWriteableColor"); + addColorXML(node, mBgReadOnlyColor, "bg_read_only_color", "ScrollBgReadOnlyColor"); + addColorXML(node, mBgSelectedColor, "bg_selected_color", "ScrollSelectedBGColor"); + addColorXML(node, mBgStripeColor, "bg_stripe_color", "ScrollBGStripeColor"); + addColorXML(node, mFgSelectedColor, "fg_selected_color", "ScrollSelectedFGColor"); + addColorXML(node, mFgUnselectedColor, "fg_unselected_color", "ScrollUnselectedColor"); + addColorXML(node, mFgDisabledColor, "fg_disable_color", "ScrollDisabledColor"); + addColorXML(node, mHighlightedColor, "highlighted_color", "ScrollHighlightedColor"); + + // Contents + + std::map<LLString, LLScrollListColumn>::const_iterator itor; + std::vector<const LLScrollListColumn*> sorted_list; + sorted_list.resize(mColumns.size()); + for (itor = mColumns.begin(); itor != mColumns.end(); ++itor) + { + sorted_list[itor->second.mIndex] = &itor->second; + } + + std::vector<const LLScrollListColumn*>::iterator itor2; + for (itor2 = sorted_list.begin(); itor2 != sorted_list.end(); ++itor2) + { + LLXMLNodePtr child_node = node->createChild("column", FALSE); + const LLScrollListColumn *column = *itor2; + + child_node->createChild("name", TRUE)->setStringValue(column->mName); + child_node->createChild("label", TRUE)->setStringValue(column->mLabel); + child_node->createChild("width", TRUE)->setIntValue(column->mWidth); + } + + return node; +} + +void LLScrollListCtrl::setScrollListParameters(LLXMLNodePtr node) +{ + // James: This is not a good way to do colors. We need a central "UI style" + // manager that sets the colors for ALL scroll lists, buttons, etc. + + LLColor4 color; + if(node->hasAttribute("fg_unselected_color")) + { + LLUICtrlFactory::getAttributeColor(node,"fg_unselected_color", color); + setFgUnselectedColor(color); + } + if(node->hasAttribute("fg_selected_color")) + { + LLUICtrlFactory::getAttributeColor(node,"fg_selected_color", color); + setFgSelectedColor(color); + } + if(node->hasAttribute("bg_selected_color")) + { + LLUICtrlFactory::getAttributeColor(node,"bg_selected_color", color); + setBgSelectedColor(color); + } + if(node->hasAttribute("fg_disable_color")) + { + LLUICtrlFactory::getAttributeColor(node,"fg_disable_color", color); + setFgDisableColor(color); + } + if(node->hasAttribute("bg_writeable_color")) + { + LLUICtrlFactory::getAttributeColor(node,"bg_writeable_color", color); + setBgWriteableColor(color); + } + if(node->hasAttribute("bg_read_only_color")) + { + LLUICtrlFactory::getAttributeColor(node,"bg_read_only_color", color); + setReadOnlyBgColor(color); + } + if (LLUICtrlFactory::getAttributeColor(node,"bg_stripe_color", color)) + { + setBgStripeColor(color); + } + if (LLUICtrlFactory::getAttributeColor(node,"highlighted_color", color)) + { + setHighlightedColor(color); + } + + if(node->hasAttribute("background_visible")) + { + BOOL background_visible; + node->getAttributeBOOL("background_visible", background_visible); + setBackgroundVisible(background_visible); + } + + if(node->hasAttribute("draw_stripes")) + { + BOOL draw_stripes; + node->getAttributeBOOL("draw_stripes", draw_stripes); + setDrawStripes(draw_stripes); + } + + if(node->hasAttribute("column_padding")) + { + S32 column_padding; + node->getAttributeS32("column_padding", column_padding); + setColumnPadding(column_padding); + } +} + +// static +LLView* LLScrollListCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("scroll_list"); + node->getAttributeString("name", name); + + LLRect rect; + createRect(node, rect, parent, LLRect()); + + BOOL multi_select = FALSE; + node->getAttributeBOOL("multi_select", multi_select); + + BOOL draw_border = TRUE; + node->getAttributeBOOL("draw_border", draw_border); + + BOOL draw_heading = FALSE; + node->getAttributeBOOL("draw_heading", draw_heading); + + BOOL collapse_empty_columns = FALSE; + node->getAttributeBOOL("collapse_empty_columns", collapse_empty_columns); + + S32 search_column = 0; + node->getAttributeS32("search_column", search_column); + + LLUICtrlCallback callback = NULL; + + LLScrollListCtrl* scroll_list = new LLScrollListCtrl( + name, + rect, + callback, + NULL, + multi_select, + draw_border); + + scroll_list->setDisplayHeading(draw_heading); + if (node->hasAttribute("heading_height")) + { + S32 heading_height; + node->getAttributeS32("heading_height", heading_height); + scroll_list->setHeadingHeight(heading_height); + } + if (node->hasAttribute("heading_font")) + { + LLString heading_font(""); + node->getAttributeString("heading_font", heading_font); + LLFontGL* gl_font = LLFontGL::fontFromName(heading_font.c_str()); + scroll_list->setHeadingFont(gl_font); + } + scroll_list->setCollapseEmptyColumns(collapse_empty_columns); + + scroll_list->setScrollListParameters(node); + + scroll_list->initFromXML(node, parent); + + scroll_list->setSearchColumn(search_column); + + LLSD columns; + S32 index = 0; + LLXMLNodePtr child; + S32 total_static = 0, num_dynamic = 0; + for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) + { + if (child->hasName("column")) + { + LLString labelname(""); + child->getAttributeString("label", labelname); + + LLString columnname(labelname); + child->getAttributeString("name", columnname); + + LLString sortname(columnname); + child->getAttributeString("sort", sortname); + + LLString imagename; + child->getAttributeString("image", imagename); + + BOOL columndynamicwidth = FALSE; + child->getAttributeBOOL("dynamicwidth", columndynamicwidth); + + S32 columnwidth = -1; + child->getAttributeS32("width", columnwidth); + + if(!columndynamicwidth) total_static += columnwidth; + else ++num_dynamic; + + F32 columnrelwidth = 0.f; + child->getAttributeF32("relwidth", columnrelwidth); + + + columns[index]["name"] = columnname; + columns[index]["sort"] = sortname; + columns[index]["image"] = imagename; + columns[index]["label"] = labelname; + columns[index]["width"] = columnwidth; + columns[index]["relwidth"] = columnrelwidth; + columns[index]["dynamicwidth"] = columndynamicwidth; + index++; + } + } + scroll_list->setNumDynamicColumns(num_dynamic); + scroll_list->setTotalStaticColumnWidth(total_static); + scroll_list->setColumnHeadings(columns); + + for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) + { + if (child->hasName("row")) + { + LLUUID id; + child->getAttributeUUID("id", id); + + LLSD row; + + row["id"] = id; + + S32 column_idx = 0; + LLXMLNodePtr row_child; + for (row_child = child->getFirstChild(); row_child.notNull(); row_child = row_child->getNextSibling()) + { + if (row_child->hasName("column")) + { + LLString value = row_child->getTextContents(); + + LLString columnname(""); + row_child->getAttributeString("name", columnname); + + LLString font(""); + row_child->getAttributeString("font", font); + + LLString font_style(""); + row_child->getAttributeString("font-style", font_style); + + row["columns"][column_idx]["column"] = columnname; + row["columns"][column_idx]["value"] = value; + row["columns"][column_idx]["font"] = font; + row["columns"][column_idx]["font-style"] = font_style; + column_idx++; + } + } + scroll_list->addElement(row); + } + } + + LLString contents = node->getTextContents(); + if (!contents.empty()) + { + typedef boost::tokenizer<boost::char_separator<char> > tokenizer; + boost::char_separator<char> sep("\t\n"); + tokenizer tokens(contents, sep); + tokenizer::iterator token_iter = tokens.begin(); + + while(token_iter != tokens.end()) + { + const char* line = token_iter->c_str(); + scroll_list->addSimpleItem(line); + ++token_iter; + } + } + + return scroll_list; +} + +// LLEditMenuHandler functions + +// virtual +void LLScrollListCtrl::copy() +{ + LLString buffer; + + std::vector<LLScrollListItem*> items = getAllSelected(); + std::vector<LLScrollListItem*>::iterator itor; + for (itor = items.begin(); itor != items.end(); ++itor) + { + buffer += (*itor)->getContentsCSV() + "\n"; + } + gClipboard.copyFromSubstring(utf8str_to_wstring(buffer), 0, buffer.length()); +} + +// virtual +BOOL LLScrollListCtrl::canCopy() +{ + return (getFirstSelected() != NULL); +} + +// virtual +void LLScrollListCtrl::cut() +{ + copy(); + doDelete(); +} + +// virtual +BOOL LLScrollListCtrl::canCut() +{ + return canCopy() && canDoDelete(); +} + +// virtual +void LLScrollListCtrl::doDelete() +{ + // Not yet implemented +} + +// virtual +BOOL LLScrollListCtrl::canDoDelete() +{ + // Not yet implemented + return FALSE; +} + +// virtual +void LLScrollListCtrl::selectAll() +{ + // Deselects all other items + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem *itemp = *iter; + if( itemp->getEnabled() ) + { + selectItem(itemp, FALSE); + } + } + + if (mCommitOnSelectionChange) + { + commitIfChanged(); + } +} + +// virtual +BOOL LLScrollListCtrl::canSelectAll() +{ + return getCanSelect() && mAllowMultipleSelection && !(mMaxSelectable > 0 && mItemList.size() > mMaxSelectable); +} + +// virtual +void LLScrollListCtrl::deselect() +{ + deselectAllItems(); +} + +// virtual +BOOL LLScrollListCtrl::canDeselect() +{ + return getCanSelect(); +} + +void LLScrollListCtrl::addColumn(const LLSD& column, EAddPosition pos) +{ + LLString name = column["name"].asString(); + if (mColumns.size() == 0) + { + mDefaultColumn = 0; + } + if (mColumns.find(name) == mColumns.end()) + { + // Add column + mColumns[name] = LLScrollListColumn(column); + LLScrollListColumn* new_column = &mColumns[name]; + new_column->mParentCtrl = this; + new_column->mIndex = mColumns.size()-1; + + // Add button + if (new_column->mWidth > 0 || new_column->mRelWidth > 0 || new_column->mDynamicWidth) + { + if (new_column->mRelWidth >= 0) + { + new_column->mWidth = (S32)llround(new_column->mRelWidth*mItemListRect.getWidth()); + } + else if(new_column->mDynamicWidth) + { + new_column->mWidth = (mItemListRect.getWidth() - mTotalStaticColumnWidth) / mNumDynamicWidthColumns; + } + S32 top = mItemListRect.mTop; + S32 left = mItemListRect.mLeft; + { + std::map<LLString, LLScrollListColumn>::iterator itor; + for (itor = mColumns.begin(); itor != mColumns.end(); ++itor) + { + if (itor->second.mIndex < new_column->mIndex && + itor->second.mWidth > 0) + { + left += itor->second.mWidth + mColumnPadding; + } + } + } + LLString button_name = "btn_" + name; + S32 right = left+new_column->mWidth; + if (new_column->mIndex != (S32)mColumns.size()-1) + { + right += mColumnPadding; + } + LLRect temp_rect = LLRect(left,top+mHeadingHeight,right,top); + new_column->mButton = new LLSquareButton(button_name, temp_rect, "", mHeadingFont, "", onClickColumn, NULL); + if(column["image"].asString() != "") + { + //new_column->mButton->setScaleImage(false); + new_column->mButton->setImageSelected(column["image"].asString()); + new_column->mButton->setImageUnselected(column["image"].asString()); + } + else + { + new_column->mButton->setLabelSelected(new_column->mLabel); + new_column->mButton->setLabelUnselected(new_column->mLabel); + } + //RN: although it might be useful to change sort order with the keyboard, + // mixing tab stops on child items along with the parent item is not supported yet + new_column->mButton->setTabStop(FALSE); + addChild(new_column->mButton); + new_column->mButton->setVisible(mDisplayColumnButtons); + + + // Move scroll to front + removeChild(mScrollbar); + addChild(mScrollbar); + + new_column->mButton->setCallbackUserData(new_column); + } + } + updateColumns(); +} + +// static +void LLScrollListCtrl::onClickColumn(void *userdata) +{ + LLScrollListColumn *info = (LLScrollListColumn*)userdata; + if (!info) return; + + U32 column_index = info->mIndex; + + LLScrollListColumn* column = info->mParentCtrl->mColumnsIndexed[info->mIndex]; + if (column->mSortingColumn != column->mName) + { + if (info->mParentCtrl->mColumns.find(column->mSortingColumn) != info->mParentCtrl->mColumns.end()) + { + LLScrollListColumn& info_redir = info->mParentCtrl->mColumns[column->mSortingColumn]; + column_index = info_redir.mIndex; + } + } + + // TomY TODO: shouldn't these be non-static members? + bool ascending = true; + if (column_index == LLScrollListCtrl::sSortColumn) + { + ascending = !LLScrollListCtrl::sSortAscending; + } + + info->mParentCtrl->sortByColumn(column_index, ascending); +} + +void LLScrollListCtrl::clearColumns() +{ + std::map<LLString, LLScrollListColumn>::iterator itor; + for (itor = mColumns.begin(); itor != mColumns.end(); ++itor) + { + LLButton *button = itor->second.mButton; + if (button) + { + removeChild(button); + delete button; + } + } + mColumns.clear(); +} + +void LLScrollListCtrl::setColumnLabel(const LLString& column, const LLString& label) +{ + std::map<LLString, LLScrollListColumn>::iterator itor = mColumns.find(column); + if (itor != mColumns.end()) + { + itor->second.mLabel = label; + if (itor->second.mButton) + { + itor->second.mButton->setLabelSelected(label); + itor->second.mButton->setLabelUnselected(label); + } + } +} + +void LLScrollListCtrl::setColumnHeadings(LLSD headings) +{ + mColumns.clear(); + LLSD::array_const_iterator itor; + for (itor = headings.beginArray(); itor != headings.endArray(); ++itor) + { + addColumn(*itor); + } +} + +LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& value, EAddPosition pos, void* userdata) +{ + // ID + LLSD id = value["id"]; + + LLScrollListItem *new_item = new LLScrollListItem(id, userdata); + if (value.has("enabled")) + { + new_item->setEnabled( value["enabled"].asBoolean() ); + } + + new_item->setNumColumns(mColumns.size()); + + // Add any columns we don't already have + LLSD columns = value["columns"]; + LLSD::array_const_iterator itor; + for (itor = columns.beginArray(); itor != columns.endArray(); ++itor) + { + LLString column = (*itor)["column"].asString(); + + if (mColumns.size() == 0) + { + mDefaultColumn = 0; + } + std::map<LLString, LLScrollListColumn>::iterator column_itor = mColumns.find(column); + if (column_itor == mColumns.end()) + { + LLSD new_column; + new_column["name"] = column; + new_column["label"] = column; + new_column["width"] = 0; + addColumn(new_column); + column_itor = mColumns.find(column); + new_item->setNumColumns(mColumns.size()); + } + + S32 index = column_itor->second.mIndex; + S32 width = column_itor->second.mWidth; + + LLSD value = (*itor)["value"]; + LLString fontname = (*itor)["font"].asString(); + LLString fontstyle = (*itor)["font-style"].asString(); + LLString type = (*itor)["type"].asString(); + + const LLFontGL *font = gResMgr->getRes(fontname); + if (!font) + { + font = gResMgr->getRes( LLFONT_SANSSERIF_SMALL ); + } + U8 font_style = LLFontGL::getStyleFromString(fontstyle); + + if (type == "icon") + { + LLUUID image_id = value.asUUID(); + LLImageGL* icon = LLUI::sImageProvider->getUIImageByID(image_id); + new_item->setColumn(index, new LLScrollListIcon(icon, width, image_id)); + } + else if (type == "checkbox") + { + LLCheckBoxCtrl* ctrl = new LLCheckBoxCtrl(value.asString(), + LLRect(0, 0, width, width), "label"); + new_item->setColumn(index, new LLScrollListCheck(ctrl,width)); + } + else + { + new_item->setColumn(index, new LLScrollListText(value.asString(), font, width, font_style)); + } + } + + S32 num_columns = mColumns.size(); + for (S32 column = 0; column < num_columns; ++column) + { + if (new_item->getColumn(column) == NULL) + { + LLScrollListColumn* column_ptr = mColumnsIndexed[column]; + new_item->setColumn(column, new LLScrollListText("", gResMgr->getRes( LLFONT_SANSSERIF_SMALL ), column_ptr->mWidth, LLFontGL::NORMAL)); + } + } + + addItem(new_item, pos); + + return new_item; +} + +LLScrollListItem* LLScrollListCtrl::addSimpleElement(const LLString& value, EAddPosition pos, const LLSD& id) +{ + LLSD entry_id = id; + + if (id.isUndefined()) + { + entry_id = value; + } + + LLScrollListItem *new_item = new LLScrollListItem(entry_id); + + const LLFontGL *font = gResMgr->getRes( LLFONT_SANSSERIF_SMALL ); + + new_item->addColumn(value, font, getRect().getWidth()); + + addItem(new_item, pos); + return new_item; +} + +void LLScrollListCtrl::setValue(const LLSD& value ) +{ + LLSD::array_const_iterator itor; + for (itor = value.beginArray(); itor != value.endArray(); ++itor) + { + addElement(*itor); + } +} + +LLSD LLScrollListCtrl::getValue() const +{ + LLScrollListItem *item = getFirstSelected(); + if (!item) return LLSD(); + return item->getValue(); +} + +BOOL LLScrollListCtrl::operateOnSelection(EOperation op) +{ + if (op == OP_DELETE) + { + deleteSelectedItems(); + return TRUE; + } + else if (op == OP_DESELECT) + { + deselectAllItems(); + } + return FALSE; +} + +BOOL LLScrollListCtrl::operateOnAll(EOperation op) +{ + if (op == OP_DELETE) + { + clearRows(); + return TRUE; + } + else if (op == OP_DESELECT) + { + deselectAllItems(); + } + else if (op == OP_SELECT) + { + selectAll(); + } + return FALSE; +} +//virtual +void LLScrollListCtrl::setFocus(BOOL b) +{ + mSearchString.clear(); + // for tabbing into pristine scroll lists (Finder) + if (!getFirstSelected()) + { + selectFirstItem(); + onCommit(); + } + LLUICtrl::setFocus(b); +} +//virtual +void LLScrollListCtrl::onFocusLost() +{ + if (mIsPopup) + { + if (getParent()) + { + getParent()->onFocusLost(); + } + } +} diff --git a/indra/llui/llscrolllistctrl.h b/indra/llui/llscrolllistctrl.h new file mode 100644 index 0000000000..426e817215 --- /dev/null +++ b/indra/llui/llscrolllistctrl.h @@ -0,0 +1,527 @@ +/** + * @file llscrolllistctrl.h + * @brief LLScrollListCtrl base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_SCROLLLISTCTRL_H +#define LL_SCROLLLISTCTRL_H + +#include <vector> +#include <deque> + +#include "lluictrl.h" +#include "llctrlselectioninterface.h" +#include "llfontgl.h" +#include "llui.h" +#include "llstring.h" +#include "llimagegl.h" +#include "lleditmenuhandler.h" +#include "llviewborder.h" +#include "llframetimer.h" +#include "llcheckboxctrl.h" + +class LLScrollbar; +class LLScrollListCtrl; + +class LLScrollListCell +{ +public: + virtual ~LLScrollListCell() {}; + virtual void drawToWidth(S32 width, const LLColor4& color, const LLColor4& highlight_color) const = 0; // truncate to given width, if possible + virtual S32 getWidth() const = 0; + virtual S32 getHeight() const = 0; + virtual const LLString& getText() const { return LLString::null; } + virtual const LLString& getTextLower() const { return LLString::null; } + virtual const BOOL getVisible() const { return TRUE; } + virtual void setWidth(S32 width) = 0; + virtual void highlightText(S32 num_chars) {} + + virtual BOOL handleClick() { return FALSE; } + virtual void setEnabled(BOOL enable) { } +}; + +class LLScrollListText : public LLScrollListCell +{ + static U32 sCount; +public: + LLScrollListText( const LLString& text, const LLFontGL* font, S32 width = 0, U8 font_style = LLFontGL::NORMAL, LLColor4& color = LLColor4::black, BOOL use_color = FALSE, BOOL visible = TRUE); + /*virtual*/ ~LLScrollListText(); + + virtual void drawToWidth(S32 width, const LLColor4& color, const LLColor4& highlight_color) const; + virtual S32 getWidth() const { return mWidth; } + virtual void setWidth(S32 width) { mWidth = width; } + virtual S32 getHeight() const { return llround(mFont->getLineHeight()); } + virtual const LLString& getText() const { return mText.getString(); } + virtual const BOOL getVisible() const { return mVisible; } + virtual void highlightText(S32 num_chars) {mHighlightChars = num_chars;} + void setText(const LLString& text); + +private: + LLUIString mText; + const LLFontGL* mFont; + LLColor4* mColor; + const U8 mFontStyle; + S32 mWidth; + S32 mEllipsisWidth; // in pixels, of "..." + BOOL mVisible; + S32 mHighlightChars; + + LLPointer<LLImageGL> mRoundedRectImage; +}; + +class LLScrollListIcon : public LLScrollListCell +{ +public: + LLScrollListIcon( LLImageGL* icon, S32 width = 0, LLUUID image_id = LLUUID::null); + /*virtual*/ ~LLScrollListIcon(); + virtual void drawToWidth(S32 width, const LLColor4& color, const LLColor4& highlight_color) const { gl_draw_image(0, 0, mIcon); } + virtual S32 getWidth() const { return mWidth; } + virtual S32 getHeight() const { return mIcon->getHeight(); } + virtual const LLString& getText() const { return mImageUUID; } + virtual const LLString& getTextLower() const { return mImageUUID; } + virtual void setWidth(S32 width) { mWidth = width; } + +private: + LLPointer<LLImageGL> mIcon; + LLString mImageUUID; + S32 mWidth; +}; + +class LLScrollListCheck : public LLScrollListCell +{ +public: + LLScrollListCheck( LLCheckBoxCtrl* check_box, S32 width = 0); + /*virtual*/ ~LLScrollListCheck(); + virtual void drawToWidth(S32 width, const LLColor4& color, const LLColor4& highlight_color) const; + virtual S32 getWidth() const { return mWidth; } + virtual S32 getHeight() const { return 0; } + virtual void setWidth(S32 width) { mWidth = width; } + + virtual BOOL handleClick(); + virtual void setEnabled(BOOL enable) { if (mCheckBox) mCheckBox->setEnabled(enable); } + + LLCheckBoxCtrl* getCheckBox() { return mCheckBox; } + +private: + LLCheckBoxCtrl* mCheckBox; + S32 mWidth; +}; + +class LLScrollListColumn +{ +public: + // Default constructor + LLScrollListColumn() : mName(""), mSortingColumn(""), mLabel(""), mWidth(-1), mRelWidth(-1.0), mDynamicWidth(FALSE), mIndex(-1), mParentCtrl(NULL), mButton(NULL) { } + + LLScrollListColumn(LLString name, LLString label, S32 width, F32 relwidth) + : mName(name), mSortingColumn(name), mLabel(label), mWidth(width), mRelWidth(relwidth), mDynamicWidth(FALSE), mIndex(-1), mParentCtrl(NULL), mButton(NULL) { } + + LLScrollListColumn(const LLSD &sd) + { + mName = sd.get("name").asString(); + mSortingColumn = mName; + if (sd.has("sort")) + { + mSortingColumn = sd.get("sort").asString(); + } + mLabel = sd.get("label").asString(); + if (sd.has("relwidth") && (F32)sd.get("relwidth").asReal() > 0) + { + mRelWidth = (F32)sd.get("relwidth").asReal(); + if (mRelWidth < 0) mRelWidth = 0; + if (mRelWidth > 1) mRelWidth = 1; + mDynamicWidth = FALSE; + mWidth = 0; + } + else if(sd.has("dynamicwidth") && (BOOL)sd.get("dynamicwidth").asBoolean() == TRUE) + { + mDynamicWidth = TRUE; + mRelWidth = -1; + mWidth = 0; + } + else + { + mWidth = sd.get("width").asInteger(); + mDynamicWidth = FALSE; + mRelWidth = -1; + } + mIndex = -1; + mParentCtrl = NULL; + mButton = NULL; + } + + LLString mName; + LLString mSortingColumn; + LLString mLabel; + S32 mWidth; + F32 mRelWidth; + BOOL mDynamicWidth; + S32 mIndex; + LLScrollListCtrl *mParentCtrl; + LLButton *mButton; +}; + +class LLScrollListItem +{ +public: + LLScrollListItem( BOOL enabled = TRUE, void* userdata = NULL, const LLUUID& uuid = LLUUID::null ) + : mSelected(FALSE), mEnabled( enabled ), mUserdata( userdata ), mItemValue( uuid ), mColumns() {} + LLScrollListItem( LLSD item_value, void* userdata = NULL ) + : mSelected(FALSE), mEnabled( TRUE ), mUserdata( userdata ), mItemValue( item_value ), mColumns() {} + + virtual ~LLScrollListItem(); + + void setSelected( BOOL b ) { mSelected = b; } + BOOL getSelected() const { return mSelected; } + + void setEnabled( BOOL b ); + BOOL getEnabled() const { return mEnabled; } + + void setUserdata( void* userdata ) { mUserdata = userdata; } + void* getUserdata() const { return mUserdata; } + + LLUUID getUUID() const { return mItemValue.asUUID(); } + LLSD getValue() const { return mItemValue; } + + // If width = 0, just use the width of the text. Otherwise override with + // specified width in pixels. + void addColumn( const LLString& text, const LLFontGL* font, S32 width = 0 , U8 font_style = LLFontGL::NORMAL, BOOL visible = TRUE) + { mColumns.push_back( new LLScrollListText(text, font, width, font_style, LLColor4::black, FALSE, visible) ); } + + void addColumn( LLImageGL* icon, S32 width = 0 ) + { mColumns.push_back( new LLScrollListIcon(icon, width) ); } + + void addColumn( LLCheckBoxCtrl* check, S32 width = 0 ) + { mColumns.push_back( new LLScrollListCheck(check,width) ); } + + void setNumColumns(S32 columns); + + void setColumn( S32 column, LLScrollListCell *cell ); + + S32 getNumColumns() const { return mColumns.size(); } + + LLScrollListCell *getColumn(const S32 i) const { if (i < (S32)mColumns.size()) { return mColumns[i]; } return NULL; } + + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + + LLString getContentsCSV(); + +private: + BOOL mSelected; + BOOL mEnabled; + void* mUserdata; + LLSD mItemValue; + std::vector<LLScrollListCell *> mColumns; +}; + + +class LLScrollListCtrl : public LLUICtrl, public LLEditMenuHandler, + public LLCtrlListInterface, public LLCtrlScrollInterface +{ +public: + LLScrollListCtrl( + const LLString& name, + const LLRect& rect, + void (*commit_callback)(LLUICtrl*, void*), + void* callback_userdata, + BOOL allow_multiple_selection, + BOOL draw_border = TRUE); + + virtual ~LLScrollListCtrl(); + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_SCROLL_LIST; } + virtual LLString getWidgetTag() const { return LL_SCROLL_LIST_CTRL_TAG; } + virtual LLXMLNodePtr getXML(bool save_children = true) const; + void setScrollListParameters(LLXMLNodePtr node); + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + + S32 isEmpty() const; + + void deleteAllItems() { clearRows(); } + + // Sets an array of column descriptors + void setColumnHeadings(LLSD headings); + // Numerical based sort by column function (used by LLComboBox) + void sortByColumn(U32 column, BOOL ascending); + + // LLCtrlListInterface functions + virtual S32 getItemCount() const; + // Adds a single column descriptor: ["name" : string, "label" : string, "width" : integer, "relwidth" : integer ] + virtual void addColumn(const LLSD& column, EAddPosition pos = ADD_BOTTOM); + virtual void clearColumns(); + virtual void setColumnLabel(const LLString& column, const LLString& label); + // Adds a single element, from an array of: + // "columns" => [ "column" => column name, "value" => value, "type" => type, "font" => font, "font-style" => style ], "id" => uuid + // Creates missing columns automatically. + virtual LLScrollListItem* addElement(const LLSD& value, EAddPosition pos = ADD_BOTTOM, void* userdata = NULL); + // Simple add element. Takes a single array of: + // [ "value" => value, "font" => font, "font-style" => style ] + virtual LLScrollListItem* addSimpleElement(const LLString& value, EAddPosition pos = ADD_BOTTOM, const LLSD& id = LLSD()); + virtual void clearRows(); // clears all elements + virtual void sortByColumn(LLString name, BOOL ascending); + + // These functions take and return an array of arrays of elements, as above + virtual void setValue(const LLSD& value ); + virtual LLSD getValue() const; + + LLCtrlSelectionInterface* getSelectionInterface() { return (LLCtrlSelectionInterface*)this; } + LLCtrlListInterface* getListInterface() { return (LLCtrlListInterface*)this; } + LLCtrlScrollInterface* getScrollInterface() { return (LLCtrlScrollInterface*)this; } + + // DEPRECATED: Use setSelectedByValue() below. + BOOL setCurrentByID( const LLUUID& id ) { return selectByID(id); } + virtual LLUUID getCurrentID() { return getStringUUIDSelectedItem(); } + + BOOL operateOnSelection(EOperation op); + BOOL operateOnAll(EOperation op); + + // returns FALSE if unable to set the max count so low + BOOL setMaxItemCount(S32 max_count); + + BOOL selectByID( const LLUUID& id ); // FALSE if item not found + + // Match item by value.asString(), which should work for string, integer, uuid. + // Returns FALSE if not found. + BOOL setSelectedByValue(LLSD value, BOOL selected); + + virtual BOOL isSelected(LLSD value); + + BOOL selectFirstItem(); + BOOL selectNthItem( S32 index ); + + void deleteSingleItem( S32 index ) ; + void deleteSelectedItems(); + void deselectAllItems(BOOL no_commit_on_change = FALSE); // by default, go ahead and commit on selection change + + void highlightNthItem( S32 index ); + void setDoubleClickCallback( void (*cb)(void*) ) { mOnDoubleClickCallback = cb; } + void setMaxiumumSelectCallback( void (*cb)(void*) ) { mOnMaximumSelectCallback = cb; } + + void swapWithNext(S32 index); + void swapWithPrevious(S32 index); + + void setCanSelect(BOOL can_select) { mCanSelect = can_select; } + virtual BOOL getCanSelect() const { return mCanSelect; } + + S32 getItemIndex( LLScrollListItem* item ); + S32 getItemIndex( LLUUID& item_id ); + + // "Simple" interface: use this when you're creating a list that contains only unique strings, only + // one of which can be selected at a time. + LLScrollListItem* addSimpleItem( const LLString& item_text, EAddPosition pos = ADD_BOTTOM, BOOL enabled = TRUE ); + // Add an item with an associated LLSD + LLScrollListItem* addSimpleItem(const LLString& item_text, LLSD sd, EAddPosition pos = ADD_BOTTOM, BOOL enabled = TRUE, S32 column_width = 0 ); + + BOOL selectSimpleItem( const LLString& item, BOOL case_sensitive = TRUE ); // FALSE if item not found + BOOL selectSimpleItemByPrefix(const LLString& target, BOOL case_sensitive); + BOOL selectSimpleItemByPrefix(const LLWString& target, BOOL case_sensitive); + const LLString& getSimpleSelectedItem(S32 column = 0) const; + LLSD getSimpleSelectedValue(); + + // DEPRECATED: Use LLSD versions of addSimpleItem() and getSimpleSelectedValue(). + // "StringUUID" interface: use this when you're creating a list that contains non-unique strings each of which + // has an associated, unique UUID, and only one of which can be selected at a time. + LLScrollListItem* addStringUUIDItem(const LLString& item_text, const LLUUID& id, EAddPosition pos = ADD_BOTTOM, BOOL enabled = TRUE, S32 column_width = 0); + LLUUID getStringUUIDSelectedItem(); + + // "Full" interface: use this when you're creating a list that has one or more of the following: + // * contains icons + // * contains multiple columns + // * allows multiple selection + // * has items that are not guarenteed to have unique names + // * has additional per-item data (e.g. a UUID or void* userdata) + // + // To add items using this approach, create new LLScrollListItems and LLScrollListCells. Add the + // cells (column entries) to each item, and add the item to the LLScrollListCtrl. + // + // The LLScrollListCtrl owns its items and is responsible for deleting them + // (except in the case that the addItem() call fails, in which case it is up + // to the caller to delete the item) + + // returns FALSE if item faile to be added to list, does NOT delete 'item' + // TomY TODO - Deprecate this API and remove it + BOOL addItem( LLScrollListItem* item, EAddPosition pos = ADD_BOTTOM ); + LLScrollListItem* getFirstSelected() const; + virtual S32 getFirstSelectedIndex(); + std::vector<LLScrollListItem*> getAllSelected() const; + + LLScrollListItem* getLastSelectedItem() const { return mLastSelected; } + + // iterate over all items + LLScrollListItem* getFirstData() const; + std::vector<LLScrollListItem*> getAllData() const; + + void setAllowMultipleSelection(BOOL mult ) { mAllowMultipleSelection = mult; } + + void setBgWriteableColor(const LLColor4 &c) { mBgWriteableColor = c; } + void setReadOnlyBgColor(const LLColor4 &c) { mBgReadOnlyColor = c; } + void setBgSelectedColor(const LLColor4 &c) { mBgSelectedColor = c; } + void setBgStripeColor(const LLColor4& c) { mBgStripeColor = c; } + void setFgSelectedColor(const LLColor4 &c) { mFgSelectedColor = c; } + void setFgUnselectedColor(const LLColor4 &c){ mFgUnselectedColor = c; } + void setHighlightedColor(const LLColor4 &c) { mHighlightedColor = c; } + void setFgDisableColor(const LLColor4 &c) { mFgDisabledColor = c; } + + void setBackgroundVisible(BOOL b) { mBackgroundVisible = b; } + void setDrawStripes(BOOL b) { mDrawStripes = b; } + void setColumnPadding(const S32 c) { mColumnPadding = c; } + void setCommitOnKeyboardMovement(BOOL b) { mCommitOnKeyboardMovement = b; } + void setCommitOnSelectionChange(BOOL b) { mCommitOnSelectionChange = b; } + void setAllowKeyboardMovement(BOOL b) { mAllowKeyboardMovement = b; } + + void setMaxSelectable(U32 max_selected) { mMaxSelectable = max_selected; } + S32 getMaxSelectable() { return mMaxSelectable; } + + + virtual S32 getScrollPos(); + virtual void setScrollPos( S32 pos ); + + S32 getSearchColumn() { return mSearchColumn; } + void setSearchColumn(S32 column) { mSearchColumn = column; } + + void clearSearchString() { mSearchString.clear(); } + + // Overridden from LLView + virtual void draw(); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask); + virtual BOOL handleHover(S32 x, S32 y, MASK mask); + virtual BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent); + virtual BOOL handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent); + virtual BOOL handleScrollWheel(S32 x, S32 y, S32 clicks); + virtual void setEnabled(BOOL enabled); + virtual void setFocus( BOOL b ); + virtual void onFocusLost(); + + virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); + virtual void arrange(S32 max_width, S32 max_height); + virtual LLRect getRequiredRect(); + static BOOL rowPreceeds(LLScrollListItem *new_row, LLScrollListItem *test_row); + + // Used "internally" by the scroll bar. + static void onScrollChange( S32 new_pos, LLScrollbar* src, void* userdata ); + + static void onClickColumn(void *userdata); + + void updateColumns(); + void updateColumnButtons(); + + void setDisplayHeading(BOOL display); + void setHeadingHeight(S32 heading_height); + void setHeadingFont(const LLFontGL* heading_font); + void setCollapseEmptyColumns(BOOL collapse); + void setIsPopup(BOOL is_popup) { mIsPopup = is_popup; } + + LLScrollListItem* hitItem(S32 x,S32 y); + virtual void scrollToShowSelected(); + + // LLEditMenuHandler functions + virtual void copy(); + virtual BOOL canCopy(); + + virtual void cut(); + virtual BOOL canCut(); + + virtual void doDelete(); + virtual BOOL canDoDelete(); + + virtual void selectAll(); + virtual BOOL canSelectAll(); + + virtual void deselect(); + virtual BOOL canDeselect(); + + void setNumDynamicColumns(int num) { mNumDynamicWidthColumns = num; } + void setTotalStaticColumnWidth(int width) { mTotalStaticColumnWidth = width; } + +protected: + void selectPrevItem(BOOL extend_selection); + void selectNextItem(BOOL extend_selection); + void drawItems(); + void updateLineHeight(); + void reportInvalidInput(); + BOOL isRepeatedChars(const LLWString& string) const; + void selectItem(LLScrollListItem* itemp, BOOL single_select = TRUE); + void deselectItem(LLScrollListItem* itemp); + void commitIfChanged(); + +protected: + S32 mCurIndex; // For get[First/Next]Data + S32 mCurSelectedIndex; // For get[First/Next]Selected + + S32 mLineHeight; // the max height of a single line + S32 mScrollLines; // how many lines we've scrolled down + S32 mPageLines; // max number of lines is it possible to see on the screen given mRect and mLineHeight + S32 mHeadingHeight; // the height of the column header buttons, if visible + U32 mMaxSelectable; + const LLFontGL* mHeadingFont; // the font to use for column head buttons, if visible + LLScrollbar* mScrollbar; + BOOL mAllowMultipleSelection; + BOOL mAllowKeyboardMovement; + BOOL mCommitOnKeyboardMovement; + BOOL mCommitOnSelectionChange; + BOOL mSelectionChanged; + BOOL mCanSelect; + BOOL mDisplayColumnButtons; + BOOL mCollapseEmptyColumns; + BOOL mIsPopup; + + typedef std::deque<LLScrollListItem *> item_list; + item_list mItemList; + + LLScrollListItem *mLastSelected; + + S32 mMaxItemCount; + + LLRect mItemListRect; + + S32 mColumnPadding; + + BOOL mBackgroundVisible; + BOOL mDrawStripes; + + LLColor4 mBgWriteableColor; + LLColor4 mBgReadOnlyColor; + LLColor4 mBgSelectedColor; + LLColor4 mBgStripeColor; + LLColor4 mFgSelectedColor; + LLColor4 mFgUnselectedColor; + LLColor4 mFgDisabledColor; + LLColor4 mHighlightedColor; + + S32 mBorderThickness; + void (*mOnDoubleClickCallback)(void* userdata); + void (*mOnMaximumSelectCallback)(void* userdata ); + + S32 mHighlightedItem; + LLViewBorder* mBorder; + + LLWString mSearchString; + LLFrameTimer mSearchTimer; + + LLString mDefaultColumn; + + S32 mSearchColumn; + S32 mNumDynamicWidthColumns; + S32 mTotalStaticColumnWidth; + + static U32 sSortColumn; + static BOOL sSortAscending; + + std::map<LLString, LLScrollListColumn> mColumns; + std::vector<LLScrollListColumn*> mColumnsIndexed; + +public: + // HACK: Did we draw one selected item this frame? + BOOL mDrewSelected; +}; + +const BOOL MULTIPLE_SELECT_YES = TRUE; +const BOOL MULTIPLE_SELECT_NO = FALSE; + +const BOOL SHOW_BORDER_YES = TRUE; +const BOOL SHOW_BORDER_NO = FALSE; + +#endif // LL_SCROLLLISTCTRL_H diff --git a/indra/llui/llslider.cpp b/indra/llui/llslider.cpp new file mode 100644 index 0000000000..2a1c2f7845 --- /dev/null +++ b/indra/llui/llslider.cpp @@ -0,0 +1,352 @@ +/** + * @file llslider.cpp + * @brief LLSlider base class + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llslider.h" +#include "llui.h" + +#include "llgl.h" +#include "llwindow.h" +#include "llfocusmgr.h" +#include "llkeyboard.h" // for the MASK constants +#include "llcontrol.h" +#include "llimagegl.h" + +const S32 THUMB_WIDTH = 8; +const S32 TRACK_HEIGHT = 6; + +LLSlider::LLSlider( + const LLString& name, + const LLRect& rect, + void (*on_commit_callback)(LLUICtrl* ctrl, void* userdata), + void* callback_userdata, + F32 initial_value, + F32 min_value, + F32 max_value, + F32 increment, + const LLString& control_name) + : + LLUICtrl( name, rect, TRUE, on_commit_callback, callback_userdata, + FOLLOWS_LEFT | FOLLOWS_TOP), + mValue( initial_value ), + mInitialValue( initial_value ), + mMinValue( min_value ), + mMaxValue( max_value ), + mIncrement( increment ), + mMouseOffset( 0 ), + mDragStartThumbRect( 0, mRect.getHeight(), THUMB_WIDTH, 0 ), + mThumbRect( 0, mRect.getHeight(), THUMB_WIDTH, 0 ), + mTrackColor( LLUI::sColorsGroup->getColor( "SliderTrackColor" ) ), + mThumbOutlineColor( LLUI::sColorsGroup->getColor( "SliderThumbOutlineColor" ) ), + mThumbCenterColor( LLUI::sColorsGroup->getColor( "SliderThumbCenterColor" ) ), + mDisabledThumbColor(LLUI::sColorsGroup->getColor( "SliderDisabledThumbColor" ) ), + mMouseDownCallback( NULL ), + mMouseUpCallback( NULL ) +{ + // prperly handle setting the starting thumb rect + // do it this way to handle both the operating-on-settings + // and standalone ways of using this + setControlName(control_name, NULL); + setValue(getValueF32()); +} + +EWidgetType LLSlider::getWidgetType() const +{ + return WIDGET_TYPE_SLIDER_BAR; +} + +LLString LLSlider::getWidgetTag() const +{ + return LL_SLIDER_TAG; +} + +void LLSlider::setValue(F32 value, BOOL from_event) +{ + value = llclamp( value, mMinValue, mMaxValue ); + + // Round to nearest increment (bias towards rounding down) + value -= mMinValue; + value += mIncrement/2.0001f; + value -= fmod(value, mIncrement); + mValue = mMinValue + value; + + if (!from_event) + { + setControlValue(mValue); + } + + F32 t = (mValue - mMinValue) / (mMaxValue - mMinValue); + + S32 left_edge = THUMB_WIDTH/2; + S32 right_edge = mRect.getWidth() - (THUMB_WIDTH/2); + + S32 x = left_edge + S32( t * (right_edge - left_edge) ); + mThumbRect.mLeft = x - (THUMB_WIDTH/2); + mThumbRect.mRight = x + (THUMB_WIDTH/2); +} + +F32 LLSlider::getValueF32() const +{ + return mValue; +} + +BOOL LLSlider::handleHover(S32 x, S32 y, MASK mask) +{ + if( gFocusMgr.getMouseCapture() == this ) + { + S32 left_edge = THUMB_WIDTH/2; + S32 right_edge = mRect.getWidth() - (THUMB_WIDTH/2); + + x += mMouseOffset; + x = llclamp( x, left_edge, right_edge ); + + F32 t = F32(x - left_edge) / (right_edge - left_edge); + setValue(t * (mMaxValue - mMinValue) + mMinValue ); + onCommit(); + + getWindow()->setCursor(UI_CURSOR_ARROW); + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" << llendl; + } + else + { + getWindow()->setCursor(UI_CURSOR_ARROW); + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (inactive)" << llendl; + } + return TRUE; +} + +BOOL LLSlider::handleMouseUp(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + if( gFocusMgr.getMouseCapture() == this ) + { + gFocusMgr.setMouseCapture( NULL, NULL ); + + if( mMouseUpCallback ) + { + mMouseUpCallback( this, mCallbackUserData ); + } + handled = TRUE; + make_ui_sound("UISndClickRelease"); + } + else + { + handled = TRUE; + } + + return handled; +} + +BOOL LLSlider::handleMouseDown(S32 x, S32 y, MASK mask) +{ + // only do sticky-focus on non-chrome widgets + if (!getIsChrome()) + { + setFocus(TRUE); + } + if( mMouseDownCallback ) + { + mMouseDownCallback( this, mCallbackUserData ); + } + + if (MASK_CONTROL & mask) // if CTRL is modifying + { + setValue(mInitialValue); + onCommit(); + } + else + { + // Find the offset of the actual mouse location from the center of the thumb. + if (mThumbRect.pointInRect(x,y)) + { + mMouseOffset = (mThumbRect.mLeft + THUMB_WIDTH/2) - x; + } + else + { + mMouseOffset = 0; + } + + // Start dragging the thumb + // No handler needed for focus lost since this class has no state that depends on it. + gFocusMgr.setMouseCapture( this, NULL ); + mDragStartThumbRect = mThumbRect; + } + make_ui_sound("UISndClick"); + + return TRUE; +} + +BOOL LLSlider::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent) +{ + BOOL handled = FALSE; + if( getVisible() && mEnabled && !called_from_parent ) + { + switch(key) + { + case KEY_UP: + case KEY_DOWN: + // eat up and down keys to be consistent + handled = TRUE; + break; + case KEY_LEFT: + setValue(getValueF32() - getIncrement()); + onCommit(); + handled = TRUE; + break; + case KEY_RIGHT: + setValue(getValueF32() + getIncrement()); + onCommit(); + handled = TRUE; + break; + default: + break; + } + } + return handled; +} + +void LLSlider::draw() +{ + if( getVisible() ) + { + // Draw background and thumb. + + // drawing solids requires texturing be disabled + LLGLSNoTexture no_texture; + + LLRect rect(mDragStartThumbRect); + + F32 opacity = mEnabled ? 1.f : 0.3f; + + // Track + + LLUUID thumb_image_id; + thumb_image_id.set(LLUI::sAssetsGroup->getString("rounded_square.tga")); + LLImageGL* thumb_imagep = LLUI::sImageProvider->getUIImageByID(thumb_image_id); + + S32 height_offset = (mRect.getHeight() - TRACK_HEIGHT) / 2; + LLRect track_rect(0, mRect.getHeight() - height_offset, mRect.getWidth(), height_offset ); + + track_rect.stretch(-1); + gl_draw_scaled_image_with_border(track_rect.mLeft, track_rect.mBottom, 16, 16, track_rect.getWidth(), track_rect.getHeight(), + thumb_imagep, mTrackColor % opacity); + //gl_rect_2d( track_rect, mThumbOutlineColor % opacity ); + + if (!thumb_imagep) + { + gl_rect_2d(mThumbRect, mThumbCenterColor, TRUE); + if (gFocusMgr.getMouseCapture() == this) + { + gl_rect_2d(mDragStartThumbRect, mThumbCenterColor % opacity, FALSE); + } + } + else if( gFocusMgr.getMouseCapture() == this ) + { + gl_draw_scaled_image_with_border(mDragStartThumbRect.mLeft, mDragStartThumbRect.mBottom, 16, 16, mDragStartThumbRect.getWidth(), mDragStartThumbRect.getHeight(), + thumb_imagep, mThumbCenterColor % 0.3f, TRUE); + + if (hasFocus()) + { + F32 lerp_amt = gFocusMgr.getFocusFlashAmt(); + LLRect highlight_rect = mThumbRect; + highlight_rect.stretch(llround(lerp(1.f, 3.f, lerp_amt))); + gl_draw_scaled_image_with_border(highlight_rect.mLeft, highlight_rect.mBottom, 16, 16, highlight_rect.getWidth(), highlight_rect.getHeight(), + thumb_imagep, gFocusMgr.getFocusColor()); + } + + + gl_draw_scaled_image_with_border(mThumbRect.mLeft, mThumbRect.mBottom, 16, 16, mThumbRect.getWidth(), mThumbRect.getHeight(), + thumb_imagep, mThumbOutlineColor, TRUE); + + //// Start Thumb + //gl_rect_2d( mDragStartThumbRect, mThumbOutlineColor % 0.3f ); + //rect.stretch(-1); + //gl_rect_2d( rect, mThumbCenterColor % 0.3f ); + + //// Thumb + //gl_rect_2d( mThumbRect, mThumbOutlineColor ); + } + else + { + if (hasFocus()) + { + F32 lerp_amt = gFocusMgr.getFocusFlashAmt(); + LLRect highlight_rect = mThumbRect; + highlight_rect.stretch(llround(lerp(1.f, 3.f, lerp_amt))); + gl_draw_scaled_image_with_border(highlight_rect.mLeft, highlight_rect.mBottom, 16, 16, highlight_rect.getWidth(), highlight_rect.getHeight(), + thumb_imagep, gFocusMgr.getFocusColor()); + } + + gl_draw_scaled_image_with_border(mThumbRect.mLeft, mThumbRect.mBottom, 16, 16, mThumbRect.getWidth(), mThumbRect.getHeight(), + thumb_imagep, mThumbCenterColor % opacity, TRUE); + //rect = mThumbRect; + + //gl_rect_2d( mThumbRect, mThumbOutlineColor % opacity ); + // + //rect.stretch(-1); + + //// Thumb + //gl_rect_2d( rect, mThumbCenterColor % opacity ); + + } + + LLUICtrl::draw(); + } +} + +// virtual +LLXMLNodePtr LLSlider::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLUICtrl::getXML(); + + node->createChild("initial_val", TRUE)->setFloatValue(getInitialValue()); + node->createChild("min_val", TRUE)->setFloatValue(getMinValue()); + node->createChild("max_val", TRUE)->setFloatValue(getMaxValue()); + node->createChild("increment", TRUE)->setFloatValue(getIncrement()); + + return node; +} + + +//static +LLView* LLSlider::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("slider_bar"); + node->getAttributeString("name", name); + + LLRect rect; + createRect(node, rect, parent, LLRect()); + + F32 initial_value = 0.f; + node->getAttributeF32("initial_val", initial_value); + + F32 min_value = 0.f; + node->getAttributeF32("min_val", min_value); + + F32 max_value = 1.f; + node->getAttributeF32("max_val", max_value); + + F32 increment = 0.1f; + node->getAttributeF32("increment", increment); + + + LLSlider* slider = new LLSlider(name, + rect, + NULL, + NULL, + initial_value, + min_value, + max_value, + increment); + + slider->initFromXML(node, parent); + + return slider; +} diff --git a/indra/llui/llslider.h b/indra/llui/llslider.h new file mode 100644 index 0000000000..a437cf2886 --- /dev/null +++ b/indra/llui/llslider.h @@ -0,0 +1,79 @@ +/** + * @file llslider.h + * @brief A simple slider with no label. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSLIDER_H +#define LL_LLSLIDER_H + +#include "lluictrl.h" +#include "v4color.h" + +class LLSlider : public LLUICtrl +{ +public: + LLSlider( + const LLString& name, + const LLRect& rect, + void (*on_commit_callback)(LLUICtrl* ctrl, void* userdata), + void* callback_userdata, + F32 initial_value, + F32 min_value, + F32 max_value, + F32 increment, + const LLString& control_name = LLString::null ); + + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + + void setValue( F32 value, BOOL from_event = FALSE ); + F32 getValueF32() const; + + virtual void setValue(const LLSD& value ) { setValue((F32)value.asReal(), TRUE); } + virtual LLSD getValue() const { return LLSD(getValueF32()); } + + virtual void setMinValue(LLSD min_value) { setMinValue((F32)min_value.asReal()); } + virtual void setMaxValue(LLSD max_value) { setMaxValue((F32)max_value.asReal()); } + + F32 getInitialValue() const { return mInitialValue; } + F32 getMinValue() const { return mMinValue; } + F32 getMaxValue() const { return mMaxValue; } + F32 getIncrement() const { return mIncrement; } + void setMinValue(F32 min_value) {mMinValue = min_value;} + void setMaxValue(F32 max_value) {mMaxValue = max_value;} + void setIncrement(F32 increment) {mIncrement = increment;} + void setMouseDownCallback( void (*cb)(LLUICtrl* ctrl, void* userdata) ) { mMouseDownCallback = cb; } + void setMouseUpCallback( void (*cb)(LLUICtrl* ctrl, void* userdata) ) { mMouseUpCallback = cb; } + + virtual BOOL handleHover(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent); + virtual void draw(); + +protected: + F32 mValue; + F32 mInitialValue; + F32 mMinValue; + F32 mMaxValue; + F32 mIncrement; + + S32 mMouseOffset; + LLRect mDragStartThumbRect; + + LLRect mThumbRect; + LLColor4 mTrackColor; + LLColor4 mThumbOutlineColor; + LLColor4 mThumbCenterColor; + LLColor4 mDisabledThumbColor; + + void (*mMouseDownCallback)(LLUICtrl* ctrl, void* userdata); + void (*mMouseUpCallback)(LLUICtrl* ctrl, void* userdata); +}; + +#endif // LL_LLSLIDER_H diff --git a/indra/llui/llsliderctrl.cpp b/indra/llui/llsliderctrl.cpp new file mode 100644 index 0000000000..6c740aa39e --- /dev/null +++ b/indra/llui/llsliderctrl.cpp @@ -0,0 +1,538 @@ +/** + * @file llsliderctrl.cpp + * @brief LLSliderCtrl base class + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llsliderctrl.h" + +#include "audioengine.h" +#include "sound_ids.h" + +#include "llmath.h" +#include "llfontgl.h" +#include "llgl.h" +#include "llkeyboard.h" +#include "lllineeditor.h" +#include "llslider.h" +#include "llstring.h" +#include "lltextbox.h" +#include "llui.h" +#include "lluiconstants.h" +#include "llcontrol.h" +#include "llfocusmgr.h" +#include "llresmgr.h" + +const U32 MAX_STRING_LENGTH = 10; + + +LLSliderCtrl::LLSliderCtrl(const LLString& name, const LLRect& rect, + const LLString& label, + const LLFontGL* font, + S32 label_width, + S32 text_left, + BOOL show_text, + BOOL can_edit_text, + void (*commit_callback)(LLUICtrl*, void*), + void* callback_user_data, + F32 initial_value, F32 min_value, F32 max_value, F32 increment, + const LLString& control_which) + : LLUICtrl(name, rect, TRUE, commit_callback, callback_user_data ), + mFont(font), + mShowText( show_text ), + mCanEditText( can_edit_text ), + mPrecision( 3 ), + mLabelBox( NULL ), + mLabelWidth( label_width ), + mValue( initial_value ), + mEditor( NULL ), + mTextBox( NULL ), + mTextEnabledColor( LLUI::sColorsGroup->getColor( "LabelTextColor" ) ), + mTextDisabledColor( LLUI::sColorsGroup->getColor( "LabelDisabledColor" ) ), + mSliderMouseUpCallback( NULL ), + mSliderMouseDownCallback( NULL ) +{ + S32 top = mRect.getHeight(); + S32 bottom = 0; + S32 left = 0; + + // Label + if( !label.empty() ) + { + if (label_width == 0) + { + label_width = font->getWidth(label); + } + LLRect label_rect( left, top, label_width, bottom ); + mLabelBox = new LLTextBox( "SliderCtrl Label", label_rect, label.c_str(), font ); + addChild(mLabelBox); + } + + S32 slider_right = mRect.getWidth(); + if( show_text ) + { + slider_right = text_left - SLIDERCTRL_SPACING; + } + + S32 slider_left = label_width ? label_width + SLIDERCTRL_SPACING : 0; + LLRect slider_rect( slider_left, top, slider_right, bottom ); + mSlider = new LLSlider( + "slider", + slider_rect, + LLSliderCtrl::onSliderCommit, this, + initial_value, min_value, max_value, increment, + control_which ); + addChild( mSlider ); + + if( show_text ) + { + LLRect text_rect( text_left, top, mRect.getWidth(), bottom ); + if( can_edit_text ) + { + mEditor = new LLLineEditor( "SliderCtrl Editor", text_rect, + "", font, + MAX_STRING_LENGTH, + &LLSliderCtrl::onEditorCommit, NULL, NULL, this, + &LLLineEditor::prevalidateFloat ); + mEditor->setFollowsLeft(); + mEditor->setFollowsBottom(); + mEditor->setFocusReceivedCallback( &LLSliderCtrl::onEditorGainFocus ); + mEditor->setIgnoreTab(TRUE); + // don't do this, as selecting the entire text is single clicking in some cases + // and double clicking in others + //mEditor->setSelectAllonFocusReceived(TRUE); + addChild(mEditor); + } + else + { + mTextBox = new LLTextBox( "SliderCtrl Text", text_rect, "", font); + mTextBox->setFollowsLeft(); + mTextBox->setFollowsBottom(); + addChild(mTextBox); + } + } + + updateText(); +} + +LLSliderCtrl::~LLSliderCtrl() +{ + // Children all cleaned up by default view destructor. +} + +// static +void LLSliderCtrl::onEditorGainFocus( LLUICtrl* caller, void *userdata ) +{ + LLSliderCtrl* self = (LLSliderCtrl*) userdata; + llassert( caller == self->mEditor ); + + self->onFocusReceived(); +} + +F32 LLSliderCtrl::getValueF32() const +{ + return mSlider->getValueF32(); +} + +void LLSliderCtrl::setValue(F32 v, BOOL from_event) +{ + mSlider->setValue( v, from_event ); + mValue = mSlider->getValueF32(); + updateText(); +} + +BOOL LLSliderCtrl::setLabelArg( const LLString& key, const LLString& text ) +{ + BOOL res = FALSE; + if (mLabelBox) + { + res = mLabelBox->setTextArg(key, text); + if (res && mLabelWidth == 0) + { + S32 label_width = mFont->getWidth(mLabelBox->getText()); + LLRect rect = mLabelBox->getRect(); + S32 prev_right = rect.mRight; + rect.mRight = rect.mLeft + label_width; + mLabelBox->setRect(rect); + + S32 delta = rect.mRight - prev_right; + rect = mSlider->getRect(); + S32 left = rect.mLeft + delta; + left = llclamp(left, 0, rect.mRight-SLIDERCTRL_SPACING); + rect.mLeft = left; + mSlider->setRect(rect); + } + } + return res; +} + +void LLSliderCtrl::clear() +{ + setValue(0.0f); + if( mEditor ) + { + mEditor->setText( "" ); + } + if( mTextBox ) + { + mTextBox->setText( "" ); + } + +} + +BOOL LLSliderCtrl::isMouseHeldDown() +{ + return gFocusMgr.getMouseCapture() == mSlider; +} + +void LLSliderCtrl::updateText() +{ + if( mEditor || mTextBox ) + { + LLLocale locale(LLLocale::USER_LOCALE); + + // Don't display very small negative values as -0.000 + F32 displayed_value = (F32)(floor(getValueF32() * pow(10, mPrecision) + 0.5) / pow(10, mPrecision)); + + LLString format = llformat("%%.%df", mPrecision); + LLString text = llformat(format.c_str(), displayed_value); + if( mEditor ) + { + mEditor->setText( text ); + } + else + { + mTextBox->setText( text ); + } + } +} + +// static +void LLSliderCtrl::onEditorCommit( LLUICtrl* caller, void *userdata ) +{ + LLSliderCtrl* self = (LLSliderCtrl*) userdata; + llassert( caller == self->mEditor ); + + BOOL success = FALSE; + F32 val = self->mValue; + F32 saved_val = self->mValue; + + LLString text = self->mEditor->getText(); + if( LLLineEditor::postvalidateFloat( text ) ) + { + LLLocale locale(LLLocale::USER_LOCALE); + val = (F32) atof( text.c_str() ); + if( self->mSlider->getMinValue() <= val && val <= self->mSlider->getMaxValue() ) + { + if( self->mValidateCallback ) + { + self->setValue( val ); // set the value temporarily so that the callback can retrieve it. + if( self->mValidateCallback( self, self->mCallbackUserData ) ) + { + success = TRUE; + } + } + else + { + self->setValue( val ); + success = TRUE; + } + } + } + + if( success ) + { + self->onCommit(); + } + else + { + if( self->getValueF32() != saved_val ) + { + self->setValue( saved_val ); + } + self->reportInvalidData(); + } + self->updateText(); +} + +// static +void LLSliderCtrl::onSliderCommit( LLUICtrl* caller, void *userdata ) +{ + LLSliderCtrl* self = (LLSliderCtrl*) userdata; + llassert( caller == self->mSlider ); + + BOOL success = FALSE; + F32 saved_val = self->mValue; + F32 new_val = self->mSlider->getValueF32(); + + if( self->mValidateCallback ) + { + self->mValue = new_val; // set the value temporarily so that the callback can retrieve it. + if( self->mValidateCallback( self, self->mCallbackUserData ) ) + { + success = TRUE; + } + } + else + { + self->mValue = new_val; + success = TRUE; + } + + if( success ) + { + self->onCommit(); + } + else + { + if( self->mValue != saved_val ) + { + self->setValue( saved_val ); + } + self->reportInvalidData(); + } + self->updateText(); +} + +void LLSliderCtrl::setEnabled(BOOL b) +{ + LLUICtrl::setEnabled( b ); + + if( mLabelBox ) + { + mLabelBox->setColor( b ? mTextEnabledColor : mTextDisabledColor ); + } + + mSlider->setEnabled( b ); + + if( mEditor ) + { + mEditor->setEnabled( b ); + } + + if( mTextBox ) + { + mTextBox->setColor( b ? mTextEnabledColor : mTextDisabledColor ); + } +} + + +void LLSliderCtrl::setTentative(BOOL b) +{ + if( mEditor ) + { + mEditor->setTentative(b); + } + LLUICtrl::setTentative(b); +} + + +void LLSliderCtrl::onCommit() +{ + setTentative(FALSE); + + if( mEditor ) + { + mEditor->setTentative(FALSE); + } + + LLUICtrl::onCommit(); +} + + +void LLSliderCtrl::setPrecision(S32 precision) +{ + if (precision < 0 || precision > 10) + { + llerrs << "LLSliderCtrl::setPrecision - precision out of range" << llendl; + return; + } + + mPrecision = precision; + updateText(); +} + +void LLSliderCtrl::setSliderMouseDownCallback( void (*slider_mousedown_callback)(LLUICtrl* caller, void* userdata) ) +{ + mSliderMouseDownCallback = slider_mousedown_callback; + mSlider->setMouseDownCallback( LLSliderCtrl::onSliderMouseDown ); +} + +// static +void LLSliderCtrl::onSliderMouseDown(LLUICtrl* caller, void* userdata) +{ + LLSliderCtrl* self = (LLSliderCtrl*) userdata; + if( self->mSliderMouseDownCallback ) + { + self->mSliderMouseDownCallback( self, self->mCallbackUserData ); + } +} + + +void LLSliderCtrl::setSliderMouseUpCallback( void (*slider_mouseup_callback)(LLUICtrl* caller, void* userdata) ) +{ + mSliderMouseUpCallback = slider_mouseup_callback; + mSlider->setMouseUpCallback( LLSliderCtrl::onSliderMouseUp ); +} + +// static +void LLSliderCtrl::onSliderMouseUp(LLUICtrl* caller, void* userdata) +{ + LLSliderCtrl* self = (LLSliderCtrl*) userdata; + if( self->mSliderMouseUpCallback ) + { + self->mSliderMouseUpCallback( self, self->mCallbackUserData ); + } +} + +void LLSliderCtrl::onTabInto() +{ + if( mEditor ) + { + mEditor->onTabInto(); + } +} + +void LLSliderCtrl::reportInvalidData() +{ + make_ui_sound("UISndBadKeystroke"); +} + +//virtual +LLString LLSliderCtrl::getControlName() const +{ + return mSlider->getControlName(); +} + +// virtual +void LLSliderCtrl::setControlName(const LLString& control_name, LLView* context) +{ + mSlider->setControlName(control_name, context); +} + +// virtual +LLXMLNodePtr LLSliderCtrl::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLUICtrl::getXML(); + + node->createChild("show_text", TRUE)->setBoolValue(mShowText); + + node->createChild("can_edit_text", TRUE)->setBoolValue(mCanEditText); + + node->createChild("decimal_digits", TRUE)->setIntValue(mPrecision); + + if (mLabelBox) + { + node->createChild("label", TRUE)->setStringValue(mLabelBox->getText()); + } + + // TomY TODO: Do we really want to export the transient state of the slider? + node->createChild("value", TRUE)->setFloatValue(mValue); + + if (mSlider) + { + node->createChild("initial_val", TRUE)->setFloatValue(mSlider->getInitialValue()); + node->createChild("min_val", TRUE)->setFloatValue(mSlider->getMinValue()); + node->createChild("max_val", TRUE)->setFloatValue(mSlider->getMaxValue()); + node->createChild("increment", TRUE)->setFloatValue(mSlider->getIncrement()); + } + addColorXML(node, mTextEnabledColor, "text_enabled_color", "LabelTextColor"); + addColorXML(node, mTextDisabledColor, "text_disabled_color", "LabelDisabledColor"); + + return node; +} + +LLView* LLSliderCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("slider"); + node->getAttributeString("name", name); + + LLString label; + node->getAttributeString("label", label); + + LLRect rect; + createRect(node, rect, parent, LLRect()); + + LLFontGL* font = LLView::selectFont(node); + + // HACK: Font might not be specified. + if (!font) + { + font = LLFontGL::sSansSerifSmall; + } + + S32 label_width = 0; + node->getAttributeS32("label_width", label_width); + + BOOL show_text = TRUE; + node->getAttributeBOOL("show_text", show_text); + + BOOL can_edit_text = FALSE; + node->getAttributeBOOL("can_edit_text", can_edit_text); + + F32 initial_value = 0.f; + node->getAttributeF32("initial_val", initial_value); + + F32 min_value = 0.f; + node->getAttributeF32("min_val", min_value); + + F32 max_value = 1.f; + node->getAttributeF32("max_val", max_value); + + F32 increment = 0.1f; + node->getAttributeF32("increment", increment); + + U32 precision = 3; + node->getAttributeU32("decimal_digits", precision); + + S32 text_left = 0; + if (show_text) + { + // calculate the size of the text box (log max_value is number of digits - 1 so plus 1) + if ( max_value ) + text_left = font->getWidth("0") * ( static_cast < S32 > ( log10 ( max_value ) ) + precision + 1 ); + + if ( increment < 1.0f ) + text_left += font->getWidth("."); // (mostly) take account of decimal point in value + + if ( min_value < 0.0f || max_value < 0.0f ) + text_left += font->getWidth("-"); // (mostly) take account of minus sign + + // padding to make things look nicer + text_left += 8; + } + + LLUICtrlCallback callback = NULL; + + if (label.empty()) + { + label.assign(node->getTextContents()); + } + + LLSliderCtrl* slider = new LLSliderCtrl(name, + rect, + label, + font, + label_width, + rect.getWidth() - text_left, + show_text, + can_edit_text, + callback, + NULL, + initial_value, + min_value, + max_value, + increment); + + slider->setPrecision(precision); + + slider->initFromXML(node, parent); + + slider->updateText(); + + return slider; +} diff --git a/indra/llui/llsliderctrl.h b/indra/llui/llsliderctrl.h new file mode 100644 index 0000000000..2185e42eb1 --- /dev/null +++ b/indra/llui/llsliderctrl.h @@ -0,0 +1,124 @@ +/** + * @file llsliderctrl.h + * @brief LLSliderCtrl base class + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSLIDERCTRL_H +#define LL_LLSLIDERCTRL_H + +#include "lluictrl.h" +#include "v4color.h" +#include "llslider.h" +#include "lltextbox.h" +#include "llrect.h" + +// +// Constants +// +const S32 SLIDERCTRL_SPACING = 4; // space between label, slider, and text +const S32 SLIDERCTRL_HEIGHT = 16; + +// +// Classes +// +class LLFontGL; +class LLLineEditor; +class LLSlider; + + +class LLSliderCtrl : public LLUICtrl +{ +public: + LLSliderCtrl(const LLString& name, + const LLRect& rect, + const LLString& label, + const LLFontGL* font, + S32 slider_left, + S32 text_left, + BOOL show_text, + BOOL can_edit_text, + void (*commit_callback)(LLUICtrl*, void*), + void* callback_userdata, + F32 initial_value, F32 min_value, F32 max_value, F32 increment, + const LLString& control_which = LLString::null ); + + virtual ~LLSliderCtrl(); + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_SLIDER; } + virtual LLString getWidgetTag() const { return LL_SLIDER_CTRL_TAG; } + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + + F32 getValueF32() const; + void setValue(F32 v, BOOL from_event = FALSE); + + virtual void setValue(const LLSD& value ) { setValue((F32)value.asReal(), TRUE); } + virtual LLSD getValue() const { return LLSD(getValueF32()); } + virtual BOOL setLabelArg( const LLString& key, const LLString& text ); + + virtual void setMinValue(LLSD min_value) { setMinValue((F32)min_value.asReal()); } + virtual void setMaxValue(LLSD max_value) { setMaxValue((F32)max_value.asReal()); } + + BOOL isMouseHeldDown(); + + virtual void setEnabled( BOOL b ); + virtual void clear(); + virtual void setPrecision(S32 precision); + void setMinValue(F32 min_value) {mSlider->setMinValue(min_value);} + void setMaxValue(F32 max_value) {mSlider->setMaxValue(max_value);} + void setIncrement(F32 increment) {mSlider->setIncrement(increment);} + + F32 getMinValue() { return mSlider->getMinValue(); } + F32 getMaxValue() { return mSlider->getMaxValue(); } + + void setLabel(const LLString& label) { if (mLabelBox) mLabelBox->setText(label); } + void setLabelColor(const LLColor4& c) { mTextEnabledColor = c; } + void setDisabledLabelColor(const LLColor4& c) { mTextDisabledColor = c; } + + void setSliderMouseDownCallback( void (*slider_mousedown_callback)(LLUICtrl* caller, void* userdata) ); + void setSliderMouseUpCallback( void (*slider_mouseup_callback)(LLUICtrl* caller, void* userdata) ); + + virtual void onTabInto(); + + virtual void setTentative(BOOL b); // marks value as tentative + virtual void onCommit(); // mark not tentative, then commit + + virtual void setControlName(const LLString& control_name, LLView* context); + virtual LLString getControlName() const; + + static void onSliderCommit(LLUICtrl* caller, void* userdata); + static void onSliderMouseDown(LLUICtrl* caller,void* userdata); + static void onSliderMouseUp(LLUICtrl* caller,void* userdata); + + static void onEditorCommit(LLUICtrl* caller, void* userdata); + static void onEditorGainFocus(LLUICtrl* caller, void *userdata); + static void onEditorChangeFocus(LLUICtrl* caller, S32 direction, void *userdata); + +private: + void updateText(); + void reportInvalidData(); + +private: + const LLFontGL* mFont; + BOOL mShowText; + BOOL mCanEditText; + + S32 mPrecision; + LLTextBox* mLabelBox; + S32 mLabelWidth; + + F32 mValue; + LLSlider* mSlider; + LLLineEditor* mEditor; + LLTextBox* mTextBox; + + LLColor4 mTextEnabledColor; + LLColor4 mTextDisabledColor; + + void (*mSliderMouseUpCallback)( LLUICtrl* ctrl, void* userdata ); + void (*mSliderMouseDownCallback)( LLUICtrl* ctrl, void* userdata ); +}; + +#endif // LL_LLSLIDERCTRL_H diff --git a/indra/llui/llspinctrl.cpp b/indra/llui/llspinctrl.cpp new file mode 100644 index 0000000000..332372011e --- /dev/null +++ b/indra/llui/llspinctrl.cpp @@ -0,0 +1,509 @@ +/** + * @file llspinctrl.cpp + * @brief LLSpinCtrl base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llspinctrl.h" + +#include "llgl.h" +#include "llui.h" +#include "lluiconstants.h" + +#include "llstring.h" +#include "llfontgl.h" +#include "lllineeditor.h" +#include "llbutton.h" +#include "lltextbox.h" +#include "llkeyboard.h" +#include "llmath.h" +#include "sound_ids.h" +#include "audioengine.h" +#include "llcontrol.h" +#include "llfocusmgr.h" +#include "llresmgr.h" + +const U32 MAX_STRING_LENGTH = 32; + + +LLSpinCtrl::LLSpinCtrl( const LLString& name, const LLRect& rect, const LLString& label, const LLFontGL* font, + void (*commit_callback)(LLUICtrl*, void*), + void* callback_user_data, + F32 initial_value, F32 min_value, F32 max_value, F32 increment, + const LLString& control_name, + S32 label_width) + : + LLUICtrl(name, rect, TRUE, commit_callback, callback_user_data, FOLLOWS_LEFT | FOLLOWS_TOP ), + mValue( initial_value ), + mInitialValue( initial_value ), + mMaxValue( max_value ), + mMinValue( min_value ), + mIncrement( increment ), + mPrecision( 3 ), + mLabelBox( NULL ), + mTextEnabledColor( LLUI::sColorsGroup->getColor( "LabelTextColor" ) ), + mTextDisabledColor( LLUI::sColorsGroup->getColor( "LabelDisabledColor" ) ), + mbHasBeenSet( FALSE ) +{ + S32 top = mRect.getHeight(); + S32 bottom = top - 2 * SPINCTRL_BTN_HEIGHT; + S32 centered_top = top; + S32 centered_bottom = bottom; + S32 btn_left = 0; + + // Label + if( !label.empty() ) + { + LLRect label_rect( 0, centered_top, label_width, centered_bottom ); + mLabelBox = new LLTextBox( "SpinCtrl Label", label_rect, label.c_str(), font ); + addChild(mLabelBox); + + btn_left += label_rect.mRight + SPINCTRL_SPACING; + } + + S32 btn_right = btn_left + SPINCTRL_BTN_WIDTH; + + // Spin buttons + LLRect up_rect( btn_left, top, btn_right, top - SPINCTRL_BTN_HEIGHT ); + LLString out_id = "UIImgBtnSpinUpOutUUID"; + LLString in_id = "UIImgBtnSpinUpInUUID"; + mUpBtn = new LLButton( + "SpinCtrl Up", up_rect, + out_id, + in_id, + "", + &LLSpinCtrl::onUpBtn, this, LLFontGL::sSansSerif ); + mUpBtn->setFollowsLeft(); + mUpBtn->setFollowsBottom(); + mUpBtn->setHeldDownCallback( &LLSpinCtrl::onUpBtn ); + mUpBtn->setTabStop(FALSE); + addChild(mUpBtn); + + LLRect down_rect( btn_left, top - SPINCTRL_BTN_HEIGHT, btn_right, bottom ); + out_id = "UIImgBtnSpinDownOutUUID"; + in_id = "UIImgBtnSpinDownInUUID"; + mDownBtn = new LLButton( + "SpinCtrl Down", down_rect, + out_id, + in_id, + "", + &LLSpinCtrl::onDownBtn, this, LLFontGL::sSansSerif ); + mDownBtn->setFollowsLeft(); + mDownBtn->setFollowsBottom(); + mDownBtn->setHeldDownCallback( &LLSpinCtrl::onDownBtn ); + mDownBtn->setTabStop(FALSE); + addChild(mDownBtn); + + LLRect editor_rect( btn_right + 1, centered_top, mRect.getWidth(), centered_bottom ); + mEditor = new LLLineEditor( "SpinCtrl Editor", editor_rect, "", font, + MAX_STRING_LENGTH, + &LLSpinCtrl::onEditorCommit, NULL, NULL, this, + &LLLineEditor::prevalidateFloat ); + mEditor->setFollowsLeft(); + mEditor->setFollowsBottom(); + mEditor->setFocusReceivedCallback( &LLSpinCtrl::onEditorGainFocus ); + //RN: this seems to be a BAD IDEA, as it makes the editor behavior different when it has focus + // than when it doesn't. Instead, if you always have to double click to select all the text, + // it's easier to understand + //mEditor->setSelectAllonFocusReceived(TRUE); + mEditor->setIgnoreTab(TRUE); + addChild(mEditor); + + updateEditor(); + setSpanChildren( TRUE ); +} + +LLSpinCtrl::~LLSpinCtrl() +{ + // Children all cleaned up by default view destructor. +} + + +// static +void LLSpinCtrl::onUpBtn( void *userdata ) +{ + LLSpinCtrl* self = (LLSpinCtrl*) userdata; + if( self->getEnabled() ) + { + // use getValue()/setValue() to force reload from/to control + F32 val = (F32)self->getValue().asReal() + self->mIncrement; + val = llmin( val, self->mMaxValue ); + + if( self->mValidateCallback ) + { + F32 saved_val = (F32)self->getValue().asReal(); + self->setValue(val); + if( !self->mValidateCallback( self, self->mCallbackUserData ) ) + { + self->setValue( saved_val ); + self->reportInvalidData(); + self->updateEditor(); + return; + } + } + else + { + self->setValue(val); + } + + self->updateEditor(); + self->onCommit(); + } +} + +// static +void LLSpinCtrl::onDownBtn( void *userdata ) +{ + LLSpinCtrl* self = (LLSpinCtrl*) userdata; + + if( self->getEnabled() ) + { + F32 val = (F32)self->getValue().asReal() - self->mIncrement; + val = llmax( val, self->mMinValue ); + + if( self->mValidateCallback ) + { + F32 saved_val = (F32)self->getValue().asReal(); + self->setValue(val); + if( !self->mValidateCallback( self, self->mCallbackUserData ) ) + { + self->setValue( saved_val ); + self->reportInvalidData(); + self->updateEditor(); + return; + } + } + else + { + self->setValue(val); + } + + self->updateEditor(); + self->onCommit(); + } +} + +// static +void LLSpinCtrl::onEditorGainFocus( LLUICtrl* caller, void *userdata ) +{ + LLSpinCtrl* self = (LLSpinCtrl*) userdata; + llassert( caller == self->mEditor ); + + self->onFocusReceived(); +} + +void LLSpinCtrl::setValue(const LLSD& value ) +{ + F32 v = (F32)value.asReal(); + if (mValue != v || !mbHasBeenSet) + { + mbHasBeenSet = TRUE; + mValue = v; + + if (!mEditor->hasFocus()) + { + updateEditor(); + } + } +} + +LLSD LLSpinCtrl::getValue() const +{ + return mValue; +} + +void LLSpinCtrl::clear() +{ + setValue(mMinValue); + mEditor->clear(); + mbHasBeenSet = FALSE; +} + + +void LLSpinCtrl::updateEditor() +{ + LLLocale locale(LLLocale::USER_LOCALE); + + // Don't display very small negative values as -0.000 + F32 displayed_value = (F32)floor(getValue().asReal() * pow(10, mPrecision) + 0.5) / (F32)pow(10, mPrecision); + +// if( S32( displayed_value * pow( 10, mPrecision ) ) == 0 ) +// { +// displayed_value = 0.f; +// } + + LLString format = llformat("%%.%df", mPrecision); + LLString text = llformat(format.c_str(), displayed_value); + mEditor->setText( text ); +} + +void LLSpinCtrl::onEditorCommit( LLUICtrl* caller, void *userdata ) +{ + BOOL success = FALSE; + + LLSpinCtrl* self = (LLSpinCtrl*) userdata; + llassert( caller == self->mEditor ); + + LLString text = self->mEditor->getText(); + if( LLLineEditor::postvalidateFloat( text ) ) + { + LLLocale locale(LLLocale::USER_LOCALE); + F32 val = (F32) atof(text.c_str()); + + if (val < self->mMinValue) val = self->mMinValue; + if (val > self->mMaxValue) val = self->mMaxValue; + + if( self->mValidateCallback ) + { + F32 saved_val = self->mValue; + self->mValue = val; + if( self->mValidateCallback( self, self->mCallbackUserData ) ) + { + success = TRUE; + self->onCommit(); + } + else + { + self->mValue = saved_val; + } + } + else + { + self->mValue = val; + self->onCommit(); + success = TRUE; + } + } + self->updateEditor(); + + if( !success ) + { + self->reportInvalidData(); + } +} + + +void LLSpinCtrl::forceEditorCommit() +{ + onEditorCommit(mEditor, this); +} + + +void LLSpinCtrl::setFocus(BOOL b) +{ + LLUICtrl::setFocus( b ); + mEditor->setFocus( b ); +} + +void LLSpinCtrl::setEnabled(BOOL b) +{ + LLUICtrl::setEnabled( b ); + mEditor->setEnabled( b ); +} + + +void LLSpinCtrl::setTentative(BOOL b) +{ + mEditor->setTentative(b); + LLUICtrl::setTentative(b); +} + + +BOOL LLSpinCtrl::isMouseHeldDown() +{ + return + gFocusMgr.getMouseCapture() == mDownBtn || + gFocusMgr.getMouseCapture() == mUpBtn; +} + +void LLSpinCtrl::onCommit() +{ + setTentative(FALSE); + + setControlValue(mValue); + + LLUICtrl::onCommit(); +} + + +void LLSpinCtrl::setPrecision(S32 precision) +{ + if (precision < 0 || precision > 10) + { + llerrs << "LLSpinCtrl::setPrecision - precision out of range" << llendl; + return; + } + + mPrecision = precision; + updateEditor(); +} + +void LLSpinCtrl::setLabel(const LLString& label) +{ + if (mLabelBox) + { + mLabelBox->setText(label); + } + else + { + llwarns << "Attempting to set label on LLSpinCtrl constructed without one " << getName() << llendl; + } +} + +void LLSpinCtrl::onTabInto() +{ + mEditor->onTabInto(); +} + + +void LLSpinCtrl::reportInvalidData() +{ + make_ui_sound("UISndBadKeystroke"); +} + +void LLSpinCtrl::draw() +{ + if( mLabelBox ) + { + mLabelBox->setColor( mEnabled ? mTextEnabledColor : mTextDisabledColor ); + } + LLUICtrl::draw(); +} + + +BOOL LLSpinCtrl::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + if( mEnabled ) + { + if( clicks > 0 ) + { + while( clicks-- ) + { + LLSpinCtrl::onDownBtn(this); + } + } + else + while( clicks++ ) + { + LLSpinCtrl::onUpBtn(this); + } + } + + return TRUE; +} + +BOOL LLSpinCtrl::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent) +{ + if (mEditor->hasFocus()) + { + if(key == KEY_ESCAPE) + { + // text editors don't support revert normally (due to user confusion) + // but not allowing revert on a spinner seems dangerous + updateEditor(); + mEditor->setFocus(FALSE); + return TRUE; + } + if(key == KEY_UP) + { + LLSpinCtrl::onUpBtn(this); + return TRUE; + } + if(key == KEY_DOWN) + { + LLSpinCtrl::onDownBtn(this); + return TRUE; + } + } + return FALSE; +} + +// virtual +LLXMLNodePtr LLSpinCtrl::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLUICtrl::getXML(); + + node->createChild("decimal_digits", TRUE)->setIntValue(mPrecision); + + if (mLabelBox) + { + node->createChild("label", TRUE)->setStringValue(mLabelBox->getText()); + + node->createChild("label_width", TRUE)->setIntValue(mLabelBox->getRect().getWidth()); + } + + node->createChild("initial_val", TRUE)->setFloatValue(mInitialValue); + + node->createChild("min_val", TRUE)->setFloatValue(mMinValue); + + node->createChild("max_val", TRUE)->setFloatValue(mMaxValue); + + node->createChild("increment", TRUE)->setFloatValue(mIncrement); + + addColorXML(node, mTextEnabledColor, "text_enabled_color", "LabelTextColor"); + addColorXML(node, mTextDisabledColor, "text_disabled_color", "LabelDisabledColor"); + + return node; +} + +LLView* LLSpinCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("spinner"); + node->getAttributeString("name", name); + + LLString label; + node->getAttributeString("label", label); + + LLRect rect; + createRect(node, rect, parent, LLRect()); + + LLFontGL* font = LLView::selectFont(node); + + F32 initial_value = 0.f; + node->getAttributeF32("initial_val", initial_value); + + F32 min_value = 0.f; + node->getAttributeF32("min_val", min_value); + + F32 max_value = 1.f; + node->getAttributeF32("max_val", max_value); + + F32 increment = 0.1f; + node->getAttributeF32("increment", increment); + + U32 precision = 3; + node->getAttributeU32("decimal_digits", precision); + + S32 label_width = llmin(40, rect.getWidth() - 40); + node->getAttributeS32("label_width", label_width); + + LLUICtrlCallback callback = NULL; + + if(label.empty()) + { + label.assign( node->getValue() ); + } + + LLSpinCtrl* spinner = new LLSpinCtrl(name, + rect, + label, + font, + callback, + NULL, + initial_value, + min_value, + max_value, + increment, + "", + label_width); + + spinner->setPrecision(precision); + + spinner->initFromXML(node, parent); + + return spinner; +} diff --git a/indra/llui/llspinctrl.h b/indra/llui/llspinctrl.h new file mode 100644 index 0000000000..d6ccd4d6bf --- /dev/null +++ b/indra/llui/llspinctrl.h @@ -0,0 +1,121 @@ +/** + * @file llspinctrl.h + * @brief LLSpinCtrl base class + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSPINCTRL_H +#define LL_LLSPINCTRL_H + + +#include "stdtypes.h" +#include "lluictrl.h" +#include "v4color.h" +#include "llrect.h" + +// +// Constants +// +const S32 SPINCTRL_BTN_HEIGHT = 8; +const S32 SPINCTRL_BTN_WIDTH = 16; +const S32 SPINCTRL_SPACING = 2; // space between label right and button left +const S32 SPINCTRL_HEIGHT = 2 * SPINCTRL_BTN_HEIGHT; +const S32 SPINCTRL_DEFAULT_LABEL_WIDTH = 10; + +// +// Classes +// +class LLFontGL; +class LLButton; +class LLTextBox; +class LLLineEditor; + + +class LLSpinCtrl +: public LLUICtrl +{ +public: + LLSpinCtrl(const LLString& name, const LLRect& rect, + const LLString& label, + const LLFontGL* font, + void (*commit_callback)(LLUICtrl*, void*), + void* callback_userdata, + F32 initial_value, F32 min_value, F32 max_value, F32 increment, + const LLString& control_name = LLString(), + S32 label_width = SPINCTRL_DEFAULT_LABEL_WIDTH ); + + virtual ~LLSpinCtrl(); + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_SPINNER; } + virtual LLString getWidgetTag() const { return LL_SPIN_CTRL_TAG; } + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + + virtual void setValue(const LLSD& value ); + virtual LLSD getValue() const; + F32 get() { return (F32)getValue().asReal(); } + void set(F32 value) { setValue(value); } + + virtual void setMinValue(LLSD min_value) { setMinValue((F32)min_value.asReal()); } + virtual void setMaxValue(LLSD max_value) { setMaxValue((F32)max_value.asReal()); } + + BOOL isMouseHeldDown(); + + virtual void setEnabled( BOOL b ); + virtual void setFocus( BOOL b ); + virtual void clear(); + virtual void setPrecision(S32 precision); + virtual void setMinValue(F32 min) { mMinValue = min; } + virtual void setMaxValue(F32 max) { mMaxValue = max; } + virtual void setIncrement(F32 inc) { mIncrement = inc; } + + void setLabel(const LLString& label); + void setLabelColor(const LLColor4& c) { mTextEnabledColor = c; } + void setDisabledLabelColor(const LLColor4& c) { mTextDisabledColor = c; } + + virtual void onTabInto(); + + virtual void setTentative(BOOL b); // marks value as tentative + virtual void onCommit(); // mark not tentative, then commit + + void forceEditorCommit(); // for commit on external button + + virtual BOOL handleScrollWheel(S32 x,S32 y,S32 clicks); + virtual BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent); + + virtual void draw(); + + static void onEditorCommit(LLUICtrl* caller, void* userdata); + static void onEditorGainFocus(LLUICtrl* caller, void *userdata); + static void onEditorChangeFocus(LLUICtrl* caller, S32 direction, void *userdata); + + static void onUpBtn(void *userdata); + static void onDownBtn(void *userdata); + +protected: + void updateEditor(); + void reportInvalidData(); + +protected: + + F32 mValue; + F32 mInitialValue; + F32 mMaxValue; + F32 mMinValue; + F32 mIncrement; + + S32 mPrecision; + LLTextBox* mLabelBox; + + LLLineEditor* mEditor; + LLColor4 mTextEnabledColor; + LLColor4 mTextDisabledColor; + + LLButton* mUpBtn; + LLButton* mDownBtn; + + BOOL mbHasBeenSet; +}; + +#endif // LL_LLSPINCTRL_H diff --git a/indra/llui/llstyle.cpp b/indra/llui/llstyle.cpp new file mode 100644 index 0000000000..e03dd987d7 --- /dev/null +++ b/indra/llui/llstyle.cpp @@ -0,0 +1,223 @@ +/** + * @file llstyle.cpp + * @brief Text style class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llstyle.h" +#include "llstring.h" +#include "llui.h" + +//#include "llviewerimagelist.h" + +LLStyle::LLStyle() +{ + init(TRUE, LLColor4(0,0,0,1),""); +} + +LLStyle::LLStyle(const LLStyle &style) +{ + if (this != &style) + { + init(style.isVisible(),style.getColor(),style.getFontString()); + if (style.isLink()) + { + setLinkHREF(style.getLinkHREF()); + } + mItalic = style.mItalic; + mBold = style.mBold; + mUnderline = style.mUnderline; + mDropShadow = style.mDropShadow; + mImageHeight = style.mImageHeight; + mImageWidth = style.mImageWidth; + mImagep = style.mImagep; + mIsEmbeddedItem = style.mIsEmbeddedItem; + } + else + { + init(TRUE, LLColor4(0,0,0,1),""); + } +} + +LLStyle::LLStyle(BOOL is_visible, const LLColor4 &color, const LLString& font_name) +{ + init(is_visible, color, font_name); +} + +LLStyle::~LLStyle() +{ + free(); +} + +void LLStyle::init(BOOL is_visible, const LLColor4 &color, const LLString& font_name) +{ + mVisible = is_visible; + mColor = color; + setFontName(font_name); + setLinkHREF(""); + mItalic = FALSE; + mBold = FALSE; + mUnderline = FALSE; + mDropShadow = FALSE; + mImageHeight = 0; + mImageWidth = 0; + mIsEmbeddedItem = FALSE; +} + +void LLStyle::free() +{ +} + +LLFONT_ID LLStyle::getFontID() const +{ + return mFontID; +} + +// Copy assignment +LLStyle &LLStyle::operator=(const LLStyle &rhs) +{ + if (this != &rhs) + { + setVisible(rhs.isVisible()); + setColor(rhs.getColor()); + this->mFontName = rhs.getFontString(); + this->mLink = rhs.getLinkHREF(); + mImagep = rhs.mImagep; + mImageHeight = rhs.mImageHeight; + mImageWidth = rhs.mImageWidth; + mItalic = rhs.mItalic; + mBold = rhs.mBold; + mUnderline = rhs.mUnderline; + mDropShadow = rhs.mUnderline; + mIsEmbeddedItem = rhs.mIsEmbeddedItem; + } + + return *this; +} + +// Compare +bool LLStyle::operator==(const LLStyle &rhs) const +{ + if ((mVisible != rhs.isVisible()) + || (mColor != rhs.getColor()) + || (mFontName != rhs.getFontString()) + || (mLink != rhs.getLinkHREF()) + || (mImagep != rhs.mImagep) + || (mImageHeight != rhs.mImageHeight) + || (mImageWidth != rhs.mImageWidth) + || (mItalic != rhs.mItalic) + || (mBold != rhs.mBold) + || (mUnderline != rhs.mUnderline) + || (mDropShadow != rhs.mDropShadow) + || (mIsEmbeddedItem != rhs.mIsEmbeddedItem) + ) + { + return FALSE; + } + return TRUE; +} + +bool LLStyle::operator!=(const LLStyle& rhs) const +{ + return !(*this == rhs); +} + + +const LLColor4& LLStyle::getColor() const +{ + return(mColor); +} + +void LLStyle::setColor(const LLColor4 &color) +{ + mColor = color; +} + +const LLString& LLStyle::getFontString() const +{ + return mFontName; +} + +void LLStyle::setFontName(const LLString& fontname) +{ + mFontName = fontname; + + LLString fontname_lc = fontname; + LLString::toLower(fontname_lc); + + mFontID = LLFONT_OCRA; // default + + if ((fontname_lc == "sansserif") || (fontname_lc == "sans-serif")) + { + mFontID = LLFONT_SANSSERIF; + } + else if ((fontname_lc == "serif")) + { + mFontID = LLFONT_SMALL; + } + else if ((fontname_lc == "sansserifbig")) + { + mFontID = LLFONT_SANSSERIF_BIG; + } + else if (fontname_lc == "small") + { + mFontID = LLFONT_SANSSERIF_SMALL; + } +} + +const LLString& LLStyle::getLinkHREF() const +{ + return mLink; +} + +void LLStyle::setLinkHREF(const LLString& href) +{ + mLink = href; +} + +BOOL LLStyle::isLink() const +{ + return mLink.size(); +} + +BOOL LLStyle::isVisible() const +{ + return mVisible; +} + +void LLStyle::setVisible(BOOL is_visible) +{ + mVisible = is_visible; +} + +LLImageGL *LLStyle::getImage() const +{ + return mImagep; +} + +void LLStyle::setImage(const LLString& src) +{ + if (src.size() < UUID_STR_LENGTH - 1) + { + return; + } + else + { + mImagep = LLUI::sImageProvider->getUIImageByID(LLUUID(src)); + } +} + +BOOL LLStyle::isImage() const +{ + return ((mImageWidth != 0) && (mImageHeight != 0)); +} + +void LLStyle::setImageSize(S32 width, S32 height) +{ + mImageWidth = width; + mImageHeight = height; +} diff --git a/indra/llui/llstyle.h b/indra/llui/llstyle.h new file mode 100644 index 0000000000..6a689dab98 --- /dev/null +++ b/indra/llui/llstyle.h @@ -0,0 +1,75 @@ +/** + * @file llstyle.h + * @brief Text style class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSTYLE_H +#define LL_LLSTYLE_H + +#include "v4color.h" +#include "llresmgr.h" +#include "llfont.h" +#include "llimagegl.h" + +class LLStyle +{ +public: + LLStyle(); + LLStyle(const LLStyle &style); + LLStyle(BOOL is_visible, const LLColor4 &color, const LLString& font_name); + + LLStyle &operator=(const LLStyle &rhs); + + virtual ~LLStyle(); + + virtual void init (BOOL is_visible, const LLColor4 &color, const LLString& font_name); + virtual void free (); + + bool operator==(const LLStyle &rhs) const; + bool operator!=(const LLStyle &rhs) const; + + virtual const LLColor4& getColor() const; + virtual void setColor(const LLColor4 &color); + + virtual BOOL isVisible() const; + virtual void setVisible(BOOL is_visible); + + virtual const LLString& getFontString() const; + virtual void setFontName(const LLString& fontname); + virtual LLFONT_ID getFontID() const; + + virtual const LLString& getLinkHREF() const; + virtual void setLinkHREF(const LLString& fontname); + virtual BOOL isLink() const; + + virtual LLImageGL *getImage() const; + virtual void setImage(const LLString& src); + virtual BOOL isImage() const; + virtual void setImageSize(S32 width, S32 height); + + BOOL getIsEmbeddedItem() const { return mIsEmbeddedItem; } + void setIsEmbeddedItem( BOOL b ) { mIsEmbeddedItem = b; } + +public: + BOOL mItalic; + BOOL mBold; + BOOL mUnderline; + BOOL mDropShadow; + S32 mImageWidth; + S32 mImageHeight; + +protected: + BOOL mVisible; + LLColor4 mColor; + LLString mFontName; + LLFONT_ID mFontID; + LLString mLink; + LLPointer<LLImageGL> mImagep; + + BOOL mIsEmbeddedItem; +}; + +#endif // LL_LLSTYLE_H diff --git a/indra/llui/lltabcontainer.cpp b/indra/llui/lltabcontainer.cpp new file mode 100644 index 0000000000..fd85dbb2f4 --- /dev/null +++ b/indra/llui/lltabcontainer.cpp @@ -0,0 +1,1469 @@ +/** + * @file lltabcontainer.cpp + * @brief LLTabContainerCommon base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "lltabcontainer.h" + +#include "llfocusmgr.h" +#include "llfontgl.h" +#include "llgl.h" + +#include "llbutton.h" +#include "llrect.h" +#include "llpanel.h" +#include "llresmgr.h" +#include "llkeyboard.h" +#include "llresizehandle.h" +#include "llui.h" +#include "lltextbox.h" +#include "llcontrol.h" +#include "llcriticaldamp.h" +#include "lluictrlfactory.h" + +#include "lltabcontainervertical.h" + +#include "llglheaders.h" + +const F32 SCROLL_STEP_TIME = 0.4f; +const S32 TAB_PADDING = 15; +const S32 TABCNTR_TAB_MIN_WIDTH = 60; +const S32 TABCNTR_TAB_MAX_WIDTH = 150; +const S32 TABCNTR_TAB_PARTIAL_WIDTH = 12; // When tabs are parially obscured, how much can you still see. +const S32 TABCNTR_TAB_HEIGHT = 16; +const S32 TABCNTR_ARROW_BTN_SIZE = 16; +const S32 TABCNTR_BUTTON_PANEL_OVERLAP = 1; // how many pixels the tab buttons and tab panels overlap. +const S32 TABCNTR_TAB_H_PAD = 4; + + +LLTabContainerCommon::LLTabContainerCommon( + const LLString& name, const LLRect& rect, + TabPosition pos, + void(*close_callback)(void*), void* callback_userdata, + BOOL bordered ) + : + LLPanel(name, rect, bordered), + mCurrentTabIdx(-1), + mScrolled(FALSE), + mScrollPos(0), + mScrollPosPixels(0), + mMaxScrollPos(0), + mCloseCallback( close_callback ), + mCallbackUserdata( callback_userdata ), + mTitleBox(NULL), + mTopBorderHeight(LLPANEL_BORDER_WIDTH), + mTabPosition(pos) +{ + setMouseOpaque(FALSE); +} + + +LLTabContainerCommon::LLTabContainerCommon( + const LLString& name, + const LLString& rect_control, + TabPosition pos, + void(*close_callback)(void*), void* callback_userdata, + BOOL bordered ) + : + LLPanel(name, rect_control, bordered), + mCurrentTabIdx(-1), + mScrolled(FALSE), + mScrollPos(0), + mScrollPosPixels(0), + mMaxScrollPos(0), + mCloseCallback( close_callback ), + mCallbackUserdata( callback_userdata ), + mTitleBox(NULL), + mTopBorderHeight(LLPANEL_BORDER_WIDTH), + mTabPosition(pos) +{ + setMouseOpaque(FALSE); +} + + +LLTabContainerCommon::~LLTabContainerCommon() +{ + std::for_each(mTabList.begin(), mTabList.end(), DeletePointer()); +} + +LLView* LLTabContainerCommon::getChildByName(const LLString& name, BOOL recurse) const +{ + tuple_list_t::const_iterator itor; + for (itor = mTabList.begin(); itor != mTabList.end(); ++itor) + { + LLPanel *panel = (*itor)->mTabPanel; + if (panel->getName() == name) + { + return panel; + } + } + if (recurse) + { + for (itor = mTabList.begin(); itor != mTabList.end(); ++itor) + { + LLPanel *panel = (*itor)->mTabPanel; + LLView *child = panel->getChildByName(name, recurse); + if (child) + { + return child; + } + } + } + return LLView::getChildByName(name, recurse); +} + +void LLTabContainerCommon::addPlaceholder(LLPanel* child, const LLString& label) +{ + addTabPanel(child, label, FALSE, NULL, NULL, 0, TRUE); +} + +void LLTabContainerCommon::removeTabPanel(LLPanel* child) +{ + BOOL has_focus = gFocusMgr.childHasKeyboardFocus(this); + + // If the tab being deleted is the selected one, select a different tab. + for(std::vector<LLTabTuple*>::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + if( tuple->mTabPanel == child ) + { + removeChild( tuple->mButton ); + delete tuple->mButton; + + removeChild( tuple->mTabPanel ); +// delete tuple->mTabPanel; + + mTabList.erase( iter ); + delete tuple; + + break; + } + } + if (mCurrentTabIdx >= (S32)mTabList.size()) + { + mCurrentTabIdx = mTabList.size()-1; + } + selectTab(mCurrentTabIdx); + if (has_focus) + { + LLPanel* panelp = getPanelByIndex(mCurrentTabIdx); + if (panelp) + { + panelp->setFocus(TRUE); + } + } + + updateMaxScrollPos(); +} + +void LLTabContainerCommon::deleteAllTabs() +{ + // Remove all the tab buttons and delete them. Also, unlink all the child panels. + for(std::vector<LLTabTuple*>::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + + removeChild( tuple->mButton ); + delete tuple->mButton; + + removeChild( tuple->mTabPanel ); +// delete tuple->mTabPanel; + } + + // Actually delete the tuples themselves + std::for_each(mTabList.begin(), mTabList.end(), DeletePointer()); + mTabList.clear(); + + // And there isn't a current tab any more + mCurrentTabIdx = -1; +} + + +LLPanel* LLTabContainerCommon::getCurrentPanel() +{ + if (mCurrentTabIdx < 0 || mCurrentTabIdx >= (S32) mTabList.size()) return NULL; + + return mTabList[mCurrentTabIdx]->mTabPanel; +} + +LLTabContainerCommon::LLTabTuple* LLTabContainerCommon::getTabByPanel(LLPanel* child) +{ + for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + if( tuple->mTabPanel == child ) + { + return tuple; + } + } + return NULL; +} + +void LLTabContainerCommon::setTabChangeCallback(LLPanel* tab, void (*on_tab_clicked)(void*, bool)) +{ + LLTabTuple* tuplep = getTabByPanel(tab); + if (tuplep) + { + tuplep->mOnChangeCallback = on_tab_clicked; + } +} + +void LLTabContainerCommon::setTabUserData(LLPanel* tab, void* userdata) +{ + LLTabTuple* tuplep = getTabByPanel(tab); + if (tuplep) + { + tuplep->mUserData = userdata; + } +} + +S32 LLTabContainerCommon::getCurrentPanelIndex() +{ + return mCurrentTabIdx; +} + +S32 LLTabContainerCommon::getTabCount() +{ + return mTabList.size(); +} + +LLPanel* LLTabContainerCommon::getPanelByIndex(S32 index) +{ + if (index >= 0 && index < (S32)mTabList.size()) + { + return mTabList[index]->mTabPanel; + } + return NULL; +} + +S32 LLTabContainerCommon::getIndexForPanel(LLPanel* panel) +{ + for (S32 index = 0; index < (S32)mTabList.size(); index++) + { + if (mTabList[index]->mTabPanel == panel) + { + return index; + } + } + return -1; +} + +S32 LLTabContainerCommon::getPanelIndexByTitle(const LLString& title) +{ + for (S32 index = 0 ; index < (S32)mTabList.size(); index++) + { + if (title == mTabList[index]->mButton->getLabelSelected()) + { + return index; + } + } + return -1; +} + +LLPanel *LLTabContainerCommon::getPanelByName(const LLString& name) +{ + for (S32 index = 0 ; index < (S32)mTabList.size(); index++) + { + LLPanel *panel = mTabList[index]->mTabPanel; + if (name == panel->getName()) + { + return panel; + } + } + return NULL; +} + + +void LLTabContainerCommon::scrollNext() +{ + // No wrap + if( mScrollPos < mMaxScrollPos ) + { + mScrollPos++; + } +} + +void LLTabContainerCommon::scrollPrev() +{ + // No wrap + if( mScrollPos > 0 ) + { + mScrollPos--; + } +} + +void LLTabContainerCommon::enableTabButton(S32 which, BOOL enable) +{ + if (which >= 0 && which < (S32)mTabList.size()) + { + mTabList[which]->mButton->setEnabled(enable); + } +} + +BOOL LLTabContainerCommon::selectTabPanel(LLPanel* child) +{ + S32 idx = 0; + for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + if( tuple->mTabPanel == child ) + { + return selectTab( idx ); + } + idx++; + } + return FALSE; +} + +BOOL LLTabContainerCommon::selectTabByName(const LLString& name) +{ + LLPanel* panel = getPanelByName(name); + if (!panel) + { + llwarns << "LLTabContainerCommon::selectTabByName(" + << name << ") failed" << llendl; + return FALSE; + } + + BOOL result = selectTabPanel(panel); + return result; +} + + +void LLTabContainerCommon::selectFirstTab() +{ + selectTab( 0 ); +} + + +void LLTabContainerCommon::selectLastTab() +{ + selectTab( mTabList.size()-1 ); +} + + +void LLTabContainerCommon::selectNextTab() +{ + BOOL tab_has_focus = FALSE; + if (mCurrentTabIdx >= 0 && mTabList[mCurrentTabIdx]->mButton->hasFocus()) + { + tab_has_focus = TRUE; + } + S32 idx = mCurrentTabIdx+1; + if (idx >= (S32)mTabList.size()) + idx = 0; + while (!selectTab(idx) && idx != mCurrentTabIdx) + { + idx = (idx + 1 ) % (S32)mTabList.size(); + } + + if (tab_has_focus) + { + mTabList[idx]->mButton->setFocus(TRUE); + } +} + +void LLTabContainerCommon::selectPrevTab() +{ + BOOL tab_has_focus = FALSE; + if (mCurrentTabIdx >= 0 && mTabList[mCurrentTabIdx]->mButton->hasFocus()) + { + tab_has_focus = TRUE; + } + S32 idx = mCurrentTabIdx-1; + if (idx < 0) + idx = mTabList.size()-1; + while (!selectTab(idx) && idx != mCurrentTabIdx) + { + idx = idx - 1; + if (idx < 0) + idx = mTabList.size()-1; + } + if (tab_has_focus) + { + mTabList[idx]->mButton->setFocus(TRUE); + } +} + + +void LLTabContainerCommon::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + LLPanel::reshape( width, height, called_from_parent ); + updateMaxScrollPos(); +} + +// static +void LLTabContainerCommon::onTabBtn( void* userdata ) +{ + LLTabTuple* tuple = (LLTabTuple*) userdata; + LLTabContainerCommon* self = tuple->mTabContainer; + self->selectTabPanel( tuple->mTabPanel ); + + if( tuple->mOnChangeCallback ) + { + tuple->mOnChangeCallback( tuple->mUserData, true ); + } + + tuple->mTabPanel->setFocus(TRUE); +} + +// static +void LLTabContainerCommon::onCloseBtn( void* userdata ) +{ + LLTabContainer* self = (LLTabContainer*) userdata; + if( self->mCloseCallback ) + { + self->mCloseCallback( self->mCallbackUserdata ); + } +} + +// static +void LLTabContainerCommon::onNextBtn( void* userdata ) +{ + // Scroll tabs to the left + LLTabContainer* self = (LLTabContainer*) userdata; + if (!self->mScrolled) + { + self->scrollNext(); + } + self->mScrolled = FALSE; +} + +// static +void LLTabContainerCommon::onNextBtnHeld( void* userdata ) +{ + LLTabContainer* self = (LLTabContainer*) userdata; + if (self->mScrollTimer.getElapsedTimeF32() > SCROLL_STEP_TIME) + { + self->mScrollTimer.reset(); + self->scrollNext(); + self->mScrolled = TRUE; + } +} + +// static +void LLTabContainerCommon::onPrevBtn( void* userdata ) +{ + LLTabContainer* self = (LLTabContainer*) userdata; + if (!self->mScrolled) + { + self->scrollPrev(); + } + self->mScrolled = FALSE; +} + +// static +void LLTabContainerCommon::onPrevBtnHeld( void* userdata ) +{ + LLTabContainer* self = (LLTabContainer*) userdata; + if (self->mScrollTimer.getElapsedTimeF32() > SCROLL_STEP_TIME) + { + self->mScrollTimer.reset(); + self->scrollPrev(); + self->mScrolled = TRUE; + } +} + +BOOL LLTabContainerCommon::getTabPanelFlashing(LLPanel *child) +{ + LLTabTuple* tuple = getTabByPanel(child); + if( tuple ) + { + return tuple->mButton->getFlashing(); + } + return FALSE; +} + +void LLTabContainerCommon::setTabPanelFlashing(LLPanel* child, BOOL state ) +{ + LLTabTuple* tuple = getTabByPanel(child); + if( tuple ) + { + tuple->mButton->setFlashing( state ); + } +} + +void LLTabContainerCommon::setTitle(const LLString& title) +{ + if (mTitleBox) + { + mTitleBox->setText( title ); + } +} + +const LLString LLTabContainerCommon::getPanelTitle(S32 index) +{ + if (index >= 0 && index < (S32)mTabList.size()) + { + LLButton* tab_button = mTabList[index]->mButton; + return tab_button->getLabelSelected(); + } + return LLString::null; +} + +void LLTabContainerCommon::setTopBorderHeight(S32 height) +{ + mTopBorderHeight = height; +} + +// Change the name of the button for the current tab. +void LLTabContainerCommon::setCurrentTabName(const LLString& name) +{ + // Might not have a tab selected + if (mCurrentTabIdx < 0) return; + + mTabList[mCurrentTabIdx]->mButton->setLabelSelected(name); + mTabList[mCurrentTabIdx]->mButton->setLabelUnselected(name); +} + +LLView* LLTabContainerCommon::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("tab_container"); + node->getAttributeString("name", name); + + // Figure out if we are creating a vertical or horizontal tab container. + bool is_vertical = false; + LLTabContainer::TabPosition tab_position = LLTabContainer::TOP; + if (node->hasAttribute("tab_position")) + { + LLString tab_position_string; + node->getAttributeString("tab_position", tab_position_string); + LLString::toLower(tab_position_string); + + if ("top" == tab_position_string) + { + tab_position = LLTabContainer::TOP; + is_vertical = false; + } + else if ("bottom" == tab_position_string) + { + tab_position = LLTabContainer::BOTTOM; + is_vertical = false; + } + else if ("left" == tab_position_string) + { + is_vertical = true; + } + } + BOOL border = FALSE; + node->getAttributeBOOL("border", border); + + // Create the correct container type. + LLTabContainerCommon* tab_container = NULL; + + if (is_vertical) + { + // Vertical tabs can specify tab width + U32 tab_width = TABCNTRV_TAB_WIDTH; + if (node->hasAttribute("tab_width")) + { + node->getAttributeU32("tab_width", tab_width); + } + + tab_container = new LLTabContainerVertical(name, + LLRect::null, + NULL, + NULL, + tab_width, + border); + + } + else // horizontal tab container + { + // Horizontal tabs can have a title (?) + LLString title(LLString::null); + if (node->hasAttribute("title")) + { + node->getAttributeString("title", title); + } + + tab_container = new LLTabContainer(name, + LLRect::null, + tab_position, + NULL, + NULL, + title, + border); + + if(node->hasAttribute("tab_min_width")) + { + S32 minTabWidth=0; + node->getAttributeS32("tab_min_width",minTabWidth); + ((LLTabContainer*)tab_container)->setMinTabWidth(minTabWidth); + } + if(node->hasAttribute("tab_max_width")) + { + S32 maxTabWidth=0; + node->getAttributeS32("tab_max_width",maxTabWidth); + ((LLTabContainer*)tab_container)->setMaxTabWidth(maxTabWidth); + } + } + + tab_container->setPanelParameters(node, parent); + + if (LLFloater::getFloaterHost()) + { + LLFloater::getFloaterHost()->setTabContainer(tab_container); + } + + //parent->addChild(tab_container); + + // Add all tab panels. + LLXMLNodePtr child; + for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) + { + LLView *control = factory->createCtrlWidget(tab_container, child); + if (control && control->isPanel()) + { + LLPanel* panelp = (LLPanel*)control; + LLString label; + child->getAttributeString("label", label); + if (label.empty()) + { + label = panelp->getLabel(); + } + BOOL placeholder = FALSE; + child->getAttributeBOOL("placeholder", placeholder); + tab_container->addTabPanel(panelp, label.c_str(), false, + NULL, NULL, 0, placeholder); + } + } + + tab_container->selectFirstTab(); + + tab_container->postBuild(); + + tab_container->initButtons(); // now that we have the correct rect + + return tab_container; +} + +void LLTabContainerCommon::insertTuple(LLTabTuple * tuple, eInsertionPoint insertion_point) +{ + switch(insertion_point) + { + case START: + // insert the new tab in the front of the list + mTabList.insert(mTabList.begin(), tuple); + break; + case RIGHT_OF_CURRENT: + // insert the new tab after the current tab + { + tuple_list_t::iterator current_iter = mTabList.begin() + mCurrentTabIdx + 1; + mTabList.insert(current_iter, tuple); + } + break; + case END: + default: + mTabList.push_back( tuple ); + } +} + + +LLTabContainer::LLTabContainer( + const LLString& name, const LLRect& rect, TabPosition pos, + void(*close_callback)(void*), void* callback_userdata, + const LLString& title, BOOL bordered ) + : + LLTabContainerCommon(name, rect, pos, close_callback, callback_userdata, bordered), + mLeftArrowBtn(NULL), + mRightArrowBtn(NULL), + mRightTabBtnOffset(0), + mMinTabWidth(TABCNTR_TAB_MIN_WIDTH), + mMaxTabWidth(TABCNTR_TAB_MAX_WIDTH), + mTotalTabWidth(0) +{ + initButtons( ); +} + +LLTabContainer::LLTabContainer( + const LLString& name, const LLString& rect_control, TabPosition pos, + void(*close_callback)(void*), void* callback_userdata, + const LLString& title, BOOL bordered ) + : + LLTabContainerCommon(name, rect_control, pos, close_callback, callback_userdata, bordered), + mLeftArrowBtn(NULL), + mRightArrowBtn(NULL), + mRightTabBtnOffset(0), + mMinTabWidth(TABCNTR_TAB_MIN_WIDTH), + mMaxTabWidth(TABCNTR_TAB_MAX_WIDTH), + mTotalTabWidth(0) +{ + initButtons( ); +} + +void LLTabContainer::initButtons() +{ + // Hack: + if (mRect.getHeight() == 0 || mLeftArrowBtn) + { + return; // Don't have a rect yet or already got called + } + + LLString out_id; + LLString in_id; + + S32 arrow_fudge = 1; // match new art better + + // tabs on bottom reserve room for resize handle (just in case) + if (mTabPosition == BOTTOM) + { + mRightTabBtnOffset = RESIZE_HANDLE_WIDTH; + } + + // Left and right scroll arrows (for when there are too many tabs to show all at once). + S32 btn_top = (mTabPosition == TOP ) ? mRect.getHeight() - mTopBorderHeight : TABCNTR_ARROW_BTN_SIZE + 1; + + LLRect left_arrow_btn_rect; + left_arrow_btn_rect.setLeftTopAndSize( LLPANEL_BORDER_WIDTH+1, btn_top + arrow_fudge, TABCNTR_ARROW_BTN_SIZE, TABCNTR_ARROW_BTN_SIZE ); + + S32 right_pad = TABCNTR_ARROW_BTN_SIZE + LLPANEL_BORDER_WIDTH + 1; + LLRect right_arrow_btn_rect; + right_arrow_btn_rect.setLeftTopAndSize( mRect.getWidth() - mRightTabBtnOffset - right_pad, + btn_top + arrow_fudge, + TABCNTR_ARROW_BTN_SIZE, TABCNTR_ARROW_BTN_SIZE ); + + out_id = "UIImgBtnScrollLeftOutUUID"; + in_id = "UIImgBtnScrollLeftInUUID"; + mLeftArrowBtn = new LLButton( + "Left Arrow", left_arrow_btn_rect, + out_id, in_id, "", + &LLTabContainer::onPrevBtn, this, LLFontGL::sSansSerif ); + mLeftArrowBtn->setHeldDownCallback(onPrevBtnHeld); + mLeftArrowBtn->setFollowsLeft(); + mLeftArrowBtn->setSaveToXML(false); + mLeftArrowBtn->setTabStop(FALSE); + addChild(mLeftArrowBtn); + + out_id = "UIImgBtnScrollRightOutUUID"; + in_id = "UIImgBtnScrollRightInUUID"; + mRightArrowBtn = new LLButton( + "Right Arrow", right_arrow_btn_rect, + out_id, in_id, "", + &LLTabContainer::onNextBtn, this, + LLFontGL::sSansSerif); + mRightArrowBtn->setFollowsRight(); + mRightArrowBtn->setHeldDownCallback(onNextBtnHeld); + mRightArrowBtn->setSaveToXML(false); + mRightArrowBtn->setTabStop(FALSE); + addChild(mRightArrowBtn); + + if( mTabPosition == TOP ) + { + mRightArrowBtn->setFollowsTop(); + mLeftArrowBtn->setFollowsTop(); + } + else + { + mRightArrowBtn->setFollowsBottom(); + mLeftArrowBtn->setFollowsBottom(); + } + + // set default tab group to be panel contents + mDefaultTabGroup = 1; +} + +LLTabContainer::~LLTabContainer() +{ +} + +void LLTabContainer::addTabPanel(LLPanel* child, + const LLString& label, + BOOL select, + void (*on_tab_clicked)(void*, bool), + void* userdata, + S32 indent, + BOOL placeholder, + eInsertionPoint insertion_point) +{ + if (child->getParent() == this) + { + // already a child of mine + return; + } + const LLFontGL* font = gResMgr->getRes( LLFONT_SANSSERIF_SMALL ); + + // Store the original label for possible xml export. + child->setLabel(label); + LLString trimmed_label = label; + LLString::trim(trimmed_label); + + S32 button_width = llclamp(font->getWidth(trimmed_label) + TAB_PADDING, mMinTabWidth, mMaxTabWidth); + + // Tab panel + S32 tab_panel_top; + S32 tab_panel_bottom; + if( LLTabContainer::TOP == mTabPosition ) + { + tab_panel_top = mRect.getHeight() - mTopBorderHeight - (TABCNTR_TAB_HEIGHT - TABCNTR_BUTTON_PANEL_OVERLAP); + tab_panel_bottom = LLPANEL_BORDER_WIDTH; + } + else + { + tab_panel_top = mRect.getHeight() - mTopBorderHeight; + tab_panel_bottom = (TABCNTR_TAB_HEIGHT - TABCNTR_BUTTON_PANEL_OVERLAP); // Run to the edge, covering up the border + } + + LLRect tab_panel_rect( + LLPANEL_BORDER_WIDTH, + tab_panel_top, + mRect.getWidth()-LLPANEL_BORDER_WIDTH, + tab_panel_bottom ); + + child->setFollowsAll(); + child->translate( tab_panel_rect.mLeft - child->getRect().mLeft, tab_panel_rect.mBottom - child->getRect().mBottom); + child->reshape( tab_panel_rect.getWidth(), tab_panel_rect.getHeight(), TRUE ); + child->setBackgroundVisible( FALSE ); // No need to overdraw + // add this child later + + child->setVisible( FALSE ); // Will be made visible when selected + + mTotalTabWidth += button_width; + + // Tab button + LLRect btn_rect; // Note: btn_rect.mLeft is just a dummy. Will be updated in draw(). + LLString tab_img; + LLString tab_selected_img; + S32 tab_fudge = 1; // To make new tab art look better, nudge buttons up 1 pel + + if( LLTabContainer::TOP == mTabPosition ) + { + btn_rect.setLeftTopAndSize( 0, mRect.getHeight() - mTopBorderHeight + tab_fudge, button_width, TABCNTR_TAB_HEIGHT ); + tab_img = "UIImgBtnTabTopOutUUID"; + tab_selected_img = "UIImgBtnTabTopInUUID"; + } + else + { + btn_rect.setOriginAndSize( 0, 0 + tab_fudge, button_width, TABCNTR_TAB_HEIGHT ); + tab_img = "UIImgBtnTabBottomOutUUID"; + tab_selected_img = "UIImgBtnTabBottomInUUID"; + } + + if (placeholder) + { + //FIXME: wont work for horizontal tabs + btn_rect.translate(0, -LLBUTTON_V_PAD-2); + LLString box_label = trimmed_label; + LLTextBox* text = new LLTextBox(box_label, btn_rect, box_label, font); + addChild( text, 0 ); + + LLButton* btn = new LLButton("", LLRect(0,0,0,0)); + LLTabTuple* tuple = new LLTabTuple( this, child, btn, on_tab_clicked, userdata, text ); + addChild( btn, 0 ); + addChild( child, 1 ); + insertTuple(tuple, insertion_point); + } + else + { + LLString tooltip = trimmed_label; + tooltip += "\nCtrl-[ for previous tab"; + tooltip += "\nCtrl-] for next tab"; + + LLButton* btn = new LLButton( + LLString(child->getName()) + " tab", + btn_rect, + tab_img, tab_selected_img, "", + &LLTabContainer::onTabBtn, NULL, // set userdata below + font, + trimmed_label, trimmed_label ); + btn->setSaveToXML(false); + btn->setVisible( FALSE ); + btn->setToolTip( tooltip ); + btn->setScaleImage(TRUE); + btn->setFixedBorder(14, 14); + + // Try to squeeze in a bit more text + btn->setLeftHPad( 4 ); + btn->setRightHPad( 2 ); + btn->setHAlign(LLFontGL::LEFT); + btn->setTabStop(FALSE); + if (indent) + { + btn->setLeftHPad(indent); + } + + if( mTabPosition == TOP ) + { + btn->setFollowsTop(); + } + else + { + btn->setFollowsBottom(); + } + + LLTabTuple* tuple = new LLTabTuple( this, child, btn, on_tab_clicked, userdata ); + btn->setCallbackUserData( tuple ); + addChild( btn, 0 ); + addChild( child, 1 ); + insertTuple(tuple, insertion_point); + } + + updateMaxScrollPos(); + + if( select ) + { + selectLastTab(); + } +} + +void LLTabContainer::removeTabPanel(LLPanel* child) +{ + // Adjust the total tab width. + for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + if( tuple->mTabPanel == child ) + { + mTotalTabWidth -= tuple->mButton->getRect().getWidth(); + break; + } + } + + LLTabContainerCommon::removeTabPanel(child); +} + +void LLTabContainer::setPanelTitle(S32 index, const LLString& title) +{ + if (index >= 0 && index < (S32)mTabList.size()) + { + LLButton* tab_button = mTabList[index]->mButton; + const LLFontGL* fontp = gResMgr->getRes( LLFONT_SANSSERIF_SMALL ); + mTotalTabWidth -= tab_button->getRect().getWidth(); + tab_button->reshape(llclamp(fontp->getWidth(title) + TAB_PADDING, mMinTabWidth, mMaxTabWidth), tab_button->getRect().getHeight()); + mTotalTabWidth += tab_button->getRect().getWidth(); + tab_button->setLabelSelected(title); + tab_button->setLabelUnselected(title); + } + updateMaxScrollPos(); +} + + +void LLTabContainer::updateMaxScrollPos() +{ + S32 tab_space = 0; + S32 available_space = 0; + tab_space = mTotalTabWidth; + available_space = mRect.getWidth() - mRightTabBtnOffset - 2 * (LLPANEL_BORDER_WIDTH + TABCNTR_TAB_H_PAD); + + if( tab_space > available_space ) + { + S32 available_width_with_arrows = mRect.getWidth() - mRightTabBtnOffset - 2 * (LLPANEL_BORDER_WIDTH + TABCNTR_ARROW_BTN_SIZE + 1); + // subtract off reserved portion on left + available_width_with_arrows -= TABCNTR_TAB_PARTIAL_WIDTH; + + S32 running_tab_width = 0; + mMaxScrollPos = mTabList.size(); + for(tuple_list_t::reverse_iterator tab_it = mTabList.rbegin(); tab_it != mTabList.rend(); ++tab_it) + { + running_tab_width += (*tab_it)->mButton->getRect().getWidth(); + if (running_tab_width > available_width_with_arrows) + { + break; + } + mMaxScrollPos--; + } + // in case last tab doesn't actually fit on screen, make it the last scrolling position + mMaxScrollPos = llmin(mMaxScrollPos, (S32)mTabList.size() - 1); + } + else + { + mMaxScrollPos = 0; + mScrollPos = 0; + } + if (mScrollPos > mMaxScrollPos) + { + mScrollPos = mMaxScrollPos; + } +} + +void LLTabContainer::commitHoveredButton(S32 x, S32 y) +{ + if (gFocusMgr.getMouseCapture() == this) + { + for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + tuple->mButton->setVisible( TRUE ); + S32 local_x = x - tuple->mButton->getRect().mLeft; + S32 local_y = y - tuple->mButton->getRect().mBottom; + if (tuple->mButton->pointInView(local_x, local_y) && tuple->mButton->getEnabled() && !tuple->mTabPanel->getVisible()) + { + tuple->mButton->onCommit(); + } + } + } +} + +void LLTabContainer::setMinTabWidth(S32 width) +{ + mMinTabWidth = width; +} + +void LLTabContainer::setMaxTabWidth(S32 width) +{ + mMaxTabWidth = width; +} + +S32 LLTabContainer::getMinTabWidth() const +{ + return mMinTabWidth; +} + +S32 LLTabContainer::getMaxTabWidth() const +{ + return mMaxTabWidth; +} + +BOOL LLTabContainer::selectTab(S32 which) +{ + if (which >= (S32)mTabList.size()) return FALSE; + if (which < 0) return FALSE; + + //if( gFocusMgr.childHasKeyboardFocus( this ) ) + //{ + // gFocusMgr.setKeyboardFocus( NULL, NULL ); + //} + + LLTabTuple* selected_tuple = mTabList[which]; + if (!selected_tuple) + { + return FALSE; + } + + if (mTabList[which]->mButton->getEnabled()) + { + mCurrentTabIdx = which; + + S32 i = 0; + for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + BOOL is_selected = ( tuple == selected_tuple ); + tuple->mTabPanel->setVisible( is_selected ); +// tuple->mTabPanel->setFocus(is_selected); // not clear that we want to do this here. + tuple->mButton->setToggleState( is_selected ); + // RN: this limits tab-stops to active button only, which would require arrow keys to switch tabs + tuple->mButton->setTabStop( is_selected && mTabList.size() > 1 ); + + if( is_selected && mMaxScrollPos > 0) + { + // Make sure selected tab is within scroll region + if( i < mScrollPos ) + { + mScrollPos = i; + } + else + { + S32 available_width_with_arrows = mRect.getWidth() - mRightTabBtnOffset - 2 * (LLPANEL_BORDER_WIDTH + TABCNTR_ARROW_BTN_SIZE + 1); + S32 running_tab_width = tuple->mButton->getRect().getWidth(); + S32 j = i - 1; + S32 min_scroll_pos = i; + if (running_tab_width < available_width_with_arrows) + { + while (j >= 0) + { + LLTabTuple* other_tuple = mTabList[j]; + running_tab_width += other_tuple->mButton->getRect().getWidth(); + if (running_tab_width > available_width_with_arrows) + { + break; + } + j--; + } + min_scroll_pos = j + 1; + } + mScrollPos = llclamp(mScrollPos, min_scroll_pos, i); + mScrollPos = llmin(mScrollPos, mMaxScrollPos); + } + } + i++; + } + if( selected_tuple->mOnChangeCallback ) + { + selected_tuple->mOnChangeCallback( selected_tuple->mUserData, false ); + } + return TRUE; + } + else + { + return FALSE; + } +} + +void LLTabContainer::draw() +{ + S32 target_pixel_scroll = 0; + S32 cur_scroll_pos = mScrollPos; + if (cur_scroll_pos > 0) + { + S32 available_width_with_arrows = mRect.getWidth() - mRightTabBtnOffset - 2 * (LLPANEL_BORDER_WIDTH + TABCNTR_ARROW_BTN_SIZE + 1); + for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + if (cur_scroll_pos == 0) + { + break; + } + target_pixel_scroll += (*iter)->mButton->getRect().getWidth(); + cur_scroll_pos--; + } + + // Show part of the tab to the left of what is fully visible + target_pixel_scroll -= TABCNTR_TAB_PARTIAL_WIDTH; + // clamp so that rightmost tab never leaves right side of screen + target_pixel_scroll = llmin(mTotalTabWidth - available_width_with_arrows, target_pixel_scroll); + } + + mScrollPosPixels = (S32)lerp((F32)mScrollPosPixels, (F32)target_pixel_scroll, LLCriticalDamp::getInterpolant(0.08f)); + if( getVisible() ) + { + BOOL has_scroll_arrows = (mMaxScrollPos > 0) || (mScrollPosPixels > 0); + mLeftArrowBtn->setVisible( has_scroll_arrows ); + mRightArrowBtn->setVisible( has_scroll_arrows ); + + // Set the leftmost position of the tab buttons. + S32 left = LLPANEL_BORDER_WIDTH + (has_scroll_arrows ? TABCNTR_ARROW_BTN_SIZE : TABCNTR_TAB_H_PAD); + left -= mScrollPosPixels; + + // Hide all the buttons + for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + tuple->mButton->setVisible( FALSE ); + } + + LLPanel::draw(); + + // Show all the buttons + for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + tuple->mButton->setVisible( TRUE ); + } + + // Draw some of the buttons... + + LLGLEnable scissor_test(has_scroll_arrows ? GL_SCISSOR_TEST : GL_FALSE); + if( has_scroll_arrows ) + { + // ...but clip them. + S32 x1 = mLeftArrowBtn->getRect().mRight; + S32 y1 = 0; + S32 x2 = mRightArrowBtn->getRect().mLeft; + S32 y2 = 1; + if (mTabList.size() > 0) + { + y2 = mTabList[0]->mButton->getRect().mTop; + } + LLUI::setScissorRegionLocal(LLRect(x1, y2, x2, y1)); + } + + S32 max_scroll_visible = mTabList.size() - mMaxScrollPos + mScrollPos; + S32 idx = 0; + for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + tuple->mButton->translate( left - tuple->mButton->getRect().mLeft, 0 ); + left += tuple->mButton->getRect().getWidth(); + + if( idx < mScrollPos ) + { + if( tuple->mButton->getFlashing() ) + { + mLeftArrowBtn->setFlashing( TRUE ); + } + } + else + if( max_scroll_visible < idx ) + { + if( tuple->mButton->getFlashing() ) + { + mRightArrowBtn->setFlashing( TRUE ); + } + } + + LLUI::pushMatrix(); + { + LLUI::translate((F32)tuple->mButton->getRect().mLeft, (F32)tuple->mButton->getRect().mBottom, 0.f); + tuple->mButton->draw(); + } + LLUI::popMatrix(); + + idx++; + } + + mLeftArrowBtn->setFlashing(FALSE); + mRightArrowBtn->setFlashing(FALSE); + } +} + + +void LLTabContainer::setRightTabBtnOffset(S32 offset) +{ + mRightArrowBtn->translate( -offset - mRightTabBtnOffset, 0 ); + mRightTabBtnOffset = offset; + updateMaxScrollPos(); +} + +BOOL LLTabContainer::handleMouseDown( S32 x, S32 y, MASK mask ) +{ + BOOL handled = FALSE; + BOOL has_scroll_arrows = (mMaxScrollPos > 0); + + if (has_scroll_arrows) + { + if (mLeftArrowBtn->getRect().pointInRect(x, y)) + { + S32 local_x = x - mLeftArrowBtn->getRect().mLeft; + S32 local_y = y - mLeftArrowBtn->getRect().mBottom; + handled = mLeftArrowBtn->handleMouseDown(local_x, local_y, mask); + } + else if (mRightArrowBtn->getRect().pointInRect(x, y)) + { + S32 local_x = x - mRightArrowBtn->getRect().mLeft; + S32 local_y = y - mRightArrowBtn->getRect().mBottom; + handled = mRightArrowBtn->handleMouseDown(local_x, local_y, mask); + } + } + if (!handled) + { + handled = LLPanel::handleMouseDown( x, y, mask ); + } + + if (mTabList.size() > 0) + { + LLTabTuple* firsttuple = mTabList[0]; + LLRect tab_rect(has_scroll_arrows ? mLeftArrowBtn->getRect().mRight : mLeftArrowBtn->getRect().mLeft, + firsttuple->mButton->getRect().mTop, + has_scroll_arrows ? mRightArrowBtn->getRect().mLeft : mRightArrowBtn->getRect().mRight, + firsttuple->mButton->getRect().mBottom ); + if( tab_rect.pointInRect( x, y ) ) + { + LLButton* tab_button = mTabList[getCurrentPanelIndex()]->mButton; + gFocusMgr.setMouseCapture(this, NULL); + gFocusMgr.setKeyboardFocus(tab_button, NULL); + } + } + return handled; +} + +BOOL LLTabContainer::handleHover( S32 x, S32 y, MASK mask ) +{ + BOOL handled = FALSE; + BOOL has_scroll_arrows = (mMaxScrollPos > 0); + + if (has_scroll_arrows) + { + if (mLeftArrowBtn->getRect().pointInRect(x, y)) + { + S32 local_x = x - mLeftArrowBtn->getRect().mLeft; + S32 local_y = y - mLeftArrowBtn->getRect().mBottom; + handled = mLeftArrowBtn->handleHover(local_x, local_y, mask); + } + else if (mRightArrowBtn->getRect().pointInRect(x, y)) + { + S32 local_x = x - mRightArrowBtn->getRect().mLeft; + S32 local_y = y - mRightArrowBtn->getRect().mBottom; + handled = mRightArrowBtn->handleHover(local_x, local_y, mask); + } + } + if (!handled) + { + handled = LLPanel::handleHover(x, y, mask); + } + + commitHoveredButton(x, y); + return handled; +} + +BOOL LLTabContainer::handleMouseUp( S32 x, S32 y, MASK mask ) +{ + BOOL handled = FALSE; + BOOL has_scroll_arrows = (mMaxScrollPos > 0); + + if (has_scroll_arrows) + { + if (mLeftArrowBtn->getRect().pointInRect(x, y)) + { + S32 local_x = x - mLeftArrowBtn->getRect().mLeft; + S32 local_y = y - mLeftArrowBtn->getRect().mBottom; + handled = mLeftArrowBtn->handleMouseUp(local_x, local_y, mask); + } + else if (mRightArrowBtn->getRect().pointInRect(x, y)) + { + S32 local_x = x - mRightArrowBtn->getRect().mLeft; + S32 local_y = y - mRightArrowBtn->getRect().mBottom; + handled = mRightArrowBtn->handleMouseUp(local_x, local_y, mask); + } + } + if (!handled) + { + handled = LLPanel::handleMouseUp( x, y, mask ); + } + + commitHoveredButton(x, y); + LLPanel* cur_panel = getCurrentPanel(); + if (gFocusMgr.getMouseCapture() == this) + { + if (cur_panel) + { + if (!cur_panel->focusFirstItem(FALSE)) + { + // if nothing in the panel gets focus, make sure the new tab does + // otherwise the last tab might keep focus + mTabList[getCurrentPanelIndex()]->mButton->setFocus(TRUE); + } + } + gFocusMgr.setMouseCapture(NULL, NULL); + } + return handled; +} + +BOOL LLTabContainer::handleToolTip( S32 x, S32 y, LLString& msg, LLRect* sticky_rect ) +{ + BOOL handled = LLPanel::handleToolTip( x, y, msg, sticky_rect ); + if (!handled && mTabList.size() > 0 && getVisible() && pointInView( x, y ) ) + { + LLTabTuple* firsttuple = mTabList[0]; + + BOOL has_scroll_arrows = (mMaxScrollPos > 0); + LLRect clip( + has_scroll_arrows ? mLeftArrowBtn->getRect().mRight : mLeftArrowBtn->getRect().mLeft, + firsttuple->mButton->getRect().mTop, + has_scroll_arrows ? mRightArrowBtn->getRect().mLeft : mRightArrowBtn->getRect().mRight, + 0 ); + if( clip.pointInRect( x, y ) ) + { + for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + tuple->mButton->setVisible( TRUE ); + S32 local_x = x - tuple->mButton->getRect().mLeft; + S32 local_y = y - tuple->mButton->getRect().mBottom; + handled = tuple->mButton->handleToolTip( local_x, local_y, msg, sticky_rect ); + if( handled ) + { + break; + } + } + } + + for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + tuple->mButton->setVisible( FALSE ); + } + } + return handled; +} + +BOOL LLTabContainer::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent) +{ + if (!getEnabled()) return FALSE; + + if (!gFocusMgr.childHasKeyboardFocus(this)) return FALSE; + + BOOL handled = FALSE; + if (key == '[' && mask == MASK_CONTROL) + { + selectPrevTab(); + handled = TRUE; + } + else if (key == ']' && mask == MASK_CONTROL) + { + selectNextTab(); + handled = TRUE; + } + + if (handled) + { + if (getCurrentPanel()) + { + getCurrentPanel()->setFocus(TRUE); + } + } + + if (!gFocusMgr.childHasKeyboardFocus(getCurrentPanel())) + { + // if child has focus, but not the current panel, focus + // is on a button + switch(key) + { + case KEY_UP: + if (getTabPosition() == BOTTOM && getCurrentPanel()) + { + getCurrentPanel()->setFocus(TRUE); + } + handled = TRUE; + break; + case KEY_DOWN: + if (getTabPosition() == TOP && getCurrentPanel()) + { + getCurrentPanel()->setFocus(TRUE); + } + handled = TRUE; + break; + case KEY_LEFT: + selectPrevTab(); + handled = TRUE; + break; + case KEY_RIGHT: + selectNextTab(); + handled = TRUE; + break; + default: + break; + } + } + return handled; +} + +// virtual +LLXMLNodePtr LLTabContainer::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLTabContainerCommon::getXML(); + + node->createChild("tab_position", TRUE)->setStringValue((mTabPosition == TOP ? "top" : "bottom")); + + return node; +} + +BOOL LLTabContainer::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDragAndDropType type, void* cargo_data, EAcceptance *accept, LLString &tooltip) +{ + BOOL has_scroll_arrows = (mMaxScrollPos > 0); + + if (has_scroll_arrows) + { + if (mLeftArrowBtn->getRect().pointInRect(x, y)) + { + S32 local_x = x - mLeftArrowBtn->getRect().mLeft; + S32 local_y = y - mLeftArrowBtn->getRect().mBottom; + mLeftArrowBtn->handleHover(local_x, local_y, mask); + } + else if (mRightArrowBtn->getRect().pointInRect(x, y)) + { + S32 local_x = x - mRightArrowBtn->getRect().mLeft; + S32 local_y = y - mRightArrowBtn->getRect().mBottom; + mRightArrowBtn->handleHover(local_x, local_y, mask); + } + } + + for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + tuple->mButton->setVisible( TRUE ); + S32 local_x = x - tuple->mButton->getRect().mLeft; + S32 local_y = y - tuple->mButton->getRect().mBottom; + if (tuple->mButton->pointInView(local_x, local_y) && tuple->mButton->getEnabled() && !tuple->mTabPanel->getVisible()) + { + tuple->mButton->onCommit(); + } + } + + return LLView::handleDragAndDrop(x, y, mask, drop, type, cargo_data, accept, tooltip); +} + diff --git a/indra/llui/lltabcontainer.h b/indra/llui/lltabcontainer.h new file mode 100644 index 0000000000..41e602eaea --- /dev/null +++ b/indra/llui/lltabcontainer.h @@ -0,0 +1,242 @@ +/** + * @file lltabcontainer.h + * @brief LLTabContainerCommon base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// Fear my script-fu! + +#ifndef LL_TABCONTAINER_H +#define LL_TABCONTAINER_H + +#include "llpanel.h" +#include "llframetimer.h" + +class LLButton; +class LLTextBox; + + +class LLTabContainerCommon : public LLPanel +{ +public: + enum TabPosition + { + TOP, + BOTTOM, + LEFT + }; + typedef enum e_insertion_point + { + START, + END, + RIGHT_OF_CURRENT + } eInsertionPoint; + + LLTabContainerCommon( const LLString& name, + const LLRect& rect, + TabPosition pos, + void(*close_callback)(void*), void* callback_userdata, + BOOL bordered = TRUE); + + LLTabContainerCommon( const LLString& name, + const LLString& rect_control, + TabPosition pos, + void(*close_callback)(void*), void* callback_userdata, + BOOL bordered = TRUE); + + virtual ~LLTabContainerCommon(); + + virtual void initButtons() = 0; + + virtual void setValue(const LLSD& value) { selectTab((S32) value.asInteger()); } + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_TAB_CONTAINER; } + virtual LLString getWidgetTag() const { return LL_TAB_CONTAINER_COMMON_TAG; } + + virtual LLView* getChildByName(const LLString& name, BOOL recurse = FALSE) const; + + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + + virtual void addTabPanel(LLPanel* child, + const LLString& label, + BOOL select = FALSE, + void (*on_tab_clicked)(void*, bool) = NULL, + void* userdata = NULL, + S32 indent = 0, + BOOL placeholder = FALSE, + eInsertionPoint insertion_point = END) = 0; + virtual void addPlaceholder(LLPanel* child, const LLString& label); + + virtual void enableTabButton(S32 which, BOOL enable); + + virtual void removeTabPanel( LLPanel* child ); + virtual void deleteAllTabs(); + virtual LLPanel* getCurrentPanel(); + virtual S32 getCurrentPanelIndex(); + virtual S32 getTabCount(); + virtual S32 getPanelIndexByTitle(const LLString& title); + virtual LLPanel* getPanelByIndex(S32 index); + virtual LLPanel* getPanelByName(const LLString& name); + virtual S32 getIndexForPanel(LLPanel* panel); + + virtual void setCurrentTabName(const LLString& name); + + + virtual void selectFirstTab(); + virtual void selectLastTab(); + virtual BOOL selectTabPanel( LLPanel* child ); + virtual BOOL selectTab(S32 which) = 0; + virtual BOOL selectTabByName(const LLString& title); + virtual void selectNextTab(); + virtual void selectPrevTab(); + + BOOL getTabPanelFlashing(LLPanel* child); + void setTabPanelFlashing(LLPanel* child, BOOL state); + void setTitle( const LLString& title ); + const LLString getPanelTitle(S32 index); + + virtual void setTopBorderHeight(S32 height); + + virtual void setTabChangeCallback(LLPanel* tab, void (*on_tab_clicked)(void*,bool)); + virtual void setTabUserData(LLPanel* tab, void* userdata); + + virtual void reshape(S32 width, S32 height, BOOL called_from_parent); + + static void onCloseBtn(void* userdata); + static void onTabBtn(void* userdata); + static void onNextBtn(void* userdata); + static void onNextBtnHeld(void* userdata); + static void onPrevBtn(void* userdata); + static void onPrevBtnHeld(void* userdata); + + virtual void setRightTabBtnOffset( S32 offset ) { } + virtual void setPanelTitle(S32 index, const LLString& title) { } + + virtual TabPosition getTabPosition() { return mTabPosition; } + + +protected: + // Structure used to map tab buttons to and from tab panels + struct LLTabTuple + { + LLTabTuple( LLTabContainerCommon* c, LLPanel* p, LLButton* b, + void (*cb)(void*,bool), void* userdata, LLTextBox* placeholder = NULL ) + : + mTabContainer(c), + mTabPanel(p), + mButton(b), + mOnChangeCallback( cb ), + mUserData( userdata ), + mOldState(FALSE), + mPlaceholderText(placeholder) + {} + + LLTabContainerCommon* mTabContainer; + LLPanel* mTabPanel; + LLButton* mButton; + void (*mOnChangeCallback)(void*, bool); + void* mUserData; + BOOL mOldState; + LLTextBox* mPlaceholderText; + }; + + typedef std::vector<LLTabTuple*> tuple_list_t; + tuple_list_t mTabList; + S32 mCurrentTabIdx; + + BOOL mScrolled; + LLFrameTimer mScrollTimer; + S32 mScrollPos; + S32 mScrollPosPixels; + S32 mMaxScrollPos; + + void (*mCloseCallback)(void*); + void* mCallbackUserdata; + + LLTextBox* mTitleBox; + + S32 mTopBorderHeight; + TabPosition mTabPosition; + +protected: + void scrollPrev(); + void scrollNext(); + + virtual void updateMaxScrollPos() = 0; + virtual void commitHoveredButton(S32 x, S32 y) = 0; + LLTabTuple* getTabByPanel(LLPanel* child); + void insertTuple(LLTabTuple * tuple, eInsertionPoint insertion_point); +}; + +class LLTabContainer : public LLTabContainerCommon +{ +public: + LLTabContainer( const LLString& name, const LLRect& rect, TabPosition pos, + void(*close_callback)(void*), void* callback_userdata, + const LLString& title=LLString::null, BOOL bordered = TRUE ); + + LLTabContainer( const LLString& name, const LLString& rect_control, TabPosition pos, + void(*close_callback)(void*), void* callback_userdata, + const LLString& title=LLString::null, BOOL bordered = TRUE ); + + ~LLTabContainer(); + + /*virtual*/ void initButtons(); + + /*virtual*/ void draw(); + + /*virtual*/ void addTabPanel(LLPanel* child, + const LLString& label, + BOOL select = FALSE, + void (*on_tab_clicked)(void*, bool) = NULL, + void* userdata = NULL, + S32 indent = 0, + BOOL placeholder = FALSE, + eInsertionPoint insertion_point = END); + + /*virtual*/ BOOL selectTab(S32 which); + /*virtual*/ void removeTabPanel( LLPanel* child ); + + /*virtual*/ void setPanelTitle(S32 index, const LLString& title); + + /*virtual*/ void setRightTabBtnOffset( S32 offset ); + + /*virtual*/ void setMinTabWidth(S32 width); + /*virtual*/ void setMaxTabWidth(S32 width); + + /*virtual*/ S32 getMinTabWidth() const; + /*virtual*/ S32 getMaxTabWidth() const; + + /*virtual*/ BOOL handleMouseDown( S32 x, S32 y, MASK mask ); + /*virtual*/ BOOL handleHover( S32 x, S32 y, MASK mask ); + /*virtual*/ BOOL handleMouseUp( S32 x, S32 y, MASK mask ); + /*virtual*/ BOOL handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect ); + /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent); + /*virtual*/ BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType type, void* cargo_data, + EAcceptance* accept, LLString& tooltip); + + virtual LLXMLNodePtr getXML(bool save_children = true) const; + + +protected: + + LLButton* mLeftArrowBtn; + LLButton* mRightArrowBtn; + + S32 mRightTabBtnOffset; // Extra room to the right of the tab buttons. + +protected: + virtual void updateMaxScrollPos(); + virtual void commitHoveredButton(S32 x, S32 y); + + S32 mMinTabWidth; + S32 mMaxTabWidth; + S32 mTotalTabWidth; +}; + +const S32 TABCNTR_CLOSE_BTN_SIZE = 16; +const S32 TABCNTR_HEADER_HEIGHT = LLPANEL_BORDER_WIDTH + TABCNTR_CLOSE_BTN_SIZE; + +#endif // LL_TABCONTAINER_H diff --git a/indra/llui/lltextbox.cpp b/indra/llui/lltextbox.cpp new file mode 100644 index 0000000000..0cd2e98514 --- /dev/null +++ b/indra/llui/lltextbox.cpp @@ -0,0 +1,438 @@ +/** + * @file lltextbox.cpp + * @brief A text display widget + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "lltextbox.h" + +#include "llerror.h" +#include "llgl.h" +#include "llui.h" +#include "lluictrlfactory.h" +#include "llcontrol.h" +#include "llfocusmgr.h" +#include "llstl.h" +#include <boost/tokenizer.hpp> + +LLTextBox::LLTextBox(const LLString& name, const LLRect& rect, const LLString& text, + const LLFontGL* font, BOOL mouse_opaque) +: LLUICtrl(name, rect, mouse_opaque, NULL, NULL, FOLLOWS_LEFT | FOLLOWS_TOP ), + mTextColor( LLUI::sColorsGroup->getColor( "LabelTextColor" ) ), + mDisabledColor( LLUI::sColorsGroup->getColor( "LabelDisabledColor" ) ), + mBackgroundColor( LLUI::sColorsGroup->getColor( "DefaultBackgroundColor" ) ), + mBorderColor( LLUI::sColorsGroup->getColor( "DefaultHighlightLight" ) ), + mBackgroundVisible( FALSE ), + mBorderVisible( FALSE ), + mDropshadowVisible( TRUE ), + mBorderDropShadowVisible( FALSE ), + mHPad(0), + mVPad(0), + mHAlign( LLFontGL::LEFT ), + mVAlign( LLFontGL::TOP ), + mClickedCallback(NULL), + mCallbackUserData(NULL) +{ + // TomY TODO Nuke this eventually + setText( !text.empty() ? text : name ); + mFontGL = font ? font : LLFontGL::sSansSerifSmall; + setTabStop(FALSE); +} + +LLTextBox::LLTextBox(const LLString& name, const LLString& text, F32 max_width, + const LLFontGL* font, BOOL mouse_opaque) : + LLUICtrl(name, LLRect(0, 0, 1, 1), mouse_opaque, NULL, NULL, FOLLOWS_LEFT | FOLLOWS_TOP), + mFontGL(font ? font : LLFontGL::sSansSerifSmall), + mTextColor(LLUI::sColorsGroup->getColor("LabelTextColor")), + mDisabledColor(LLUI::sColorsGroup->getColor("LabelDisabledColor")), + mBackgroundColor(LLUI::sColorsGroup->getColor("DefaultBackgroundColor")), + mBorderColor(LLUI::sColorsGroup->getColor("DefaultHighlightLight")), + mBackgroundVisible(FALSE), + mBorderVisible(FALSE), + mDropshadowVisible(TRUE), + mBorderDropShadowVisible(FALSE), + mHPad(0), + mVPad(0), + mHAlign(LLFontGL::LEFT), + mVAlign( LLFontGL::TOP ), + mClickedCallback(NULL), + mCallbackUserData(NULL) +{ + setWrappedText(!text.empty() ? text : name, max_width); + reshapeToFitText(); + setTabStop(FALSE); +} + +LLTextBox::~LLTextBox() +{ +} + +// virtual +EWidgetType LLTextBox::getWidgetType() const +{ + return WIDGET_TYPE_TEXT_BOX; +} + +// virtual +LLString LLTextBox::getWidgetTag() const +{ + return LL_TEXT_BOX_TAG; +} + +BOOL LLTextBox::handleMouseDown(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + // HACK: Only do this if there actually is a click callback, so that + // overly large text boxes in the older UI won't start eating clicks. + if (mClickedCallback) + { + handled = TRUE; + + // Route future Mouse messages here preemptively. (Release on mouse up.) + gFocusMgr.setMouseCapture( this, NULL ); + + if (mSoundFlags & MOUSE_DOWN) + { + make_ui_sound("UISndClick"); + } + } + + return handled; +} + + +BOOL LLTextBox::handleMouseUp(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + // We only handle the click if the click both started and ended within us + + // HACK: Only do this if there actually is a click callback, so that + // overly large text boxes in the older UI won't start eating clicks. + if (mClickedCallback + && this == gFocusMgr.getMouseCapture()) + { + handled = TRUE; + + // Release the mouse + gFocusMgr.setMouseCapture( NULL, NULL ); + + if (mSoundFlags & MOUSE_UP) + { + make_ui_sound("UISndClickRelease"); + } + + // 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 (mClickedCallback) + { + (*mClickedCallback)( mCallbackUserData ); + } + } + + return handled; +} + +void LLTextBox::setText(const LLString& text) +{ + mText.assign(text); + setLineLengths(); +} + +void LLTextBox::setLineLengths() +{ + mLineLengthList.clear(); + + LLString::size_type cur = 0; + LLString::size_type len = mText.getWString().size(); + + while (cur < len) + { + LLString::size_type end = mText.getWString().find('\n', cur); + LLString::size_type runLen; + + if (end == LLString::npos) + { + runLen = len - cur; + cur = len; + } + else + { + runLen = end - cur; + cur = end + 1; // skip the new line character + } + + mLineLengthList.push_back( (S32)runLen ); + } +} + +void LLTextBox::setWrappedText(const LLString& in_text, F32 max_width) +{ + if (max_width < 0.0) + { + max_width = (F32)getRect().getWidth(); + } + + LLWString wtext = utf8str_to_wstring(in_text); + LLWString final_wtext; + + LLWString::size_type cur = 0;; + LLWString::size_type len = wtext.size(); + + while (cur < len) + { + LLWString::size_type end = wtext.find('\n', cur); + if (end == LLWString::npos) + { + end = len; + } + + LLWString::size_type runLen = end - cur; + if (runLen > 0) + { + LLWString run(wtext, cur, runLen); + LLWString::size_type useLen = + mFontGL->maxDrawableChars(run.c_str(), max_width, runLen, TRUE); + + final_wtext.append(wtext, cur, useLen); + cur += useLen; + } + + if (cur < len) + { + if (wtext[cur] == '\n') + { + cur += 1; + } + final_wtext += '\n'; + } + } + + LLString final_text = wstring_to_utf8str(final_wtext); + setText(final_text); +} + +S32 LLTextBox::getTextPixelWidth() +{ + S32 max_line_width = 0; + if( mLineLengthList.size() > 0 ) + { + S32 cur_pos = 0; + for (std::vector<S32>::iterator iter = mLineLengthList.begin(); + iter != mLineLengthList.end(); ++iter) + { + S32 line_length = *iter; + S32 line_width = mFontGL->getWidth( mText.getWString().c_str(), cur_pos, line_length ); + if( line_width > max_line_width ) + { + max_line_width = line_width; + } + cur_pos += line_length+1; + } + } + else + { + max_line_width = mFontGL->getWidth(mText.getWString().c_str()); + } + return max_line_width; +} + +S32 LLTextBox::getTextPixelHeight() +{ + S32 num_lines = mLineLengthList.size(); + if( num_lines < 1 ) + { + num_lines = 1; + } + return (S32)(num_lines * mFontGL->getLineHeight()); +} + + +void LLTextBox::setValue(const LLSD& value ) +{ + setText(value.asString()); +} + +LLSD LLTextBox::getValue() const +{ + return LLSD(getText()); +} + +BOOL LLTextBox::setTextArg( const LLString& key, const LLString& text ) +{ + mText.setArg(key, text); + setLineLengths(); + return TRUE; +} + +void LLTextBox::draw() +{ + if( getVisible() ) + { + if (mBorderVisible) + { + gl_rect_2d_offset_local(getLocalRect(), 2, FALSE); + } + + if( mBorderDropShadowVisible ) + { + static LLColor4 color_drop_shadow = LLUI::sColorsGroup->getColor("ColorDropShadow"); + static S32 drop_shadow_tooltip = LLUI::sConfigGroup->getS32("DropShadowTooltip"); + gl_drop_shadow(0, mRect.getHeight(), mRect.getWidth(), 0, + color_drop_shadow, drop_shadow_tooltip); + } + + if (mBackgroundVisible) + { + LLRect r( 0, mRect.getHeight(), mRect.getWidth(), 0 ); + gl_rect_2d( r, mBackgroundColor ); + } + + S32 text_x = 0; + switch( mHAlign ) + { + case LLFontGL::LEFT: + text_x = mHPad; + break; + case LLFontGL::HCENTER: + text_x = mRect.getWidth() / 2; + break; + case LLFontGL::RIGHT: + text_x = mRect.getWidth() - mHPad; + break; + } + + S32 text_y = mRect.getHeight() - mVPad; + + if ( getEnabled() ) + { + drawText( text_x, text_y, mTextColor ); + } + else + { + drawText( text_x, text_y, mDisabledColor ); + } + + if (sDebugRects) + { + drawDebugRect(); + } + } +} + +void LLTextBox::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + // reparse line lengths + setText(mText); + LLView::reshape(width, height, called_from_parent); +} + +void LLTextBox::drawText( S32 x, S32 y, const LLColor4& color ) +{ + if( !mLineLengthList.empty() ) + { + S32 cur_pos = 0; + for (std::vector<S32>::iterator iter = mLineLengthList.begin(); + iter != mLineLengthList.end(); ++iter) + { + S32 line_length = *iter; + mFontGL->render(mText.getWString(), cur_pos, (F32)x, (F32)y, color, + mHAlign, mVAlign, + mDropshadowVisible ? LLFontGL::DROP_SHADOW : LLFontGL::NORMAL, + line_length, mRect.getWidth(), NULL, TRUE ); + cur_pos += line_length + 1; + y -= llfloor(mFontGL->getLineHeight()); + } + } + else + { + mFontGL->render(mText.getWString(), 0, (F32)x, (F32)y, color, + mHAlign, mVAlign, + mDropshadowVisible ? LLFontGL::DROP_SHADOW : LLFontGL::NORMAL, + S32_MAX, mRect.getWidth(), NULL, TRUE); + } +} + + +void LLTextBox::reshapeToFitText() +{ + S32 width = getTextPixelWidth(); + S32 height = getTextPixelHeight(); + reshape( width + 2 * mHPad, height + 2 * mVPad ); +} + +// virtual +LLXMLNodePtr LLTextBox::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLUICtrl::getXML(); + + // Attributes + + node->createChild("font", TRUE)->setStringValue(LLFontGL::nameFromFont(mFontGL)); + + node->createChild("halign", TRUE)->setStringValue(LLFontGL::nameFromHAlign(mHAlign)); + + addColorXML(node, mTextColor, "text_color", "LabelTextColor"); + addColorXML(node, mDisabledColor, "disabled_color", "LabelDisabledColor"); + addColorXML(node, mBackgroundColor, "bg_color", "DefaultBackgroundColor"); + addColorXML(node, mBorderColor, "border_color", "DefaultHighlightLight"); + + node->createChild("bg_visible", TRUE)->setBoolValue(mBackgroundVisible); + + node->createChild("border_visible", TRUE)->setBoolValue(mBorderVisible); + + node->createChild("drop_shadow_visible", TRUE)->setBoolValue(mDropshadowVisible); + + node->createChild("border_drop_shadow_visible", TRUE)->setBoolValue(mBorderDropShadowVisible); + + node->createChild("h_pad", TRUE)->setIntValue(mHPad); + + node->createChild("v_pad", TRUE)->setIntValue(mVPad); + + // Contents + + node->setStringValue(mText); + + return node; +} + +// static +LLView* LLTextBox::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("text_box"); + node->getAttributeString("name", name); + LLFontGL* font = LLView::selectFont(node); + + LLString text = node->getTextContents(); + + // TomY Yes I know this is a hack, but insert a space to make a blank text field + if (text == "") + { + text = " "; + } + + LLTextBox* text_box = new LLTextBox(name, + LLRect(), + text, + font, + FALSE); + + LLFontGL::HAlign halign = LLView::selectFontHAlign(node); + text_box->setHAlign(halign); + + text_box->initFromXML(node, parent); + + if(node->hasAttribute("text_color")) + { + LLColor4 color; + LLUICtrlFactory::getAttributeColor(node, "text_color", color); + text_box->setColor(color); + } + + return text_box; +} diff --git a/indra/llui/lltextbox.h b/indra/llui/lltextbox.h new file mode 100644 index 0000000000..0c09ae26b4 --- /dev/null +++ b/indra/llui/lltextbox.h @@ -0,0 +1,106 @@ +/** + * @file lltextbox.h + * @brief A single text item display + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLTEXTBOX_H +#define LL_LLTEXTBOX_H + +#include "lluictrl.h" +#include "v4color.h" +#include "llstring.h" +#include "llfontgl.h" +#include "lluistring.h" + + +class LLTextBox +: public LLUICtrl +{ +public: + // By default, follows top and left and is mouse-opaque. + // If no text, text = name. + // If no font, uses default system font. + LLTextBox(const LLString& name, const LLRect& rect, const LLString& text = LLString::null, + const LLFontGL* font = NULL, BOOL mouse_opaque = TRUE ); + + // Construct a textbox which handles word wrapping for us. + LLTextBox(const LLString& name, const LLString& text, F32 max_width = 200, + const LLFontGL* font = NULL, BOOL mouse_opaque = TRUE ); + + virtual ~LLTextBox(); + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + + virtual void draw(); + virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); + + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); + + void setColor( const LLColor4& c ) { mTextColor = c; } + void setDisabledColor( const LLColor4& c) { mDisabledColor = c; } + void setBackgroundColor( const LLColor4& c) { mBackgroundColor = c; } + void setBorderColor( const LLColor4& c) { mBorderColor = c; } + void setText( const LLString& text ); + void setWrappedText(const LLString& text, F32 max_width = -1.0); + // default width means use existing control width + + void setBackgroundVisible(BOOL visible) { mBackgroundVisible = visible; } + void setBorderVisible(BOOL visible) { mBorderVisible = visible; } + void setDropshadowVisible(BOOL visible) { mDropshadowVisible = visible; } + void setBorderDropshadowVisible(BOOL visible){ mBorderDropShadowVisible = visible; } + void setHPad(S32 pixels) { mHPad = pixels; } + void setVPad(S32 pixels) { mVPad = pixels; } + void setRightAlign() { mHAlign = LLFontGL::RIGHT; } + void setHAlign( LLFontGL::HAlign align ) { mHAlign = align; } + void setClickedCallback( void (*cb)(void *data) ){ mClickedCallback = cb; } // mouse down and up within button + void setCallbackUserData( void* data ) { mCallbackUserData = data; } + + const LLFontGL* getFont() const { return mFontGL; } + + void reshapeToFitText(); + + const LLString& getText() const { return mText.getString(); } + S32 getTextPixelWidth(); + S32 getTextPixelHeight(); + + + virtual void setValue(const LLSD& value ); + virtual LLSD getValue() const; + virtual BOOL setTextArg( const LLString& key, const LLString& text ); + +protected: + void setLineLengths(); + void drawText(S32 x, S32 y, const LLColor4& color ); + +protected: + LLUIString mText; + const LLFontGL* mFontGL; + LLColor4 mTextColor; + LLColor4 mDisabledColor; + + LLColor4 mBackgroundColor; + LLColor4 mBorderColor; + + BOOL mBackgroundVisible; + BOOL mBorderVisible; + + BOOL mDropshadowVisible; // Draws black dropshadow below and to the right of the text. + BOOL mBorderDropShadowVisible; + + S32 mHPad; + S32 mVPad; + LLFontGL::HAlign mHAlign; + LLFontGL::VAlign mVAlign; + + std::vector<S32> mLineLengthList; + void (*mClickedCallback)(void* data ); + void* mCallbackUserData; +}; + +#endif diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp new file mode 100644 index 0000000000..a4747aef67 --- /dev/null +++ b/indra/llui/lltexteditor.cpp @@ -0,0 +1,4144 @@ +/** + * @file lltexteditor.cpp + * @brief LLTextEditor base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// Text editor widget to let users enter a a multi-line ASCII document. + +#include "linden_common.h" + +#include "lltexteditor.h" + +#include "llfontgl.h" +#include "llgl.h" +#include "llui.h" +#include "lluictrlfactory.h" +#include "llrect.h" +#include "llfocusmgr.h" +#include "sound_ids.h" +#include "lltimer.h" +#include "llmath.h" + +#include "audioengine.h" +#include "llclipboard.h" +#include "llscrollbar.h" +#include "llstl.h" +#include "llkeyboard.h" +#include "llkeywords.h" +#include "llundo.h" +#include "llviewborder.h" +#include "llcontrol.h" +#include "llimagegl.h" +#include "llwindow.h" +#include "llglheaders.h" +#include <queue> + +// +// Globals +// + +BOOL gDebugTextEditorTips = FALSE; + +// +// Constants +// + +const S32 UI_TEXTEDITOR_BUFFER_BLOCK_SIZE = 512; + +const S32 UI_TEXTEDITOR_BORDER = 1; +const S32 UI_TEXTEDITOR_H_PAD = 4; +const S32 UI_TEXTEDITOR_V_PAD_TOP = 4; +const F32 CURSOR_FLASH_DELAY = 1.0f; // in seconds +const S32 CURSOR_THICKNESS = 2; +const S32 SPACES_PER_TAB = 4; + +LLColor4 LLTextEditor::mLinkColor = LLColor4::blue; +void (* LLTextEditor::mURLcallback)(const char*) = NULL; +BOOL (* LLTextEditor::mSecondlifeURLcallback)(LLString) = NULL; + +/////////////////////////////////////////////////////////////////// +//virtuals +BOOL LLTextCmd::canExtend(S32 pos) +{ + return FALSE; +} + +void LLTextCmd::blockExtensions() +{ +} + +BOOL LLTextCmd::extendAndExecute( LLTextEditor* editor, S32 pos, llwchar c, S32* delta ) +{ + llassert(0); + return 0; +} + +BOOL LLTextCmd::hasExtCharValue( llwchar value ) +{ + return FALSE; +} + +// Utility funcs +S32 LLTextCmd::insert(LLTextEditor* editor, S32 pos, const LLWString &utf8str) +{ + return editor->insertStringNoUndo( pos, utf8str ); +} +S32 LLTextCmd::remove(LLTextEditor* editor, S32 pos, S32 length) +{ + return editor->removeStringNoUndo( pos, length ); +} +S32 LLTextCmd::overwrite(LLTextEditor* editor, S32 pos, llwchar wc) +{ + return editor->overwriteCharNoUndo(pos, wc); +} + +/////////////////////////////////////////////////////////////////// + +class LLTextCmdInsert : public LLTextCmd +{ +public: + LLTextCmdInsert(S32 pos, BOOL group_with_next, const LLWString &ws) + : LLTextCmd(pos, group_with_next), mString(ws) + { + } + virtual BOOL execute( LLTextEditor* editor, S32* delta ) + { + *delta = insert(editor, mPos, mString ); + LLWString::truncate(mString, *delta); + //mString = wstring_truncate(mString, *delta); + return (*delta != 0); + } + virtual S32 undo( LLTextEditor* editor ) + { + remove(editor, mPos, mString.length() ); + return mPos; + } + virtual S32 redo( LLTextEditor* editor ) + { + insert(editor, mPos, mString ); + return mPos + mString.length(); + } + +private: + LLWString mString; +}; + +/////////////////////////////////////////////////////////////////// + +class LLTextCmdAddChar : public LLTextCmd +{ +public: + LLTextCmdAddChar( S32 pos, BOOL group_with_next, llwchar wc) + : LLTextCmd(pos, group_with_next), mString(1, wc), mBlockExtensions(FALSE) + { + } + virtual void blockExtensions() + { + mBlockExtensions = TRUE; + } + virtual BOOL canExtend(S32 pos) + { + return !mBlockExtensions && (pos == mPos + (S32)mString.length()); + } + virtual BOOL execute( LLTextEditor* editor, S32* delta ) + { + *delta = insert(editor, mPos, mString); + LLWString::truncate(mString, *delta); + //mString = wstring_truncate(mString, *delta); + return (*delta != 0); + } + virtual BOOL extendAndExecute( LLTextEditor* editor, S32 pos, llwchar wc, S32* delta ) + { + LLWString ws; + ws += wc; + + *delta = insert(editor, pos, ws); + if( *delta > 0 ) + { + mString += wc; + } + return (*delta != 0); + } + virtual S32 undo( LLTextEditor* editor ) + { + remove(editor, mPos, mString.length() ); + return mPos; + } + virtual S32 redo( LLTextEditor* editor ) + { + insert(editor, mPos, mString ); + return mPos + mString.length(); + } + +private: + LLWString mString; + BOOL mBlockExtensions; + +}; + +/////////////////////////////////////////////////////////////////// + +class LLTextCmdOverwriteChar : public LLTextCmd +{ +public: + LLTextCmdOverwriteChar( S32 pos, BOOL group_with_next, llwchar wc) + : LLTextCmd(pos, group_with_next), mChar(wc), mOldChar(0) {} + + virtual BOOL execute( LLTextEditor* editor, S32* delta ) + { + mOldChar = editor->getWChar(mPos); + overwrite(editor, mPos, mChar); + *delta = 0; + return TRUE; + } + virtual S32 undo( LLTextEditor* editor ) + { + overwrite(editor, mPos, mOldChar); + return mPos; + } + virtual S32 redo( LLTextEditor* editor ) + { + overwrite(editor, mPos, mChar); + return mPos+1; + } + +private: + llwchar mChar; + llwchar mOldChar; +}; + +/////////////////////////////////////////////////////////////////// + +class LLTextCmdRemove : public LLTextCmd +{ +public: + LLTextCmdRemove( S32 pos, BOOL group_with_next, S32 len ) : + LLTextCmd(pos, group_with_next), mLen(len) + { + } + virtual BOOL execute( LLTextEditor* editor, S32* delta ) + { + mString = editor->getWSubString(mPos, mLen); + *delta = remove(editor, mPos, mLen ); + return (*delta != 0); + } + virtual S32 undo( LLTextEditor* editor ) + { + insert(editor, mPos, mString ); + return mPos + mString.length(); + } + virtual S32 redo( LLTextEditor* editor ) + { + remove(editor, mPos, mLen ); + return mPos; + } +private: + LLWString mString; + S32 mLen; +}; + +/////////////////////////////////////////////////////////////////// + +// +// Member functions +// + +LLTextEditor::LLTextEditor( + const LLString& name, + const LLRect& rect, + S32 max_length, + const LLString &default_text, + const LLFontGL* font, + BOOL allow_embedded_items) + : + LLUICtrl( name, rect, TRUE, NULL, NULL, FOLLOWS_TOP | FOLLOWS_LEFT ), + mTextIsUpToDate(TRUE), + mMaxTextLength( max_length ), + mBaseDocIsPristine(TRUE), + mPristineCmd( NULL ), + mLastCmd( NULL ), + mCursorPos( 0 ), + mIsSelecting( FALSE ), + mSelectionStart( 0 ), + mSelectionEnd( 0 ), + mOnScrollEndCallback( NULL ), + mOnScrollEndData( NULL ), + mCursorColor( LLUI::sColorsGroup->getColor( "TextCursorColor" ) ), + mFgColor( LLUI::sColorsGroup->getColor( "TextFgColor" ) ), + mReadOnlyFgColor( LLUI::sColorsGroup->getColor( "TextFgReadOnlyColor" ) ), + mWriteableBgColor( LLUI::sColorsGroup->getColor( "TextBgWriteableColor" ) ), + mReadOnlyBgColor( LLUI::sColorsGroup->getColor( "TextBgReadOnlyColor" ) ), + mFocusBgColor( LLUI::sColorsGroup->getColor( "TextBgFocusColor" ) ), + mReadOnly(FALSE), + mWordWrap( FALSE ), + mTabToNextField( TRUE ), + mCommitOnFocusLost( FALSE ), + mTakesFocus( TRUE ), + mHideScrollbarForShortDocs( FALSE ), + mTakesNonScrollClicks( TRUE ), + mAllowEmbeddedItems( allow_embedded_items ), + mAcceptCallingCardNames(FALSE), + mHandleEditKeysDirectly( FALSE ), + mMouseDownX(0), + mMouseDownY(0), + mLastSelectionX(-1), + mLastSelectionY(-1) +{ + mSourceID.generate(); + + if (font) + { + mGLFont = font; + } + else + { + mGLFont = LLFontGL::sSansSerif; + } + + updateTextRect(); + + S32 line_height = llround( mGLFont->getLineHeight() ); + S32 page_size = mTextRect.getHeight() / line_height; + + // Init the scrollbar + LLRect scroll_rect; + scroll_rect.setOriginAndSize( + mRect.getWidth() - UI_TEXTEDITOR_BORDER - SCROLLBAR_SIZE, + UI_TEXTEDITOR_BORDER, + SCROLLBAR_SIZE, + mRect.getHeight() - 2 * UI_TEXTEDITOR_BORDER ); + S32 lines_in_doc = getLineCount(); + mScrollbar = new LLScrollbar( "Scrollbar", scroll_rect, + LLScrollbar::VERTICAL, + lines_in_doc, + 0, + page_size, + NULL, this ); + mScrollbar->setFollowsRight(); + mScrollbar->setFollowsTop(); + mScrollbar->setFollowsBottom(); + mScrollbar->setEnabled( TRUE ); + mScrollbar->setVisible( TRUE ); + mScrollbar->setOnScrollEndCallback(mOnScrollEndCallback, mOnScrollEndData); + addChild(mScrollbar); + + mBorder = new LLViewBorder( "text ed border", LLRect(0, mRect.getHeight(), mRect.getWidth(), 0), LLViewBorder::BEVEL_IN, LLViewBorder::STYLE_LINE, UI_TEXTEDITOR_BORDER ); + addChild( mBorder ); + + setText(default_text); + + mParseHTML=FALSE; + mHTML=""; +} + + +LLTextEditor::~LLTextEditor() +{ + gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit() + + // Route menu back to the default + if( gEditMenuHandler == this ) + { + gEditMenuHandler = NULL; + } + + // Scrollbar is deleted by LLView + mHoverSegment = NULL; + std::for_each(mSegments.begin(), mSegments.end(), DeletePointer()); + + std::for_each(mUndoStack.begin(), mUndoStack.end(), DeletePointer()); +} + +//virtual +LLString LLTextEditor::getWidgetTag() const +{ + return LL_TEXT_EDITOR_TAG; +} + +void LLTextEditor::setTrackColor( const LLColor4& color ) +{ + mScrollbar->setTrackColor(color); +} + +void LLTextEditor::setThumbColor( const LLColor4& color ) +{ + mScrollbar->setThumbColor(color); +} + +void LLTextEditor::setHighlightColor( const LLColor4& color ) +{ + mScrollbar->setHighlightColor(color); +} + +void LLTextEditor::setShadowColor( const LLColor4& color ) +{ + mScrollbar->setShadowColor(color); +} + +void LLTextEditor::updateLineStartList(S32 startpos) +{ + updateSegments(); + + bindEmbeddedChars( mGLFont ); + + S32 seg_num = mSegments.size(); + S32 seg_idx = 0; + S32 seg_offset = 0; + + if (!mLineStartList.empty()) + { + getSegmentAndOffset(startpos, &seg_idx, &seg_offset); + line_info t(seg_idx, seg_offset); + line_list_t::iterator iter = std::upper_bound(mLineStartList.begin(), mLineStartList.end(), t, line_info_compare()); + if (iter != mLineStartList.begin()) --iter; + seg_idx = iter->mSegment; + seg_offset = iter->mOffset; + mLineStartList.erase(iter, mLineStartList.end()); + } + + while( seg_idx < seg_num ) + { + mLineStartList.push_back(line_info(seg_idx,seg_offset)); + BOOL line_ended = FALSE; + S32 line_width = 0; + while(!line_ended && seg_idx < seg_num) + { + LLTextSegment* segment = mSegments[seg_idx]; + S32 start_idx = segment->getStart() + seg_offset; + S32 end_idx = start_idx; + while (end_idx < segment->getEnd() && mWText[end_idx] != '\n') + { + end_idx++; + } + if (start_idx == end_idx) + { + if (end_idx >= segment->getEnd()) + { + // empty segment + seg_idx++; + seg_offset = 0; + } + else + { + // empty line + line_ended = TRUE; + seg_offset++; + } + } + else + { + const llwchar* str = mWText.c_str() + start_idx; + S32 drawn = mGLFont->maxDrawableChars(str, (F32)mTextRect.getWidth() - line_width, + end_idx - start_idx, mWordWrap, mAllowEmbeddedItems ); + if( 0 == drawn && line_width == 0) + { + // If at the beginning of a line, draw at least one character, even if it doesn't all fit. + drawn = 1; + } + seg_offset += drawn; + line_width += mGLFont->getWidth(str, 0, drawn, mAllowEmbeddedItems); + end_idx = segment->getStart() + seg_offset; + if (end_idx < segment->getEnd()) + { + line_ended = TRUE; + if (mWText[end_idx] == '\n') + { + seg_offset++; // skip newline + } + } + else + { + // finished with segment + seg_idx++; + seg_offset = 0; + } + } + } + } + + unbindEmbeddedChars(mGLFont); + + mScrollbar->setDocSize( getLineCount() ); + + if (mHideScrollbarForShortDocs) + { + BOOL short_doc = (mScrollbar->getDocSize() <= mScrollbar->getPageSize()); + mScrollbar->setVisible(!short_doc); + } + +} + +//////////////////////////////////////////////////////////// +// LLTextEditor +// Public methods + +//static +BOOL LLTextEditor::isPartOfWord(llwchar c) { return (c == '_') || isalnum(c); } + + + +void LLTextEditor::truncate() +{ + if (mWText.size() > (size_t)mMaxTextLength) + { + LLWString::truncate(mWText, mMaxTextLength); + mTextIsUpToDate = FALSE; + } +} + +void LLTextEditor::setText(const LLString &utf8str) +{ + mUTF8Text = utf8str; + mWText = utf8str_to_wstring(utf8str); + mTextIsUpToDate = TRUE; + + truncate(); + blockUndo(); + + setCursorPos(0); + deselect(); + + updateLineStartList(); + updateScrollFromCursor(); +} + +void LLTextEditor::setWText(const LLWString &wtext) +{ + mWText = wtext; + mUTF8Text.clear(); + mTextIsUpToDate = FALSE; + + truncate(); + blockUndo(); + + setCursorPos(0); + deselect(); + + updateLineStartList(); + updateScrollFromCursor(); +} + +void LLTextEditor::setValue(const LLSD& value) +{ + setText(value.asString()); +} + +const LLString& LLTextEditor::getText() const +{ + if (!mTextIsUpToDate) + { + if (mAllowEmbeddedItems) + { + llwarns << "getText() called on text with embedded items (not supported)" << llendl; + } + mUTF8Text = wstring_to_utf8str(mWText); + mTextIsUpToDate = TRUE; + } + return mUTF8Text; +} + +LLSD LLTextEditor::getValue() const +{ + return LLSD(getText()); +} + +void LLTextEditor::setWordWrap(BOOL b) +{ + mWordWrap = b; + + setCursorPos(0); + deselect(); + + updateLineStartList(); + updateScrollFromCursor(); +} + + +void LLTextEditor::setBorderVisible(BOOL b) +{ + mBorder->setVisible(b); +} + + + +void LLTextEditor::setHideScrollbarForShortDocs(BOOL b) +{ + mHideScrollbarForShortDocs = b; + + if (mHideScrollbarForShortDocs) + { + BOOL short_doc = (mScrollbar->getDocSize() <= mScrollbar->getPageSize()); + mScrollbar->setVisible(!short_doc); + } +} + +void LLTextEditor::selectNext(const LLString& search_text_in, BOOL case_insensitive, BOOL wrap) +{ + if (search_text_in.empty()) + { + return; + } + + LLWString text = getWText(); + LLWString search_text = utf8str_to_wstring(search_text_in); + if (case_insensitive) + { + LLWString::toLower(text); + LLWString::toLower(search_text); + } + + if (mIsSelecting) + { + LLWString selected_text = text.substr(mSelectionEnd, mSelectionStart - mSelectionEnd); + + if (selected_text == search_text) + { + // We already have this word selected, we are searching for the next. + mCursorPos += search_text.size(); + } + } + + S32 loc = text.find(search_text,mCursorPos); + + // If Maybe we wrapped, search again + if (wrap && (-1 == loc)) + { + loc = text.find(search_text); + } + + // If still -1, then search_text just isn't found. + if (-1 == loc) + { + mIsSelecting = FALSE; + mSelectionEnd = 0; + mSelectionStart = 0; + return; + } + + setCursorPos(loc); + + mIsSelecting = TRUE; + mSelectionEnd = mCursorPos; + mSelectionStart = llmin((S32)getLength(), (S32)(mCursorPos + search_text.size())); +} + +BOOL LLTextEditor::replaceText(const LLString& search_text_in, const LLString& replace_text, + BOOL case_insensitive, BOOL wrap) +{ + BOOL replaced = FALSE; + + if (search_text_in.empty()) + { + return replaced; + } + + LLWString search_text = utf8str_to_wstring(search_text_in); + if (mIsSelecting) + { + LLWString text = getWText(); + LLWString selected_text = text.substr(mSelectionEnd, mSelectionStart - mSelectionEnd); + + if (case_insensitive) + { + LLWString::toLower(selected_text); + LLWString::toLower(search_text); + } + + if (selected_text == search_text) + { + insertText(replace_text); + replaced = TRUE; + } + } + + selectNext(search_text_in, case_insensitive, wrap); + return replaced; +} + +void LLTextEditor::replaceTextAll(const LLString& search_text, const LLString& replace_text, BOOL case_insensitive) +{ + S32 cur_pos = mScrollbar->getDocPos(); + + setCursorPos(0); + selectNext(search_text, case_insensitive, FALSE); + + BOOL replaced = TRUE; + while ( replaced ) + { + replaced = replaceText(search_text,replace_text, case_insensitive, FALSE); + } + + mScrollbar->setDocPos(cur_pos); +} + +void LLTextEditor::setTakesNonScrollClicks(BOOL b) +{ + mTakesNonScrollClicks = b; +} + + +// Picks a new cursor position based on the screen size of text being drawn. +void LLTextEditor::setCursorAtLocalPos( S32 local_x, S32 local_y, BOOL round ) +{ + setCursorPos(getCursorPosFromLocalCoord(local_x, local_y, round)); +} + +S32 LLTextEditor::prevWordPos(S32 cursorPos) const +{ + const LLWString& wtext = mWText; + while( (cursorPos > 0) && (wtext[cursorPos-1] == ' ') ) + { + cursorPos--; + } + while( (cursorPos > 0) && isPartOfWord( wtext[cursorPos-1] ) ) + { + cursorPos--; + } + return cursorPos; +} + +S32 LLTextEditor::nextWordPos(S32 cursorPos) const +{ + const LLWString& wtext = mWText; + while( (cursorPos < getLength()) && isPartOfWord( wtext[cursorPos+1] ) ) + { + cursorPos++; + } + while( (cursorPos < getLength()) && (wtext[cursorPos+1] == ' ') ) + { + cursorPos++; + } + return cursorPos; +} + +S32 LLTextEditor::getLineCount() +{ + return mLineStartList.size(); +} + +S32 LLTextEditor::getLineStart( S32 line ) +{ + S32 num_lines = getLineCount(); + if (num_lines == 0) + { + return 0; + } + line = llclamp(line, 0, num_lines-1); + S32 segidx = mLineStartList[line].mSegment; + S32 segoffset = mLineStartList[line].mOffset; + LLTextSegment* seg = mSegments[segidx]; + S32 res = seg->getStart() + segoffset; + if (res > seg->getEnd()) llerrs << "wtf" << llendl; + return res; +} + +// Given an offset into text (pos), find the corresponding line (from the start of the doc) and an offset into the line. +void LLTextEditor::getLineAndOffset( S32 startpos, S32* linep, S32* offsetp ) +{ + if (mLineStartList.empty()) + { + *linep = 0; + *offsetp = startpos; + } + else + { + S32 seg_idx, seg_offset; + getSegmentAndOffset( startpos, &seg_idx, &seg_offset ); + + line_info tline(seg_idx, seg_offset); + line_list_t::iterator iter = std::upper_bound(mLineStartList.begin(), mLineStartList.end(), tline, line_info_compare()); + if (iter != mLineStartList.begin()) --iter; + *linep = iter - mLineStartList.begin(); + S32 line_start = mSegments[iter->mSegment]->getStart() + iter->mOffset; + *offsetp = startpos - line_start; + } +} + +void LLTextEditor::getSegmentAndOffset( S32 startpos, S32* segidxp, S32* offsetp ) +{ + if (mSegments.empty()) + { + *segidxp = -1; + *offsetp = startpos; + } + + LLTextSegment tseg(startpos); + segment_list_t::iterator seg_iter; + seg_iter = std::upper_bound(mSegments.begin(), mSegments.end(), &tseg, LLTextSegment::compare()); + if (seg_iter != mSegments.begin()) --seg_iter; + *segidxp = seg_iter - mSegments.begin(); + *offsetp = startpos - (*seg_iter)->getStart(); +} + +const LLWString& LLTextEditor::getWText() const +{ + return mWText; +} + +S32 LLTextEditor::getLength() const +{ + return mWText.length(); +} + +llwchar LLTextEditor::getWChar(S32 pos) +{ + return mWText[pos]; +} + +LLWString LLTextEditor::getWSubString(S32 pos, S32 len) +{ + return mWText.substr(pos, len); +} + +S32 LLTextEditor::getCursorPosFromLocalCoord( S32 local_x, S32 local_y, BOOL round ) +{ + // If round is true, if the position is on the right half of a character, the cursor + // will be put to its right. If round is false, the cursor will always be put to the + // character's left. + + // Figure out which line we're nearest to. + S32 total_lines = getLineCount(); + S32 line_height = llround( mGLFont->getLineHeight() ); + S32 max_visible_lines = mTextRect.getHeight() / line_height; + S32 scroll_lines = mScrollbar->getDocPos(); + S32 visible_lines = llmin( total_lines - scroll_lines, max_visible_lines ); // Lines currently visible + + //S32 line = S32( 0.5f + ((mTextRect.mTop - local_y) / mGLFont->getLineHeight()) ); + S32 line = (mTextRect.mTop - 1 - local_y) / line_height; + if (line >= total_lines) + { + return getLength(); // past the end + } + + line = llclamp( line, 0, visible_lines ) + scroll_lines; + + S32 line_start = getLineStart(line); + S32 next_start = getLineStart(line+1); + S32 line_end = (next_start != line_start) ? next_start - 1 : getLength(); + + if(line_start == -1) + { + return 0; + } + else + { + S32 line_len = line_end - line_start; + S32 pos; + + if (mAllowEmbeddedItems) + { + // Figure out which character we're nearest to. + bindEmbeddedChars(mGLFont); + pos = mGLFont->charFromPixelOffset(mWText.c_str(), line_start, + (F32)(local_x - mTextRect.mLeft), + (F32)(mTextRect.getWidth()), + line_len, + round, TRUE); + unbindEmbeddedChars(mGLFont); + } + else + { + pos = mGLFont->charFromPixelOffset(mWText.c_str(), line_start, + (F32)(local_x - mTextRect.mLeft), + (F32)mTextRect.getWidth(), + line_len, + round); + } + + return line_start + pos; + } +} + +void LLTextEditor::setCursor(S32 row, S32 column) +{ + const llwchar* doc = mWText.c_str(); + const char CR = 10; + while(row--) + { + while (CR != *doc++); + } + doc += column; + setCursorPos(doc - mWText.c_str()); + updateScrollFromCursor(); +} + +void LLTextEditor::setCursorPos(S32 offset) +{ + mCursorPos = llclamp(offset, 0, (S32)getLength()); + updateScrollFromCursor(); +} + + +BOOL LLTextEditor::canDeselect() +{ + return hasSelection(); +} + + +void LLTextEditor::deselect() +{ + mSelectionStart = 0; + mSelectionEnd = 0; + mIsSelecting = FALSE; +} + + +void LLTextEditor::startSelection() +{ + if( !mIsSelecting ) + { + mIsSelecting = TRUE; + mSelectionStart = mCursorPos; + mSelectionEnd = mCursorPos; + } +} + +void LLTextEditor::endSelection() +{ + if( mIsSelecting ) + { + mIsSelecting = FALSE; + mSelectionEnd = mCursorPos; + } + if (mParseHTML && mHTML.length() > 0) + { + //Special handling for slurls + if ( (mSecondlifeURLcallback!=NULL) && !(*mSecondlifeURLcallback)(mHTML) ) + { + if (mURLcallback!=NULL) (*mURLcallback)(mHTML.c_str()); + + //load_url(url.c_str()); + } + mHTML=""; + } +} + +BOOL LLTextEditor::selectionContainsLineBreaks() +{ + if (hasSelection()) + { + S32 left = llmin(mSelectionStart, mSelectionEnd); + S32 right = left + abs(mSelectionStart - mSelectionEnd); + + const LLWString &wtext = mWText; + for( S32 i = left; i < right; i++ ) + { + if (wtext[i] == '\n') + { + return TRUE; + } + } + } + return FALSE; +} + + +S32 LLTextEditor::indentLine( S32 pos, S32 spaces ) +{ + // Assumes that pos is at the start of the line + // spaces may be positive (indent) or negative (unindent). + // Returns the actual number of characters added or removed. + + llassert(pos >= 0); + llassert(pos <= getLength() ); + + S32 delta_spaces = 0; + + if (spaces >= 0) + { + // Indent + for(S32 i=0; i < spaces; i++) + { + delta_spaces += addChar(pos, ' '); + } + } + else + { + // Unindent + for(S32 i=0; i < -spaces; i++) + { + const LLWString &wtext = mWText; + if (wtext[pos] == ' ') + { + delta_spaces += remove( pos, 1, FALSE ); + } + } + } + + return delta_spaces; +} + +void LLTextEditor::indentSelectedLines( S32 spaces ) +{ + if( hasSelection() ) + { + const LLWString &text = mWText; + S32 left = llmin( mSelectionStart, mSelectionEnd ); + S32 right = left + abs( mSelectionStart - mSelectionEnd ); + BOOL cursor_on_right = (mSelectionEnd > mSelectionStart); + S32 cur = left; + + // Expand left to start of line + while( (cur > 0) && (text[cur] != '\n') ) + { + cur--; + } + left = cur; + if( cur > 0 ) + { + left++; + } + + // Expand right to end of line + if( text[right - 1] == '\n' ) + { + right--; + } + else + { + while( (text[right] != '\n') && (right <= getLength() ) ) + { + right++; + } + } + + // Find each start-of-line and indent it + do + { + if( text[cur] == '\n' ) + { + cur++; + } + + S32 delta_spaces = indentLine( cur, spaces ); + if( delta_spaces > 0 ) + { + cur += delta_spaces; + } + right += delta_spaces; + + //text = mWText; + + // Find the next new line + while( (cur < right) && (text[cur] != '\n') ) + { + cur++; + } + } + while( cur < right ); + + if( (right < getLength()) && (text[right] == '\n') ) + { + right++; + } + + // Set the selection and cursor + if( cursor_on_right ) + { + mSelectionStart = left; + mSelectionEnd = right; + } + else + { + mSelectionStart = right; + mSelectionEnd = left; + } + mCursorPos = mSelectionEnd; + } +} + + +BOOL LLTextEditor::canSelectAll() +{ + return TRUE; +} + +void LLTextEditor::selectAll() +{ + mSelectionStart = getLength(); + mSelectionEnd = 0; + mCursorPos = mSelectionEnd; +} + + +BOOL LLTextEditor::handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect_screen) +{ + if (pointInView(x, y) && getVisible()) + { + 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->handleToolTip(local_x, local_y, msg, sticky_rect_screen ) ) + { + return TRUE; + } + } + + if( mSegments.empty() ) + { + return TRUE; + } + + LLTextSegment* cur_segment = getSegmentAtLocalPos( x, y ); + if( cur_segment ) + { + BOOL has_tool_tip = FALSE; + has_tool_tip = cur_segment->getToolTip( msg ); + + if( has_tool_tip ) + { + // Just use a slop area around the cursor + // Convert rect local to screen coordinates + S32 SLOP = 8; + localPointToScreen( + x - SLOP, y - SLOP, + &(sticky_rect_screen->mLeft), &(sticky_rect_screen->mBottom) ); + sticky_rect_screen->mRight = sticky_rect_screen->mLeft + 2 * SLOP; + sticky_rect_screen->mTop = sticky_rect_screen->mBottom + 2 * SLOP; + } + } + return TRUE; + } + return FALSE; +} + +BOOL LLTextEditor::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + // Pretend the mouse is over the scrollbar + if (getVisible()) + { + return mScrollbar->handleScrollWheel( 0, 0, clicks ); + } + else + { + return FALSE; + } +} + +BOOL LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + // Let scrollbar have first dibs + handled = LLView::childrenHandleMouseDown(x, y, mask) != NULL; + + if( !handled && mTakesNonScrollClicks) + { + if (!(mask & MASK_SHIFT)) + { + deselect(); + } + + BOOL start_select = TRUE; + if( start_select ) + { + // If we're not scrolling (handled by child), then we're selecting + if (mask & MASK_SHIFT) + { + S32 old_cursor_pos = mCursorPos; + setCursorAtLocalPos( x, y, TRUE ); + + if (hasSelection()) + { + /* Mac-like behavior - extend selection towards the cursor + if (mCursorPos < mSelectionStart + && mCursorPos < mSelectionEnd) + { + // ...left of selection + mSelectionStart = llmax(mSelectionStart, mSelectionEnd); + mSelectionEnd = mCursorPos; + } + else if (mCursorPos > mSelectionStart + && mCursorPos > mSelectionEnd) + { + // ...right of selection + mSelectionStart = llmin(mSelectionStart, mSelectionEnd); + mSelectionEnd = mCursorPos; + } + else + { + mSelectionEnd = mCursorPos; + } + */ + // Windows behavior + mSelectionEnd = mCursorPos; + } + else + { + mSelectionStart = old_cursor_pos; + mSelectionEnd = mCursorPos; + } + // assume we're starting a drag select + mIsSelecting = TRUE; + } + else + { + setCursorAtLocalPos( x, y, TRUE ); + startSelection(); + } + gFocusMgr.setMouseCapture( this, &LLTextEditor::onMouseCaptureLost ); + } + + handled = TRUE; + } + + if (mTakesFocus) + { + setFocus( TRUE ); + handled = TRUE; + } + + // Delay cursor flashing + mKeystrokeTimer.reset(); + + return handled; +} + + +BOOL LLTextEditor::handleHover(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + mHoverSegment = NULL; + if( getVisible() ) + { + if(gFocusMgr.getMouseCapture() == this ) + { + if( mIsSelecting ) + { + if (x != mLastSelectionX || y != mLastSelectionY) + { + mLastSelectionX = x; + mLastSelectionY = y; + } + + if( y > mTextRect.mTop ) + { + mScrollbar->setDocPos( mScrollbar->getDocPos() - 1 ); + } + else + if( y < mTextRect.mBottom ) + { + mScrollbar->setDocPos( mScrollbar->getDocPos() + 1 ); + } + + setCursorAtLocalPos( x, y, TRUE ); + mSelectionEnd = mCursorPos; + + updateScrollFromCursor(); + } + + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" << llendl; + getWindow()->setCursor(UI_CURSOR_IBEAM); + handled = TRUE; + } + + if( !handled ) + { + // Pass to children + handled = LLView::childrenHandleHover(x, y, mask) != NULL; + } + + if( handled ) + { + // Delay cursor flashing + mKeystrokeTimer.reset(); + } + + // Opaque + if( !handled && mTakesNonScrollClicks) + { + // Check to see if we're over an HTML-style link + if( !mSegments.empty() ) + { + LLTextSegment* cur_segment = getSegmentAtLocalPos( x, y ); + if( cur_segment ) + { + if(cur_segment->getStyle().isLink()) + { + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (over link, inactive)" << llendl; + getWindow()->setCursor(UI_CURSOR_HAND); + handled = TRUE; + } + else + if(cur_segment->getStyle().getIsEmbeddedItem()) + { + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (over embedded item, inactive)" << llendl; + getWindow()->setCursor(UI_CURSOR_HAND); + //getWindow()->setCursor(UI_CURSOR_ARROW); + handled = TRUE; + } + mHoverSegment = cur_segment; + } + } + + if( !handled ) + { + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (inactive)" << llendl; + if (!mScrollbar->getVisible() || x < mRect.getWidth() - SCROLLBAR_SIZE) + { + getWindow()->setCursor(UI_CURSOR_IBEAM); + } + else + { + getWindow()->setCursor(UI_CURSOR_ARROW); + } + handled = TRUE; + } + } + } + + if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax())) + { + mOnScrollEndCallback(mOnScrollEndData); + } + return handled; +} + + +BOOL LLTextEditor::handleMouseUp(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + // let scrollbar have first dibs + handled = LLView::childrenHandleMouseUp(x, y, mask) != NULL; + + if( !handled && mTakesNonScrollClicks) + { + if( mIsSelecting ) + { + // Finish selection + if( y > mTextRect.mTop ) + { + mScrollbar->setDocPos( mScrollbar->getDocPos() - 1 ); + } + else + if( y < mTextRect.mBottom ) + { + mScrollbar->setDocPos( mScrollbar->getDocPos() + 1 ); + } + + setCursorAtLocalPos( x, y, TRUE ); + endSelection(); + + updateScrollFromCursor(); + } + + if( !hasSelection() ) + { + handleMouseUpOverSegment( x, y, mask ); + } + + handled = TRUE; + } + + // Delay cursor flashing + mKeystrokeTimer.reset(); + + if( gFocusMgr.getMouseCapture() == this ) + { + gFocusMgr.setMouseCapture( NULL, NULL ); + handled = TRUE; + } + + return handled; +} + + +BOOL LLTextEditor::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + // let scrollbar have first dibs + handled = LLView::childrenHandleDoubleClick(x, y, mask) != NULL; + + if( !handled && mTakesNonScrollClicks) + { + if (mTakesFocus) + { + setFocus( TRUE ); + } + + setCursorAtLocalPos( x, y, FALSE ); + deselect(); + + const LLWString &text = mWText; + + if( isPartOfWord( text[mCursorPos] ) ) + { + // Select word the cursor is over + while ((mCursorPos > 0) && isPartOfWord(text[mCursorPos-1])) + { + mCursorPos--; + } + startSelection(); + + while ((mCursorPos < (S32)text.length()) && isPartOfWord( text[mCursorPos] ) ) + { + mCursorPos++; + } + + mSelectionEnd = mCursorPos; + } + else if ((mCursorPos < (S32)text.length()) && !iswspace( text[mCursorPos]) ) + { + // Select the character the cursor is over + startSelection(); + mCursorPos++; + mSelectionEnd = mCursorPos; + } + + // We don't want handleMouseUp() to "finish" the selection (and thereby + // set mSelectionEnd to where the mouse is), so we finish the selection here. + mIsSelecting = FALSE; + + // delay cursor flashing + mKeystrokeTimer.reset(); + + handled = TRUE; + } + return handled; +} + + +// Allow calling cards to be dropped onto text fields. Append the name and +// a carriage return. +// virtual +BOOL LLTextEditor::handleDragAndDrop(S32 x, S32 y, MASK mask, + BOOL drop, EDragAndDropType cargo_type, void *cargo_data, + EAcceptance *accept, + LLString& tooltip_msg) +{ + *accept = ACCEPT_NO; + + return TRUE; +} + +//---------------------------------------------------------------------------- +// Returns change in number of characters in mText + +S32 LLTextEditor::execute( LLTextCmd* cmd ) +{ + S32 delta = 0; + if( cmd->execute(this, &delta) ) + { + // Delete top of undo stack + undo_stack_t::iterator enditer = std::find(mUndoStack.begin(), mUndoStack.end(), mLastCmd); + if (enditer != mUndoStack.begin()) + { + --enditer; + std::for_each(mUndoStack.begin(), enditer, DeletePointer()); + mUndoStack.erase(mUndoStack.begin(), enditer); + } + // Push the new command is now on the top (front) of the undo stack. + mUndoStack.push_front(cmd); + mLastCmd = cmd; + } + else + { + // Operation failed, so don't put it on the undo stack. + delete cmd; + } + + return delta; +} + +S32 LLTextEditor::insert(const S32 pos, const LLWString &wstr, const BOOL group_with_next_op) +{ + return execute( new LLTextCmdInsert( pos, group_with_next_op, wstr ) ); +} + +S32 LLTextEditor::remove(const S32 pos, const S32 length, const BOOL group_with_next_op) +{ + return execute( new LLTextCmdRemove( pos, group_with_next_op, length ) ); +} + +S32 LLTextEditor::append(const LLWString &wstr, const BOOL group_with_next_op) +{ + return insert(mWText.length(), wstr, group_with_next_op); +} + +S32 LLTextEditor::overwriteChar(S32 pos, llwchar wc) +{ + if ((S32)mWText.length() == pos) + { + return addChar(pos, wc); + } + else + { + return execute(new LLTextCmdOverwriteChar(pos, FALSE, wc)); + } +} + +// Remove a single character from the text. Tries to remove +// a pseudo-tab (up to for spaces in a row) +void LLTextEditor::removeCharOrTab() +{ + if( getEnabled() ) + { + if( mCursorPos > 0 ) + { + S32 chars_to_remove = 1; + + const LLWString &text = mWText; + if (text[mCursorPos - 1] == ' ') + { + // Try to remove a "tab" + S32 line, offset; + getLineAndOffset(mCursorPos, &line, &offset); + if (offset > 0) + { + chars_to_remove = offset % SPACES_PER_TAB; + if( chars_to_remove == 0 ) + { + chars_to_remove = SPACES_PER_TAB; + } + + for( S32 i = 0; i < chars_to_remove; i++ ) + { + if (text[ mCursorPos - i - 1] != ' ') + { + // Fewer than a full tab's worth of spaces, so + // just delete a single character. + chars_to_remove = 1; + break; + } + } + } + } + + for (S32 i = 0; i < chars_to_remove; i++) + { + setCursorPos(mCursorPos - 1); + remove( mCursorPos, 1, FALSE ); + } + } + else + { + reportBadKeystroke(); + } + } +} + +// Remove a single character from the text +S32 LLTextEditor::removeChar(S32 pos) +{ + return remove( pos, 1, FALSE ); +} + +void LLTextEditor::removeChar() +{ + if (getEnabled()) + { + if (mCursorPos > 0) + { + setCursorPos(mCursorPos - 1); + removeChar(mCursorPos); + } + else + { + reportBadKeystroke(); + } + } +} + +// Add a single character to the text +S32 LLTextEditor::addChar(S32 pos, llwchar wc) +{ + if ((S32)mWText.length() == mMaxTextLength) + { + make_ui_sound("UISndBadKeystroke"); + return 0; + } + + if (mLastCmd && mLastCmd->canExtend(pos)) + { + S32 delta = 0; + mLastCmd->extendAndExecute(this, pos, wc, &delta); + return delta; + } + else + { + return execute(new LLTextCmdAddChar(pos, FALSE, wc)); + } +} + +void LLTextEditor::addChar(llwchar wc) +{ + if( getEnabled() ) + { + if( hasSelection() ) + { + deleteSelection(TRUE); + } + else if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) + { + removeChar(mCursorPos); + } + + setCursorPos(mCursorPos + addChar( mCursorPos, wc )); + } +} + + +BOOL LLTextEditor::handleSelectionKey(const KEY key, const MASK mask) +{ + BOOL handled = FALSE; + + if( mask & MASK_SHIFT ) + { + handled = TRUE; + + switch( key ) + { + case KEY_LEFT: + if( 0 < mCursorPos ) + { + startSelection(); + mCursorPos--; + if( mask & MASK_CONTROL ) + { + mCursorPos = prevWordPos(mCursorPos); + } + mSelectionEnd = mCursorPos; + } + break; + + case KEY_RIGHT: + if( mCursorPos < getLength() ) + { + startSelection(); + mCursorPos++; + if( mask & MASK_CONTROL ) + { + mCursorPos = nextWordPos(mCursorPos); + } + mSelectionEnd = mCursorPos; + } + break; + + case KEY_UP: + startSelection(); + changeLine( -1 ); + mSelectionEnd = mCursorPos; + break; + + case KEY_PAGE_UP: + startSelection(); + changePage( -1 ); + mSelectionEnd = mCursorPos; + break; + + case KEY_HOME: + startSelection(); + if( mask & MASK_CONTROL ) + { + mCursorPos = 0; + } + else + { + startOfLine(); + } + mSelectionEnd = mCursorPos; + break; + + case KEY_DOWN: + startSelection(); + changeLine( 1 ); + mSelectionEnd = mCursorPos; + break; + + case KEY_PAGE_DOWN: + startSelection(); + changePage( 1 ); + mSelectionEnd = mCursorPos; + break; + + case KEY_END: + startSelection(); + if( mask & MASK_CONTROL ) + { + mCursorPos = getLength(); + } + else + { + endOfLine(); + } + mSelectionEnd = mCursorPos; + break; + + default: + handled = FALSE; + break; + } + } + + + if( !handled && mHandleEditKeysDirectly ) + { + if( (MASK_CONTROL & mask) && ('A' == key) ) + { + if( canSelectAll() ) + { + selectAll(); + } + else + { + reportBadKeystroke(); + } + handled = TRUE; + } + } + + return handled; +} + +BOOL LLTextEditor::handleNavigationKey(const KEY key, const MASK mask) +{ + BOOL handled = FALSE; + + // Ignore capslock key + if( MASK_NONE == mask ) + { + handled = TRUE; + switch( key ) + { + case KEY_UP: + if (mReadOnly) + { + mScrollbar->setDocPos(mScrollbar->getDocPos() - 1); + } + else + { + changeLine( -1 ); + } + break; + + case KEY_PAGE_UP: + changePage( -1 ); + break; + + case KEY_HOME: + if (mReadOnly) + { + mScrollbar->setDocPos(0); + } + else + { + startOfLine(); + } + break; + + case KEY_DOWN: + if (mReadOnly) + { + mScrollbar->setDocPos(mScrollbar->getDocPos() + 1); + } + else + { + changeLine( 1 ); + } + break; + + case KEY_PAGE_DOWN: + changePage( 1 ); + break; + + case KEY_END: + if (mReadOnly) + { + mScrollbar->setDocPos(mScrollbar->getDocPosMax()); + } + else + { + endOfLine(); + } + break; + + case KEY_LEFT: + if (mReadOnly) + { + break; + } + if( hasSelection() ) + { + setCursorPos(llmin( mCursorPos - 1, mSelectionStart, mSelectionEnd )); + } + else + { + if( 0 < mCursorPos ) + { + setCursorPos(mCursorPos - 1); + } + else + { + reportBadKeystroke(); + } + } + break; + + case KEY_RIGHT: + if (mReadOnly) + { + break; + } + if( hasSelection() ) + { + setCursorPos(llmax( mCursorPos + 1, mSelectionStart, mSelectionEnd )); + } + else + { + if( mCursorPos < getLength() ) + { + setCursorPos(mCursorPos + 1); + } + else + { + reportBadKeystroke(); + } + } + break; + + default: + handled = FALSE; + break; + } + } + + if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax())) + { + mOnScrollEndCallback(mOnScrollEndData); + } + return handled; +} + +void LLTextEditor::deleteSelection(BOOL group_with_next_op ) +{ + if( getEnabled() && hasSelection() ) + { + S32 pos = llmin( mSelectionStart, mSelectionEnd ); + S32 length = abs( mSelectionStart - mSelectionEnd ); + + remove( pos, length, group_with_next_op ); + + deselect(); + setCursorPos(pos); + } +} + +BOOL LLTextEditor::canCut() +{ + return !mReadOnly && hasSelection(); +} + +// cut selection to clipboard +void LLTextEditor::cut() +{ + if( canCut() ) + { + S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); + S32 length = abs( mSelectionStart - mSelectionEnd ); + gClipboard.copyFromSubstring( mWText, left_pos, length, mSourceID ); + deleteSelection( FALSE ); + + updateLineStartList(); + updateScrollFromCursor(); + } +} + +BOOL LLTextEditor::canCopy() +{ + return hasSelection(); +} + + +// copy selection to clipboard +void LLTextEditor::copy() +{ + if( canCopy() ) + { + S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); + S32 length = abs( mSelectionStart - mSelectionEnd ); + gClipboard.copyFromSubstring(mWText, left_pos, length, mSourceID); + } +} + +BOOL LLTextEditor::canPaste() +{ + return !mReadOnly && gClipboard.canPasteString(); +} + + +// paste from clipboard +void LLTextEditor::paste() +{ + if (canPaste()) + { + LLUUID source_id; + LLWString paste = gClipboard.getPasteWString(&source_id); + if (!paste.empty()) + { + // Delete any selected characters (the paste replaces them) + if( hasSelection() ) + { + deleteSelection(TRUE); + } + + // Clean up string (replace tabs and remove characters that our fonts don't support). + LLWString clean_string(paste); + LLWString::replaceTabsWithSpaces(clean_string, SPACES_PER_TAB); + if( mAllowEmbeddedItems ) + { + const llwchar LF = 10; + S32 len = clean_string.length(); + for( S32 i = 0; i < len; i++ ) + { + llwchar wc = clean_string[i]; + if( (wc < LLFont::FIRST_CHAR) && (wc != LF) ) + { + clean_string[i] = LL_UNKNOWN_CHAR; + } + else if (wc >= FIRST_EMBEDDED_CHAR && wc <= LAST_EMBEDDED_CHAR) + { + clean_string[i] = pasteEmbeddedItem(wc); + } + } + } + + // Insert the new text into the existing text. + setCursorPos(mCursorPos + insert(mCursorPos, clean_string, FALSE)); + deselect(); + + updateLineStartList(); + updateScrollFromCursor(); + } + } +} + + +BOOL LLTextEditor::handleControlKey(const KEY key, const MASK mask) +{ + BOOL handled = FALSE; + + if( mask & MASK_CONTROL ) + { + handled = TRUE; + + switch( key ) + { + case KEY_HOME: + if( mask & MASK_SHIFT ) + { + startSelection(); + mCursorPos = 0; + mSelectionEnd = mCursorPos; + } + else + { + // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down + // all move the cursor as if clicking, so should deselect. + deselect(); + setCursorPos(0); + } + break; + + case KEY_END: + { + if( mask & MASK_SHIFT ) + { + startSelection(); + } + else + { + // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down + // all move the cursor as if clicking, so should deselect. + deselect(); + } + endOfDoc(); + if( mask & MASK_SHIFT ) + { + mSelectionEnd = mCursorPos; + } + break; + } + + case KEY_RIGHT: + if( mCursorPos < getLength() ) + { + // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down + // all move the cursor as if clicking, so should deselect. + deselect(); + + setCursorPos(nextWordPos(mCursorPos + 1)); + } + break; + + + case KEY_LEFT: + if( mCursorPos > 0 ) + { + // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down + // all move the cursor as if clicking, so should deselect. + deselect(); + + setCursorPos(prevWordPos(mCursorPos - 1)); + } + break; + + default: + handled = FALSE; + break; + } + } + + return handled; +} + +BOOL LLTextEditor::handleEditKey(const KEY key, const MASK mask) +{ + BOOL handled = FALSE; + + // Standard edit keys (Ctrl-X, Delete, etc,) are handled here instead of routed by the menu system. + if( KEY_DELETE == key ) + { + if( canDoDelete() ) + { + doDelete(); + } + else + { + reportBadKeystroke(); + } + handled = TRUE; + } + else + if( MASK_CONTROL & mask ) + { + if( 'C' == key ) + { + if( canCopy() ) + { + copy(); + } + else + { + reportBadKeystroke(); + } + handled = TRUE; + } + else + if( 'V' == key ) + { + if( canPaste() ) + { + paste(); + } + else + { + reportBadKeystroke(); + } + handled = TRUE; + } + else + if( 'X' == key ) + { + if( canCut() ) + { + cut(); + } + else + { + reportBadKeystroke(); + } + handled = TRUE; + } + } + + return handled; +} + + +BOOL LLTextEditor::handleSpecialKey(const KEY key, const MASK mask, BOOL* return_key_hit) +{ + *return_key_hit = FALSE; + BOOL handled = TRUE; + + switch( key ) + { + case KEY_INSERT: + if (mask == MASK_NONE) + { + gKeyboard->toggleInsertMode(); + } + break; + + case KEY_BACKSPACE: + if( hasSelection() ) + { + deleteSelection(FALSE); + } + else + if( 0 < mCursorPos ) + { + removeCharOrTab(); + } + else + { + reportBadKeystroke(); + } + break; + + + case KEY_RETURN: + if (mask == MASK_NONE) + { + if( hasSelection() ) + { + deleteSelection(FALSE); + } + autoIndent(); // TODO: make this optional + } + else + { + handled = FALSE; + break; + } + break; + + case KEY_TAB: + if (mask & MASK_CONTROL) + { + handled = FALSE; + break; + } + if( hasSelection() && selectionContainsLineBreaks() ) + { + indentSelectedLines( (mask & MASK_SHIFT) ? -SPACES_PER_TAB : SPACES_PER_TAB ); + } + else + { + if( hasSelection() ) + { + deleteSelection(FALSE); + } + + S32 line, offset; + getLineAndOffset( mCursorPos, &line, &offset ); + + S32 spaces_needed = SPACES_PER_TAB - (offset % SPACES_PER_TAB); + for( S32 i=0; i < spaces_needed; i++ ) + { + addChar( ' ' ); + } + } + break; + + default: + handled = FALSE; + break; + } + + return handled; +} + + +void LLTextEditor::unindentLineBeforeCloseBrace() +{ + if( mCursorPos >= 1 ) + { + const LLWString &text = mWText; + if( ' ' == text[ mCursorPos - 1 ] ) + { + removeCharOrTab(); + } + } +} + + +BOOL LLTextEditor::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent ) +{ + BOOL handled = FALSE; + BOOL selection_modified = FALSE; + BOOL return_key_hit = FALSE; + BOOL text_may_have_changed = TRUE; + + if ( (gFocusMgr.getKeyboardFocus() == this) && getVisible()) + { + // Special case for TAB. If want to move to next field, report + // not handled and let the parent take care of field movement. + if (KEY_TAB == key && mTabToNextField) + { + return FALSE; + } + + handled = handleNavigationKey( key, mask ); + if( handled ) + { + text_may_have_changed = FALSE; + } + + if( !handled ) + { + handled = handleSelectionKey( key, mask ); + if( handled ) + { + selection_modified = TRUE; + } + } + + if( !handled ) + { + handled = handleControlKey( key, mask ); + if( handled ) + { + selection_modified = TRUE; + } + } + + if( !handled && mHandleEditKeysDirectly ) + { + handled = handleEditKey( key, mask ); + if( handled ) + { + selection_modified = TRUE; + text_may_have_changed = TRUE; + } + } + + // Handle most keys only if the text editor is writeable. + if( !mReadOnly ) + { + if( !handled ) + { + handled = handleSpecialKey( key, mask, &return_key_hit ); + if( handled ) + { + selection_modified = TRUE; + text_may_have_changed = TRUE; + } + } + + } + + if( handled ) + { + mKeystrokeTimer.reset(); + + // Most keystrokes will make the selection box go away, but not all will. + if( !selection_modified && + KEY_SHIFT != key && + KEY_CONTROL != key && + KEY_ALT != key && + KEY_CAPSLOCK ) + { + deselect(); + } + + if(text_may_have_changed) + { + updateLineStartList(); + } + updateScrollFromCursor(); + } + } + + return handled; +} + + +BOOL LLTextEditor::handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent) +{ + if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL + { + return FALSE; + } + + BOOL handled = FALSE; + + if ( (gFocusMgr.getKeyboardFocus() == this) && getVisible()) + { + // Handle most keys only if the text editor is writeable. + if( !mReadOnly ) + { + if( '}' == uni_char ) + { + unindentLineBeforeCloseBrace(); + } + + // TODO: KLW Add auto show of tool tip on ( + addChar( uni_char ); + + // Keys that add characters temporarily hide the cursor + getWindow()->hideCursorUntilMouseMove(); + + handled = TRUE; + } + + if( handled ) + { + mKeystrokeTimer.reset(); + + // Most keystrokes will make the selection box go away, but not all will. + deselect(); + + updateLineStartList(); + updateScrollFromCursor(); + } + } + + return handled; +} + + + +BOOL LLTextEditor::canDoDelete() +{ + return !mReadOnly && ( hasSelection() || (mCursorPos < getLength()) ); +} + +void LLTextEditor::doDelete() +{ + if( canDoDelete() ) + { + if( hasSelection() ) + { + deleteSelection(FALSE); + } + else + if( mCursorPos < getLength() ) + { + S32 i; + S32 chars_to_remove = 1; + const LLWString &text = mWText; + if( (text[ mCursorPos ] == ' ') && (mCursorPos + SPACES_PER_TAB < getLength()) ) + { + // Try to remove a full tab's worth of spaces + S32 line, offset; + getLineAndOffset( mCursorPos, &line, &offset ); + chars_to_remove = SPACES_PER_TAB - (offset % SPACES_PER_TAB); + if( chars_to_remove == 0 ) + { + chars_to_remove = SPACES_PER_TAB; + } + + for( i = 0; i < chars_to_remove; i++ ) + { + if( text[mCursorPos + i] != ' ' ) + { + chars_to_remove = 1; + break; + } + } + } + + + for( i = 0; i < chars_to_remove; i++ ) + { + setCursorPos(mCursorPos + 1); + removeChar(); + } + } + + updateLineStartList(); + updateScrollFromCursor(); + } +} + +//---------------------------------------------------------------------------- + + +void LLTextEditor::blockUndo() +{ + mBaseDocIsPristine = FALSE; + mLastCmd = NULL; + std::for_each(mUndoStack.begin(), mUndoStack.end(), DeletePointer()); + mUndoStack.clear(); +} + + +BOOL LLTextEditor::canUndo() +{ + return !mReadOnly && mLastCmd != NULL; +} + +void LLTextEditor::undo() +{ + if( canUndo() ) + { + deselect(); + + S32 pos = 0; + do + { + pos = mLastCmd->undo(this); + undo_stack_t::iterator iter = std::find(mUndoStack.begin(), mUndoStack.end(), mLastCmd); + if (iter != mUndoStack.end()) + ++iter; + if (iter != mUndoStack.end()) + mLastCmd = *iter; + else + mLastCmd = NULL; + + } while( mLastCmd && mLastCmd->groupWithNext() ); + + setCursorPos(pos); + + updateLineStartList(); + updateScrollFromCursor(); + } +} + +BOOL LLTextEditor::canRedo() +{ + return !mReadOnly && (mUndoStack.size() > 0) && (mLastCmd != mUndoStack.front()); +} + +void LLTextEditor::redo() +{ + if( canRedo() ) + { + deselect(); + + S32 pos = 0; + do + { + if( !mLastCmd ) + { + mLastCmd = mUndoStack.back(); + } + else + { + undo_stack_t::iterator iter = std::find(mUndoStack.begin(), mUndoStack.end(), mLastCmd); + if (iter != mUndoStack.begin()) + mLastCmd = *(--iter); + else + mLastCmd = NULL; + } + + if( mLastCmd ) + { + pos = mLastCmd->redo(this); + } + } while( + mLastCmd && + mLastCmd->groupWithNext() && + (mLastCmd != mUndoStack.front()) ); + + setCursorPos(pos); + + updateLineStartList(); + updateScrollFromCursor(); + } +} + + +// virtual, from LLView +void LLTextEditor::onFocusLost() +{ + // Route menu back to the default + if( gEditMenuHandler == this ) + { + gEditMenuHandler = NULL; + } + + if (mCommitOnFocusLost) + { + onCommit(); + } + + // Make sure cursor is shown again + getWindow()->showCursorFromMouseMove(); +} + +void LLTextEditor::setEnabled(BOOL enabled) +{ + // just treat enabled as read-only flag + BOOL read_only = !enabled; + if (read_only != mReadOnly) + { + mReadOnly = read_only; + updateSegments(); + } +} + +void LLTextEditor::drawBackground() +{ + S32 left = 0; + S32 top = mRect.getHeight(); + S32 right = mRect.getWidth(); + S32 bottom = 0; + + LLColor4 bg_color = mReadOnlyBgColor; + + if( !mReadOnly ) + { + if (gFocusMgr.getKeyboardFocus() == this) + { + bg_color = mFocusBgColor; + } + else + { + bg_color = mWriteableBgColor; + } + } + gl_rect_2d(left, top, right, bottom, bg_color); + + LLView::draw(); +} + +// Draws the black box behind the selected text +void LLTextEditor::drawSelectionBackground() +{ + // Draw selection even if we don't have keyboard focus for search/replace + if( hasSelection() ) + { + const LLWString &text = mWText; + const S32 text_len = getLength(); + std::queue<S32> line_endings; + + S32 line_height = llround( mGLFont->getLineHeight() ); + + S32 selection_left = llmin( mSelectionStart, mSelectionEnd ); + S32 selection_right = llmax( mSelectionStart, mSelectionEnd ); + S32 selection_left_x = mTextRect.mLeft; + S32 selection_left_y = mTextRect.mTop - line_height; + S32 selection_right_x = mTextRect.mRight; + S32 selection_right_y = mTextRect.mBottom; + + BOOL selection_left_visible = FALSE; + BOOL selection_right_visible = FALSE; + + // Skip through the lines we aren't drawing. + S32 cur_line = mScrollbar->getDocPos(); + + S32 left_line_num = cur_line; + S32 num_lines = getLineCount(); + S32 right_line_num = num_lines - 1; + + S32 line_start = -1; + if (cur_line >= num_lines) + { + return; + } + + line_start = getLineStart(cur_line); + + S32 left_visible_pos = line_start; + S32 right_visible_pos = line_start; + + S32 text_y = mTextRect.mTop - line_height; + + // Find the coordinates of the selected area + while((cur_line < num_lines)) + { + S32 next_line = -1; + S32 line_end = text_len; + + if ((cur_line + 1) < num_lines) + { + next_line = getLineStart(cur_line + 1); + line_end = next_line; + + line_end = ( (line_end - line_start)==0 || text[next_line-1] == '\n' || text[next_line-1] == '\0' || text[next_line-1] == ' ' || text[next_line-1] == '\t' ) ? next_line-1 : next_line; + } + + const llwchar* line = text.c_str() + line_start; + + if( line_start <= selection_left && selection_left <= line_end ) + { + left_line_num = cur_line; + selection_left_visible = TRUE; + selection_left_x = mTextRect.mLeft + mGLFont->getWidth(line, 0, selection_left - line_start, mAllowEmbeddedItems); + selection_left_y = text_y; + } + if( line_start <= selection_right && selection_right <= line_end ) + { + right_line_num = cur_line; + selection_right_visible = TRUE; + selection_right_x = mTextRect.mLeft + mGLFont->getWidth(line, 0, selection_right - line_start, mAllowEmbeddedItems); + if (selection_right == line_end) + { + // add empty space for "newline" + //selection_right_x += mGLFont->getWidth("n"); + } + selection_right_y = text_y; + } + + // if selection spans end of current line... + if (selection_left <= line_end && line_end < selection_right && selection_left != selection_right) + { + // extend selection slightly beyond end of line + // to indicate selection of newline character (use "n" character to determine width) + const LLWString nstr(utf8str_to_wstring(LLString("n"))); + line_endings.push(mTextRect.mLeft + mGLFont->getWidth(line, 0, line_end - line_start, mAllowEmbeddedItems) + mGLFont->getWidth(nstr.c_str())); + } + + // move down one line + text_y -= line_height; + + right_visible_pos = line_end; + line_start = next_line; + cur_line++; + + if (selection_right_visible) + { + break; + } + } + + // Draw the selection box (we're using a box instead of reversing the colors on the selected text). + BOOL selection_visible = (left_visible_pos <= selection_right) && (selection_left <= right_visible_pos); + if( selection_visible ) + { + LLGLSNoTexture no_texture; + const LLColor4& color = mReadOnly ? mReadOnlyBgColor : mWriteableBgColor; + glColor3f( 1.f - color.mV[0], 1.f - color.mV[1], 1.f - color.mV[2] ); + + if( selection_left_y == selection_right_y ) + { + // Draw from selection start to selection end + gl_rect_2d( selection_left_x, selection_left_y + line_height + 1, + selection_right_x, selection_right_y); + } + else + { + // Draw from selection start to the end of the first line + if( mTextRect.mRight == selection_left_x ) + { + selection_left_x -= CURSOR_THICKNESS; + } + + S32 line_end = line_endings.front(); + line_endings.pop(); + gl_rect_2d( selection_left_x, selection_left_y + line_height + 1, + line_end, selection_left_y ); + + S32 line_num = left_line_num + 1; + while(line_endings.size()) + { + S32 vert_offset = -(line_num - left_line_num) * line_height; + // Draw the block between the two lines + gl_rect_2d( mTextRect.mLeft, selection_left_y + vert_offset + line_height + 1, + line_endings.front(), selection_left_y + vert_offset); + line_endings.pop(); + line_num++; + } + + // Draw from the start of the last line to selection end + if( mTextRect.mLeft == selection_right_x ) + { + selection_right_x += CURSOR_THICKNESS; + } + gl_rect_2d( mTextRect.mLeft, selection_right_y + line_height + 1, + selection_right_x, selection_right_y ); + } + } + } +} + +void LLTextEditor::drawCursor() +{ + if( gFocusMgr.getKeyboardFocus() == this + && gShowTextEditCursor && !mReadOnly) + { + const LLWString &text = mWText; + const S32 text_len = getLength(); + + // Skip through the lines we aren't drawing. + S32 cur_pos = mScrollbar->getDocPos(); + + S32 num_lines = getLineCount(); + if (cur_pos >= num_lines) + { + return; + } + S32 line_start = getLineStart(cur_pos); + + F32 line_height = mGLFont->getLineHeight(); + F32 text_y = (F32)(mTextRect.mTop) - line_height; + + F32 cursor_left = 0.f; + F32 next_char_left = 0.f; + F32 cursor_bottom = 0.f; + BOOL cursor_visible = FALSE; + + S32 line_end = 0; + // Determine if the cursor is visible and if so what its coordinates are. + while( (mTextRect.mBottom <= llround(text_y)) && (cur_pos < num_lines)) + { + line_end = text_len + 1; + S32 next_line = -1; + + if ((cur_pos + 1) < num_lines) + { + next_line = getLineStart(cur_pos + 1); + line_end = next_line - 1; + } + + const llwchar* line = text.c_str() + line_start; + + // Find the cursor and selection bounds + if( line_start <= mCursorPos && mCursorPos <= line_end ) + { + cursor_visible = TRUE; + next_char_left = (F32)mTextRect.mLeft + mGLFont->getWidthF32(line, 0, mCursorPos - line_start, mAllowEmbeddedItems ); + cursor_left = next_char_left - 1.f; + cursor_bottom = text_y; + break; + } + + // move down one line + text_y -= line_height; + line_start = next_line; + cur_pos++; + } + + // Draw the cursor + if( cursor_visible ) + { + // (Flash the cursor every half second starting a fixed time after the last keystroke) + F32 elapsed = mKeystrokeTimer.getElapsedTimeF32(); + if( (elapsed < CURSOR_FLASH_DELAY ) || (S32(elapsed * 2) & 1) ) + { + F32 cursor_top = cursor_bottom + line_height + 1.f; + F32 cursor_right = cursor_left + (F32)CURSOR_THICKNESS; + if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection()) + { + cursor_left += CURSOR_THICKNESS; + const LLWString space(utf8str_to_wstring(LLString(" "))); + F32 spacew = mGLFont->getWidthF32(space.c_str()); + if (mCursorPos == line_end) + { + cursor_right = cursor_left + spacew; + } + else + { + F32 width = mGLFont->getWidthF32(text.c_str(), mCursorPos, 1, mAllowEmbeddedItems); + cursor_right = cursor_left + llmax(spacew, width); + } + } + + LLGLSNoTexture no_texture; + + glColor4fv( mCursorColor.mV ); + + gl_rect_2d(llfloor(cursor_left), llfloor(cursor_top), + llfloor(cursor_right), llfloor(cursor_bottom)); + + if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection() && text[mCursorPos] != '\n') + { + LLTextSegment* segmentp = getSegmentAtOffset(mCursorPos); + LLColor4 text_color; + if (segmentp) + { + text_color = segmentp->getColor(); + } + else if (mReadOnly) + { + text_color = mReadOnlyFgColor; + } + else + { + text_color = mFgColor; + } + LLGLSTexture texture; + mGLFont->render(text, mCursorPos, next_char_left, cursor_bottom + line_height, + LLColor4(1.f - text_color.mV[VRED], 1.f - text_color.mV[VGREEN], 1.f - text_color.mV[VBLUE], 1.f), + LLFontGL::LEFT, LLFontGL::TOP, + LLFontGL::NORMAL, + 1); + } + + + } + } + } +} + + +void LLTextEditor::drawText() +{ + const LLWString &text = mWText; + const S32 text_len = getLength(); + + if( text_len > 0 ) + { + S32 selection_left = -1; + S32 selection_right = -1; + // Draw selection even if we don't have keyboard focus for search/replace + if( hasSelection()) + { + selection_left = llmin( mSelectionStart, mSelectionEnd ); + selection_right = llmax( mSelectionStart, mSelectionEnd ); + } + + LLGLSUIDefault gls_ui; + + S32 cur_line = mScrollbar->getDocPos(); + S32 num_lines = getLineCount(); + if (cur_line >= num_lines) + { + return; + } + + S32 line_start = getLineStart(cur_line); + LLTextSegment t(line_start); + segment_list_t::iterator seg_iter; + seg_iter = std::upper_bound(mSegments.begin(), mSegments.end(), &t, LLTextSegment::compare()); + if (seg_iter == mSegments.end() || (*seg_iter)->getStart() > line_start) --seg_iter; + LLTextSegment* cur_segment = *seg_iter; + + S32 line_height = llround( mGLFont->getLineHeight() ); + F32 text_y = (F32)(mTextRect.mTop - line_height); + while((mTextRect.mBottom <= text_y) && (cur_line < num_lines)) + { + S32 next_start = -1; + S32 line_end = text_len; + + if ((cur_line + 1) < num_lines) + { + next_start = getLineStart(cur_line + 1); + line_end = next_start; + } + if ( text[line_end-1] == '\n' ) + { + --line_end; + } + + F32 text_x = (F32)mTextRect.mLeft; + + S32 seg_start = line_start; + while( seg_start < line_end ) + { + while( cur_segment->getEnd() <= seg_start ) + { + seg_iter++; + if (seg_iter == mSegments.end()) + { + llwarns << "Ran off the segmentation end!" << llendl; + return; + } + cur_segment = *seg_iter; + } + + // Draw a segment within the line + S32 clipped_end = llmin( line_end, cur_segment->getEnd() ); + S32 clipped_len = clipped_end - seg_start; + if( clipped_len > 0 ) + { + LLStyle style = cur_segment->getStyle(); + if ( style.isImage() && (cur_segment->getStart() >= seg_start) && (cur_segment->getStart() <= clipped_end)) + { + LLImageGL *image = style.getImage(); + + gl_draw_scaled_image( llround(text_x), llround(text_y)+line_height-style.mImageHeight, style.mImageWidth, style.mImageHeight, image, LLColor4::white ); + + } + + if (cur_segment == mHoverSegment && style.getIsEmbeddedItem()) + { + style.mUnderline = TRUE; + } + + S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); + + if ( (mParseHTML) && (left_pos > seg_start) && (left_pos < clipped_end) && mIsSelecting && (mSelectionStart == mSelectionEnd) ) + { + mHTML = style.getLinkHREF(); + } + + drawClippedSegment( text, seg_start, clipped_end, text_x, text_y, selection_left, selection_right, style, &text_x ); + + // Note: text_x is incremented by drawClippedSegment() + seg_start += clipped_len; + } + } + + // move down one line + text_y -= (F32)line_height; + + line_start = next_start; + cur_line++; + } + } +} + +// Draws a single text segment, reversing the color for selection if needed. +void LLTextEditor::drawClippedSegment(const LLWString &text, S32 seg_start, S32 seg_end, F32 x, F32 y, S32 selection_left, S32 selection_right, const LLStyle& style, F32* right_x ) +{ + const LLFontGL* font = mGLFont; + + LLColor4 color; + + if (!style.isVisible()) + { + return; + } + + color = style.getColor(); + + if ( style.getFontString()[0] ) + { + font = gResMgr->getRes(style.getFontID()); + } + + U8 font_flags = LLFontGL::NORMAL; + + if (style.mBold) + { + font_flags |= LLFontGL::BOLD; + } + if (style.mItalic) + { + font_flags |= LLFontGL::ITALIC; + } + if (style.mUnderline) + { + font_flags |= LLFontGL::UNDERLINE; + } + + if (style.getIsEmbeddedItem()) + { + if (mReadOnly) + { + color = LLUI::sColorsGroup->getColor("TextEmbeddedItemReadOnlyColor"); + } + else + { + color = LLUI::sColorsGroup->getColor("TextEmbeddedItemColor"); + } + } + + F32 y_top = y + (F32)llround(font->getLineHeight()); + + if( selection_left > seg_start ) + { + // Draw normally + S32 start = seg_start; + S32 end = llmin( selection_left, seg_end ); + S32 length = end - start; + font->render(text, start, x, y_top, color, LLFontGL::LEFT, LLFontGL::TOP, font_flags, length, S32_MAX, right_x, mAllowEmbeddedItems); + } + x = *right_x; + + if( (selection_left < seg_end) && (selection_right > seg_start) ) + { + // Draw reversed + S32 start = llmax( selection_left, seg_start ); + S32 end = llmin( selection_right, seg_end ); + S32 length = end - start; + + font->render(text, start, x, y_top, + LLColor4( 1.f - color.mV[0], 1.f - color.mV[1], 1.f - color.mV[2], 1.f ), + LLFontGL::LEFT, LLFontGL::TOP, font_flags, length, S32_MAX, right_x, mAllowEmbeddedItems); + } + x = *right_x; + if( selection_right < seg_end ) + { + // Draw normally + S32 start = llmax( selection_right, seg_start ); + S32 end = seg_end; + S32 length = end - start; + font->render(text, start, x, y_top, color, LLFontGL::LEFT, LLFontGL::TOP, font_flags, length, S32_MAX, right_x, mAllowEmbeddedItems); + } + } + + +void LLTextEditor::draw() +{ + if( getVisible() ) + { + { + LLGLEnable scissor_test(GL_SCISSOR_TEST); + LLUI::setScissorRegionLocal(LLRect(0, mRect.getHeight(), mRect.getWidth() - (mScrollbar->getVisible() ? SCROLLBAR_SIZE : 0), 0)); + + bindEmbeddedChars( mGLFont ); + + drawBackground(); + drawSelectionBackground(); + drawText(); + drawCursor(); + + unbindEmbeddedChars( mGLFont ); + + //RN: the decision was made to always show the orange border for keyboard focus but do not put an insertion caret + // when in readonly mode + mBorder->setKeyboardFocusHighlight( gFocusMgr.getKeyboardFocus() == this);// && !mReadOnly); + } + LLView::draw(); // Draw children (scrollbar and border) + } +} + +void LLTextEditor::reportBadKeystroke() +{ + make_ui_sound("UISndBadKeystroke"); +} + + +void LLTextEditor::onTabInto() +{ + // selecting all on tabInto causes users to hit tab twice and replace their text with a tab character + // theoretically, one could selectAll if mTabToNextField is true, but we couldn't think of a use case + // where you'd want to select all anyway + // preserve insertion point when returning to the editor + //selectAll(); +} + +void LLTextEditor::clear() +{ + setText(""); +} + +// Start or stop the editor from accepting text-editing keystrokes +// see also LLLineEditor +void LLTextEditor::setFocus( BOOL new_state ) +{ + BOOL old_state = hasFocus(); + + // Don't change anything if the focus state didn't change + if (new_state == old_state) return; + + LLUICtrl::setFocus( new_state ); + + if( new_state ) + { + // Route menu to this class + gEditMenuHandler = this; + + // Don't start the cursor flashing right away + mKeystrokeTimer.reset(); + } + else + { + // Route menu back to the default + if( gEditMenuHandler == this ) + { + gEditMenuHandler = NULL; + } + + endSelection(); + } +} + +BOOL LLTextEditor::acceptsTextInput() const +{ + return !mReadOnly; +} + +// Given a line (from the start of the doc) and an offset into the line, find the offset (pos) into text. +S32 LLTextEditor::getPos( S32 line, S32 offset ) +{ + S32 line_start = getLineStart(line); + S32 next_start = getLineStart(line+1); + if (next_start == line_start) + { + next_start = getLength() + 1; + } + S32 line_length = next_start - line_start - 1; + line_length = llmax(line_length, 0); + return line_start + llmin( offset, line_length ); +} + + +void LLTextEditor::changePage( S32 delta ) +{ + S32 line, offset; + getLineAndOffset( mCursorPos, &line, &offset ); + + // allow one line overlap + S32 page_size = mScrollbar->getPageSize() - 1; + if( delta == -1 ) + { + line = llmax( line - page_size, 0); + setCursorPos(getPos( line, offset )); + mScrollbar->setDocPos( mScrollbar->getDocPos() - page_size ); + } + else + if( delta == 1 ) + { + setCursorPos(getPos( line + page_size, offset )); + mScrollbar->setDocPos( mScrollbar->getDocPos() + page_size ); + } + if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax())) + { + mOnScrollEndCallback(mOnScrollEndData); + } +} + +void LLTextEditor::changeLine( S32 delta ) +{ + bindEmbeddedChars( mGLFont ); + + S32 line, offset; + getLineAndOffset( mCursorPos, &line, &offset ); + + S32 line_start = getLineStart(line); + + S32 desired_x_pixel; + + desired_x_pixel = mGLFont->getWidth(mWText.c_str(), line_start, offset, mAllowEmbeddedItems ); + + S32 new_line = 0; + if( (delta < 0) && (line > 0 ) ) + { + new_line = line - 1; + } + else + if( (delta > 0) && (line < (getLineCount() - 1)) ) + { + new_line = line + 1; + } + else + { + unbindEmbeddedChars( mGLFont ); + return; + } + + S32 num_lines = getLineCount(); + S32 new_line_start = getLineStart(new_line); + S32 new_line_end = getLength(); + if (new_line + 1 < num_lines) + { + new_line_end = getLineStart(new_line + 1) - 1; + } + + S32 new_line_len = new_line_end - new_line_start; + + S32 new_offset; + new_offset = mGLFont->charFromPixelOffset(mWText.c_str(), new_line_start, + (F32)desired_x_pixel, + (F32)mTextRect.getWidth(), + new_line_len, + mAllowEmbeddedItems); + + setCursorPos (getPos( new_line, new_offset )); + unbindEmbeddedChars( mGLFont ); +} + +void LLTextEditor::startOfLine() +{ + S32 line, offset; + getLineAndOffset( mCursorPos, &line, &offset ); + setCursorPos(mCursorPos - offset); +} + + +// public +void LLTextEditor::setCursorAndScrollToEnd() +{ + deselect(); + endOfDoc(); + updateScrollFromCursor(); +} + + +void LLTextEditor::getCurrentLineAndColumn( S32* line, S32* col, BOOL include_wordwrap ) +{ + if( include_wordwrap ) + { + getLineAndOffset( mCursorPos, line, col ); + } + else + { + const LLWString &text = mWText; + S32 line_count = 0; + S32 line_start = 0; + S32 i; + for( i = 0; text[i] && (i < mCursorPos); i++ ) + { + if( '\n' == text[i] ) + { + line_start = i + 1; + line_count++; + } + } + *line = line_count; + *col = i - line_start; + } +} + + +void LLTextEditor::endOfLine() +{ + S32 line, offset; + getLineAndOffset( mCursorPos, &line, &offset ); + S32 num_lines = getLineCount(); + if (line + 1 >= num_lines) + { + setCursorPos(getLength()); + } + else + { + setCursorPos( getLineStart(line + 1) - 1 ); + } +} + +void LLTextEditor::endOfDoc() +{ + mScrollbar->setDocPos( mScrollbar->getDocPosMax() ); + S32 len = getLength(); + if( len ) + { + setCursorPos(len); + } + if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax())) + { + mOnScrollEndCallback(mOnScrollEndData); + } +} + +// Sets the scrollbar from the cursor position +void LLTextEditor::updateScrollFromCursor() +{ + mScrollbar->setDocSize( getLineCount() ); + + if (mReadOnly) + { + // no cursor in read only mode + return; + } + + S32 line, offset; + getLineAndOffset( mCursorPos, &line, &offset ); + + S32 page_size = mScrollbar->getPageSize(); + + if( line < mScrollbar->getDocPos() ) + { + // scroll so that the cursor is at the top of the page + mScrollbar->setDocPos( line ); + } + else if( line >= mScrollbar->getDocPos() + page_size - 1 ) + { + S32 new_pos = 0; + if( line < mScrollbar->getDocSize() - 1 ) + { + // scroll so that the cursor is one line above the bottom of the page, + new_pos = line - page_size + 1; + } + else + { + // if there is less than a page of text remaining, scroll so that the cursor is at the bottom + new_pos = mScrollbar->getDocPosMax(); + } + mScrollbar->setDocPos( new_pos ); + } + + // Check if we've scrolled to bottom for callback if asked for callback + if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax())) + { + mOnScrollEndCallback(mOnScrollEndData); + } +} + +void LLTextEditor::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + LLView::reshape( width, height, called_from_parent ); + + updateTextRect(); + + S32 line_height = llround( mGLFont->getLineHeight() ); + S32 page_lines = mTextRect.getHeight() / line_height; + mScrollbar->setPageSize( page_lines ); + + updateLineStartList(); +} + +void LLTextEditor::autoIndent() +{ + // Count the number of spaces in the current line + S32 line, offset; + getLineAndOffset( mCursorPos, &line, &offset ); + S32 line_start = getLineStart(line); + S32 space_count = 0; + S32 i; + + const LLWString &text = mWText; + while( ' ' == text[line_start] ) + { + space_count++; + line_start++; + } + + // If we're starting a braced section, indent one level. + if( (mCursorPos > 0) && (text[mCursorPos -1] == '{') ) + { + space_count += SPACES_PER_TAB; + } + + // Insert that number of spaces on the new line + addChar( '\n' ); + for( i = 0; i < space_count; i++ ) + { + addChar( ' ' ); + } +} + +// Inserts new text at the cursor position +void LLTextEditor::insertText(const LLString &new_text) +{ + BOOL enabled = getEnabled(); + setEnabled( TRUE ); + + // Delete any selected characters (the insertion replaces them) + if( hasSelection() ) + { + deleteSelection(TRUE); + } + + setCursorPos(mCursorPos + insert( mCursorPos, utf8str_to_wstring(new_text), FALSE )); + + updateLineStartList(); + updateScrollFromCursor(); + + setEnabled( enabled ); +} + + +void LLTextEditor::appendColoredText(const LLString &new_text, + bool allow_undo, + bool prepend_newline, + const LLColor4 &color, + const LLString& font_name) +{ + LLStyle style; + style.setVisible(true); + style.setColor(color); + style.setFontName(font_name); + if(mParseHTML) + { + + S32 start=0,end=0; + LLString text = new_text; + while ( findHTML(text, &start, &end) ) + { + LLStyle html; + html.setVisible(true); + html.setColor(mLinkColor); + html.setFontName(font_name); + html.mUnderline = TRUE; + + if (start > 0) appendText(text.substr(0,start),allow_undo, prepend_newline, &style); + html.setLinkHREF(text.substr(start,end-start)); + appendText(text.substr(start, end-start),allow_undo, prepend_newline, &html); + if (end < (S32)text.length()) + { + text = text.substr(end,text.length() - end); + end=0; + } + else + { + break; + } + } + if (end < (S32)text.length()) appendText(text,allow_undo, prepend_newline, &style); + } + else + { + appendText(new_text, allow_undo, prepend_newline, &style); + } +} + +void LLTextEditor::appendStyledText(const LLString &new_text, + bool allow_undo, + bool prepend_newline, + const LLStyle &style) +{ + appendText(new_text, allow_undo, prepend_newline, &style); +} + +// Appends new text to end of document +void LLTextEditor::appendText(const LLString &new_text, bool allow_undo, bool prepend_newline, + const LLStyle* segment_style) +{ + // Save old state + BOOL was_scrolled_to_bottom = (mScrollbar->getDocPos() == mScrollbar->getDocPosMax()); + S32 selection_start = mSelectionStart; + S32 selection_end = mSelectionEnd; + S32 cursor_pos = mCursorPos; + S32 old_length = getLength(); + BOOL cursor_was_at_end = (mCursorPos == old_length); + + deselect(); + + setCursorPos(old_length); + + // Add carriage return if not first line + if (getLength() != 0 + && prepend_newline) + { + LLString final_text = "\n"; + final_text += new_text; + append(utf8str_to_wstring(final_text), TRUE); + } + else + { + append(utf8str_to_wstring(new_text), TRUE ); + } + + if (segment_style) + { + S32 segment_start = old_length; + S32 segment_end = getLength(); + LLTextSegment* segment = new LLTextSegment(*segment_style, segment_start, segment_end ); + mSegments.push_back(segment); + } + + updateLineStartList(old_length); + + // Set the cursor and scroll position + // Maintain the scroll position unless the scroll was at the end of the doc + // (in which case, move it to the new end of the doc) + if( was_scrolled_to_bottom ) + { + endOfDoc(); + } + else if( selection_start != selection_end ) + { + mSelectionStart = selection_start; + + mSelectionEnd = selection_end; + setCursorPos(cursor_pos); + } + else if( cursor_was_at_end ) + { + setCursorPos(getLength()); + } + else + { + setCursorPos(cursor_pos); + } + + if( !allow_undo ) + { + blockUndo(); + } +} + +void LLTextEditor::removeTextFromEnd(S32 num_chars) +{ + if (num_chars <= 0) return; + + remove(getLength() - num_chars, num_chars, FALSE); + + S32 len = getLength(); + mCursorPos = llclamp(mCursorPos, 0, len); + mSelectionStart = llclamp(mSelectionStart, 0, len); + mSelectionEnd = llclamp(mSelectionEnd, 0, len); + + pruneSegments(); + updateLineStartList(); +} + +/////////////////////////////////////////////////////////////////// +// Returns change in number of characters in mWText + +S32 LLTextEditor::insertStringNoUndo(const S32 pos, const LLWString &wstr) +{ + S32 len = mWText.length(); + S32 s_len = wstr.length(); + S32 new_len = len + s_len; + if( new_len > mMaxTextLength ) + { + new_len = mMaxTextLength; + + // The user's not getting everything he's hoping for + make_ui_sound("UISndBadKeystroke"); + } + + mWText.insert(pos, wstr); + mTextIsUpToDate = FALSE; + truncate(); + + return new_len - len; +} + +S32 LLTextEditor::removeStringNoUndo(S32 pos, S32 length) +{ + mWText.erase(pos, length); + mTextIsUpToDate = FALSE; + return -length; // This will be wrong if someone calls removeStringNoUndo with an excessive length +} + +S32 LLTextEditor::overwriteCharNoUndo(S32 pos, llwchar wc) +{ + if (pos > (S32)mWText.length()) + { + return 0; + } + mWText[pos] = wc; + mTextIsUpToDate = FALSE; + return 1; +} + +//---------------------------------------------------------------------------- + +void LLTextEditor::makePristine() +{ + mPristineCmd = mLastCmd; + mBaseDocIsPristine = !mLastCmd; + + // Create a clean partition in the undo stack. We don't want a single command to extend from + // the "pre-pristine" state to the "post-pristine" state. + if( mLastCmd ) + { + mLastCmd->blockExtensions(); + } +} + +BOOL LLTextEditor::isPristine() const +{ + if( mPristineCmd ) + { + return (mPristineCmd == mLastCmd); + } + else + { + // No undo stack, so check if the version before and commands were done was the original version + return !mLastCmd && mBaseDocIsPristine; + } +} + +BOOL LLTextEditor::tryToRevertToPristineState() +{ + if( !isPristine() ) + { + deselect(); + S32 i = 0; + while( !isPristine() && canUndo() ) + { + undo(); + i--; + } + + while( !isPristine() && canRedo() ) + { + redo(); + i++; + } + + if( !isPristine() ) + { + // failed, so go back to where we started + while( i > 0 ) + { + undo(); + i--; + } + } + + updateLineStartList(); + updateScrollFromCursor(); + } + + return isPristine(); // TRUE => success +} + + + +void LLTextEditor::updateTextRect() +{ + mTextRect.setOriginAndSize( + UI_TEXTEDITOR_BORDER + UI_TEXTEDITOR_H_PAD, + UI_TEXTEDITOR_BORDER, + mRect.getWidth() - SCROLLBAR_SIZE - 2 * (UI_TEXTEDITOR_BORDER + UI_TEXTEDITOR_H_PAD), + mRect.getHeight() - 2 * UI_TEXTEDITOR_BORDER - UI_TEXTEDITOR_V_PAD_TOP ); +} + +void LLTextEditor::loadKeywords(const LLString& filename, + const LLDynamicArray<const char*>& funcs, + const LLDynamicArray<const char*>& tooltips, + const LLColor3& color) +{ + if(mKeywords.loadFromFile(filename)) + { + S32 count = funcs.count(); + LLString name; + for(S32 i = 0; i < count; i++) + { + name = funcs.get(i); + name = utf8str_trim(name); + mKeywords.addToken(LLKeywordToken::WORD, name.c_str(), color, tooltips.get(i) ); + } + + mKeywords.findSegments( &mSegments, mWText ); + + llassert( mSegments.front()->getStart() == 0 ); + llassert( mSegments.back()->getEnd() == getLength() ); + } +} + +void LLTextEditor::updateSegments() +{ + if (mKeywords.isLoaded()) + { + // HACK: No non-ascii keywords for now + mKeywords.findSegments(&mSegments, mWText); + } + else if (mAllowEmbeddedItems) + { + findEmbeddedItemSegments(); + } + // Make sure we have at least one segment + if (mSegments.size() == 1 && mSegments[0]->getIsDefault()) + { + delete mSegments[0]; + mSegments.clear(); // create default segment + } + if (mSegments.empty()) + { + LLColor4& text_color = ( mReadOnly ? mReadOnlyFgColor : mFgColor ); + LLTextSegment* default_segment = new LLTextSegment( text_color, 0, mWText.length() ); + default_segment->setIsDefault(TRUE); + mSegments.push_back(default_segment); + } +} + +// Only effective if text was removed from the end of the editor +void LLTextEditor::pruneSegments() +{ + S32 len = mWText.length(); + // Find and update the first valid segment + segment_list_t::iterator iter = mSegments.end(); + while(iter != mSegments.begin()) + { + --iter; + LLTextSegment* seg = *iter; + if (seg->getStart() < len) + { + // valid segment + if (seg->getEnd() > len) + { + seg->setEnd(len); + } + break; // done + } + } + // erase invalid segments + ++iter; + std::for_each(iter, mSegments.end(), DeletePointer()); + mSegments.erase(iter, mSegments.end()); +} + +void LLTextEditor::findEmbeddedItemSegments() +{ + mHoverSegment = NULL; + std::for_each(mSegments.begin(), mSegments.end(), DeletePointer()); + mSegments.clear(); + + BOOL found_embedded_items = FALSE; + const LLWString &text = mWText; + S32 idx = 0; + while( text[idx] ) + { + if( text[idx] >= FIRST_EMBEDDED_CHAR && text[idx] <= LAST_EMBEDDED_CHAR ) + { + found_embedded_items = TRUE; + break; + } + ++idx; + } + + if( !found_embedded_items ) + { + return; + } + + S32 text_len = text.length(); + + BOOL in_text = FALSE; + + LLColor4& text_color = ( mReadOnly ? mReadOnlyFgColor : mFgColor ); + + if( idx > 0 ) + { + mSegments.push_back( new LLTextSegment( text_color, 0, text_len ) ); // text + in_text = TRUE; + } + + LLStyle embedded_style; + embedded_style.setIsEmbeddedItem( TRUE ); + + // Start with i just after the first embedded item + while ( text[idx] ) + { + if( text[idx] >= FIRST_EMBEDDED_CHAR && text[idx] <= LAST_EMBEDDED_CHAR ) + { + if( in_text ) + { + mSegments.back()->setEnd( idx ); + } + mSegments.push_back( new LLTextSegment( embedded_style, idx, idx + 1 ) ); // item + in_text = FALSE; + } + else + if( !in_text ) + { + mSegments.push_back( new LLTextSegment( text_color, idx, text_len ) ); // text + in_text = TRUE; + } + ++idx; + } +} + +BOOL LLTextEditor::handleMouseUpOverSegment(S32 x, S32 y, MASK mask) +{ + return FALSE; +} + +llwchar LLTextEditor::pasteEmbeddedItem(llwchar ext_char) +{ + return ext_char; +} + +void LLTextEditor::bindEmbeddedChars(const LLFontGL* font) +{ +} + +void LLTextEditor::unbindEmbeddedChars(const LLFontGL* font) +{ +} + +// Finds the text segment (if any) at the give local screen position +LLTextSegment* LLTextEditor::getSegmentAtLocalPos( S32 x, S32 y ) +{ + // Find the cursor position at the requested local screen position + S32 offset = getCursorPosFromLocalCoord( x, y, FALSE ); + S32 idx = getSegmentIdxAtOffset(offset); + return idx >= 0 ? mSegments[idx] : NULL; +} + +LLTextSegment* LLTextEditor::getSegmentAtOffset(S32 offset) +{ + S32 idx = getSegmentIdxAtOffset(offset); + return idx >= 0 ? mSegments[idx] : NULL; +} + +S32 LLTextEditor::getSegmentIdxAtOffset(S32 offset) +{ + if (mSegments.empty() || offset < 0 || offset >= getLength()) + { + return -1; + } + else + { + S32 segidx, segoff; + getSegmentAndOffset(offset, &segidx, &segoff); + return segidx; + } +} + +//static +void LLTextEditor::onMouseCaptureLost( LLMouseHandler* old_captor ) +{ + LLTextEditor* self = (LLTextEditor*) old_captor; + self->endSelection(); +} + +void LLTextEditor::setOnScrollEndCallback(void (*callback)(void*), void* userdata) +{ + mOnScrollEndCallback = callback; + mOnScrollEndData = userdata; + mScrollbar->setOnScrollEndCallback(callback, userdata); +} + +/////////////////////////////////////////////////////////////////// +// Hack for Notecards + +BOOL LLTextEditor::importBuffer(const LLString& buffer ) +{ + std::istringstream instream(buffer); + + // Version 1 format: + // Linden text version 1\n + // {\n + // <EmbeddedItemList chunk> + // Text length <bytes without \0>\n + // <text without \0> (text may contain ext_char_values) + // }\n + + char tbuf[MAX_STRING]; + + S32 version = 0; + instream.getline(tbuf, MAX_STRING); + if( 1 != sscanf(tbuf, "Linden text version %d", &version) ) + { + llwarns << "Invalid Linden text file header " << llendl; + return FALSE; + } + + if( 1 != version ) + { + llwarns << "Invalid Linden text file version: " << version << llendl; + return FALSE; + } + + instream.getline(tbuf, MAX_STRING); + if( 0 != sscanf(tbuf, "{") ) + { + llwarns << "Invalid Linden text file format" << llendl; + return FALSE; + } + + S32 text_len = 0; + instream.getline(tbuf, MAX_STRING); + if( 1 != sscanf(tbuf, "Text length %d", &text_len) ) + { + llwarns << "Invalid Linden text length field" << llendl; + return FALSE; + } + + if( text_len > mMaxTextLength ) + { + llwarns << "Invalid Linden text length: " << text_len << llendl; + return FALSE; + } + + BOOL success = TRUE; + + char* text = new char[ text_len + 1]; + instream.get(text, text_len + 1, '\0'); + text[text_len] = '\0'; + if( text_len != (S32)strlen(text) ) + { + llwarns << llformat("Invalid text length: %d != %d ",strlen(text),text_len) << llendl; + success = FALSE; + } + + instream.getline(tbuf, MAX_STRING); + if( success && (0 != sscanf(tbuf, "}")) ) + { + llwarns << "Invalid Linden text file format: missing terminal }" << llendl; + success = FALSE; + } + + if( success ) + { + // Actually set the text + setText( text ); + } + + delete[] text; + + setCursorPos(0); + deselect(); + + updateLineStartList(); + updateScrollFromCursor(); + + return success; +} + +BOOL LLTextEditor::exportBuffer(LLString &buffer ) +{ + std::ostringstream outstream(buffer); + + outstream << "Linden text version 1\n"; + outstream << "{\n"; + + outstream << llformat("Text length %d\n", mWText.length() ); + outstream << getText(); + outstream << "}\n"; + + return TRUE; +} + +////////////////////////////////////////////////////////////////////////// +// LLTextSegment + +LLTextSegment::LLTextSegment(S32 start) : mStart(start) +{ +} +LLTextSegment::LLTextSegment( const LLStyle& style, S32 start, S32 end ) : + mStyle( style ), + mStart( start), + mEnd( end ), + mToken(NULL), + mIsDefault(FALSE) +{ +} +LLTextSegment::LLTextSegment( + const LLColor4& color, S32 start, S32 end, BOOL is_visible) : + mStyle( is_visible, color,"" ), + mStart( start), + mEnd( end ), + mToken(NULL), + mIsDefault(FALSE) +{ +} +LLTextSegment::LLTextSegment( const LLColor4& color, S32 start, S32 end ) : + mStyle( TRUE, color,"" ), + mStart( start), + mEnd( end ), + mToken(NULL), + mIsDefault(FALSE) +{ +} +LLTextSegment::LLTextSegment( const LLColor3& color, S32 start, S32 end ) : + mStyle( TRUE, color,"" ), + mStart( start), + mEnd( end ), + mToken(NULL), + mIsDefault(FALSE) +{ +} + +BOOL LLTextSegment::getToolTip(LLString& msg) +{ + if (mToken && !mToken->getToolTip().empty()) + { + const LLWString& wmsg = mToken->getToolTip(); + msg = wstring_to_utf8str(wmsg); + return TRUE; + } + return FALSE; +} + + + +void LLTextSegment::dump() +{ + llinfos << "Segment [" << +// mColor.mV[VX] << ", " << +// mColor.mV[VY] << ", " << +// mColor.mV[VZ] << "]\t[" << + mStart << ", " << + getEnd() << "]" << + llendl; + +} + +// virtual +LLXMLNodePtr LLTextEditor::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLUICtrl::getXML(); + + // Attributes + + node->createChild("max_length", TRUE)->setIntValue(getMaxLength()); + + node->createChild("embedded_items", TRUE)->setBoolValue(mAllowEmbeddedItems); + + node->createChild("font", TRUE)->setStringValue(LLFontGL::nameFromFont(mGLFont)); + + node->createChild("word_wrap", TRUE)->setBoolValue(mWordWrap); + + addColorXML(node, mCursorColor, "cursor_color", "TextCursorColor"); + addColorXML(node, mFgColor, "text_color", "TextFgColor"); + addColorXML(node, mReadOnlyFgColor, "text_readonly_color", "TextFgReadOnlyColor"); + addColorXML(node, mReadOnlyBgColor, "bg_readonly_color", "TextBgReadOnlyColor"); + addColorXML(node, mWriteableBgColor, "bg_writeable_color", "TextBgWriteableColor"); + addColorXML(node, mFocusBgColor, "bg_focus_color", "TextBgFocusColor"); + + // Contents + node->setStringValue(getText()); + + return node; +} + +// static +LLView* LLTextEditor::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("text_editor"); + node->getAttributeString("name", name); + + LLRect rect; + createRect(node, rect, parent, LLRect()); + + U32 max_text_length = 255; + node->getAttributeU32("max_length", max_text_length); + + BOOL allow_embedded_items; + node->getAttributeBOOL("embedded_items", allow_embedded_items); + + LLFontGL* font = LLView::selectFont(node); + + LLString text = node->getTextContents().substr(0, max_text_length - 1); + + LLTextEditor* text_editor = new LLTextEditor(name, + rect, + max_text_length, + text, + font, + allow_embedded_items); + + text_editor->setTextEditorParameters(node); + + BOOL hide_scrollbar = FALSE; + node->getAttributeBOOL("hide_scrollbar",hide_scrollbar); + text_editor->setHideScrollbarForShortDocs(hide_scrollbar); + + text_editor->initFromXML(node, parent); + + return text_editor; +} + +void LLTextEditor::setTextEditorParameters(LLXMLNodePtr node) +{ + BOOL word_wrap = FALSE; + node->getAttributeBOOL("word_wrap", word_wrap); + setWordWrap(word_wrap); + + LLColor4 color; + if (LLUICtrlFactory::getAttributeColor(node,"cursor_color", color)) + { + setCursorColor(color); + } + if(LLUICtrlFactory::getAttributeColor(node,"text_color", color)) + { + setFgColor(color); + } + if(LLUICtrlFactory::getAttributeColor(node,"text_readonly_color", color)) + { + setReadOnlyFgColor(color); + } + if(LLUICtrlFactory::getAttributeColor(node,"bg_readonly_color", color)) + { + setReadOnlyBgColor(color); + } + if(LLUICtrlFactory::getAttributeColor(node,"bg_writeable_color", color)) + { + setWriteableBgColor(color); + } +} + +/////////////////////////////////////////////////////////////////// +S32 LLTextEditor::findHTMLToken(const LLString &line, S32 pos, BOOL reverse) +{ + LLString openers=" \t('\"[{<>"; + LLString closers=" \t)'\"]}><;"; + + S32 m2; + S32 retval; + + if (reverse) + { + + for (retval=pos; retval>0; retval--) + { + m2 = openers.find(line.substr(retval,1)); + if (m2 >= 0) + { + retval++; + break; + } + } + } + else + { + + for (retval=pos; retval<(S32)line.length(); retval++) + { + m2 = closers.find(line.substr(retval,1)); + if (m2 >= 0) + { + break; + } + } + } + + return retval; +} + +BOOL LLTextEditor::findHTML(const LLString &line, S32 *begin, S32 *end) +{ + + S32 m1,m2,m3; + BOOL matched = FALSE; + + m1=line.find("://",*end); + + if (m1 >= 0) //Easy match. + { + *begin = findHTMLToken(line, m1, TRUE); + *end = findHTMLToken(line, m1, FALSE); + + //Load_url only handles http and https so don't hilite ftp, smb, etc. + m2 = line.substr(*begin,(m1 - *begin)).find("http"); + m3 = line.substr(*begin,(m1 - *begin)).find("secondlife"); + + LLString badneighbors=".,<>/?';\"][}{=-+_)(*&^%$#@!~`\t\r\n\\"; + + if (m2 >= 0 || m3>=0) + { + S32 bn = badneighbors.find(line.substr(m1+3,1)); + + if (bn < 0) + { + matched = TRUE; + } + } + } +/* matches things like secondlife.com (no http://) needs a whitelist to really be effective. + else //Harder match. + { + m1 = line.find(".",*end); + + if (m1 >= 0) + { + *end = findHTMLToken(line, m1, FALSE); + *begin = findHTMLToken(line, m1, TRUE); + + m1 = line.rfind(".",*end); + + if ( ( *end - m1 ) > 2 && m1 > *begin) + { + LLString badneighbors=".,<>/?';\"][}{=-+_)(*&^%$#@!~`"; + m2 = badneighbors.find(line.substr(m1+1,1)); + m3 = badneighbors.find(line.substr(m1-1,1)); + if (m3<0 && m2<0) + { + matched = TRUE; + } + } + } + } + */ + + if (matched) + { + S32 strpos, strpos2; + + LLString url = line.substr(*begin,*end - *begin); + LLString slurlID = "slurl.com/secondlife/"; + strpos = url.find(slurlID); + + if (strpos < 0) + { + slurlID="secondlife://"; + strpos = url.find(slurlID); + } + + if (strpos >= 0) + { + strpos+=slurlID.length(); + + while ( ( strpos2=url.find("/",strpos) ) == -1 ) + { + if ((*end+2) >= (S32)line.length() || line.substr(*end,1) != " " ) + { + matched=FALSE; + break; + } + + strpos = (*end + 1) - *begin; + + *end = findHTMLToken(line,(*begin + strpos),FALSE); + url = line.substr(*begin,*end - *begin); + } + } + + } + + if (!matched) + { + *begin=*end=0; + } + return matched; +} diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h new file mode 100644 index 0000000000..bebf2b31f2 --- /dev/null +++ b/indra/llui/lltexteditor.h @@ -0,0 +1,483 @@ +/** + * @file lltexteditor.h + * @brief LLTextEditor base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// Text editor widget to let users enter a a multi-line ASCII document// + +#ifndef LL_LLTEXTEDITOR_H +#define LL_LLTEXTEDITOR_H + +#include "llrect.h" +#include "llkeywords.h" +#include "lluictrl.h" +#include "llframetimer.h" +#include "lldarray.h" +#include "llstyle.h" +#include "lleditmenuhandler.h" +#include "lldarray.h" + +class LLFontGL; +class LLScrollbar; +class LLViewBorder; +class LLKeywordToken; +class LLTextCmd; + +// +// Constants +// + +const llwchar FIRST_EMBEDDED_CHAR = 0x100000; +const llwchar LAST_EMBEDDED_CHAR = 0x10ffff; +const S32 MAX_EMBEDDED_ITEMS = LAST_EMBEDDED_CHAR - FIRST_EMBEDDED_CHAR + 1; + +// +// Classes +// +class LLTextSegment; +class LLTextCmd; + +class LLTextEditor : public LLUICtrl, LLEditMenuHandler +{ + friend class LLTextCmd; +public: + LLTextEditor(const LLString& name, + const LLRect& rect, + S32 max_length, + const LLString &default_text, + const LLFontGL* glfont = NULL, + BOOL allow_embedded_items = FALSE); + + virtual ~LLTextEditor(); + + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_TEXT_EDITOR; } + virtual LLString getWidgetTag() const; + + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + void setTextEditorParameters(LLXMLNodePtr node); + void setParseHTML(BOOL parsing) {mParseHTML=parsing;} + + // mousehandler overrides + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); + virtual BOOL handleHover(S32 x, S32 y, MASK mask); + virtual BOOL handleScrollWheel(S32 x, S32 y, S32 clicks); + virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask ); + virtual BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent ); + virtual BOOL handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent); + + virtual BOOL handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect); + virtual BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, void *cargo_data, + EAcceptance *accept, LLString& tooltip_msg); + + // view overrides + virtual void reshape(S32 width, S32 height, BOOL called_from_parent); + virtual void draw(); + virtual void onFocusLost(); + virtual void setEnabled(BOOL enabled); + + // uictrl overrides + virtual void onTabInto(); + virtual void clear(); + virtual void setFocus( BOOL b ); + virtual BOOL acceptsTextInput() const; + + // LLEditMenuHandler interface + virtual void undo(); + virtual BOOL canUndo(); + + virtual void redo(); + virtual BOOL canRedo(); + + virtual void cut(); + virtual BOOL canCut(); + + virtual void copy(); + virtual BOOL canCopy(); + + virtual void paste(); + virtual BOOL canPaste(); + + virtual void doDelete(); + virtual BOOL canDoDelete(); + + virtual void selectAll(); + virtual BOOL canSelectAll(); + + virtual void deselect(); + virtual BOOL canDeselect(); + + void selectNext(const LLString& search_text_in, BOOL case_insensitive, BOOL wrap = TRUE); + BOOL replaceText(const LLString& search_text, const LLString& replace_text, BOOL case_insensitive, BOOL wrap = TRUE); + void replaceTextAll(const LLString& search_text, const LLString& replace_text, BOOL case_insensitive); + + // Undo/redo stack + void blockUndo(); + + // Text editing + virtual void makePristine(); + BOOL isPristine() const; + + // inserts text at cursor + void insertText(const LLString &text); + // appends text at end + void appendText(const LLString &wtext, bool allow_undo, bool prepend_newline, + const LLStyle* segment_style = NULL); + + void appendColoredText(const LLString &wtext, bool allow_undo, + bool prepend_newline, + const LLColor4 &color, + const LLString& font_name = LLString::null); + // if styled text starts a line, you need to prepend a newline. + void appendStyledText(const LLString &new_text, bool allow_undo, + bool prepend_newline, + const LLStyle &style); + + // Removes text from the end of document + // Does not change highlight or cursor position. + void removeTextFromEnd(S32 num_chars); + + BOOL tryToRevertToPristineState(); + + void setCursor(S32 row, S32 column); + void setCursorPos(S32 offset); + void setCursorAndScrollToEnd(); + + void getCurrentLineAndColumn( S32* line, S32* col, BOOL include_wordwrap ); + + void loadKeywords(const LLString& filename, + const LLDynamicArray<const char*>& funcs, + const LLDynamicArray<const char*>& tooltips, + const LLColor3& func_color); + + void setCursorColor(const LLColor4& c) { mCursorColor = c; } + void setFgColor( const LLColor4& c ) { mFgColor = c; } + void setReadOnlyFgColor( const LLColor4& c ) { mReadOnlyFgColor = c; } + void setWriteableBgColor( const LLColor4& c ) { mWriteableBgColor = c; } + void setReadOnlyBgColor( const LLColor4& c ) { mReadOnlyBgColor = c; } + + void setTrackColor( const LLColor4& color ); + void setThumbColor( const LLColor4& color ); + void setHighlightColor( const LLColor4& color ); + void setShadowColor( const LLColor4& color ); + + // Hacky methods to make it into a word-wrapping, potentially scrolling, + // read-only text box. + void setBorderVisible(BOOL b); + void setTakesNonScrollClicks(BOOL b); + void setHideScrollbarForShortDocs(BOOL b); + + void setWordWrap( BOOL b ); + void setTabToNextField(BOOL b) { mTabToNextField = b; } + void setCommitOnFocusLost(BOOL b) { mCommitOnFocusLost = b; } + + // If takes focus, will take keyboard focus on click. + void setTakesFocus(BOOL b) { mTakesFocus = b; } + + // Hack to handle Notecards + virtual BOOL importBuffer(const LLString& buffer ); + virtual BOOL exportBuffer(LLString& buffer ); + + void setSourceID(const LLUUID& id) { mSourceID = id; } + void setAcceptCallingCardNames(BOOL enable) { mAcceptCallingCardNames = enable; } + + void setHandleEditKeysDirectly( BOOL b ) { mHandleEditKeysDirectly = b; } + + // Callbacks + static void onMouseCaptureLost( LLMouseHandler* old_captor ); + static void setLinkColor(LLColor4 color) { mLinkColor = color; } + static void setURLCallbacks( void (*callback1) (const char* url), + BOOL (*callback2) (LLString url) ) + { mURLcallback = callback1; mSecondlifeURLcallback = callback2;} + + void setOnScrollEndCallback(void (*callback)(void*), void* userdata); + + // new methods + void setValue(const LLSD& value); + LLSD getValue() const; + + const LLString& getText() const; + + // Non-undoable + void setText(const LLString &utf8str); + void setWText(const LLWString &wtext); + + S32 getMaxLength() const { return mMaxTextLength; } + + // Change cursor + void startOfLine(); + void endOfLine(); + void endOfDoc(); + + // Getters + const LLWString& getWText() const; + llwchar getWChar(S32 pos); + LLWString getWSubString(S32 pos, S32 len); + +protected: + S32 getLength() const; + void getSegmentAndOffset( S32 startpos, S32* segidxp, S32* offsetp ); + + void drawBackground(); + void drawSelectionBackground(); + void drawCursor(); + void drawText(); + void drawClippedSegment(const LLWString &wtext, S32 seg_start, S32 seg_end, F32 x, F32 y, S32 selection_left, S32 selection_right, const LLStyle& color, F32* right_x); + + void updateLineStartList(S32 startpos = 0); + void updateScrollFromCursor(); + void updateTextRect(); + void updateSegments(); + void pruneSegments(); + + void assignEmbedded(const LLString &s); + void truncate(); + + static BOOL isPartOfWord(llwchar c); + + void removeCharOrTab(); + void setCursorAtLocalPos(S32 x, S32 y, BOOL round); + S32 getCursorPosFromLocalCoord( S32 local_x, S32 local_y, BOOL round ); + + void indentSelectedLines( S32 spaces ); + S32 indentLine( S32 pos, S32 spaces ); + void unindentLineBeforeCloseBrace(); + + S32 getSegmentIdxAtOffset(S32 offset); + LLTextSegment* getSegmentAtLocalPos(S32 x, S32 y); + LLTextSegment* getSegmentAtOffset(S32 offset); + + void reportBadKeystroke(); + + BOOL handleNavigationKey(const KEY key, const MASK mask); + BOOL handleSpecialKey(const KEY key, const MASK mask, BOOL* return_key_hit); + BOOL handleSelectionKey(const KEY key, const MASK mask); + BOOL handleControlKey(const KEY key, const MASK mask); + BOOL handleEditKey(const KEY key, const MASK mask); + + BOOL hasSelection() { return (mSelectionStart !=mSelectionEnd); } + BOOL selectionContainsLineBreaks(); + void startSelection(); + void endSelection(); + void deleteSelection(BOOL transient_operation); + + S32 prevWordPos(S32 cursorPos) const; + S32 nextWordPos(S32 cursorPos) const; + + S32 getLineCount(); + S32 getLineStart( S32 line ); + void getLineAndOffset(S32 pos, S32* linep, S32* offsetp); + S32 getPos(S32 line, S32 offset); + + void changePage(S32 delta); + void changeLine(S32 delta); + + void autoIndent(); + + S32 execute(LLTextCmd* cmd); + + void findEmbeddedItemSegments(); + + virtual BOOL handleMouseUpOverSegment(S32 x, S32 y, MASK mask); + virtual llwchar pasteEmbeddedItem(llwchar ext_char); + virtual void bindEmbeddedChars(const LLFontGL* font); + virtual void unbindEmbeddedChars(const LLFontGL* font); + + S32 findHTMLToken(const LLString &line, S32 pos, BOOL reverse); + BOOL findHTML(const LLString &line, S32 *begin, S32 *end); + +protected: + // Undoable operations + void addChar(llwchar c); // at mCursorPos + S32 addChar(S32 pos, llwchar wc); + S32 overwriteChar(S32 pos, llwchar wc); + void removeChar(); + S32 removeChar(S32 pos); + S32 insert(const S32 pos, const LLWString &wstr, const BOOL group_with_next_op); + S32 remove(const S32 pos, const S32 length, const BOOL group_with_next_op); + S32 append(const LLWString &wstr, const BOOL group_with_next_op); + + // direct operations + S32 insertStringNoUndo(S32 pos, const LLWString &utf8str); // returns num of chars actually inserted + S32 removeStringNoUndo(S32 pos, S32 length); + S32 overwriteCharNoUndo(S32 pos, llwchar wc); + +public: + LLKeywords mKeywords; + static LLColor4 mLinkColor; + static void (*mURLcallback) (const char* url); + static BOOL (*mSecondlifeURLcallback) (LLString url); +protected: + LLWString mWText; + mutable LLString mUTF8Text; + mutable BOOL mTextIsUpToDate; + + S32 mMaxTextLength; // Maximum length mText is allowed to be + + const LLFontGL* mGLFont; + + LLScrollbar* mScrollbar; + LLViewBorder* mBorder; + + BOOL mBaseDocIsPristine; + LLTextCmd* mPristineCmd; + + LLTextCmd* mLastCmd; + + typedef std::deque<LLTextCmd*> undo_stack_t; + undo_stack_t mUndoStack; + + S32 mCursorPos; // I-beam is just after the mCursorPos-th character. + LLRect mTextRect; // The rect in which text is drawn. Excludes borders. + // List of offsets and segment index of the start of each line. Always has at least one node (0). + struct line_info + { + line_info(S32 segment, S32 offset) : mSegment(segment), mOffset(offset) {} + S32 mSegment; + S32 mOffset; + }; + struct line_info_compare + { + bool operator()(const line_info& a, const line_info& b) const + { + if (a.mSegment < b.mSegment) + return true; + else if (a.mSegment > b.mSegment) + return false; + else + return a.mOffset < b.mOffset; + } + }; + typedef std::vector<line_info> line_list_t; + line_list_t mLineStartList; + + // Are we in the middle of a drag-select? To figure out if there is a current + // selection, call hasSelection(). + BOOL mIsSelecting; + + S32 mSelectionStart; + S32 mSelectionEnd; + + void (*mOnScrollEndCallback)(void*); + void *mOnScrollEndData; + + typedef std::vector<LLTextSegment *> segment_list_t; + segment_list_t mSegments; + LLTextSegment* mHoverSegment; + LLFrameTimer mKeystrokeTimer; + + LLColor4 mCursorColor; + + LLColor4 mFgColor; + LLColor4 mReadOnlyFgColor; + LLColor4 mWriteableBgColor; + LLColor4 mReadOnlyBgColor; + LLColor4 mFocusBgColor; + + BOOL mReadOnly; + BOOL mWordWrap; + + BOOL mTabToNextField; // if true, tab moves focus to next field, else inserts spaces + BOOL mCommitOnFocusLost; + BOOL mTakesFocus; + BOOL mHideScrollbarForShortDocs; + BOOL mTakesNonScrollClicks; + + BOOL mAllowEmbeddedItems; + + BOOL mAcceptCallingCardNames; + + LLUUID mSourceID; + + BOOL mHandleEditKeysDirectly; // If true, the standard edit keys (Ctrl-X, Delete, etc,) are handled here instead of routed by the menu system + + // Use these to determine if a click on an embedded item is a drag + // or not. + S32 mMouseDownX; + S32 mMouseDownY; + + S32 mLastSelectionX; + S32 mLastSelectionY; + + BOOL mParseHTML; + LLString mHTML; +}; + +class LLTextSegment +{ +public: + // for creating a compare value + LLTextSegment(S32 start); + LLTextSegment( const LLStyle& style, S32 start, S32 end ); + LLTextSegment( const LLColor4& color, S32 start, S32 end, BOOL is_visible); + LLTextSegment( const LLColor4& color, S32 start, S32 end ); + LLTextSegment( const LLColor3& color, S32 start, S32 end ); + + S32 getStart() { return mStart; } + S32 getEnd() { return mEnd; } + void setEnd( S32 end ) { mEnd = end; } + const LLColor4& getColor() { return mStyle.getColor(); } + void setColor(const LLColor4 &color) { mStyle.setColor(color); } + const LLStyle& getStyle() { return mStyle; } + void setStyle(const LLStyle &style) { mStyle = style; } + void setIsDefault(BOOL b) { mIsDefault = b; } + BOOL getIsDefault() { return mIsDefault; } + + void setToken( LLKeywordToken* token ) { mToken = token; } + LLKeywordToken* getToken() { return mToken; } + BOOL getToolTip( LLString& msg ); + + void dump(); + + struct compare + { + bool operator()(const LLTextSegment* a, const LLTextSegment* b) const + { + return a->mStart < b->mStart; + } + }; + +private: + LLStyle mStyle; + S32 mStart; + S32 mEnd; + LLKeywordToken* mToken; + BOOL mIsDefault; +}; + +class LLTextCmd +{ +public: + LLTextCmd( S32 pos, BOOL group_with_next ) + : mPos(pos), + mGroupWithNext(group_with_next) + { + } + virtual ~LLTextCmd() {} + virtual BOOL execute(LLTextEditor* editor, S32* delta) = 0; + virtual S32 undo(LLTextEditor* editor) = 0; + virtual S32 redo(LLTextEditor* editor) = 0; + virtual BOOL canExtend(S32 pos); + virtual void blockExtensions(); + virtual BOOL extendAndExecute( LLTextEditor* editor, S32 pos, llwchar c, S32* delta ); + virtual BOOL hasExtCharValue( llwchar value ); + + // Define these here so they can access LLTextEditor through the friend relationship + S32 insert(LLTextEditor* editor, S32 pos, const LLWString &utf8str); + S32 remove(LLTextEditor* editor, S32 pos, S32 length); + S32 overwrite(LLTextEditor* editor, S32 pos, llwchar wc); + + BOOL groupWithNext() { return mGroupWithNext; } + +protected: + S32 mPos; + BOOL mGroupWithNext; +}; + + +#endif // LL_TEXTEDITOR_ diff --git a/indra/llui/llui.cpp b/indra/llui/llui.cpp new file mode 100644 index 0000000000..d951cb70f6 --- /dev/null +++ b/indra/llui/llui.cpp @@ -0,0 +1,1764 @@ +/** + * @file llui.cpp + * @brief UI implementation + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// Utilities functions the user interface needs + +//#include "llviewerprecompiledheaders.h" +#include "linden_common.h" + +#include <string> +#include <map> + +// Linden library includes +#include "audioengine.h" +#include "v2math.h" +#include "v4color.h" +#include "llgl.h" +#include "llrect.h" +#include "llimagegl.h" +//#include "llviewerimage.h" +#include "lldir.h" +#include "llfontgl.h" + +// Project includes +//#include "audioengine.h" +#include "llcontrol.h" +//#include "llstartup.h" +#include "llui.h" +#include "llview.h" +#include "llwindow.h" + +#include "llglheaders.h" + +// +// Globals +// +const LLColor4 UI_VERTEX_COLOR(1.f, 1.f, 1.f, 1.f); + +// Used to hide the flashing text cursor when window doesn't have focus. +BOOL gShowTextEditCursor = TRUE; + +// Language for UI construction +LLString gLanguage = "english-usa"; +std::map<LLString, LLString> gTranslation; +std::list<LLString> gUntranslated; + +LLControlGroup* LLUI::sConfigGroup = NULL; +LLControlGroup* LLUI::sColorsGroup = NULL; +LLControlGroup* LLUI::sAssetsGroup = NULL; +LLImageProviderInterface* LLUI::sImageProvider = NULL; +LLUIAudioCallback LLUI::sAudioCallback = NULL; +LLVector2 LLUI::sGLScaleFactor(1.f, 1.f); +LLWindow* LLUI::sWindow = NULL; +BOOL LLUI::sShowXUINames = FALSE; +// +// Functions +// +void make_ui_sound(const LLString& name) +{ + if (!LLUI::sConfigGroup->controlExists(name)) + { + llwarns << "tried to make ui sound for unknown sound name: " << name << llendl; + } + else + { + LLUUID uuid(LLUI::sConfigGroup->getString(name)); + if (uuid.isNull()) + { + if ("00000000-0000-0000-0000-000000000000" == LLUI::sConfigGroup->getString(name)) + { + if (LLUI::sConfigGroup->getBOOL("UISndDebugSpamToggle")) + { + llinfos << "ui sound name: " << name << " triggered but silent (null uuid)" << llendl; + } + } + else + { + llwarns << "ui sound named: " << name << " does not translate to a valid uuid" << llendl; + } + + } + else if (LLUI::sAudioCallback != NULL) + { + if (LLUI::sConfigGroup->getBOOL("UISndDebugSpamToggle")) + { + llinfos << "ui sound name: " << name << llendl; + } + LLUI::sAudioCallback(uuid, LLUI::sConfigGroup->getF32("AudioLevelUI")); + } + } +} + +BOOL ui_point_in_rect(S32 x, S32 y, S32 left, S32 top, S32 right, S32 bottom) +{ + if (x < left || right < x) return FALSE; + if (y < bottom || top < y) return FALSE; + return TRUE; +} + + +// Puts GL into 2D drawing mode by turning off lighting, setting to an +// orthographic projection, etc. +void gl_state_for_2d(S32 width, S32 height) +{ + stop_glerror(); + F32 window_width = (F32) width;//gViewerWindow->getWindowWidth(); + F32 window_height = (F32) height;//gViewerWindow->getWindowHeight(); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0.0f, window_width, 0.0f, window_height, -1.0f, 1.0f); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + stop_glerror(); +} + + +void gl_draw_x(const LLRect& rect, const LLColor4& color) +{ + LLGLSNoTexture no_texture; + + glColor4fv( color.mV ); + + glBegin( GL_LINES ); + glVertex2i( rect.mLeft, rect.mTop ); + glVertex2i( rect.mRight, rect.mBottom ); + glVertex2i( rect.mLeft, rect.mBottom ); + glVertex2i( rect.mRight, rect.mTop ); + glEnd(); +} + + +void gl_rect_2d_offset_local( S32 left, S32 top, S32 right, S32 bottom, const LLColor4 &color, S32 pixel_offset, BOOL filled) +{ + glColor4fv(color.mV); + gl_rect_2d_offset_local(left, top, right, bottom, pixel_offset, filled); +} + +void gl_rect_2d_offset_local( S32 left, S32 top, S32 right, S32 bottom, S32 pixel_offset, BOOL filled) +{ + glPushMatrix(); + left += LLFontGL::sCurOrigin.mX; + right += LLFontGL::sCurOrigin.mX; + bottom += LLFontGL::sCurOrigin.mY; + top += LLFontGL::sCurOrigin.mY; + + glLoadIdentity(); + gl_rect_2d(llfloor((F32)left * LLUI::sGLScaleFactor.mV[VX]) - pixel_offset, + llfloor((F32)top * LLUI::sGLScaleFactor.mV[VY]) + pixel_offset, + llfloor((F32)right * LLUI::sGLScaleFactor.mV[VX]) + pixel_offset, + llfloor((F32)bottom * LLUI::sGLScaleFactor.mV[VY]) - pixel_offset, + filled); + glPopMatrix(); +} + + +void gl_rect_2d(S32 left, S32 top, S32 right, S32 bottom, BOOL filled ) +{ + stop_glerror(); + LLGLSNoTexture no_texture; + + // Counterclockwise quad will face the viewer + if( filled ) + { + glBegin( GL_QUADS ); + glVertex2i(left, top); + glVertex2i(left, bottom); + glVertex2i(right, bottom); + glVertex2i(right, top); + glEnd(); + } + else + { + if( gGLManager.mATIOffsetVerticalLines ) + { + // Work around bug in ATI driver: vertical lines are offset by (-1,-1) + glBegin( GL_LINES ); + + // Verticals + glVertex2i(left + 1, top); + glVertex2i(left + 1, bottom); + + glVertex2i(right, bottom); + glVertex2i(right, top); + + // Horizontals + top--; + right--; + glVertex2i(left, bottom); + glVertex2i(right, bottom); + + glVertex2i(left, top); + glVertex2i(right, top); + glEnd(); + } + else + { + top--; + right--; + glBegin( GL_LINE_STRIP ); + glVertex2i(left, top); + glVertex2i(left, bottom); + glVertex2i(right, bottom); + glVertex2i(right, top); + glVertex2i(left, top); + glEnd(); + } + } + stop_glerror(); +} + +void gl_rect_2d(S32 left, S32 top, S32 right, S32 bottom, const LLColor4 &color, BOOL filled ) +{ + glColor4fv( color.mV ); + gl_rect_2d( left, top, right, bottom, filled ); +} + + +void gl_rect_2d( const LLRect& rect, const LLColor4& color, BOOL filled ) +{ + glColor4fv( color.mV ); + gl_rect_2d( rect.mLeft, rect.mTop, rect.mRight, rect.mBottom, filled ); +} + +// Given a rectangle on the screen, draws a drop shadow _outside_ +// the right and bottom edges of it. Along the right it has width "lines" +// and along the bottom it has height "lines". +void gl_drop_shadow(S32 left, S32 top, S32 right, S32 bottom, const LLColor4 &start_color, S32 lines) +{ + stop_glerror(); + LLGLSNoTexture no_texture; + + // HACK: Overlap with the rectangle by a single pixel. + right--; + bottom++; + lines++; + + LLColor4 end_color = start_color; + end_color.mV[VALPHA] = 0.f; + + glBegin(GL_QUADS); + + // Right edge, CCW faces screen + glColor4fv(start_color.mV); + glVertex2i(right, top-lines); + glVertex2i(right, bottom); + glColor4fv(end_color.mV); + glVertex2i(right+lines, bottom); + glVertex2i(right+lines, top-lines); + + // Bottom edge, CCW faces screen + glColor4fv(start_color.mV); + glVertex2i(right, bottom); + glVertex2i(left+lines, bottom); + glColor4fv(end_color.mV); + glVertex2i(left+lines, bottom-lines); + glVertex2i(right, bottom-lines); + + // bottom left Corner + glColor4fv(start_color.mV); + glVertex2i(left+lines, bottom); + glColor4fv(end_color.mV); + glVertex2i(left, bottom); + // make the bottom left corner not sharp + glVertex2i(left+1, bottom-lines+1); + glVertex2i(left+lines, bottom-lines); + + // bottom right corner + glColor4fv(start_color.mV); + glVertex2i(right, bottom); + glColor4fv(end_color.mV); + glVertex2i(right, bottom-lines); + // make the rightmost corner not sharp + glVertex2i(right+lines-1, bottom-lines+1); + glVertex2i(right+lines, bottom); + + // top right corner + glColor4fv(start_color.mV); + glVertex2i( right, top-lines ); + glColor4fv(end_color.mV); + glVertex2i( right+lines, top-lines ); + // make the corner not sharp + glVertex2i( right+lines-1, top-1 ); + glVertex2i( right, top ); + + glEnd(); + stop_glerror(); +} + +void gl_line_2d(S32 x1, S32 y1, S32 x2, S32 y2 ) +{ + // Work around bug in ATI driver: vertical lines are offset by (-1,-1) + if( gGLManager.mATIOffsetVerticalLines && (x1 == x2) ) + { + x1++; + x2++; + y1++; + y2++; + } + + LLGLSNoTexture no_texture; + + glBegin(GL_LINES); + glVertex2i(x1, y1); + glVertex2i(x2, y2); + glEnd(); +} + +void gl_line_2d(S32 x1, S32 y1, S32 x2, S32 y2, const LLColor4 &color ) +{ + // Work around bug in ATI driver: vertical lines are offset by (-1,-1) + if( gGLManager.mATIOffsetVerticalLines && (x1 == x2) ) + { + x1++; + x2++; + y1++; + y2++; + } + + LLGLSNoTexture no_texture; + + glColor4fv( color.mV ); + + glBegin(GL_LINES); + glVertex2i(x1, y1); + glVertex2i(x2, y2); + glEnd(); +} + +void gl_triangle_2d(S32 x1, S32 y1, S32 x2, S32 y2, S32 x3, S32 y3, const LLColor4& color, BOOL filled) +{ + LLGLSNoTexture no_texture; + + glColor4fv(color.mV); + + if (filled) + { + glBegin(GL_TRIANGLES); + } + else + { + glBegin(GL_LINE_LOOP); + } + glVertex2i(x1, y1); + glVertex2i(x2, y2); + glVertex2i(x3, y3); + glEnd(); +} + +void gl_corners_2d(S32 left, S32 top, S32 right, S32 bottom, S32 length, F32 max_frac) +{ + LLGLSNoTexture no_texture; + + length = llmin((S32)(max_frac*(right - left)), length); + length = llmin((S32)(max_frac*(top - bottom)), length); + glBegin(GL_LINES); + glVertex2i(left, top); + glVertex2i(left + length, top); + + glVertex2i(left, top); + glVertex2i(left, top - length); + + glVertex2i(left, bottom); + glVertex2i(left + length, bottom); + + glVertex2i(left, bottom); + glVertex2i(left, bottom + length); + + glVertex2i(right, top); + glVertex2i(right - length, top); + + glVertex2i(right, top); + glVertex2i(right, top - length); + + glVertex2i(right, bottom); + glVertex2i(right - length, bottom); + + glVertex2i(right, bottom); + glVertex2i(right, bottom + length); + glEnd(); +} + + +void gl_draw_image( S32 x, S32 y, LLImageGL* image, const LLColor4& color ) +{ + gl_draw_scaled_rotated_image( x, y, image->getWidth(), image->getHeight(), 0.f, image, color ); +} + +void gl_draw_scaled_image(S32 x, S32 y, S32 width, S32 height, LLImageGL* image, const LLColor4& color) +{ + gl_draw_scaled_rotated_image( x, y, width, height, 0.f, image, color ); +} + +void gl_draw_scaled_image_with_border(S32 x, S32 y, S32 border_width, S32 border_height, S32 width, S32 height, LLImageGL* image, const LLColor4& color, BOOL solid_color) +{ + stop_glerror(); + F32 border_scale = 1.f; + + if (border_height * 2 > height) + { + border_scale = (F32)height / ((F32)border_height * 2.f); + } + if (border_width * 2 > width) + { + border_scale = llmin(border_scale, (F32)width / ((F32)border_width * 2.f)); + } + + // scale screen size of borders down + S32 scaled_border_width = llfloor(border_scale * (F32)border_width); + S32 scaled_border_height = llfloor(border_scale * (F32)border_height); + + LLGLSUIDefault gls_ui; + + if (solid_color) + { + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB); + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE); + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_MODULATE); + + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR); + + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA); + + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_ARB, GL_PRIMARY_COLOR_ARB); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_ARB, GL_SRC_ALPHA); + } + + glPushMatrix(); + { + glTranslatef((F32)x, (F32)y, 0.f); + + image->bind(); + + glColor4fv(color.mV); + + F32 border_width_fraction = (F32)border_width / (F32)image->getWidth(); + F32 border_height_fraction = (F32)border_height / (F32)image->getHeight(); + + glBegin(GL_QUADS); + { + // draw bottom left + glTexCoord2f(0.f, 0.f); + glVertex2i(0, 0); + + glTexCoord2f(border_width_fraction, 0.f); + glVertex2i(scaled_border_width, 0); + + glTexCoord2f(border_width_fraction, border_height_fraction); + glVertex2i(scaled_border_width, scaled_border_height); + + glTexCoord2f(0.f, border_height_fraction); + glVertex2i(0, scaled_border_height); + + // draw bottom middle + glTexCoord2f(border_width_fraction, 0.f); + glVertex2i(scaled_border_width, 0); + + glTexCoord2f(1.f - border_width_fraction, 0.f); + glVertex2i(width - scaled_border_width, 0); + + glTexCoord2f(1.f - border_width_fraction, border_height_fraction); + glVertex2i(width - scaled_border_width, scaled_border_height); + + glTexCoord2f(border_width_fraction, border_height_fraction); + glVertex2i(scaled_border_width, scaled_border_height); + + // draw bottom right + glTexCoord2f(1.f - border_width_fraction, 0.f); + glVertex2i(width - scaled_border_width, 0); + + glTexCoord2f(1.f, 0.f); + glVertex2i(width, 0); + + glTexCoord2f(1.f, border_height_fraction); + glVertex2i(width, scaled_border_height); + + glTexCoord2f(1.f - border_width_fraction, border_height_fraction); + glVertex2i(width - scaled_border_width, scaled_border_height); + + // draw left + glTexCoord2f(0.f, border_height_fraction); + glVertex2i(0, scaled_border_height); + + glTexCoord2f(border_width_fraction, border_height_fraction); + glVertex2i(scaled_border_width, scaled_border_height); + + glTexCoord2f(border_width_fraction, 1.f - border_height_fraction); + glVertex2i(scaled_border_width, height - scaled_border_height); + + glTexCoord2f(0.f, 1.f - border_height_fraction); + glVertex2i(0, height - scaled_border_height); + + // draw middle + glTexCoord2f(border_width_fraction, border_height_fraction); + glVertex2i(scaled_border_width, scaled_border_height); + + glTexCoord2f(1.f - border_width_fraction, border_height_fraction); + glVertex2i(width - scaled_border_width, scaled_border_height); + + glTexCoord2f(1.f - border_width_fraction, 1.f - border_height_fraction); + glVertex2i(width - scaled_border_width, height - scaled_border_height); + + glTexCoord2f(border_width_fraction, 1.f - border_height_fraction); + glVertex2i(scaled_border_width, height - scaled_border_height); + + // draw right + glTexCoord2f(1.f - border_width_fraction, border_height_fraction); + glVertex2i(width - scaled_border_width, scaled_border_height); + + glTexCoord2f(1.f, border_height_fraction); + glVertex2i(width, scaled_border_height); + + glTexCoord2f(1.f, 1.f - border_height_fraction); + glVertex2i(width, height - scaled_border_height); + + glTexCoord2f(1.f - border_width_fraction, 1.f - border_height_fraction); + glVertex2i(width - scaled_border_width, height - scaled_border_height); + + // draw top left + glTexCoord2f(0.f, 1.f - border_height_fraction); + glVertex2i(0, height - scaled_border_height); + + glTexCoord2f(border_width_fraction, 1.f - border_height_fraction); + glVertex2i(scaled_border_width, height - scaled_border_height); + + glTexCoord2f(border_width_fraction, 1.f); + glVertex2i(scaled_border_width, height); + + glTexCoord2f(0.f, 1.f); + glVertex2i(0, height); + + // draw top middle + glTexCoord2f(border_width_fraction, 1.f - border_height_fraction); + glVertex2i(scaled_border_width, height - scaled_border_height); + + glTexCoord2f(1.f - border_width_fraction, 1.f - border_height_fraction); + glVertex2i(width - scaled_border_width, height - scaled_border_height); + + glTexCoord2f(1.f - border_width_fraction, 1.f); + glVertex2i(width - scaled_border_width, height); + + glTexCoord2f(border_width_fraction, 1.f); + glVertex2i(scaled_border_width, height); + + // draw top right + glTexCoord2f(1.f - border_width_fraction, 1.f - border_height_fraction); + glVertex2i(width - scaled_border_width, height - scaled_border_height); + + glTexCoord2f(1.f, 1.f - border_height_fraction); + glVertex2i(width, height - scaled_border_height); + + glTexCoord2f(1.f, 1.f); + glVertex2i(width, height); + + glTexCoord2f(1.f - border_width_fraction, 1.f); + glVertex2i(width - scaled_border_width, height); + } + glEnd(); + } + glPopMatrix(); + + if (solid_color) + { + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + } +} + +void gl_draw_rotated_image(S32 x, S32 y, F32 degrees, LLImageGL* image, const LLColor4& color) +{ + gl_draw_scaled_rotated_image( x, y, image->getWidth(), image->getHeight(), degrees, image, color ); +} + +void gl_draw_scaled_rotated_image(S32 x, S32 y, S32 width, S32 height, F32 degrees, LLImageGL* image, const LLColor4& color) +{ + LLGLSUIDefault gls_ui; + + glPushMatrix(); + { + glTranslatef((F32)x, (F32)y, 0.f); + if( degrees ) + { + F32 offset_x = F32(width/2); + F32 offset_y = F32(height/2); + glTranslatef( offset_x, offset_y, 0.f); + glRotatef( degrees, 0.f, 0.f, 1.f ); + glTranslatef( -offset_x, -offset_y, 0.f ); + } + + image->bind(); + + glColor4fv(color.mV); + + glBegin(GL_QUADS); + { + glTexCoord2f(1.f, 1.f); + glVertex2i(width, height ); + + glTexCoord2f(0.f, 1.f); + glVertex2i(0, height ); + + glTexCoord2f(0.f, 0.f); + glVertex2i(0, 0); + + glTexCoord2f(1.f, 0.f); + glVertex2i(width, 0); + } + glEnd(); + } + glPopMatrix(); +} + + +void gl_draw_scaled_image_inverted(S32 x, S32 y, S32 width, S32 height, LLImageGL* image, const LLColor4& color) +{ + LLGLSUIDefault gls_ui; + + glPushMatrix(); + { + glTranslatef((F32)x, (F32)y, 0.f); + + image->bind(); + + glColor4fv(color.mV); + + glBegin(GL_QUADS); + { + glTexCoord2f(1.f, 0.f); + glVertex2i(width, height ); + + glTexCoord2f(0.f, 0.f); + glVertex2i(0, height ); + + glTexCoord2f(0.f, 1.f); + glVertex2i(0, 0); + + glTexCoord2f(1.f, 1.f); + glVertex2i(width, 0); + } + glEnd(); + } + glPopMatrix(); +} + + +void gl_stippled_line_3d( const LLVector3& start, const LLVector3& end, const LLColor4& color, F32 phase ) +{ + phase = fmod(phase, 1.f); + + S32 shift = S32(phase * 4.f) % 4; + + // Stippled line + LLGLEnable stipple(GL_LINE_STIPPLE); + + glColor4f(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], color.mV[VALPHA]); + glLineWidth(2.5f); + glLineStipple(2, 0x3333 << shift); + + glBegin(GL_LINES); + { + glVertex3fv( start.mV ); + glVertex3fv( end.mV ); + } + glEnd(); + + LLUI::setLineWidth(1.f); +} + + +void gl_rect_2d_xor(S32 left, S32 top, S32 right, S32 bottom) +{ + glColor4fv( LLColor4::white.mV ); + glLogicOp( GL_XOR ); + stop_glerror(); + + glBegin(GL_QUADS); + glVertex2i(left, top); + glVertex2i(left, bottom); + glVertex2i(right, bottom); + glVertex2i(right, top); + glEnd(); + + glLogicOp( GL_COPY ); + stop_glerror(); +} + + +void gl_arc_2d(F32 center_x, F32 center_y, F32 radius, S32 steps, BOOL filled, F32 start_angle, F32 end_angle) +{ + if (end_angle < start_angle) + { + end_angle += F_TWO_PI; + } + + glPushMatrix(); + { + glTranslatef(center_x, center_y, 0.f); + + // Inexact, but reasonably fast. + F32 delta = (end_angle - start_angle) / steps; + F32 sin_delta = sin( delta ); + F32 cos_delta = cos( delta ); + F32 x = cosf(start_angle) * radius; + F32 y = sinf(start_angle) * radius; + + if (filled) + { + glBegin(GL_TRIANGLE_FAN); + glVertex2f(0.f, 0.f); + // make sure circle is complete + steps += 1; + } + else + { + glBegin(GL_LINE_STRIP); + } + + while( steps-- ) + { + // Successive rotations + glVertex2f( x, y ); + F32 x_new = x * cos_delta - y * sin_delta; + y = x * sin_delta + y * cos_delta; + x = x_new; + } + glEnd(); + } + glPopMatrix(); +} + +void gl_circle_2d(F32 center_x, F32 center_y, F32 radius, S32 steps, BOOL filled) +{ + glPushMatrix(); + { + LLGLSNoTexture gls_no_texture; + glTranslatef(center_x, center_y, 0.f); + + // Inexact, but reasonably fast. + F32 delta = F_TWO_PI / steps; + F32 sin_delta = sin( delta ); + F32 cos_delta = cos( delta ); + F32 x = radius; + F32 y = 0.f; + + if (filled) + { + glBegin(GL_TRIANGLE_FAN); + glVertex2f(0.f, 0.f); + // make sure circle is complete + steps += 1; + } + else + { + glBegin(GL_LINE_LOOP); + } + + while( steps-- ) + { + // Successive rotations + glVertex2f( x, y ); + F32 x_new = x * cos_delta - y * sin_delta; + y = x * sin_delta + y * cos_delta; + x = x_new; + } + glEnd(); + } + glPopMatrix(); +} + +// Renders a ring with sides (tube shape) +void gl_deep_circle( F32 radius, F32 depth, S32 steps ) +{ + F32 x = radius; + F32 y = 0.f; + F32 angle_delta = F_TWO_PI / (F32)steps; + glBegin( GL_TRIANGLE_STRIP ); + { + S32 step = steps + 1; // An extra step to close the circle. + while( step-- ) + { + glVertex3f( x, y, depth ); + glVertex3f( x, y, 0.f ); + + F32 x_new = x * cosf(angle_delta) - y * sinf(angle_delta); + y = x * sinf(angle_delta) + y * cosf(angle_delta); + x = x_new; + } + } + glEnd(); +} + +void gl_ring( F32 radius, F32 width, const LLColor4& center_color, const LLColor4& side_color, S32 steps, BOOL render_center ) +{ + glPushMatrix(); + { + glTranslatef(0.f, 0.f, -width / 2); + if( render_center ) + { + glColor4fv(center_color.mV); + gl_deep_circle( radius, width, steps ); + } + else + { + gl_washer_2d(radius, radius - width, steps, side_color, side_color); + glTranslatef(0.f, 0.f, width); + gl_washer_2d(radius - width, radius, steps, side_color, side_color); + } + } + glPopMatrix(); +} + +// Draw gray and white checkerboard with black border +void gl_rect_2d_checkerboard(const LLRect& rect) +{ + // Initialize the first time this is called. + const S32 PIXELS = 32; + static GLubyte checkerboard[PIXELS * PIXELS]; + static BOOL first = TRUE; + if( first ) + { + for( S32 i = 0; i < PIXELS; i++ ) + { + for( S32 j = 0; j < PIXELS; j++ ) + { + checkerboard[i * PIXELS + j] = ((i & 1) ^ (j & 1)) * 0xFF; + } + } + first = FALSE; + } + + LLGLSNoTexture gls_no_texture; + + // ...white squares + glColor3f( 1.f, 1.f, 1.f ); + gl_rect_2d(rect); + + // ...gray squares + glColor3f( .7f, .7f, .7f ); + glPolygonStipple( checkerboard ); + + LLGLEnable polygon_stipple(GL_POLYGON_STIPPLE); + gl_rect_2d(rect); +} + + +// Draws the area between two concentric circles, like +// a doughnut or washer. +void gl_washer_2d(F32 outer_radius, F32 inner_radius, S32 steps, const LLColor4& inner_color, const LLColor4& outer_color) +{ + const F32 DELTA = F_TWO_PI / steps; + const F32 SIN_DELTA = sin( DELTA ); + const F32 COS_DELTA = cos( DELTA ); + + F32 x1 = outer_radius; + F32 y1 = 0.f; + F32 x2 = inner_radius; + F32 y2 = 0.f; + + LLGLSNoTexture gls_no_texture; + + glBegin( GL_TRIANGLE_STRIP ); + { + steps += 1; // An extra step to close the circle. + while( steps-- ) + { + glColor4fv(outer_color.mV); + glVertex2f( x1, y1 ); + glColor4fv(inner_color.mV); + glVertex2f( x2, y2 ); + + F32 x1_new = x1 * COS_DELTA - y1 * SIN_DELTA; + y1 = x1 * SIN_DELTA + y1 * COS_DELTA; + x1 = x1_new; + + F32 x2_new = x2 * COS_DELTA - y2 * SIN_DELTA; + y2 = x2 * SIN_DELTA + y2 * COS_DELTA; + x2 = x2_new; + } + } + glEnd(); +} + +// Draws the area between two concentric circles, like +// a doughnut or washer. +void gl_washer_segment_2d(F32 outer_radius, F32 inner_radius, F32 start_radians, F32 end_radians, S32 steps, const LLColor4& inner_color, const LLColor4& outer_color) +{ + const F32 DELTA = (end_radians - start_radians) / steps; + const F32 SIN_DELTA = sin( DELTA ); + const F32 COS_DELTA = cos( DELTA ); + + F32 x1 = outer_radius * cos( start_radians ); + F32 y1 = outer_radius * sin( start_radians ); + F32 x2 = inner_radius * cos( start_radians ); + F32 y2 = inner_radius * sin( start_radians ); + + LLGLSNoTexture gls_no_texture; + glBegin( GL_TRIANGLE_STRIP ); + { + steps += 1; // An extra step to close the circle. + while( steps-- ) + { + glColor4fv(outer_color.mV); + glVertex2f( x1, y1 ); + glColor4fv(inner_color.mV); + glVertex2f( x2, y2 ); + + F32 x1_new = x1 * COS_DELTA - y1 * SIN_DELTA; + y1 = x1 * SIN_DELTA + y1 * COS_DELTA; + x1 = x1_new; + + F32 x2_new = x2 * COS_DELTA - y2 * SIN_DELTA; + y2 = x2 * SIN_DELTA + y2 * COS_DELTA; + x2 = x2_new; + } + } + glEnd(); +} + +// Draws spokes around a circle. +void gl_washer_spokes_2d(F32 outer_radius, F32 inner_radius, S32 count, const LLColor4& inner_color, const LLColor4& outer_color) +{ + const F32 DELTA = F_TWO_PI / count; + const F32 HALF_DELTA = DELTA * 0.5f; + const F32 SIN_DELTA = sin( DELTA ); + const F32 COS_DELTA = cos( DELTA ); + + F32 x1 = outer_radius * cos( HALF_DELTA ); + F32 y1 = outer_radius * sin( HALF_DELTA ); + F32 x2 = inner_radius * cos( HALF_DELTA ); + F32 y2 = inner_radius * sin( HALF_DELTA ); + + LLGLSNoTexture gls_no_texture; + + glBegin( GL_LINES ); + { + while( count-- ) + { + glColor4fv(outer_color.mV); + glVertex2f( x1, y1 ); + glColor4fv(inner_color.mV); + glVertex2f( x2, y2 ); + + F32 x1_new = x1 * COS_DELTA - y1 * SIN_DELTA; + y1 = x1 * SIN_DELTA + y1 * COS_DELTA; + x1 = x1_new; + + F32 x2_new = x2 * COS_DELTA - y2 * SIN_DELTA; + y2 = x2 * SIN_DELTA + y2 * COS_DELTA; + x2 = x2_new; + } + } + glEnd(); +} + +void gl_rect_2d_simple_tex( S32 width, S32 height ) +{ + glBegin( GL_QUADS ); + + glTexCoord2f(1.f, 1.f); + glVertex2i(width, height); + + glTexCoord2f(0.f, 1.f); + glVertex2i(0, height); + + glTexCoord2f(0.f, 0.f); + glVertex2i(0, 0); + + glTexCoord2f(1.f, 0.f); + glVertex2i(width, 0); + + glEnd(); +} + +void gl_rect_2d_simple( S32 width, S32 height ) +{ + glBegin( GL_QUADS ); + glVertex2i(width, height); + glVertex2i(0, height); + glVertex2i(0, 0); + glVertex2i(width, 0); + glEnd(); +} + +void gl_segmented_rect_2d_tex(const S32 left, + const S32 top, + const S32 right, + const S32 bottom, + const S32 texture_width, + const S32 texture_height, + const S32 border_size, + const U32 edges) +{ + S32 width = llabs(right - left); + S32 height = llabs(top - bottom); + + glPushMatrix(); + + glTranslatef((F32)left, (F32)bottom, 0.f); + LLVector2 border_uv_scale((F32)border_size / (F32)texture_width, (F32)border_size / (F32)texture_height); + + if (border_uv_scale.mV[VX] > 0.5f) + { + border_uv_scale *= 0.5f / border_uv_scale.mV[VX]; + } + if (border_uv_scale.mV[VY] > 0.5f) + { + border_uv_scale *= 0.5f / border_uv_scale.mV[VY]; + } + + F32 border_scale = llmin((F32)border_size, (F32)width * 0.5f, (F32)height * 0.5f); + LLVector2 border_width_left = ((edges & (~(U32)ROUNDED_RECT_RIGHT)) != 0) ? LLVector2(border_scale, 0.f) : LLVector2::zero; + LLVector2 border_width_right = ((edges & (~(U32)ROUNDED_RECT_LEFT)) != 0) ? LLVector2(border_scale, 0.f) : LLVector2::zero; + LLVector2 border_height_bottom = ((edges & (~(U32)ROUNDED_RECT_TOP)) != 0) ? LLVector2(0.f, border_scale) : LLVector2::zero; + LLVector2 border_height_top = ((edges & (~(U32)ROUNDED_RECT_BOTTOM)) != 0) ? LLVector2(0.f, border_scale) : LLVector2::zero; + LLVector2 width_vec((F32)width, 0.f); + LLVector2 height_vec(0.f, (F32)height); + + glBegin(GL_QUADS); + { + // draw bottom left + glTexCoord2f(0.f, 0.f); + glVertex2f(0.f, 0.f); + + glTexCoord2f(border_uv_scale.mV[VX], 0.f); + glVertex2fv(border_width_left.mV); + + glTexCoord2f(border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + glVertex2fv((border_width_left + border_height_bottom).mV); + + glTexCoord2f(0.f, border_uv_scale.mV[VY]); + glVertex2fv(border_height_bottom.mV); + + // draw bottom middle + glTexCoord2f(border_uv_scale.mV[VX], 0.f); + glVertex2fv(border_width_left.mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], 0.f); + glVertex2fv((width_vec - border_width_right).mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + glVertex2fv((width_vec - border_width_right + border_height_bottom).mV); + + glTexCoord2f(border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + glVertex2fv((border_width_left + border_height_bottom).mV); + + // draw bottom right + glTexCoord2f(1.f - border_uv_scale.mV[VX], 0.f); + glVertex2fv((width_vec - border_width_right).mV); + + glTexCoord2f(1.f, 0.f); + glVertex2fv(width_vec.mV); + + glTexCoord2f(1.f, border_uv_scale.mV[VY]); + glVertex2fv((width_vec + border_height_bottom).mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + glVertex2fv((width_vec - border_width_right + border_height_bottom).mV); + + // draw left + glTexCoord2f(0.f, border_uv_scale.mV[VY]); + glVertex2fv(border_height_bottom.mV); + + glTexCoord2f(border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + glVertex2fv((border_width_left + border_height_bottom).mV); + + glTexCoord2f(border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + glVertex2fv((border_width_left + height_vec - border_height_top).mV); + + glTexCoord2f(0.f, 1.f - border_uv_scale.mV[VY]); + glVertex2fv((height_vec - border_height_top).mV); + + // draw middle + glTexCoord2f(border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + glVertex2fv((border_width_left + border_height_bottom).mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + glVertex2fv((width_vec - border_width_right + border_height_bottom).mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + glVertex2fv((width_vec - border_width_right + height_vec - border_height_top).mV); + + glTexCoord2f(border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + glVertex2fv((border_width_left + height_vec - border_height_top).mV); + + // draw right + glTexCoord2f(1.f - border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + glVertex2fv((width_vec - border_width_right + border_height_bottom).mV); + + glTexCoord2f(1.f, border_uv_scale.mV[VY]); + glVertex2fv((width_vec + border_height_bottom).mV); + + glTexCoord2f(1.f, 1.f - border_uv_scale.mV[VY]); + glVertex2fv((width_vec + height_vec - border_height_top).mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + glVertex2fv((width_vec - border_width_right + height_vec - border_height_top).mV); + + // draw top left + glTexCoord2f(0.f, 1.f - border_uv_scale.mV[VY]); + glVertex2fv((height_vec - border_height_top).mV); + + glTexCoord2f(border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + glVertex2fv((border_width_left + height_vec - border_height_top).mV); + + glTexCoord2f(border_uv_scale.mV[VX], 1.f); + glVertex2fv((border_width_left + height_vec).mV); + + glTexCoord2f(0.f, 1.f); + glVertex2fv((height_vec).mV); + + // draw top middle + glTexCoord2f(border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + glVertex2fv((border_width_left + height_vec - border_height_top).mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + glVertex2fv((width_vec - border_width_right + height_vec - border_height_top).mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], 1.f); + glVertex2fv((width_vec - border_width_right + height_vec).mV); + + glTexCoord2f(border_uv_scale.mV[VX], 1.f); + glVertex2fv((border_width_left + height_vec).mV); + + // draw top right + glTexCoord2f(1.f - border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + glVertex2fv((width_vec - border_width_right + height_vec - border_height_top).mV); + + glTexCoord2f(1.f, 1.f - border_uv_scale.mV[VY]); + glVertex2fv((width_vec + height_vec - border_height_top).mV); + + glTexCoord2f(1.f, 1.f); + glVertex2fv((width_vec + height_vec).mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], 1.f); + glVertex2fv((width_vec - border_width_right + height_vec).mV); + } + glEnd(); + + glPopMatrix(); +} + +void gl_segmented_rect_2d_fragment_tex(const S32 left, + const S32 top, + const S32 right, + const S32 bottom, + const S32 texture_width, + const S32 texture_height, + const S32 border_size, + const F32 start_fragment, + const F32 end_fragment, + const U32 edges) +{ + S32 width = llabs(right - left); + S32 height = llabs(top - bottom); + + glPushMatrix(); + + glTranslatef((F32)left, (F32)bottom, 0.f); + LLVector2 border_uv_scale((F32)border_size / (F32)texture_width, (F32)border_size / (F32)texture_height); + + if (border_uv_scale.mV[VX] > 0.5f) + { + border_uv_scale *= 0.5f / border_uv_scale.mV[VX]; + } + if (border_uv_scale.mV[VY] > 0.5f) + { + border_uv_scale *= 0.5f / border_uv_scale.mV[VY]; + } + + F32 border_scale = llmin((F32)border_size, (F32)width * 0.5f, (F32)height * 0.5f); + LLVector2 border_width_left = ((edges & (~(U32)ROUNDED_RECT_RIGHT)) != 0) ? LLVector2(border_scale, 0.f) : LLVector2::zero; + LLVector2 border_width_right = ((edges & (~(U32)ROUNDED_RECT_LEFT)) != 0) ? LLVector2(border_scale, 0.f) : LLVector2::zero; + LLVector2 border_height_bottom = ((edges & (~(U32)ROUNDED_RECT_TOP)) != 0) ? LLVector2(0.f, border_scale) : LLVector2::zero; + LLVector2 border_height_top = ((edges & (~(U32)ROUNDED_RECT_BOTTOM)) != 0) ? LLVector2(0.f, border_scale) : LLVector2::zero; + LLVector2 width_vec((F32)width, 0.f); + LLVector2 height_vec(0.f, (F32)height); + + F32 middle_start = border_scale / (F32)width; + F32 middle_end = 1.f - middle_start; + + F32 u_min; + F32 u_max; + LLVector2 x_min; + LLVector2 x_max; + + glBegin(GL_QUADS); + { + if (start_fragment < middle_start) + { + u_min = (start_fragment / middle_start) * border_uv_scale.mV[VX]; + u_max = llmin(end_fragment / middle_start, 1.f) * border_uv_scale.mV[VX]; + x_min = (start_fragment / middle_start) * border_width_left; + x_max = llmin(end_fragment / middle_start, 1.f) * border_width_left; + + // draw bottom left + glTexCoord2f(u_min, 0.f); + glVertex2fv(x_min.mV); + + glTexCoord2f(border_uv_scale.mV[VX], 0.f); + glVertex2fv(x_max.mV); + + glTexCoord2f(u_max, border_uv_scale.mV[VY]); + glVertex2fv((x_max + border_height_bottom).mV); + + glTexCoord2f(u_min, border_uv_scale.mV[VY]); + glVertex2fv((x_min + border_height_bottom).mV); + + // draw left + glTexCoord2f(u_min, border_uv_scale.mV[VY]); + glVertex2fv((x_min + border_height_bottom).mV); + + glTexCoord2f(u_max, border_uv_scale.mV[VY]); + glVertex2fv((x_max + border_height_bottom).mV); + + glTexCoord2f(u_max, 1.f - border_uv_scale.mV[VY]); + glVertex2fv((x_max + height_vec - border_height_top).mV); + + glTexCoord2f(u_min, 1.f - border_uv_scale.mV[VY]); + glVertex2fv((x_min + height_vec - border_height_top).mV); + + // draw top left + glTexCoord2f(u_min, 1.f - border_uv_scale.mV[VY]); + glVertex2fv((x_min + height_vec - border_height_top).mV); + + glTexCoord2f(u_max, 1.f - border_uv_scale.mV[VY]); + glVertex2fv((x_max + height_vec - border_height_top).mV); + + glTexCoord2f(u_max, 1.f); + glVertex2fv((x_max + height_vec).mV); + + glTexCoord2f(u_min, 1.f); + glVertex2fv((x_min + height_vec).mV); + } + + if (end_fragment > middle_start || start_fragment < middle_end) + { + x_min = border_width_left + ((llclamp(start_fragment, middle_start, middle_end) - middle_start)) * width_vec; + x_max = border_width_left + ((llclamp(end_fragment, middle_start, middle_end) - middle_start)) * width_vec; + + // draw bottom middle + glTexCoord2f(border_uv_scale.mV[VX], 0.f); + glVertex2fv(x_min.mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], 0.f); + glVertex2fv((x_max).mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + glVertex2fv((x_max + border_height_bottom).mV); + + glTexCoord2f(border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + glVertex2fv((x_min + border_height_bottom).mV); + + // draw middle + glTexCoord2f(border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + glVertex2fv((x_min + border_height_bottom).mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + glVertex2fv((x_max + border_height_bottom).mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + glVertex2fv((x_max + height_vec - border_height_top).mV); + + glTexCoord2f(border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + glVertex2fv((x_min + height_vec - border_height_top).mV); + + // draw top middle + glTexCoord2f(border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + glVertex2fv((x_min + height_vec - border_height_top).mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + glVertex2fv((x_max + height_vec - border_height_top).mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], 1.f); + glVertex2fv((x_max + height_vec).mV); + + glTexCoord2f(border_uv_scale.mV[VX], 1.f); + glVertex2fv((x_min + height_vec).mV); + } + + if (end_fragment > middle_end) + { + u_min = (1.f - llmax(0.f, ((start_fragment - middle_end) / middle_start))) * border_uv_scale.mV[VX]; + u_max = (1.f - ((end_fragment - middle_end) / middle_start)) * border_uv_scale.mV[VX]; + x_min = width_vec - ((1.f - llmax(0.f, ((start_fragment - middle_end) / middle_start))) * border_width_right); + x_max = width_vec - ((1.f - ((end_fragment - middle_end) / middle_start)) * border_width_right); + + // draw bottom right + glTexCoord2f(u_min, 0.f); + glVertex2fv((x_min).mV); + + glTexCoord2f(u_max, 0.f); + glVertex2fv(x_max.mV); + + glTexCoord2f(u_max, border_uv_scale.mV[VY]); + glVertex2fv((x_max + border_height_bottom).mV); + + glTexCoord2f(u_min, border_uv_scale.mV[VY]); + glVertex2fv((x_min + border_height_bottom).mV); + + // draw right + glTexCoord2f(u_min, border_uv_scale.mV[VY]); + glVertex2fv((x_min + border_height_bottom).mV); + + glTexCoord2f(u_max, border_uv_scale.mV[VY]); + glVertex2fv((x_max + border_height_bottom).mV); + + glTexCoord2f(u_max, 1.f - border_uv_scale.mV[VY]); + glVertex2fv((x_max + height_vec - border_height_top).mV); + + glTexCoord2f(u_min, 1.f - border_uv_scale.mV[VY]); + glVertex2fv((x_min + height_vec - border_height_top).mV); + + // draw top right + glTexCoord2f(u_min, 1.f - border_uv_scale.mV[VY]); + glVertex2fv((x_min + height_vec - border_height_top).mV); + + glTexCoord2f(u_max, 1.f - border_uv_scale.mV[VY]); + glVertex2fv((x_max + height_vec - border_height_top).mV); + + glTexCoord2f(u_max, 1.f); + glVertex2fv((x_max + height_vec).mV); + + glTexCoord2f(u_min, 1.f); + glVertex2fv((x_min + height_vec).mV); + } + } + glEnd(); + + glPopMatrix(); +} + +void gl_segmented_rect_3d_tex(const LLVector2& border_scale, const LLVector3& border_width, + const LLVector3& border_height, const LLVector3& width_vec, const LLVector3& height_vec, + const U32 edges) +{ + LLVector3 left_border_width = ((edges & (~(U32)ROUNDED_RECT_RIGHT)) != 0) ? border_width : LLVector3::zero; + LLVector3 right_border_width = ((edges & (~(U32)ROUNDED_RECT_LEFT)) != 0) ? border_width : LLVector3::zero; + + LLVector3 top_border_height = ((edges & (~(U32)ROUNDED_RECT_BOTTOM)) != 0) ? border_height : LLVector3::zero; + LLVector3 bottom_border_height = ((edges & (~(U32)ROUNDED_RECT_TOP)) != 0) ? border_height : LLVector3::zero; + + glBegin(GL_QUADS); + { + // draw bottom left + glTexCoord2f(0.f, 0.f); + glVertex3f(0.f, 0.f, 0.f); + + glTexCoord2f(border_scale.mV[VX], 0.f); + glVertex3fv(left_border_width.mV); + + glTexCoord2f(border_scale.mV[VX], border_scale.mV[VY]); + glVertex3fv((left_border_width + bottom_border_height).mV); + + glTexCoord2f(0.f, border_scale.mV[VY]); + glVertex3fv(bottom_border_height.mV); + + // draw bottom middle + glTexCoord2f(border_scale.mV[VX], 0.f); + glVertex3fv(left_border_width.mV); + + glTexCoord2f(1.f - border_scale.mV[VX], 0.f); + glVertex3fv((width_vec - right_border_width).mV); + + glTexCoord2f(1.f - border_scale.mV[VX], border_scale.mV[VY]); + glVertex3fv((width_vec - right_border_width + bottom_border_height).mV); + + glTexCoord2f(border_scale.mV[VX], border_scale.mV[VY]); + glVertex3fv((left_border_width + bottom_border_height).mV); + + // draw bottom right + glTexCoord2f(1.f - border_scale.mV[VX], 0.f); + glVertex3fv((width_vec - right_border_width).mV); + + glTexCoord2f(1.f, 0.f); + glVertex3fv(width_vec.mV); + + glTexCoord2f(1.f, border_scale.mV[VY]); + glVertex3fv((width_vec + bottom_border_height).mV); + + glTexCoord2f(1.f - border_scale.mV[VX], border_scale.mV[VY]); + glVertex3fv((width_vec - right_border_width + bottom_border_height).mV); + + // draw left + glTexCoord2f(0.f, border_scale.mV[VY]); + glVertex3fv(bottom_border_height.mV); + + glTexCoord2f(border_scale.mV[VX], border_scale.mV[VY]); + glVertex3fv((left_border_width + bottom_border_height).mV); + + glTexCoord2f(border_scale.mV[VX], 1.f - border_scale.mV[VY]); + glVertex3fv((left_border_width + height_vec - top_border_height).mV); + + glTexCoord2f(0.f, 1.f - border_scale.mV[VY]); + glVertex3fv((height_vec - top_border_height).mV); + + // draw middle + glTexCoord2f(border_scale.mV[VX], border_scale.mV[VY]); + glVertex3fv((left_border_width + bottom_border_height).mV); + + glTexCoord2f(1.f - border_scale.mV[VX], border_scale.mV[VY]); + glVertex3fv((width_vec - right_border_width + bottom_border_height).mV); + + glTexCoord2f(1.f - border_scale.mV[VX], 1.f - border_scale.mV[VY]); + glVertex3fv((width_vec - right_border_width + height_vec - top_border_height).mV); + + glTexCoord2f(border_scale.mV[VX], 1.f - border_scale.mV[VY]); + glVertex3fv((left_border_width + height_vec - top_border_height).mV); + + // draw right + glTexCoord2f(1.f - border_scale.mV[VX], border_scale.mV[VY]); + glVertex3fv((width_vec - right_border_width + bottom_border_height).mV); + + glTexCoord2f(1.f, border_scale.mV[VY]); + glVertex3fv((width_vec + bottom_border_height).mV); + + glTexCoord2f(1.f, 1.f - border_scale.mV[VY]); + glVertex3fv((width_vec + height_vec - top_border_height).mV); + + glTexCoord2f(1.f - border_scale.mV[VX], 1.f - border_scale.mV[VY]); + glVertex3fv((width_vec - right_border_width + height_vec - top_border_height).mV); + + // draw top left + glTexCoord2f(0.f, 1.f - border_scale.mV[VY]); + glVertex3fv((height_vec - top_border_height).mV); + + glTexCoord2f(border_scale.mV[VX], 1.f - border_scale.mV[VY]); + glVertex3fv((left_border_width + height_vec - top_border_height).mV); + + glTexCoord2f(border_scale.mV[VX], 1.f); + glVertex3fv((left_border_width + height_vec).mV); + + glTexCoord2f(0.f, 1.f); + glVertex3fv((height_vec).mV); + + // draw top middle + glTexCoord2f(border_scale.mV[VX], 1.f - border_scale.mV[VY]); + glVertex3fv((left_border_width + height_vec - top_border_height).mV); + + glTexCoord2f(1.f - border_scale.mV[VX], 1.f - border_scale.mV[VY]); + glVertex3fv((width_vec - right_border_width + height_vec - top_border_height).mV); + + glTexCoord2f(1.f - border_scale.mV[VX], 1.f); + glVertex3fv((width_vec - right_border_width + height_vec).mV); + + glTexCoord2f(border_scale.mV[VX], 1.f); + glVertex3fv((left_border_width + height_vec).mV); + + // draw top right + glTexCoord2f(1.f - border_scale.mV[VX], 1.f - border_scale.mV[VY]); + glVertex3fv((width_vec - right_border_width + height_vec - top_border_height).mV); + + glTexCoord2f(1.f, 1.f - border_scale.mV[VY]); + glVertex3fv((width_vec + height_vec - top_border_height).mV); + + glTexCoord2f(1.f, 1.f); + glVertex3fv((width_vec + height_vec).mV); + + glTexCoord2f(1.f - border_scale.mV[VX], 1.f); + glVertex3fv((width_vec - right_border_width + height_vec).mV); + } + glEnd(); +} + +void gl_segmented_rect_3d_tex_top(const LLVector2& border_scale, const LLVector3& border_width, const LLVector3& border_height, const LLVector3& width_vec, const LLVector3& height_vec) +{ + gl_segmented_rect_3d_tex(border_scale, border_width, border_height, width_vec, height_vec, ROUNDED_RECT_TOP); +} + +#if 0 // No longer used +void load_tr(const LLString& lang) +{ + LLString inname = "words." + lang + ".txt"; + LLString filename = gDirUtilp->getExpandedFilename(LL_PATH_SKINS, inname.c_str()); + + llifstream file; + file.open(filename.c_str(), std::ios_base::binary); + if (!file) + { + llinfos << "No translation dictionary for: " << filename << llendl; + return; + } + + llinfos << "Reading language translation dictionary: " << filename << llendl; + + gTranslation.clear(); + gUntranslated.clear(); + + const S32 MAX_LINE_LEN = 1024; + char buffer[MAX_LINE_LEN]; + while (!file.eof()) + { + file.getline(buffer, MAX_LINE_LEN); + LLString line(buffer); + S32 commentpos = line.find("//"); + if (commentpos != LLString::npos) + { + line = line.substr(0, commentpos); + } + S32 offset = line.find('\t'); + if (offset != LLString::npos) + { + LLString english = line.substr(0,offset); + LLString translation = line.substr(offset+1); + //llinfos << "TR: " << english << " = " << translation << llendl; + gTranslation[english] = translation; + } + } + + file.close(); +} + +void init_tr(const LLString& language) +{ + if (!language.empty()) + { + gLanguage = language; + } + load_tr(gLanguage); +} + +void cleanup_tr() +{ + // Dump untranslated phrases to help with translation + if (gUntranslated.size() > 0) + { + LLString outname = "untranslated_" + gLanguage + ".txt"; + LLString outfilename = gDirUtilp->getExpandedFilename(LL_PATH_SKINS, outname.c_str()); + llofstream outfile; + outfile.open(outfilename.c_str()); + if (!outfile) + { + return; + } + llinfos << "Writing untranslated words to: " << outfilename << llendl; + LLString outtext; + for (std::list<LLString>::iterator iter = gUntranslated.begin(); + iter != gUntranslated.end(); ++iter) + { + // output: english_phrase english_phrase + outtext += *iter; + outtext += "\t"; + outtext += *iter; + outtext += "\n"; + } + outfile << outtext.c_str(); + outfile.close(); + } +} + +LLString tr(const LLString& english_string) +{ + std::map<LLString, LLString>::iterator it = gTranslation.find(english_string); + if (it != gTranslation.end()) + { + return it->second; + } + else + { + gUntranslated.push_back(english_string); + return english_string; + } +} + +#endif + + +class LLShowXUINamesListener: public LLSimpleListener +{ + bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata) + { + LLUI::sShowXUINames = (BOOL) event->getValue().asBoolean(); + return true; + } +}; +static LLShowXUINamesListener show_xui_names_listener; + + +void LLUI::initClass(LLControlGroup* config, + LLControlGroup* colors, + LLControlGroup* assets, + LLImageProviderInterface* image_provider, + LLUIAudioCallback audio_callback, + const LLVector2* scale_factor, + const LLString& language) +{ + sConfigGroup = config; + sColorsGroup = colors; + sAssetsGroup = assets; + sImageProvider = image_provider; + sAudioCallback = audio_callback; + sGLScaleFactor = (scale_factor == NULL) ? LLVector2(1.f, 1.f) : *scale_factor; + sWindow = NULL; // set later in startup + LLFontGL::sShadowColor = colors->getColor("ColorDropShadow"); + + LLUI::sShowXUINames = LLUI::sConfigGroup->getBOOL("ShowXUINames"); + LLUI::sConfigGroup->getControl("ShowXUINames")->addListener(&show_xui_names_listener); +// init_tr(language); +} + +void LLUI::cleanupClass() +{ +// cleanup_tr(); +} + + +//static +void LLUI::translate(F32 x, F32 y, F32 z) +{ + glTranslatef(x,y,z); + LLFontGL::sCurOrigin.mX += (S32) x; + LLFontGL::sCurOrigin.mY += (S32) y; + LLFontGL::sCurOrigin.mZ += z; +} + +//static +void LLUI::pushMatrix() +{ + glPushMatrix(); + LLFontGL::sOriginStack.push_back(LLFontGL::sCurOrigin); +} + +//static +void LLUI::popMatrix() +{ + glPopMatrix(); + LLFontGL::sCurOrigin = *LLFontGL::sOriginStack.rbegin(); + LLFontGL::sOriginStack.pop_back(); +} + +//static +void LLUI::loadIdentity() +{ + glLoadIdentity(); + LLFontGL::sCurOrigin.mX = 0; + LLFontGL::sCurOrigin.mY = 0; + LLFontGL::sCurOrigin.mZ = 0; +} + +//static +void LLUI::setScissorRegionScreen(const LLRect& rect) +{ + stop_glerror(); + S32 x,y,w,h; + x = llround(rect.mLeft * LLUI::sGLScaleFactor.mV[VX]); + y = llround(rect.mBottom * LLUI::sGLScaleFactor.mV[VY]); + w = llround(rect.getWidth() * LLUI::sGLScaleFactor.mV[VX]); + h = llround(rect.getHeight() * LLUI::sGLScaleFactor.mV[VY]); + glScissor( x,y,w,h ); + stop_glerror(); +} + +//static +void LLUI::setScissorRegionLocal(const LLRect& rect) +{ + stop_glerror(); + S32 screen_left = LLFontGL::sCurOrigin.mX + rect.mLeft; + S32 screen_bottom = LLFontGL::sCurOrigin.mY + rect.mBottom; + + S32 x,y,w,h; + + x = llround((F32)screen_left * LLUI::sGLScaleFactor.mV[VX]); + y = llround((F32)screen_bottom * LLUI::sGLScaleFactor.mV[VY]); + w = llround((F32)rect.getWidth() * LLUI::sGLScaleFactor.mV[VX]); + h = llround((F32)rect.getHeight() * LLUI::sGLScaleFactor.mV[VY]); + + w = llmax(0,w); + h = llmax(0,h); + + glScissor(x,y,w,h); + stop_glerror(); +} + +//static +void LLUI::setScaleFactor(const LLVector2 &scale_factor) +{ + sGLScaleFactor = scale_factor; +} + +//static +void LLUI::setLineWidth(F32 width) +{ + glLineWidth(width * lerp(sGLScaleFactor.mV[VX], sGLScaleFactor.mV[VY], 0.5f)); +} + +//static +void LLUI::setCursorPositionScreen(S32 x, S32 y) +{ + S32 screen_x, screen_y; + screen_x = llround((F32)x * sGLScaleFactor.mV[VX]); + screen_y = llround((F32)y * sGLScaleFactor.mV[VY]); + + LLCoordWindow window_point; + LLView::getWindow()->convertCoords(LLCoordGL(screen_x, screen_y), &window_point); + + LLView::getWindow()->setCursorPosition(window_point); +} + +//static +void LLUI::setCursorPositionLocal(LLView* viewp, S32 x, S32 y) +{ + S32 screen_x, screen_y; + viewp->localPointToScreen(x, y, &screen_x, &screen_y); + + setCursorPositionScreen(screen_x, screen_y); +} + +//static +LLString LLUI::locateSkin(const LLString& filename) +{ + LLString slash = gDirUtilp->getDirDelimiter(); + LLString found_file = filename; + if (!gDirUtilp->fileExists(found_file)) + { + found_file = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, filename); // Should be CUSTOM_SKINS? + } + if (sConfigGroup && sConfigGroup->controlExists("Language")) + { + if (!gDirUtilp->fileExists(found_file)) + { + LLString localization(sConfigGroup->getString("Language")); + LLString local_skin = "xui" + slash + localization + slash + filename; + found_file = gDirUtilp->getExpandedFilename(LL_PATH_SKINS, local_skin); + } + } + if (!gDirUtilp->fileExists(found_file)) + { + LLString local_skin = "xui" + slash + "en-us" + slash + filename; + found_file = gDirUtilp->getExpandedFilename(LL_PATH_SKINS, local_skin); + } + if (!gDirUtilp->fileExists(found_file)) + { + found_file = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, filename); + } + return found_file; +} + +//static +LLVector2 LLUI::getWindowSize() +{ + LLCoordWindow window_rect; + sWindow->getSize(&window_rect); + + return LLVector2(window_rect.mX / sGLScaleFactor.mV[VX], window_rect.mY / sGLScaleFactor.mV[VY]); +} + +//static +LLUUID LLUI::findAssetUUIDByName(const LLString &asset_name) +{ + if(asset_name == LLString::null) return LLUUID::null; + LLString foundValue = LLUI::sConfigGroup->findString(asset_name); + if(foundValue==LLString::null) + { + foundValue = LLUI::sAssetsGroup->findString(asset_name); + } + if(foundValue == LLString::null){ + return LLUUID::null; + } + return LLUUID( foundValue ); +} diff --git a/indra/llui/llui.h b/indra/llui/llui.h new file mode 100644 index 0000000000..282e41a113 --- /dev/null +++ b/indra/llui/llui.h @@ -0,0 +1,255 @@ +/** + * @file llui.h + * @brief UI implementation + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// All immediate-mode gl drawing should happen here. + +#ifndef LL_LLUI_H +#define LL_LLUI_H + +#include "llrect.h" +#include "llcontrol.h" +#include "llrect.h" +#include "llcoord.h" + +class LLColor4; +class LLVector3; +class LLVector2; +class LLImageGL; +class LLUUID; +class LLWindow; +class LLView; + +// UI colors +extern const LLColor4 UI_VERTEX_COLOR; +void make_ui_sound(const LLString& name); + +BOOL ui_point_in_rect(S32 x, S32 y, S32 left, S32 top, S32 right, S32 bottom); +void gl_state_for_2d(S32 width, S32 height); + +void gl_line_2d(S32 x1, S32 y1, S32 x2, S32 y2); +void gl_line_2d(S32 x1, S32 y1, S32 x2, S32 y2, const LLColor4 &color ); +void gl_triangle_2d(S32 x1, S32 y1, S32 x2, S32 y2, S32 x3, S32 y3, const LLColor4& color, BOOL filled); +void gl_rect_2d_simple( S32 width, S32 height ); + +void gl_draw_x(const LLRect& rect, const LLColor4& color); + +void gl_rect_2d(S32 left, S32 top, S32 right, S32 bottom, BOOL filled = TRUE ); +void gl_rect_2d(S32 left, S32 top, S32 right, S32 bottom, const LLColor4 &color, BOOL filled = TRUE ); +void gl_rect_2d_offset_local( S32 left, S32 top, S32 right, S32 bottom, const LLColor4 &color, S32 pixel_offset = 0, BOOL filled = TRUE ); +void gl_rect_2d_offset_local( S32 left, S32 top, S32 right, S32 bottom, S32 pixel_offset = 0, BOOL filled = TRUE ); +void gl_rect_2d(const LLRect& rect, BOOL filled = TRUE ); +void gl_rect_2d(const LLRect& rect, const LLColor4& color, BOOL filled = TRUE ); +void gl_rect_2d_checkerboard(const LLRect& rect); + +void gl_drop_shadow(S32 left, S32 top, S32 right, S32 bottom, const LLColor4 &start_color, S32 lines); + +void gl_circle_2d(F32 x, F32 y, F32 radius, S32 steps, BOOL filled); +void gl_arc_2d(F32 center_x, F32 center_y, F32 radius, S32 steps, BOOL filled, F32 start_angle, F32 end_angle); +void gl_deep_circle( F32 radius, F32 depth ); +void gl_ring( F32 radius, F32 width, const LLColor4& center_color, const LLColor4& side_color, S32 steps, BOOL render_center ); +void gl_corners_2d(S32 left, S32 top, S32 right, S32 bottom, S32 length, F32 max_frac); +void gl_washer_2d(F32 outer_radius, F32 inner_radius, S32 steps, const LLColor4& inner_color, const LLColor4& outer_color); +void gl_washer_segment_2d(F32 outer_radius, F32 inner_radius, F32 start_radians, F32 end_radians, S32 steps, const LLColor4& inner_color, const LLColor4& outer_color); +void gl_washer_spokes_2d(F32 outer_radius, F32 inner_radius, S32 count, const LLColor4& inner_color, const LLColor4& outer_color); + +void gl_draw_image(S32 x, S32 y, LLImageGL* image, const LLColor4& color = UI_VERTEX_COLOR); +void gl_draw_scaled_image(S32 x, S32 y, S32 width, S32 height, LLImageGL* image, const LLColor4& color = UI_VERTEX_COLOR); +void gl_draw_rotated_image(S32 x, S32 y, F32 degrees, LLImageGL* image, const LLColor4& color = UI_VERTEX_COLOR); +void gl_draw_scaled_rotated_image(S32 x, S32 y, S32 width, S32 height, F32 degrees,LLImageGL* image, const LLColor4& color = UI_VERTEX_COLOR); +void gl_draw_scaled_image_with_border(S32 x, S32 y, S32 border_width, S32 border_height, S32 width, S32 height, LLImageGL* image, const LLColor4 &color, BOOL solid_color = FALSE); +// Flip vertical, used for LLFloaterHTML +void gl_draw_scaled_image_inverted(S32 x, S32 y, S32 width, S32 height, LLImageGL* image, const LLColor4& color = UI_VERTEX_COLOR); + +void gl_rect_2d_xor(S32 left, S32 top, S32 right, S32 bottom); +void gl_stippled_line_3d( const LLVector3& start, const LLVector3& end, const LLColor4& color, F32 phase = 0.f ); + +void gl_rect_2d_simple_tex( S32 width, S32 height ); + +// segmented rectangles + +/* + TL |______TOP_________| TR + /| |\ + _/_|__________________|_\_ + L| | MIDDLE | |R + _|_|__________________|_|_ + \ | BOTTOM | / + BL\|__________________|/ BR + | | +*/ + +typedef enum e_rounded_edge +{ + ROUNDED_RECT_LEFT = 0x1, + ROUNDED_RECT_TOP = 0x2, + ROUNDED_RECT_RIGHT = 0x4, + ROUNDED_RECT_BOTTOM = 0x8, + ROUNDED_RECT_ALL = 0xf +}ERoundedEdge; + + +void gl_segmented_rect_2d_tex(const S32 left, const S32 top, const S32 right, const S32 bottom, const S32 texture_width, const S32 texture_height, const S32 border_size, const U32 edges = ROUNDED_RECT_ALL); +void gl_segmented_rect_2d_fragment_tex(const S32 left, const S32 top, const S32 right, const S32 bottom, const S32 texture_width, const S32 texture_height, const S32 border_size, const F32 start_fragment, const F32 end_fragment, const U32 edges = ROUNDED_RECT_ALL); +void gl_segmented_rect_3d_tex(const LLVector2& border_scale, const LLVector3& border_width, const LLVector3& border_height, const LLVector3& width_vec, const LLVector3& height_vec, U32 edges = ROUNDED_RECT_ALL); +void gl_segmented_rect_3d_tex_top(const LLVector2& border_scale, const LLVector3& border_width, const LLVector3& border_height, const LLVector3& width_vec, const LLVector3& height_vec); + +inline void gl_rect_2d( const LLRect& rect, BOOL filled ) +{ + gl_rect_2d( rect.mLeft, rect.mTop, rect.mRight, rect.mBottom, filled ); +} + +inline void gl_rect_2d_offset_local( const LLRect& rect, S32 pixel_offset, BOOL filled) +{ + gl_rect_2d_offset_local( rect.mLeft, rect.mTop, rect.mRight, rect.mBottom, pixel_offset, filled ); +} + +// No longer used +// Initializes translation table +// void init_tr(); + +// Returns a string from the string table in the correct language +// LLString tr(const LLString& english_chars); + +// Used to hide the flashing text cursor when window doesn't have focus. +extern BOOL gShowTextEditCursor; + +// Language +extern LLString gLanguage; + +class LLImageProviderInterface; +typedef void (*LLUIAudioCallback)(const LLUUID& uuid, F32 volume); + +class LLUI +{ +public: + static void initClass(LLControlGroup* config, + LLControlGroup* colors, + LLControlGroup* assets, + LLImageProviderInterface* image_provider, + LLUIAudioCallback audio_callback = NULL, + const LLVector2 *scale_factor = NULL, + const LLString& language = LLString::null); + static void cleanupClass(); + + static void pushMatrix(); + static void popMatrix(); + static void loadIdentity(); + static void translate(F32 x, F32 y, F32 z = 0.0f); + + //helper functions (should probably move free standing rendering helper functions here) + static LLString locateSkin(const LLString& filename); + static void setScissorRegionScreen(const LLRect& rect); + static void setScissorRegionLocal(const LLRect& rect); // works assuming LLUI::translate has been called + static void setCursorPositionScreen(S32 x, S32 y); + static void setCursorPositionLocal(LLView* viewp, S32 x, S32 y); + static void setScaleFactor(const LLVector2& scale_factor); + static void setLineWidth(F32 width); + static LLUUID findAssetUUIDByName(const LLString& name); + static LLVector2 getWindowSize(); +public: + static LLControlGroup* sConfigGroup; + static LLControlGroup* sColorsGroup; + static LLControlGroup* sAssetsGroup; + static LLImageProviderInterface* sImageProvider; + static LLUIAudioCallback sAudioCallback; + static LLVector2 sGLScaleFactor; + static LLWindow* sWindow; + static BOOL sShowXUINames; +}; + +// UI widgets +// This MUST match UICtrlNames in lluictrlfactory.cpp +typedef enum e_widget_type +{ + WIDGET_TYPE_VIEW = 0, + WIDGET_TYPE_ROOT_VIEW, + WIDGET_TYPE_FLOATER_VIEW, + WIDGET_TYPE_BUTTON, + WIDGET_TYPE_JOYSTICK_TURN, + WIDGET_TYPE_JOYSTICK_SLIDE, + WIDGET_TYPE_CHECKBOX, + WIDGET_TYPE_COLOR_SWATCH, + WIDGET_TYPE_COMBO_BOX, + WIDGET_TYPE_LINE_EDITOR, + WIDGET_TYPE_SEARCH_EDITOR, + WIDGET_TYPE_SCROLL_LIST, + WIDGET_TYPE_NAME_LIST, + WIDGET_TYPE_WEBBROWSER, + WIDGET_TYPE_SLIDER, // actually LLSliderCtrl + WIDGET_TYPE_SLIDER_BAR, // actually LLSlider + WIDGET_TYPE_VOLUME_SLIDER,//actually LLVolumeSliderCtrl + WIDGET_TYPE_SPINNER, + WIDGET_TYPE_TEXT_EDITOR, + WIDGET_TYPE_TEXTURE_PICKER, + WIDGET_TYPE_TEXT_BOX, + WIDGET_TYPE_PAD, // used in XML for positioning, not a real widget + WIDGET_TYPE_RADIO_GROUP, + WIDGET_TYPE_ICON, + WIDGET_TYPE_LOCATE, // used in XML for positioning, not a real widget + WIDGET_TYPE_VIEW_BORDER, // decorative border + WIDGET_TYPE_PANEL, + WIDGET_TYPE_MENU, + WIDGET_TYPE_PIE_MENU, + WIDGET_TYPE_PIE_MENU_BRANCH, + WIDGET_TYPE_MENU_ITEM, + WIDGET_TYPE_MENU_ITEM_SEPARATOR, + WIDGET_TYPE_MENU_SEPARATOR_VERTICAL, + WIDGET_TYPE_MENU_ITEM_CALL, + WIDGET_TYPE_MENU_ITEM_CHECK, + WIDGET_TYPE_MENU_ITEM_BRANCH, + WIDGET_TYPE_MENU_ITEM_BRANCH_DOWN, + WIDGET_TYPE_MENU_ITEM_BLANK, + WIDGET_TYPE_TEAROFF_MENU, + WIDGET_TYPE_MENU_BAR, + WIDGET_TYPE_TAB_CONTAINER, + WIDGET_TYPE_SCROLL_CONTAINER, // LLScrollableContainerView + WIDGET_TYPE_SCROLLBAR, + WIDGET_TYPE_INVENTORY_PANEL, // LLInventoryPanel + WIDGET_TYPE_FLOATER, + WIDGET_TYPE_DRAG_HANDLE_TOP, + WIDGET_TYPE_DRAG_HANDLE_LEFT, + WIDGET_TYPE_RESIZE_HANDLE, + WIDGET_TYPE_RESIZE_BAR, + WIDGET_TYPE_NAME_EDITOR, + WIDGET_TYPE_MULTI_FLOATER, + WIDGET_TYPE_MEDIA_REMOTE, + WIDGET_TYPE_FOLDER_VIEW, + WIDGET_TYPE_FOLDER_ITEM, + WIDGET_TYPE_FOLDER, + WIDGET_TYPE_STAT_GRAPH, + WIDGET_TYPE_STAT_VIEW, + WIDGET_TYPE_STAT_BAR, + WIDGET_TYPE_DROP_TARGET, + WIDGET_TYPE_TEXTURE_BAR, + WIDGET_TYPE_TEX_MEM_BAR, + WIDGET_TYPE_SNAPSHOT_LIVE_PREVIEW, + WIDGET_TYPE_STATUS_BAR, + WIDGET_TYPE_PROGRESS_VIEW, + WIDGET_TYPE_TALK_VIEW, + WIDGET_TYPE_OVERLAY_BAR, + WIDGET_TYPE_HUD_VIEW, + WIDGET_TYPE_HOVER_VIEW, + WIDGET_TYPE_MORPH_VIEW, + WIDGET_TYPE_NET_MAP, + WIDGET_TYPE_PERMISSIONS_VIEW, + WIDGET_TYPE_MENU_HOLDER, + WIDGET_TYPE_DEBUG_VIEW, + WIDGET_TYPE_SCROLLING_PANEL_LIST, + WIDGET_TYPE_AUDIO_STATUS, + WIDGET_TYPE_CONTAINER_VIEW, + WIDGET_TYPE_CONSOLE, + WIDGET_TYPE_FAST_TIMER_VIEW, + WIDGET_TYPE_VELOCITY_BAR, + WIDGET_TYPE_TEXTURE_VIEW, + WIDGET_TYPE_MEMORY_VIEW, + WIDGET_TYPE_FRAME_STAT_VIEW, + WIDGET_TYPE_DONTCARE, + WIDGET_TYPE_COUNT +} EWidgetType; + +#endif diff --git a/indra/llui/lluiconstants.h b/indra/llui/lluiconstants.h new file mode 100644 index 0000000000..01663ed233 --- /dev/null +++ b/indra/llui/lluiconstants.h @@ -0,0 +1,32 @@ +/** + * @file lluiconstants.h + * @brief Compile-time configuration for UI + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLUICONSTANTS_H +#define LL_LLUICONSTANTS_H + +// spacing for small font lines of text, like LLTextBoxes +const S32 LINE = 16; + +// spacing for larger lines of text +const S32 LINE_BIG = 24; + +// default vertical padding +const S32 VPAD = 4; + +// default horizontal padding +const S32 HPAD = 4; + +// Account History, how far to look into past +const S32 SUMMARY_INTERVAL = 7; // one week +const S32 SUMMARY_MAX = 8; // +const S32 DETAILS_INTERVAL = 1; // one day +const S32 DETAILS_MAX = 30; // one month +const S32 TRANSACTIONS_INTERVAL = 1;// one day +const S32 TRANSACTIONS_MAX = 30; // one month + +#endif diff --git a/indra/llui/lluictrl.cpp b/indra/llui/lluictrl.cpp new file mode 100644 index 0000000000..0d9791c660 --- /dev/null +++ b/indra/llui/lluictrl.cpp @@ -0,0 +1,341 @@ +/** + * @file lluictrl.cpp + * @author James Cook, Richard Nelson, Tom Yedwab + * @brief Abstract base class for UI controls + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//#include "llviewerprecompiledheaders.h" +#include "linden_common.h" + +#include "lluictrl.h" + +#include "llgl.h" +#include "llui.h" +#include "lluiconstants.h" +#include "llfocusmgr.h" +#include "v3color.h" + +#include "llstring.h" +#include "llfontgl.h" +#include "llkeyboard.h" + +const U32 MAX_STRING_LENGTH = 10; + +LLUICtrl::LLUICtrl() : + mCommitCallback(NULL), + mFocusReceivedCallback(NULL), + mFocusChangedCallback(NULL), + mValidateCallback(NULL), + mCallbackUserData(NULL), + mTentative(FALSE), + mTabStop(TRUE), + mIsChrome(FALSE) +{ +} + +LLUICtrl::LLUICtrl(const LLString& name, const LLRect& rect, BOOL mouse_opaque, + void (*on_commit_callback)(LLUICtrl*, void*), + void* callback_userdata, + U32 reshape) +: // can't make this automatically follow top and left, breaks lots + // of buttons in the UI. JC 7/20/2002 + LLView( name, rect, mouse_opaque, reshape ), + mCommitCallback( on_commit_callback) , + mFocusReceivedCallback( NULL ), + mFocusChangedCallback( NULL ), + mValidateCallback( NULL ), + mCallbackUserData( callback_userdata ), + mTentative( FALSE ), + mTabStop( TRUE ), + mIsChrome(FALSE) +{ +} + +LLUICtrl::~LLUICtrl() +{ + gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit() +} + +void LLUICtrl::onCommit() +{ + if( mCommitCallback ) + { + mCommitCallback( this, mCallbackUserData ); + } +} + +// virtual +BOOL LLUICtrl::setTextArg( const LLString& key, const LLString& text ) +{ + return FALSE; +} + +// virtual +BOOL LLUICtrl::setLabelArg( const LLString& key, const LLString& text ) +{ + return FALSE; +} + +// virtual +LLCtrlSelectionInterface* LLUICtrl::getSelectionInterface() +{ + return NULL; +} + +// virtual +LLCtrlListInterface* LLUICtrl::getListInterface() +{ + return NULL; +} + +// virtual +LLCtrlScrollInterface* LLUICtrl::getScrollInterface() +{ + return NULL; +} + +// virtual +void LLUICtrl::setTabStop( BOOL b ) +{ + mTabStop = b; +} + +// virtual +BOOL LLUICtrl::hasTabStop() const +{ + return mTabStop; +} + +// virtual +BOOL LLUICtrl::acceptsTextInput() const +{ + return FALSE; +} + +// virtual +void LLUICtrl::onTabInto() +{ +} + +// virtual +void LLUICtrl::clear() +{ +} + +// virtual +void LLUICtrl::setIsChrome(BOOL is_chrome) +{ + mIsChrome = is_chrome; +} + +// virtual +BOOL LLUICtrl::getIsChrome() const +{ + return mIsChrome; +} + +void LLUICtrl::onFocusReceived() +{ + if( mFocusReceivedCallback ) + { + mFocusReceivedCallback( this, mCallbackUserData ); + } + if( mFocusChangedCallback ) + { + mFocusChangedCallback( this, mCallbackUserData ); + } +} + +void LLUICtrl::onFocusLost() +{ + if( mFocusChangedCallback ) + { + mFocusChangedCallback( this, mCallbackUserData ); + } +} + +BOOL LLUICtrl::hasFocus() const +{ + return (gFocusMgr.childHasKeyboardFocus(this)); +} + +void LLUICtrl::setFocus(BOOL b) +{ + // focus NEVER goes to ui ctrls that are disabled! + if (!mEnabled) + { + return; + } + if( b ) + { + if (!hasFocus()) + { + gFocusMgr.setKeyboardFocus( this, &LLUICtrl::onFocusLostCallback ); + onFocusReceived(); + } + } + else + { + if( gFocusMgr.childHasKeyboardFocus(this)) + { + gFocusMgr.setKeyboardFocus( NULL, NULL ); + onFocusLost(); + } + } +} + +// static +void LLUICtrl::onFocusLostCallback( LLUICtrl* old_focus ) +{ + old_focus->onFocusLost(); +} + +// this comparator uses the crazy disambiguating logic of LLCompareByTabOrder, +// but to switch up the order so that children that have the default tab group come first +// and those that are prior to the default tab group come last +class CompareByDefaultTabGroup: public LLCompareByTabOrder +{ +public: + CompareByDefaultTabGroup(LLView::child_tab_order_t order, S32 default_tab_group): + LLCompareByTabOrder(order), + mDefaultTabGroup(default_tab_group) {} +protected: + /*virtual*/ bool compareTabOrders(const LLView::tab_order_t & a, const LLView::tab_order_t & b) const + { + S32 ag = a.first; // tab group for a + S32 bg = b.first; // tab group for b + // these two ifs have the effect of moving elements prior to the default tab group to the end of the list + // (still sorted relative to each other, though) + if(ag < mDefaultTabGroup && bg >= mDefaultTabGroup) return false; + if(bg < mDefaultTabGroup && ag >= mDefaultTabGroup) return true; + return a < b; // sort correctly if they're both on the same side of the default tab group + } + S32 mDefaultTabGroup; +}; + +// sorter for plugging into the query +class DefaultTabGroupFirstSorter : public LLQuerySorter, public LLSingleton<DefaultTabGroupFirstSorter> +{ +public: + /*virtual*/ void operator() (LLView * parent, viewList_t &children) const + { + children.sort(CompareByDefaultTabGroup(parent->getCtrlOrder(), parent->getDefaultTabGroup())); + } +}; + +BOOL LLUICtrl::focusFirstItem(BOOL prefer_text_fields) +{ + // try to select default tab group child + LLCtrlQuery query = LLView::getTabOrderQuery(); + // sort things such that the default tab group is at the front + query.setSorter(DefaultTabGroupFirstSorter::getInstance()); + LLView::child_list_t result = query(this); + if(result.size() > 0) + { + LLUICtrl * ctrl = static_cast<LLUICtrl*>(result.front()); + if(!ctrl->hasFocus()) + { + ctrl->setFocus(TRUE); + ctrl->onTabInto(); + gFocusMgr.triggerFocusFlash(); + } + return TRUE; + } + // fall back on default behavior if we didn't find anything + return LLView::focusFirstItem(prefer_text_fields); +} + +/* +// Don't let the children handle the tool tip. Handle it here instead. +BOOL LLUICtrl::handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect_screen) +{ + BOOL handled = FALSE; + if (getVisible() && pointInView( x, y ) ) + { + if( !mToolTipMsg.empty() ) + { + msg = mToolTipMsg; + + // Convert rect local to screen coordinates + localPointToScreen( + 0, 0, + &(sticky_rect_screen->mLeft), &(sticky_rect_screen->mBottom) ); + localPointToScreen( + mRect.getWidth(), mRect.getHeight(), + &(sticky_rect_screen->mRight), &(sticky_rect_screen->mTop) ); + + handled = TRUE; + } + } + + if (!handled) + { + return LLView::handleToolTip(x, y, msg, sticky_rect_screen); + } + + return handled; +}*/ + +void LLUICtrl::initFromXML(LLXMLNodePtr node, LLView* parent) +{ + BOOL has_tab_stop = hasTabStop(); + node->getAttributeBOOL("tab_stop", has_tab_stop); + + setTabStop(has_tab_stop); + + LLView::initFromXML(node, parent); +} + +LLXMLNodePtr LLUICtrl::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLView::getXML(save_children); + node->createChild("tab_stop", TRUE)->setBoolValue(hasTabStop()); + + return node; +} + +// *NOTE: If other classes derive from LLPanel, they will need to be +// added to this function. +LLPanel* LLUICtrl::getParentPanel() const +{ + LLView* parent = getParent(); + while (parent + && parent->getWidgetType() != WIDGET_TYPE_PANEL + && parent->getWidgetType() != WIDGET_TYPE_FLOATER) + { + parent = parent->getParent(); + } + return reinterpret_cast<LLPanel*>(parent); +} + +// virtual +void LLUICtrl::setTentative(BOOL b) +{ + mTentative = b; +} + +// virtual +BOOL LLUICtrl::getTentative() const +{ + return mTentative; +} + +// virtual +void LLUICtrl::setDoubleClickCallback( void (*cb)(void*) ) +{ +} + +// virtual +void LLUICtrl::setColor(const LLColor4& color) +{ } + +// virtual +void LLUICtrl::setMinValue(LLSD min_value) +{ } + +// virtual +void LLUICtrl::setMaxValue(LLSD max_value) +{ } diff --git a/indra/llui/lluictrl.h b/indra/llui/lluictrl.h new file mode 100644 index 0000000000..f58b7d6e16 --- /dev/null +++ b/indra/llui/lluictrl.h @@ -0,0 +1,153 @@ +/** + * @file lluictrl.h + * @author James Cook, Richard Nelson, Tom Yedwab + * @brief Abstract base class for UI controls + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLUICTRL_H +#define LL_LLUICTRL_H + +#include "llview.h" +#include "llrect.h" +#include "llsd.h" + +// +// Classes +// +class LLViewerImage; +class LLFontGL; +class LLButton; +class LLTextBox; +class LLLineEditor; +class LLUICtrl; +class LLPanel; +class LLCtrlSelectionInterface; +class LLCtrlListInterface; +class LLCtrlScrollInterface; + +typedef void (*LLUICtrlCallback)(LLUICtrl* ctrl, void* userdata); +typedef BOOL (*LLUICtrlValidate)(LLUICtrl* ctrl, void* userdata); + +class LLUICtrl +: public LLView +{ +public: + LLUICtrl(); + LLUICtrl( const LLString& name, const LLRect& rect, BOOL mouse_opaque, + LLUICtrlCallback callback, + void* callback_userdata, + U32 reshape=FOLLOWS_NONE); + virtual ~LLUICtrl(); + + // LLView interface + //virtual BOOL handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect); + virtual void initFromXML(LLXMLNodePtr node, LLView* parent); + virtual LLXMLNodePtr getXML(bool save_children = true) const; + + virtual LLSD getValue() const { return LLSD(); } + + // Defaults to no-op + virtual BOOL setTextArg( const LLString& key, const LLString& text ); + + // Defaults to no-op + virtual BOOL setLabelArg( const LLString& key, const LLString& text ); + + // Defaults to return NULL + virtual LLCtrlSelectionInterface* getSelectionInterface(); + virtual LLCtrlListInterface* getListInterface(); + virtual LLCtrlScrollInterface* getScrollInterface(); + + virtual void setFocus( BOOL b ); + virtual BOOL hasFocus() const; + + virtual void setTabStop( BOOL b ); + virtual BOOL hasTabStop() const; + + // Defaults to false + virtual BOOL acceptsTextInput() const; + + // Default to no-op + virtual void onTabInto(); + virtual void clear(); + + virtual void setIsChrome(BOOL is_chrome); + virtual BOOL getIsChrome() const; + + virtual void onCommit(); + + virtual BOOL isCtrl() const { return TRUE; } + // "Tentative" controls have a proposed value, but haven't committed + // it yet. This is used when multiple objects are selected and we + // want to display a parameter that differs between the objects. + virtual void setTentative(BOOL b); + virtual BOOL getTentative() const; + + // Returns containing panel/floater or NULL if none found. + LLPanel* getParentPanel() const; + + void* getCallbackUserData() const { return mCallbackUserData; } + void setCallbackUserData( void* data ) { mCallbackUserData = data; } + + void setCommitCallback( void (*cb)(LLUICtrl*, void*) ) { mCommitCallback = cb; } + void setValidateBeforeCommit( BOOL(*cb)(LLUICtrl*, void*) ) { mValidateCallback = cb; } + + // Defaults to no-op! + virtual void setDoubleClickCallback( void (*cb)(void*) ); + + // Defaults to no-op + virtual void setColor(const LLColor4& color); + + // Defaults to no-op + virtual void setMinValue(LLSD min_value); + virtual void setMaxValue(LLSD max_value); + + // In general, only LLPanel uses these. + void setFocusReceivedCallback( void (*cb)(LLUICtrl*, void*) ) { mFocusReceivedCallback = cb; } + void setFocusChangedCallback( void (*cb)(LLUICtrl*, void*) ) { mFocusChangedCallback = cb; } + + static void onFocusLostCallback(LLUICtrl* old_focus); + + /*virtual*/ BOOL focusFirstItem(BOOL prefer_text_fields = FALSE ); + + class LLTabStopPostFilter : public LLQueryFilter, public LLSingleton<LLTabStopPostFilter> + { + /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const + { + return filterResult_t(view->isCtrl() && static_cast<const LLUICtrl * const>(view)->hasTabStop() && children.size() == 0, TRUE); + } + }; + + class LLTextInputFilter : public LLQueryFilter, public LLSingleton<LLTextInputFilter> + { + /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const + { + return filterResult_t(view->isCtrl() && static_cast<const LLUICtrl * const>(view)->acceptsTextInput(), TRUE); + } + }; + +protected: + virtual void onFocusReceived(); + virtual void onFocusLost(); + void onChangeFocus( S32 direction ); + +protected: + + void (*mCommitCallback)( LLUICtrl* ctrl, void* userdata ); + void (*mFocusReceivedCallback)( LLUICtrl* ctrl, void* userdata ); + void (*mFocusChangedCallback)( LLUICtrl* ctrl, void* userdata ); + BOOL (*mValidateCallback)( LLUICtrl* ctrl, void* userdata ); + + void* mCallbackUserData; + BOOL mTentative; + BOOL mTabStop; + +private: + BOOL mIsChrome; + + +}; + +#endif // LL_LLUICTRL_H diff --git a/indra/llui/lluictrlfactory.cpp b/indra/llui/lluictrlfactory.cpp new file mode 100644 index 0000000000..7e286f0bee --- /dev/null +++ b/indra/llui/lluictrlfactory.cpp @@ -0,0 +1,722 @@ +/** + * @file lluictrlfactory.cpp + * @brief Factory class for creating UI controls + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "lluictrlfactory.h" + +#include <fstream> +#include <boost/tokenizer.hpp> + +// other library includes +#include "llcontrol.h" +#include "lldir.h" +#include "v4color.h" + +// this library includes +#include "llbutton.h" +#include "llcheckboxctrl.h" +//#include "llcolorswatch.h" +#include "llcombobox.h" +#include "llcontrol.h" +#include "lldir.h" +#include "llevent.h" +#include "llfloater.h" +#include "lliconctrl.h" +#include "lllineeditor.h" +#include "llmenugl.h" +#include "llradiogroup.h" +#include "llscrollcontainer.h" +#include "llscrollingpanellist.h" +#include "llscrolllistctrl.h" +#include "llslider.h" +#include "llsliderctrl.h" +#include "llspinctrl.h" +#include "lltabcontainer.h" +#include "lltabcontainervertical.h" +#include "lltextbox.h" +#include "lltexteditor.h" +#include "llui.h" +#include "llviewborder.h" + + +const char XML_HEADER[] = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\" ?>\n"; + +// *NOTE: If you add a new class derived from LLPanel, add a check for its +// widget type to LLUICtrl::getParentPanel(). +// *NOTE: This MUST match EWidgetType in llui.h +//static +const LLString LLUICtrlFactory::sUICtrlNames[WIDGET_TYPE_COUNT] = +{ + LLString("view"), //WIDGET_TYPE_VIEW + LLString("root_view"), //WIDGET_TYPE_ROOT_VIEW + LLString("floater_view"), //WIDGET_TYPE_FLOATER_VIEW + LLString("button"), //WIDGET_TYPE_BUTTON + LLString("joystick_turn"), //WIDGET_TYPE_JOYSTICK_TURN + LLString("joystick_slide"), //WIDGET_TYPE_JOYSTICK_SLIDE + LLString("check_box"), //WIDGET_TYPE_CHECKBOX + LLString("color_swatch"), //WIDGET_TYPE_COLOR_SWATCH + LLString("combo_box"), //WIDGET_TYPE_COMBO_BOX + LLString("line_editor"), //WIDGET_TYPE_LINE_EDITOR + LLString("search_editor"), //WIDGET_TYPE_SEARCH_EDITOR + LLString("scroll_list"), //WIDGET_TYPE_SCROLL_LIST + LLString("name_list"), //WIDGET_TYPE_NAME_LIST + LLString("web_browser"), //WIDGET_TYPE_WEBBROWSER + LLString("slider"), //WIDGET_TYPE_SLIDER, actually LLSliderCtrl + LLString("slider_bar"), //WIDGET_TYPE_SLIDER_BAR, actually LLSlider + LLString("volume_slider"), //WIDGET_TYPE_VOLUME_SLIDER, actually LLVolumeSliderCtrl + LLString("spinner"), //WIDGET_TYPE_SPINNER, actually LLSpinCtrl + LLString("text_editor"), //WIDGET_TYPE_TEXT_EDITOR + LLString("texture_picker"),//WIDGET_TYPE_TEXTURE_PICKER + LLString("text"), //WIDGET_TYPE_TEXT_BOX + LLString("pad"), //WIDGET_TYPE_PAD + LLString("radio_group"), //WIDGET_TYPE_RADIO_GROUP + LLString("icon"), //WIDGET_TYPE_ICON + LLString("locate"), //WIDGET_TYPE_LOCATE + LLString("view_border"), //WIDGET_TYPE_VIEW_BORDER + LLString("panel"), //WIDGET_TYPE_PANEL + LLString("menu"), //WIDGET_TYPE_MENU + LLString("pie_menu"), //WIDGET_TYPE_PIE_MENU + LLString("pie_menu_branch"), //WIDGET_TYPE_PIE_MENU_BRANCH + LLString("menu_item"), //WIDGET_TYPE_MENU_ITEM + LLString("menu_item_separator"), //WIDGET_TYPE_MENU_ITEM_SEPARATOR + LLString("menu_separator_vertical"), // WIDGET_TYPE_MENU_SEPARATOR_VERTICAL + LLString("menu_item_call"), // WIDGET_TYPE_MENU_ITEM_CALL + LLString("menu_item_check"),// WIDGET_TYPE_MENU_ITEM_CHECK + LLString("menu_item_branch"), // WIDGET_TYPE_MENU_ITEM_BRANCH + LLString("menu_item_branch_down"), //WIDGET_TYPE_MENU_ITEM_BRANCH_DOWN, + LLString("menu_item_blank"), //WIDGET_TYPE_MENU_ITEM_BLANK, + LLString("tearoff_menu"), //WIDGET_TYPE_TEAROFF_MENU + LLString("menu_bar"), //WIDGET_TYPE_MENU_BAR + LLString("tab_container"),//WIDGET_TYPE_TAB_CONTAINER + LLString("scroll_container"),//WIDGET_TYPE_SCROLL_CONTAINER + LLString("scrollbar"), //WIDGET_TYPE_SCROLLBAR + LLString("inventory_panel"), //WIDGET_TYPE_INVENTORY_PANEL + LLString("floater"), //WIDGET_TYPE_FLOATER + LLString("drag_handle_top"), //WIDGET_TYPE_DRAG_HANDLE_TOP + LLString("drag_handle_left"), //WIDGET_TYPE_DRAG_HANDLE_LEFT + LLString("resize_handle"), //WIDGET_TYPE_RESIZE_HANDLE + LLString("resize_bar"), //WIDGET_TYPE_RESIZE_BAR + LLString("name_editor"), //WIDGET_TYPE_NAME_EDITOR + LLString("multi_floater"), //WIDGET_TYPE_MULTI_FLOATER + LLString("media_remote"), //WIDGET_TYPE_MEDIA_REMOTE + LLString("folder_view"), //WIDGET_TYPE_FOLDER_VIEW + LLString("folder_item"), //WIDGET_TYPE_FOLDER_ITEM + LLString("folder"), //WIDGET_TYPE_FOLDER + LLString("stat_graph"), //WIDGET_TYPE_STAT_GRAPH + LLString("stat_view"), //WIDGET_TYPE_STAT_VIEW + LLString("stat_bar"), //WIDGET_TYPE_STAT_BAR + LLString("drop_target"), //WIDGET_TYPE_DROP_TARGET + LLString("texture_bar"), //WIDGET_TYPE_TEXTURE_BAR + LLString("tex_mem_bar"), //WIDGET_TYPE_TEX_MEM_BAR + LLString("snapshot_live_preview"), //WIDGET_TYPE_SNAPSHOT_LIVE_PREVIEW + LLString("status_bar"), //WIDGET_TYPE_STATUS_BAR + LLString("progress_view"), //WIDGET_TYPE_PROGRESS_VIEW + LLString("talk_view"), //WIDGET_TYPE_TALK_VIEW + LLString("overlay_bar"), //WIDGET_TYPE_OVERLAY_BAR + LLString("hud_view"), //WIDGET_TYPE_HUD_VIEW + LLString("hover_view"), //WIDGET_TYPE_HOVER_VIEW + LLString("morph_view"), //WIDGET_TYPE_MORPH_VIEW + LLString("net_map"), //WIDGET_TYPE_NET_MAP + LLString("permissions_view"), //WIDGET_TYPE_PERMISSIONS_VIEW + LLString("menu_holder"), //WIDGET_TYPE_MENU_HOLDER + LLString("debug_view"), //WIDGET_TYPE_DEBUG_VIEW + LLString("scrolling_panel_list"), //WIDGET_TYPE_SCROLLING_PANEL_LIST + LLString("audio_status"), //WIDGET_TYPE_AUDIO_STATUS + LLString("container_view"), //WIDGET_TYPE_CONTAINER_VIEW + LLString("console"), //WIDGET_TYPE_CONSOLE + LLString("fast_timer_view"), //WIDGET_TYPE_FAST_TIMER_VIEW + LLString("velocity_bar"), //WIDGET_TYPE_VELOCITY_BAR + LLString("texture_view"), //WIDGET_TYPE_TEXTURE_VIEW + LLString("memory_view"), //WIDGET_TYPE_MEMORY_VIEW + LLString("frame_stat_view"), //WIDGET_TYPE_FRAME_STAT_VIEW + LLString("DONT_CARE"), //WIDGET_TYPE_DONTCARE +}; + +const S32 HPAD = 4; +const S32 VPAD = 4; +const S32 FLOATER_H_MARGIN = 15; +const S32 MIN_WIDGET_HEIGHT = 10; + +std::vector<LLString> LLUICtrlFactory::mXUIPaths; + +// UI Ctrl class for padding +class LLUICtrlLocate : public LLUICtrl +{ +public: + LLUICtrlLocate() : LLUICtrl("locate", LLRect(0,0,0,0), FALSE, NULL, NULL) {} + virtual void draw() { } + + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_LOCATE; } + virtual LLString getWidgetTag() const { return LL_UI_CTRL_LOCATE_TAG; } + + static LLView *fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) + { + LLUICtrlLocate *new_ctrl = new LLUICtrlLocate(); + new_ctrl->initFromXML(node, parent); + return new_ctrl; + } +}; + +//----------------------------------------------------------------------------- +// LLUICtrlFactory() +//----------------------------------------------------------------------------- +LLUICtrlFactory::LLUICtrlFactory() +{ + // Register controls + LLUICtrlCreator<LLButton>::registerCreator(LL_BUTTON_TAG, this); + LLUICtrlCreator<LLCheckBoxCtrl>::registerCreator(LL_CHECK_BOX_CTRL_TAG, this); + LLUICtrlCreator<LLComboBox>::registerCreator(LL_COMBO_BOX_TAG, this); + LLUICtrlCreator<LLLineEditor>::registerCreator(LL_LINE_EDITOR_TAG, this); + LLUICtrlCreator<LLSearchEditor>::registerCreator(LL_SEARCH_EDITOR_TAG, this); + LLUICtrlCreator<LLScrollListCtrl>::registerCreator(LL_SCROLL_LIST_CTRL_TAG, this); + LLUICtrlCreator<LLSliderCtrl>::registerCreator(LL_SLIDER_CTRL_TAG, this); + LLUICtrlCreator<LLSlider>::registerCreator(LL_SLIDER_TAG, this); + LLUICtrlCreator<LLSpinCtrl>::registerCreator(LL_SPIN_CTRL_TAG, this); + LLUICtrlCreator<LLTextBox>::registerCreator(LL_TEXT_BOX_TAG, this); + LLUICtrlCreator<LLRadioGroup>::registerCreator(LL_RADIO_GROUP_TAG, this); + LLUICtrlCreator<LLIconCtrl>::registerCreator(LL_ICON_CTRL_TAG, this); + LLUICtrlCreator<LLUICtrlLocate>::registerCreator(LL_UI_CTRL_LOCATE_TAG, this); + LLUICtrlCreator<LLUICtrlLocate>::registerCreator(LL_PAD_TAG, this); + LLUICtrlCreator<LLViewBorder>::registerCreator(LL_VIEW_BORDER_TAG, this); + LLUICtrlCreator<LLTabContainerCommon>::registerCreator(LL_TAB_CONTAINER_COMMON_TAG, this); + LLUICtrlCreator<LLScrollableContainerView>::registerCreator(LL_SCROLLABLE_CONTAINER_VIEW_TAG, this); + LLUICtrlCreator<LLPanel>::registerCreator(LL_PANEL_TAG, this); + LLUICtrlCreator<LLMenuGL>::registerCreator(LL_MENU_GL_TAG, this); + LLUICtrlCreator<LLMenuBarGL>::registerCreator(LL_MENU_BAR_GL_TAG, this); + LLUICtrlCreator<LLScrollingPanelList>::registerCreator(LL_SCROLLING_PANEL_LIST_TAG, this); + + + LLString filename = gDirUtilp->getExpandedFilename(LL_PATH_SKINS, "paths.xml"); + + LLXMLNodePtr root; + BOOL success = LLXMLNode::parseFile(filename, root, NULL); + + if (!success) + { + LLString slash = gDirUtilp->getDirDelimiter(); + LLString dir = gDirUtilp->getAppRODataDir() + slash + "skins" + slash + "xui" + slash + "en-us" + slash; + llwarns << "XUI::config file unable to open." << llendl; + mXUIPaths.push_back(dir); + } + else + { + LLXMLNodePtr path; + LLString app_dir = gDirUtilp->getAppRODataDir(); + + for (path = root->getFirstChild(); path.notNull(); path = path->getNextSibling()) + { + LLUIString path_val_ui(path->getValue()); + LLString language = "en-us"; + if (LLUI::sConfigGroup) + { + language = LLUI::sConfigGroup->getString("Language"); + } + path_val_ui.setArg("[Language]", language); + LLString fullpath = app_dir + path_val_ui.getString(); + + if (mXUIPaths.empty() || (find(mXUIPaths.begin(), mXUIPaths.end(), fullpath) == mXUIPaths.end()) ) + { + mXUIPaths.push_back(app_dir + path_val_ui.getString()); + } + } + } + + +} + +//----------------------------------------------------------------------------- +// ~LLUICtrlFactory() +//----------------------------------------------------------------------------- +LLUICtrlFactory::~LLUICtrlFactory() +{ +} + + +//----------------------------------------------------------------------------- +// getLayeredXMLNode() +//----------------------------------------------------------------------------- +bool LLUICtrlFactory::getLayeredXMLNode(const LLString &filename, LLXMLNodePtr& root) +{ + + if (!LLXMLNode::parseFile(mXUIPaths.front() + filename, root, NULL)) + { + llwarns << "Problem reading UI description file: " << mXUIPaths.front() + filename << llendl; + return FALSE; + } + + LLXMLNodePtr updateRoot; + + std::vector<LLString>::const_iterator itor; + + for (itor = mXUIPaths.begin(), ++itor; itor != mXUIPaths.end(); ++itor) + { + LLString nodeName; + LLString updateName; + + LLXMLNode::parseFile((*itor) + filename, updateRoot, NULL); + + updateRoot->getAttributeString("name", updateName); + root->getAttributeString("name", nodeName); + + if (updateName == nodeName) + { + LLXMLNode::updateNode(root, updateRoot); + } + } + + return TRUE; +} + + +//----------------------------------------------------------------------------- +// buildFloater() +//----------------------------------------------------------------------------- +void LLUICtrlFactory::buildFloater(LLFloater* floaterp, const LLString &filename, + const LLCallbackMap::map_t* factory_map, BOOL open) +{ + LLXMLNodePtr root; + + if (!LLUICtrlFactory::getLayeredXMLNode(filename, root)) + { + return; + } + + // root must be called floater + if( !(root->hasName("floater") || root->hasName("multi_floater") ) ) + { + llwarns << "Root node should be named floater in: " << filename << llendl; + return; + } + + if (factory_map) + { + mFactoryStack.push_front(factory_map); + } + + floaterp->initFloaterXML(root, NULL, this, open); + + if (LLUI::sShowXUINames) + { + floaterp->mToolTipMsg = filename; + } + + if (factory_map) + { + mFactoryStack.pop_front(); + } + + LLViewHandle handle = floaterp->getHandle(); + mBuiltFloaters[handle] = filename; +} + +//----------------------------------------------------------------------------- +// saveToXML() +//----------------------------------------------------------------------------- +S32 LLUICtrlFactory::saveToXML(LLView* viewp, const LLString& filename) +{ + llofstream out(filename.c_str()); + if (!out.good()) + { + llwarns << "Unable to open " << filename << " for output." << llendl; + return 1; + } + + out << XML_HEADER; + + LLXMLNodePtr xml_node = viewp->getXML(); + + xml_node->writeToOstream(out); + + out.close(); + return 0; +} + +//----------------------------------------------------------------------------- +// buildPanel() +//----------------------------------------------------------------------------- +void LLUICtrlFactory::buildPanel(LLPanel* panelp, const LLString &filename, + const LLCallbackMap::map_t* factory_map) +{ + LLXMLNodePtr root; + + if (!LLUICtrlFactory::getLayeredXMLNode(filename, root)) + { + return; + } + + // root must be called panel + if( !root->hasName("panel" ) ) + { + llwarns << "Root node should be named panel in : " << filename << llendl; + return; + } + + if (factory_map) + { + mFactoryStack.push_front(factory_map); + } + + panelp->initPanelXML(root, NULL, this); + + if (LLUI::sShowXUINames) + { + panelp->mToolTipMsg = filename; + } + + LLViewHandle handle = panelp->getHandle(); + mBuiltPanels[handle] = filename; + + if (factory_map) + { + mFactoryStack.pop_front(); + } +} + +//----------------------------------------------------------------------------- +// buildMenu() +//----------------------------------------------------------------------------- +LLMenuGL *LLUICtrlFactory::buildMenu(const LLString &filename, LLView* parentp) +{ + // TomY TODO: Break this function into buildMenu and buildMenuBar + LLXMLNodePtr root; + + if (!LLUICtrlFactory::getLayeredXMLNode(filename, root)) + { + return NULL; + } + + // root must be called panel + if( !root->hasName( "menu_bar" ) && !root->hasName( "menu" )) + { + llwarns << "Root node should be named menu bar or menu in : " << filename << llendl; + return NULL; + } + + if (root->hasName("menu")) + { + return (LLMenuGL*)LLMenuGL::fromXML(root, parentp, this); + } + + return (LLMenuGL*)LLMenuBarGL::fromXML(root, parentp, this); +} + +//----------------------------------------------------------------------------- +// buildMenu() +//----------------------------------------------------------------------------- +LLPieMenu *LLUICtrlFactory::buildPieMenu(const LLString &filename, LLView* parentp) +{ + + LLXMLNodePtr root; + + if (!LLUICtrlFactory::getLayeredXMLNode(filename, root)) + { + return NULL; + } + + // root must be called panel + if( !root->hasName( LL_PIE_MENU_TAG )) + { + llwarns << "Root node should be named " LL_PIE_MENU_TAG " in : " << filename << llendl; + return NULL; + } + + LLString name("menu"); + root->getAttributeString("name", name); + + LLPieMenu *menu = new LLPieMenu(name); + parentp->addChild(menu); + menu->initXML(root, parentp, this); + return menu; +} + +//----------------------------------------------------------------------------- +// removePanel() +//----------------------------------------------------------------------------- +void LLUICtrlFactory::removePanel(LLPanel* panelp) +{ + mBuiltPanels.erase(panelp->getHandle()); +} + +//----------------------------------------------------------------------------- +// removeFloater() +//----------------------------------------------------------------------------- +void LLUICtrlFactory::removeFloater(LLFloater* floaterp) +{ + mBuiltFloaters.erase(floaterp->getHandle()); +} + +//----------------------------------------------------------------------------- +// rebuild() +//----------------------------------------------------------------------------- +void LLUICtrlFactory::rebuild() +{ + built_panel_t::iterator built_panel_it; + for (built_panel_it = mBuiltPanels.begin(); + built_panel_it != mBuiltPanels.end(); + ++built_panel_it) + { + LLString filename = built_panel_it->second; + LLPanel* panelp = LLPanel::getPanelByHandle(built_panel_it->first); + if (!panelp) + { + continue; + } + llinfos << "Rebuilding UI panel " << panelp->getName() + << " from " << filename + << llendl; + BOOL visible = panelp->getVisible(); + panelp->setVisible(FALSE); + panelp->setFocus(FALSE); + panelp->deleteAllChildren(); + + buildPanel(panelp, filename.c_str(), &panelp->getFactoryMap()); + panelp->setVisible(visible); + } + + built_floater_t::iterator built_floater_it; + for (built_floater_it = mBuiltFloaters.begin(); + built_floater_it != mBuiltFloaters.end(); + ++built_floater_it) + { + LLFloater* floaterp = LLFloater::getFloaterByHandle(built_floater_it->first); + if (!floaterp) + { + continue; + } + LLString filename = built_floater_it->second; + llinfos << "Rebuilding UI floater " << floaterp->getName() + << " from " << filename + << llendl; + BOOL visible = floaterp->getVisible(); + floaterp->setVisible(FALSE); + floaterp->setFocus(FALSE); + floaterp->deleteAllChildren(); + + gFloaterView->removeChild(floaterp); + buildFloater(floaterp, filename, &floaterp->getFactoryMap()); + floaterp->setVisible(visible); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +// static +EWidgetType LLUICtrlFactory::getWidgetType(const LLString& ctrl_type) +{ + U32 ctrl_id; + for (ctrl_id = 0; ctrl_id < WIDGET_TYPE_COUNT; ctrl_id++) + { + if (sUICtrlNames[ctrl_id] == ctrl_type) + { + break; + } + } + return (EWidgetType) ctrl_id; +} + +LLString LLUICtrlFactory::getWidgetType(EWidgetType ctrl_type) +{ + return sUICtrlNames[ctrl_type]; +} + +LLView *LLUICtrlFactory::createCtrlWidget(LLPanel *parent, LLXMLNodePtr node) +{ + LLString ctrl_type = node->getName()->mString; + LLString::toLower(ctrl_type); + + creator_list_t::const_iterator it = mCreatorFunctions.find(ctrl_type); + if (it == mCreatorFunctions.end()) + { + llwarns << "Unknown control type " << ctrl_type << llendl; + return NULL; + } + + LLView *ctrl = (*it->second)(node, parent, this); + + return ctrl; +} + +void LLUICtrlFactory::createWidget(LLPanel *parent, LLXMLNodePtr node) +{ + LLView* view = createCtrlWidget(parent, node); + + S32 tab_group = parent->getLastTabGroup(); + node->getAttributeS32("tab_group", tab_group); + + if (view) + { + parent->addChild(view, tab_group); + } +} + +//----------------------------------------------------------------------------- +// createFactoryPanel() +//----------------------------------------------------------------------------- +LLPanel* LLUICtrlFactory::createFactoryPanel(LLString name) +{ + std::deque<const LLCallbackMap::map_t*>::iterator itor; + for (itor = mFactoryStack.begin(); itor != mFactoryStack.end(); ++itor) + { + const LLCallbackMap::map_t* factory_map = *itor; + + // Look up this panel's name in the map. + LLCallbackMap::map_const_iter_t iter = factory_map->find( name ); + if (iter != factory_map->end()) + { + // Use the factory to create the panel, instead of using a default LLPanel. + LLPanel *ret = (LLPanel*) iter->second.mCallback( iter->second.mData ); + return ret; + } + } + return NULL; +} + +//----------------------------------------------------------------------------- + +//static +BOOL LLUICtrlFactory::getAttributeColor(LLXMLNodePtr node, const LLString& name, LLColor4& color) +{ + LLString colorstring; + BOOL res = node->getAttributeString(name, colorstring); + if (res && LLUI::sColorsGroup) + { + if (LLUI::sColorsGroup->controlExists(colorstring)) + { + color.setVec(LLUI::sColorsGroup->getColor(colorstring)); + } + else + { + res = FALSE; + } + } + if (!res) + { + res = LLColor4::parseColor(colorstring.c_str(), &color); + } + if (!res) + { + res = node->getAttributeColor(name, color); + } + return res; +} + +//============================================================================ + +LLButton* LLUICtrlFactory::getButtonByName(LLPanel* panelp, const LLString& name) +{ + return (LLButton*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_BUTTON); +} + +LLCheckBoxCtrl* LLUICtrlFactory::getCheckBoxByName(LLPanel* panelp, const LLString& name) +{ + return (LLCheckBoxCtrl*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_CHECKBOX); +} + +LLComboBox* LLUICtrlFactory::getComboBoxByName(LLPanel* panelp, const LLString& name) +{ + return (LLComboBox*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_COMBO_BOX); +} + +LLIconCtrl* LLUICtrlFactory::getIconByName(LLPanel* panelp, const LLString& name) +{ + return (LLIconCtrl*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_ICON); +} + +LLLineEditor* LLUICtrlFactory::getLineEditorByName(LLPanel* panelp, const LLString& name) +{ + return (LLLineEditor*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_LINE_EDITOR); +} + +LLNameListCtrl* LLUICtrlFactory::getNameListByName(LLPanel* panelp, const LLString& name) +{ + return (LLNameListCtrl*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_NAME_LIST); +} + +LLRadioGroup* LLUICtrlFactory::getRadioGroupByName(LLPanel* panelp, const LLString& name) +{ + return (LLRadioGroup*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_RADIO_GROUP); +} + +LLScrollListCtrl* LLUICtrlFactory::getScrollListByName(LLPanel* panelp, const LLString& name) +{ + return (LLScrollListCtrl*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_SCROLL_LIST); +} + +LLSliderCtrl* LLUICtrlFactory::getSliderByName(LLPanel* panelp, const LLString& name) +{ + return (LLSliderCtrl*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_SLIDER); +} + +LLSlider* LLUICtrlFactory::getSliderBarByName(LLPanel* panelp, const LLString& name) +{ + return (LLSlider*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_SLIDER_BAR); +} + +LLSpinCtrl* LLUICtrlFactory::getSpinnerByName(LLPanel* panelp, const LLString& name) +{ + return (LLSpinCtrl*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_SPINNER); +} + +LLTextBox* LLUICtrlFactory::getTextBoxByName(LLPanel* panelp, const LLString& name) +{ + return (LLTextBox*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_TEXT_BOX); +} + +LLTextEditor* LLUICtrlFactory::getTextEditorByName(LLPanel* panelp, const LLString& name) +{ + return (LLTextEditor*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_TEXT_EDITOR); +} + +LLTabContainerCommon* LLUICtrlFactory::getTabContainerByName(LLPanel* panelp, const LLString& name) +{ + return (LLTabContainerCommon*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_TAB_CONTAINER); +} + +LLScrollableContainerView* LLUICtrlFactory::getScrollableContainerByName(LLPanel* panelp, const LLString& name) +{ + return (LLScrollableContainerView*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_SCROLL_CONTAINER); +} + +LLTextureCtrl* LLUICtrlFactory::getTexturePickerByName(LLPanel* panelp, const LLString& name) +{ + return (LLTextureCtrl*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_TEXTURE_PICKER); +} + +LLPanel* LLUICtrlFactory::getPanelByName(LLPanel* panelp, const LLString& name) +{ + return (LLPanel*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_PANEL); +} + +LLColorSwatchCtrl* LLUICtrlFactory::getColorSwatchByName(LLPanel* panelp, const LLString& name) +{ + return (LLColorSwatchCtrl*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_COLOR_SWATCH); +} + +LLWebBrowserCtrl* LLUICtrlFactory::getWebBrowserCtrlByName(LLPanel* panelp, const LLString& name) +{ + return (LLWebBrowserCtrl*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_WEBBROWSER); +} + +LLMenuItemCallGL* LLUICtrlFactory::getMenuItemCallByName(LLPanel* panelp, const LLString& name) +{ + return (LLMenuItemCallGL*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_MENU_ITEM_CALL); +} + +LLScrollingPanelList* LLUICtrlFactory::getScrollingPanelList(LLPanel* panelp, const LLString& name) +{ + return (LLScrollingPanelList*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_SCROLLING_PANEL_LIST); +} + +void LLUICtrlFactory::registerCreator(LLString ctrlname, creator_function_t function) +{ + LLString::toLower(ctrlname); + mCreatorFunctions[ctrlname] = function; +} + diff --git a/indra/llui/lluictrlfactory.h b/indra/llui/lluictrlfactory.h new file mode 100644 index 0000000000..b3bd5c9020 --- /dev/null +++ b/indra/llui/lluictrlfactory.h @@ -0,0 +1,142 @@ +/** + * @file lluictrlfactory.h + * @brief Factory class for creating UI controls + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LLUICTRLFACTORY_H +#define LLUICTRLFACTORY_H + +#include <iosfwd> +#include <stack> + +#include "llcallbackmap.h" +#include "llfloater.h" + +class LLControlGroup; +class LLView; +class LLFontGL; + +class LLFloater; +class LLPanel; +class LLButton; +class LLCheckBoxCtrl; +class LLComboBox; +class LLIconCtrl; +class LLLineEditor; +class LLMenuGL; +class LLMenuBarGL; +class LLMenuItemCallGL; +class LLNameListCtrl; +class LLPieMenu; +class LLRadioGroup; +class LLSearchEditor; +class LLScrollableContainerView; +class LLScrollListCtrl; +class LLSlider; +class LLSliderCtrl; +class LLSpinCtrl; +class LLTextBox; +class LLTextEditor; +class LLTextureCtrl; +class LLWebBrowserCtrl; +class LLViewBorder; +class LLColorSwatchCtrl; +class LLScrollingPanelList; + +// Widget + +class LLUICtrlFactory +{ +public: + LLUICtrlFactory(); + // do not call! needs to be public so run-time can clean up the singleton + virtual ~LLUICtrlFactory(); + + void buildFloater(LLFloater* floaterp, const LLString &filename, + const LLCallbackMap::map_t* factory_map = NULL, BOOL open = TRUE); + + void buildPanel(LLPanel* panelp, const LLString &filename, + const LLCallbackMap::map_t* factory_map = NULL); + + LLMenuGL *buildMenu(const LLString &filename, LLView* parentp); + + LLPieMenu *buildPieMenu(const LLString &filename, LLView* parentp); + + // Does what you want for LLFloaters and LLPanels + // Returns 0 on success + S32 saveToXML(LLView* viewp, const LLString& filename); + + void removePanel(LLPanel* panelp); + void removeFloater(LLFloater* floaterp); + + void rebuild(); + + static EWidgetType getWidgetType(const LLString& ctrl_type); + static LLString getWidgetType(EWidgetType ctrl_type); + static BOOL getAttributeColor(LLXMLNodePtr node, const LLString& name, LLColor4& color); + + // specific typed getters + static LLButton* getButtonByName( LLPanel* panelp, const LLString& name); + static LLCheckBoxCtrl* getCheckBoxByName( LLPanel* panelp, const LLString& name); + static LLComboBox* getComboBoxByName( LLPanel* panelp, const LLString& name); + static LLIconCtrl* getIconByName( LLPanel* panelp, const LLString& name); + static LLLineEditor* getLineEditorByName( LLPanel* panelp, const LLString& name); + static LLNameListCtrl* getNameListByName( LLPanel* panelp, const LLString& name); + static LLRadioGroup* getRadioGroupByName( LLPanel* panelp, const LLString& name); + static LLScrollListCtrl* getScrollListByName( LLPanel* panelp, const LLString& name); + static LLSliderCtrl* getSliderByName( LLPanel* panelp, const LLString& name); + static LLSlider* getSliderBarByName( LLPanel* panelp, const LLString& name); + static LLSpinCtrl* getSpinnerByName( LLPanel* panelp, const LLString& name); + static LLTextBox* getTextBoxByName( LLPanel* panelp, const LLString& name); + static LLTextEditor* getTextEditorByName( LLPanel* panelp, const LLString& name); + static LLTabContainerCommon* getTabContainerByName( LLPanel* panelp, const LLString& name); + static LLScrollableContainerView* getScrollableContainerByName(LLPanel* panelp, const LLString& name); + static LLTextureCtrl* getTexturePickerByName( LLPanel* panelp, const LLString& name); + static LLPanel* getPanelByName(LLPanel* panelp, const LLString& name); + static LLColorSwatchCtrl* getColorSwatchByName(LLPanel* panelp, const LLString& name); + static LLWebBrowserCtrl* getWebBrowserCtrlByName(LLPanel* panelp, const LLString& name); + static LLMenuItemCallGL* getMenuItemCallByName(LLPanel* panelp, const LLString& name); + static LLScrollingPanelList* getScrollingPanelList(LLPanel* panelp, const LLString& name); + + LLPanel* createFactoryPanel(LLString name); + + virtual LLView* createCtrlWidget(LLPanel *parent, LLXMLNodePtr node); + virtual void createWidget(LLPanel *parent, LLXMLNodePtr node); + + // Creator library + typedef LLView* (*creator_function_t)(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + void registerCreator(LLString ctrlname, creator_function_t function); + + static bool getLayeredXMLNode(const LLString &filename, LLXMLNodePtr& root); + +protected: + + + typedef std::map<LLViewHandle, LLString> built_panel_t; + built_panel_t mBuiltPanels; + typedef std::map<LLViewHandle, LLString> built_floater_t; + built_floater_t mBuiltFloaters; + + std::deque<const LLCallbackMap::map_t*> mFactoryStack; + + static const LLString sUICtrlNames[]; + + typedef std::map<LLString, creator_function_t> creator_list_t; + creator_list_t mCreatorFunctions; + static std::vector<LLString> mXUIPaths; +}; + +template<class T> +class LLUICtrlCreator +{ +public: + static void registerCreator(LLString name, LLUICtrlFactory *factory) + { + factory->registerCreator(name, T::fromXML); + } +}; + +#endif //LL_LLWIDGETFACTORY_H diff --git a/indra/llui/lluistring.cpp b/indra/llui/lluistring.cpp new file mode 100755 index 0000000000..8c5b587158 --- /dev/null +++ b/indra/llui/lluistring.cpp @@ -0,0 +1,87 @@ +/** + * @file lluistring.cpp + * @brief LLUIString base class + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "lluistring.h" + +// public + +LLUIString::LLUIString(const LLString& instring, const LLString::format_map_t& args) + : mOrig(instring), + mArgs(args) +{ + format(); +} + +void LLUIString::assign(const LLString& s) +{ + mOrig = s; + format(); +} + +void LLUIString::setArgList(const LLString::format_map_t& args) +{ + mArgs = args; + format(); +} + +void LLUIString::setArg(const LLString& key, const LLString& replacement) +{ + mArgs[key] = replacement; + format(); +} + +void LLUIString::truncate(S32 maxchars) +{ + if (mWResult.size() > (size_t)maxchars) + { + LLWString::truncate(mWResult, maxchars); + mResult = wstring_to_utf8str(mWResult); + } +} + +void LLUIString::erase(S32 charidx, S32 len) +{ + mWResult.erase(charidx, len); + mResult = wstring_to_utf8str(mWResult); +} + +void LLUIString::insert(S32 charidx, const LLWString& wchars) +{ + mWResult.insert(charidx, wchars); + mResult = wstring_to_utf8str(mWResult); +} + +void LLUIString::replace(S32 charidx, llwchar wc) +{ + mWResult[charidx] = wc; + mResult = wstring_to_utf8str(mWResult); +} + +void LLUIString::clear() +{ + // Keep Args + mOrig.clear(); + mResult.clear(); + mWResult.clear(); +} + +void LLUIString::clearArgs() +{ + mArgs.clear(); +} + +// private + +void LLUIString::format() +{ + mResult = mOrig; + LLString::format(mResult, mArgs); + mWResult = utf8str_to_wstring(mResult); +} diff --git a/indra/llui/lluistring.h b/indra/llui/lluistring.h new file mode 100755 index 0000000000..8c2e3c481c --- /dev/null +++ b/indra/llui/lluistring.h @@ -0,0 +1,87 @@ +/** + * @file lluistring.h + * @author: Steve Bennetts + * @brief LLUIString base class + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLUISTRING_H +#define LL_LLUISTRING_H + +// lluistring.h +// +// Copyright 2006, Linden Research, Inc. +// Original aurthor: Steve + +#include "stdtypes.h" +#include "llstring.h" +#include <string> + +// Use this class to store translated text that may have arguments +// e.g. "Welcome [USERNAME] to [SECONDLIFE]!" + +// Adding or changing an argument will update the result string, preserving the origianl +// Thus, subsequent changes to arguments or even the original string will produce +// the correct result + +// Example Usage: +// LLUIString mMessage("Welcome [USERNAME] to [SECONDLIFE]!"); +// mMessage.setArg("[USERNAME]", "Steve"); +// mMessage.setArg("[SECONDLIFE]", "Second Life"); +// llinfos << mMessage.getString().c_str() << llendl; // outputs "Welcome Steve to Second Life" +// mMessage.setArg("[USERNAME]", "Joe"); +// llinfos << mMessage.getString().c_str() << llendl; // outputs "Welcome Joe to Second Life" +// mMessage = "Recepción a la [SECONDLIFE] [USERNAME]" +// mMessage.setArg("[SECONDLIFE]", "Segunda Vida"); +// llinfos << mMessage.getString().c_str() << llendl; // outputs "Recepción a la Segunda Vida Joe" + +// Implementation Notes: +// Attempting to have operator[](const LLString& s) return mArgs[s] fails because we have +// to call format() after the assignment happens. + +class LLUIString +{ +public: + // These methods all perform appropriate argument substitution + // and modify mOrig where appropriate + LLUIString() {} + LLUIString(const LLString& instring, const LLString::format_map_t& args); + LLUIString(const LLString& instring) { assign(instring); } + + void assign(const LLString& instring); + LLUIString& operator=(const LLString& s) { assign(s); return *this; } + + void setArgList(const LLString::format_map_t& args); + void setArg(const LLString& key, const LLString& replacement); + + const LLString& getString() const { return mResult; } + operator LLString() const { return mResult; } + + const LLWString& getWString() const { return mWResult; } + operator LLWString() const { return mWResult; } + + bool empty() const { return mWResult.empty(); } + S32 length() const { return mWResult.size(); } + + void clear(); + void clearArgs(); + + // These utuilty functions are included for text editing. + // They do not affect mOrig and do not perform argument substitution + void truncate(S32 maxchars); + void erase(S32 charidx, S32 len); + void insert(S32 charidx, const LLWString& wchars); + void replace(S32 charidx, llwchar wc); + +private: + void format(); + + LLString mOrig; + LLString mResult; + LLWString mWResult; // for displaying + LLString::format_map_t mArgs; +}; + +#endif // LL_LLUISTRING_H diff --git a/indra/llui/llundo.cpp b/indra/llui/llundo.cpp new file mode 100644 index 0000000000..61e17c1879 --- /dev/null +++ b/indra/llui/llundo.cpp @@ -0,0 +1,161 @@ +/** + * @file llundo.cpp + * @brief LLUndo class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// Generic interface for undo/redo circular buffer + +#include "linden_common.h" + +#include "llundo.h" +#include "llerror.h" + + +// TODO: +// implement doubly linked circular list for ring buffer +// this will allow us to easily change the size of an undo buffer on the fly + +//----------------------------------------------------------------------------- +// LLUndoBuffer() +//----------------------------------------------------------------------------- +LLUndoBuffer::LLUndoBuffer( LLUndoAction (*create_func()), S32 initial_count ) +{ + mNextAction = 0; + mLastAction = 0; + mFirstAction = 0; + mOperationID = 0; + + mNumActions = initial_count; + + mActions = new LLUndoAction *[initial_count]; + + //initialize buffer with actions + for (S32 i = 0; i < initial_count; i++) + { + mActions[i] = create_func(); + if (!mActions[i]) + { + llerrs << "Unable to create action for undo buffer" << llendl; + } + } +} + +//----------------------------------------------------------------------------- +// ~LLUndoBuffer() +//----------------------------------------------------------------------------- +LLUndoBuffer::~LLUndoBuffer() +{ + for (S32 i = 0; i < mNumActions; i++) + { + delete mActions[i]; + } + + delete [] mActions; +} + +//----------------------------------------------------------------------------- +// getNextAction() +//----------------------------------------------------------------------------- +LLUndoAction *LLUndoBuffer::getNextAction(BOOL setClusterBegin) +{ + LLUndoAction *nextAction = mActions[mNextAction]; + + if (setClusterBegin) + { + mOperationID++; + } + mActions[mNextAction]->mClusterID = mOperationID; + + mNextAction = (mNextAction + 1) % mNumActions; + mLastAction = mNextAction; + + if (mNextAction == mFirstAction) + { + mActions[mFirstAction]->cleanup(); + mFirstAction = (mFirstAction + 1) % mNumActions; + } + + return nextAction; +} + +//----------------------------------------------------------------------------- +// undoAction() +//----------------------------------------------------------------------------- +BOOL LLUndoBuffer::undoAction() +{ + if (!canUndo()) + { + return FALSE; + } + + S32 prevAction = (mNextAction + mNumActions - 1) % mNumActions; + + while(mActions[prevAction]->mClusterID == mOperationID) + { + // go ahead and decrement action index + mNextAction = prevAction; + + // undo this action + mActions[mNextAction]->undo(); + + // we're at the first action, so we don't know if we've actually undid everything + if (mNextAction == mFirstAction) + { + mOperationID--; + return FALSE; + } + + // do wrap-around of index, but avoid negative numbers for modulo operator + prevAction = (mNextAction + mNumActions - 1) % mNumActions; + } + + mOperationID--; + + return TRUE; +} + +//----------------------------------------------------------------------------- +// redoAction() +//----------------------------------------------------------------------------- +BOOL LLUndoBuffer::redoAction() +{ + if (!canRedo()) + { + return FALSE; + } + + mOperationID++; + + while(mActions[mNextAction]->mClusterID == mOperationID) + { + if (mNextAction == mLastAction) + { + return FALSE; + } + + mActions[mNextAction]->redo(); + + // do wrap-around of index + mNextAction = (mNextAction + 1) % mNumActions; + } + + return TRUE; +} + +//----------------------------------------------------------------------------- +// flushActions() +//----------------------------------------------------------------------------- +void LLUndoBuffer::flushActions() +{ + for (S32 i = 0; i < mNumActions; i++) + { + mActions[i]->cleanup(); + } + mNextAction = 0; + mLastAction = 0; + mFirstAction = 0; + mOperationID = 0; +} diff --git a/indra/llui/llundo.h b/indra/llui/llundo.h new file mode 100644 index 0000000000..8d0428ad3e --- /dev/null +++ b/indra/llui/llundo.h @@ -0,0 +1,52 @@ +/** + * @file llundo.h + * @brief LLUndo class header file + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLUNDO_H +#define LL_LLUNDO_H + +class LLUndoAction +{ + friend class LLUndoBuffer; +protected: + S32 mClusterID; +protected: + LLUndoAction(): mClusterID(0) {}; + virtual ~LLUndoAction(){}; + +public: + static LLUndoAction *create() { return NULL; } + + virtual void undo() = 0; + virtual void redo() = 0; + virtual void cleanup() {}; +}; + +class LLUndoBuffer +{ +protected: + LLUndoAction **mActions; // array of pointers to undoactions + S32 mNumActions; // total number of actions in ring buffer + S32 mNextAction; // next action to perform undo/redo on + S32 mLastAction; // last action actually added to undo buffer + S32 mFirstAction; // beginning of ring buffer (don't undo any further) + S32 mOperationID; // current operation id, for undoing and redoing in clusters + +public: + LLUndoBuffer( LLUndoAction (*create_func()), S32 initial_count ); + virtual ~LLUndoBuffer(); + + LLUndoAction *getNextAction(BOOL setClusterBegin = TRUE); + BOOL undoAction(); + BOOL redoAction(); + BOOL canUndo() { return (mNextAction != mFirstAction); } + BOOL canRedo() { return (mNextAction != mLastAction); } + + void flushActions(); +}; + +#endif //LL_LLUNDO_H diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp new file mode 100644 index 0000000000..f8d1504f3c --- /dev/null +++ b/indra/llui/llview.cpp @@ -0,0 +1,2924 @@ +/** + * @file llview.cpp + * @author James Cook + * @brief Container for other views, anything that draws. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llview.h" + +#include "llstring.h" +#include "llrect.h" +#include "llgl.h" +#include "llevent.h" +#include "llfontgl.h" +#include "llfocusmgr.h" +#include "llglheaders.h" +#include "llwindow.h" +#include "llstl.h" +#include "lluictrl.h" +#include "llui.h" // colors saved settings +#include "v3color.h" +#include "llstl.h" + +#include <boost/tokenizer.hpp> + +#include <assert.h> + +BOOL LLView::sDebugRects = FALSE; +BOOL LLView::sDebugKeys = FALSE; +S32 LLView::sDepth = 0; +LLView* LLView::sFastFrameView = NULL; +BOOL LLView::sDebugMouseHandling = FALSE; +LLString LLView::sMouseHandlerMessage; +S32 LLView::sSelectID = GL_NAME_UI_RESERVED; +BOOL LLView::sEditingUI = FALSE; +BOOL LLView::sForceReshape = FALSE; +LLView* LLView::sEditingUIView = NULL; +S32 LLView::sLastLeftXML = S32_MIN; +S32 LLView::sLastBottomXML = S32_MIN; +std::map<LLViewHandle,LLView*> LLView::sViewHandleMap; + +S32 LLViewHandle::sNextID = 0; +LLViewHandle LLViewHandle::sDeadHandle; + +#if LL_DEBUG +BOOL LLView::sIsDrawing = FALSE; +#endif + +//static +LLView* LLView::getViewByHandle(LLViewHandle handle) +{ + if (handle == LLViewHandle::sDeadHandle) + { + return NULL; + } + std::map<LLViewHandle,LLView*>::iterator iter = sViewHandleMap.find(handle); + if (iter != sViewHandleMap.end()) + { + return iter->second; + } + else + { + return NULL; + } +} + +//static +BOOL LLView::deleteViewByHandle(LLViewHandle handle) +{ + std::map<LLViewHandle,LLView*>::iterator iter = sViewHandleMap.find(handle); + if (iter != sViewHandleMap.end()) + { + delete iter->second; // will remove from map + return TRUE; + } + else + { + return FALSE; + } +} + +LLView::LLView() : + mParentView(NULL), + mReshapeFlags(FOLLOWS_NONE), + mDefaultTabGroup(0), + mEnabled(TRUE), + mMouseOpaque(TRUE), + mSoundFlags(MOUSE_UP), // default to only make sound on mouse up + mSaveToXML(TRUE), + mIsFocusRoot(FALSE), + mLastVisible(TRUE), + mRenderInFastFrame(TRUE), + mSpanChildren(FALSE), + mVisible(TRUE), + mHidden(FALSE), + mNextInsertionOrdinal(0) +{ + mViewHandle.init(); + sViewHandleMap[mViewHandle] = this; +} + +LLView::LLView(const LLString& name, BOOL mouse_opaque) : + mParentView(NULL), + mName(name), + mReshapeFlags(FOLLOWS_NONE), + mDefaultTabGroup(0), + mEnabled(TRUE), + mMouseOpaque(mouse_opaque), + mSoundFlags(MOUSE_UP), // default to only make sound on mouse up + mSaveToXML(TRUE), + mIsFocusRoot(FALSE), + mLastVisible(TRUE), + mRenderInFastFrame(TRUE), + mSpanChildren(FALSE), + mVisible(TRUE), + mHidden(FALSE), + mNextInsertionOrdinal(0) +{ + mViewHandle.init(); + sViewHandleMap[mViewHandle] = this; +} + + +LLView::LLView( + const LLString& name, const LLRect& rect, BOOL mouse_opaque, U32 reshape) : + mParentView(NULL), + mName(name), + mRect(rect), + mReshapeFlags(reshape), + mDefaultTabGroup(0), + mEnabled(TRUE), + mMouseOpaque(mouse_opaque), + mSoundFlags(MOUSE_UP), // default to only make sound on mouse up + mSaveToXML(TRUE), + mIsFocusRoot(FALSE), + mLastVisible(TRUE), + mRenderInFastFrame(TRUE), + mSpanChildren(FALSE), + mVisible(TRUE), + mHidden(FALSE), + mNextInsertionOrdinal(0) +{ + mViewHandle.init(); + sViewHandleMap[mViewHandle] = this; +} + + +LLView::~LLView() +{ + //llinfos << "Deleting view " << mName << ":" << (void*) this << llendl; +// llassert(LLView::sIsDrawing == FALSE); + if( gFocusMgr.getKeyboardFocus() == this ) + { + llwarns << "View holding keyboard focus deleted: " << getName() << ". Keyboard focus removed." << llendl; + gFocusMgr.removeKeyboardFocusWithoutCallback( this ); + } + + if( gFocusMgr.getMouseCapture() == this ) + { + llwarns << "View holding mouse capture deleted: " << getName() << ". Mouse capture removed." << llendl; + gFocusMgr.removeMouseCaptureWithoutCallback( this ); + } + + if( gFocusMgr.getTopView() == this ) + { + llwarns << "View holding top view deleted: " << getName() << ". Top view removed." << llendl; + gFocusMgr.removeTopViewWithoutCallback( this ); + } + + sViewHandleMap.erase(mViewHandle); + + deleteAllChildren(); + + if (mParentView != NULL) + { + mParentView->removeChild(this); + } + + if(LLView::sFastFrameView == this) + { + LLView::sFastFrameView = NULL; + } + + dispatch_list_t::iterator itor; + for (itor = mDispatchList.begin(); itor != mDispatchList.end(); ++itor) + { + (*itor).second->clearDispatchers(); + delete (*itor).second; + } +} + +// virtual +BOOL LLView::isView() +{ + return TRUE; +} + +// virtual +BOOL LLView::isCtrl() const +{ + return FALSE; +} + +// virtual +BOOL LLView::isPanel() +{ + return FALSE; +} + +void LLView::setMouseOpaque(BOOL b) +{ + mMouseOpaque = b; +} + +void LLView::setToolTip(const LLString& msg) +{ + mToolTipMsg = msg; +} + +// virtual +void LLView::setRect(const LLRect& rect) +{ + mRect = rect; +} + + +void LLView::setFollows(U32 flags) +{ + mReshapeFlags = flags; +} + +void LLView::setFollowsNone() +{ + mReshapeFlags = FOLLOWS_NONE; +} + +void LLView::setFollowsLeft() +{ + mReshapeFlags |= FOLLOWS_LEFT; +} + +void LLView::setFollowsTop() +{ + mReshapeFlags |= FOLLOWS_TOP; +} + +void LLView::setFollowsRight() +{ + mReshapeFlags |= FOLLOWS_RIGHT; +} + +void LLView::setFollowsBottom() +{ + mReshapeFlags |= FOLLOWS_BOTTOM; +} + +void LLView::setFollowsAll() +{ + mReshapeFlags |= FOLLOWS_ALL; +} + +void LLView::setSoundFlags(U8 flags) +{ + mSoundFlags = flags; +} + +void LLView::setName(LLString name) +{ + mName = name; +} + +void LLView::setSpanChildren( BOOL span_children ) +{ + mSpanChildren = span_children; updateRect(); +} + +const LLString& LLView::getToolTip() +{ + return mToolTipMsg; +} + +// virtual +const LLString& LLView::getName() const +{ + static const LLString unnamed("(no name)"); + return mName.empty() ? unnamed : mName; +} + +void LLView::sendChildToFront(LLView* child) +{ + if (child->mParentView == this) + { + mChildList.remove( child ); + mChildList.push_front(child); + } +} + +void LLView::sendChildToBack(LLView* child) +{ + if (child->mParentView == this) + { + mChildList.remove( child ); + mChildList.push_back(child); + } +} + +void LLView::moveChildToFrontOfTabGroup(LLUICtrl* child) +{ + if(mCtrlOrder.find(child) != mCtrlOrder.end()) + { + mCtrlOrder[child].second = -1 * mNextInsertionOrdinal++; + } +} + +void LLView::addChild(LLView* child, S32 tab_group) +{ + // remove from current parent + if (child->mParentView) + { + child->mParentView->removeChild(child); + } + + // add to front of child list, as normal + mChildList.push_front(child); + + // add to ctrl list if is LLUICtrl + if (child->isCtrl()) + { + // controls are stored in reverse order from render order + addCtrlAtEnd((LLUICtrl*) child, tab_group); + } + + child->mParentView = this; + updateRect(); +} + + +void LLView::addChildAtEnd(LLView* child, S32 tab_group) +{ + // remove from current parent + if (child->mParentView) + { + child->mParentView->removeChild(child); + } + + // add to back of child list + mChildList.push_back(child); + + // add to ctrl list if is LLUICtrl + if (child->isCtrl()) + { + // controls are stored in reverse order from render order + addCtrl((LLUICtrl*) child, tab_group); + } + + child->mParentView = this; + updateRect(); +} + +// remove the specified child from the view, and set it's parent to NULL. +void LLView::removeChild( LLView* child ) +{ + if (child->mParentView == this) + { + mChildList.remove( child ); + child->mParentView = NULL; + } + else + { + llerrs << "LLView::removeChild called with non-child" << llendl; + } + + if (child->isCtrl()) + { + removeCtrl((LLUICtrl*)child); + } +} + +void LLView::addCtrlAtEnd(LLUICtrl* ctrl, S32 tab_group) +{ + mCtrlOrder.insert(tab_order_pair_t(ctrl, + tab_order_t(tab_group, mNextInsertionOrdinal++))); +} + +void LLView::addCtrl( LLUICtrl* ctrl, S32 tab_group) +{ + // add to front of list by using negative ordinal, which monotonically increases + mCtrlOrder.insert(tab_order_pair_t(ctrl, + tab_order_t(tab_group, -1 * mNextInsertionOrdinal++))); +} + +void LLView::removeCtrl(LLUICtrl* ctrl) +{ + child_tab_order_t::iterator found = mCtrlOrder.find(ctrl); + if(found != mCtrlOrder.end()) + { + mCtrlOrder.erase(found); + } +} + +S32 LLView::getDefaultTabGroup() const { return mDefaultTabGroup; } + +LLView::ctrl_list_t LLView::getCtrlList() const +{ + ctrl_list_t controls; + for(child_list_const_iter_t iter = mChildList.begin(); + iter != mChildList.end(); + iter++) + { + if((*iter)->isCtrl()) + { + controls.push_back(static_cast<LLUICtrl*>(*iter)); + } + } + return controls; +} + +LLView::ctrl_list_t LLView::getCtrlListSorted() const +{ + ctrl_list_t controls = getCtrlList(); + std::sort(controls.begin(), controls.end(), LLCompareByTabOrder(mCtrlOrder)); + return controls; +} + +LLCompareByTabOrder::LLCompareByTabOrder(LLView::child_tab_order_t order): mTabOrder(order) {} + +bool LLCompareByTabOrder::compareTabOrders(const LLView::tab_order_t & a, const LLView::tab_order_t & b) const +{ + return a < b; +} + +// This method compares two LLViews by the tab order specified in the comparator object. The +// code for this is a little convoluted because each argument can have four states: +// 1) not a control, 2) a control but not in the tab order, 3) a control in the tab order, 4) null +bool LLCompareByTabOrder::operator() (const LLView* const a, const LLView* const b) const +{ + S32 a_score = 0, b_score = 0; + if(a) a_score--; + if(b) b_score--; + if(a && a->isCtrl()) a_score--; + if(b && b->isCtrl()) b_score--; + if(a_score == -2 && b_score == -2) + { + const LLUICtrl * const a_ctrl = static_cast<const LLUICtrl* const>(a); + const LLUICtrl * const b_ctrl = static_cast<const LLUICtrl* const>(b); + LLView::child_tab_order_const_iter_t a_found = mTabOrder.find(a_ctrl), b_found = mTabOrder.find(b_ctrl); + if(a_found != mTabOrder.end()) a_score--; + if(b_found != mTabOrder.end()) b_score--; + if(a_score == -3 && b_score == -3) + { + // whew! Once we're in here, they're both in the tab order, and we can compare based on that + return compareTabOrders(a_found->second, b_found->second); + } + } + return (a_score == b_score) ? a < b : a_score < b_score; +} + +BOOL LLView::isInVisibleChain() const +{ + const LLView* cur_view = this; + while(cur_view) + { + if (!cur_view->getVisible()) + { + return FALSE; + } + cur_view = cur_view->getParent(); + } + return TRUE; +} + +BOOL LLView::isInEnabledChain() const +{ + const LLView* cur_view = this; + while(cur_view) + { + if (!cur_view->getEnabled()) + { + return FALSE; + } + cur_view = cur_view->getParent(); + } + return TRUE; +} + +BOOL LLView::isFocusRoot() const +{ + return mIsFocusRoot; +} + +LLView* LLView::findRootMostFocusRoot() +{ + LLView* focus_root = NULL; + LLView* next_view = this; + while(next_view) + { + if (next_view->isFocusRoot()) + { + focus_root = next_view; + } + next_view = next_view->getParent(); + } + return focus_root; +} + +BOOL LLView::canFocusChildren() const +{ + return TRUE; +} + +BOOL LLView::focusNextRoot() +{ + LLView::child_list_t result = LLView::getFocusRootsQuery().run(this); + return LLView::focusNext(result); +} + +BOOL LLView::focusPrevRoot() +{ + LLView::child_list_t result = LLView::getFocusRootsQuery().run(this); + return LLView::focusPrev(result); +} + +BOOL LLView::focusNextItem(BOOL text_fields_only) +{ + // this assumes that this method is called on the focus root. + LLCtrlQuery query = LLView::getTabOrderQuery(); + if(text_fields_only || LLUI::sConfigGroup->getBOOL("TabToTextFieldsOnly")) + { + query.addPreFilter(LLUICtrl::LLTextInputFilter::getInstance()); + } + LLView::child_list_t result = query(this); + return LLView::focusNext(result); +} + +BOOL LLView::focusPrevItem(BOOL text_fields_only) +{ + // this assumes that this method is called on the focus root. + LLCtrlQuery query = LLView::getTabOrderQuery(); + if(text_fields_only || LLUI::sConfigGroup->getBOOL("TabToTextFieldsOnly")) + { + query.addPreFilter(LLUICtrl::LLTextInputFilter::getInstance()); + } + LLView::child_list_t result = query(this); + return LLView::focusPrev(result); +} + + +// static +BOOL LLView::focusNext(LLView::child_list_t & result) +{ + LLView::child_list_iter_t focused = result.end(); + for(LLView::child_list_iter_t iter = result.begin(); + iter != result.end(); + ++iter) + { + if(gFocusMgr.childHasKeyboardFocus(*iter)) + { + focused = iter; + break; + } + } + LLView::child_list_iter_t next = focused; + next = (next == result.end()) ? result.begin() : ++next; + while(next != focused) + { + // wrap around to beginning if necessary + if(next == result.end()) + { + next = result.begin(); + } + if((*next)->isCtrl()) + { + LLUICtrl * ctrl = static_cast<LLUICtrl*>(*next); + ctrl->setFocus(TRUE); + ctrl->onTabInto(); + gFocusMgr.triggerFocusFlash(); + return TRUE; + } + ++next; + } + return FALSE; +} + +// static +BOOL LLView::focusPrev(LLView::child_list_t & result) +{ + LLView::child_list_reverse_iter_t focused = result.rend(); + for(LLView::child_list_reverse_iter_t iter = result.rbegin(); + iter != result.rend(); + ++iter) + { + if(gFocusMgr.childHasKeyboardFocus(*iter)) + { + focused = iter; + break; + } + } + LLView::child_list_reverse_iter_t next = focused; + next = (next == result.rend()) ? result.rbegin() : ++next; + while(next != focused) + { + // wrap around to beginning if necessary + if(next == result.rend()) + { + next = result.rbegin(); + } + if((*next)->isCtrl()) + { + LLUICtrl * ctrl = static_cast<LLUICtrl*>(*next); + if (!ctrl->hasFocus()) + { + ctrl->setFocus(TRUE); + ctrl->onTabInto(); + gFocusMgr.triggerFocusFlash(); + } + return TRUE; + } + ++next; + } + return FALSE; +} + +BOOL LLView::focusFirstItem(BOOL prefer_text_fields) +{ + // search for text field first + if(prefer_text_fields) + { + LLCtrlQuery query = LLView::getTabOrderQuery(); + query.addPreFilter(LLUICtrl::LLTextInputFilter::getInstance()); + LLView::child_list_t result = query(this); + if(result.size() > 0) + { + LLUICtrl * ctrl = static_cast<LLUICtrl*>(result.front()); + if(!ctrl->hasFocus()) + { + ctrl->setFocus(TRUE); + ctrl->onTabInto(); + gFocusMgr.triggerFocusFlash(); + } + return TRUE; + } + } + // no text field found, or we don't care about text fields + LLView::child_list_t result = LLView::getTabOrderQuery().run(this); + if(result.size() > 0) + { + LLUICtrl * ctrl = static_cast<LLUICtrl*>(result.front()); + if(!ctrl->hasFocus()) + { + ctrl->setFocus(TRUE); + ctrl->onTabInto(); + gFocusMgr.triggerFocusFlash(); + } + return TRUE; + } + return FALSE; +} + +BOOL LLView::focusLastItem(BOOL prefer_text_fields) +{ + // search for text field first + if(prefer_text_fields) + { + LLCtrlQuery query = LLView::getTabOrderQuery(); + query.addPreFilter(LLUICtrl::LLTextInputFilter::getInstance()); + LLView::child_list_t result = query(this); + if(result.size() > 0) + { + LLUICtrl * ctrl = static_cast<LLUICtrl*>(result.back()); + if(!ctrl->hasFocus()) + { + ctrl->setFocus(TRUE); + ctrl->onTabInto(); + gFocusMgr.triggerFocusFlash(); + } + return TRUE; + } + } + // no text field found, or we don't care about text fields + LLView::child_list_t result = LLView::getTabOrderQuery().run(this); + if(result.size() > 0) + { + LLUICtrl * ctrl = static_cast<LLUICtrl*>(result.back()); + if(!ctrl->hasFocus()) + { + ctrl->setFocus(TRUE); + ctrl->onTabInto(); + gFocusMgr.triggerFocusFlash(); + } + return TRUE; + } + return FALSE; +} + + + +// delete all children. Override this function if you need to +// perform any extra clean up such as cached pointers to selected +// children, etc. +void LLView::deleteAllChildren() +{ + // clear out the control ordering + mCtrlOrder.clear(); + + while (!mChildList.empty()) + { + LLView* viewp = mChildList.front(); + delete viewp; // will remove the child from mChildList + } +} + +void LLView::setAllChildrenEnabled(BOOL b) +{ + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + viewp->setEnabled(b); + } +} + +// virtual +void LLView::setTentative(BOOL b) +{ +} + +// virtual +BOOL LLView::getTentative() const +{ + return FALSE; +} + +// virtual +void LLView::setEnabled(BOOL enabled) +{ + mEnabled = enabled; +} + +// virtual +void LLView::setVisible(BOOL visible) +{ + if( !visible && (gFocusMgr.getTopView() == this) ) + { + gFocusMgr.setTopView( NULL, NULL ); + } + + if ( mVisible != visible ) + { + // tell all children of this view that the visibility may have changed + onVisibilityChange ( visible ); + } + + mVisible = visible; +} + +// virtual +void LLView::setHidden(BOOL hidden) +{ + mHidden = hidden; +} + +// virtual +BOOL LLView::setLabelArg(const LLString& key, const LLString& text) +{ + return FALSE; +} + +void LLView::onVisibilityChange ( BOOL curVisibilityIn ) +{ + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + // only views that are themselves visible will have their overall visibility affected by their ancestors + if (viewp->getVisible()) + { + viewp->onVisibilityChange ( curVisibilityIn ); + } + } +} + +// virtual +void LLView::translate(S32 x, S32 y) +{ + mRect.translate(x, y); +} + +// virtual +BOOL LLView::canSnapTo(LLView* other_view) +{ + return other_view->getVisible(); +} + +// virtual +void LLView::snappedTo(LLView* snap_view) +{ +} + +BOOL LLView::handleHover(S32 x, S32 y, MASK mask) +{ + BOOL handled = childrenHandleHover( x, y, mask ) != NULL; + if( !handled && mMouseOpaque && pointInView( x, y ) ) + { + LLUI::sWindow->setCursor(UI_CURSOR_ARROW); + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << llendl; + handled = TRUE; + } + + return handled; +} + +BOOL LLView::handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect_screen) +{ + BOOL handled = FALSE; + + LLString tool_tip; + + if ( getVisible() && getEnabled()) + { + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + S32 local_x = x - viewp->mRect.mLeft; + S32 local_y = y - viewp->mRect.mBottom; + if( viewp->handleToolTip(local_x, local_y, msg, sticky_rect_screen ) ) + { + handled = TRUE; + break; + } + } + + if (LLUI::sShowXUINames && (mToolTipMsg.find(".xml", 0) == LLString::npos) && + (mName.find("Drag", 0) == LLString::npos)) + { + tool_tip = mName; + } + else + { + tool_tip = mToolTipMsg; + } + + + + BOOL showNamesTextBox = LLUI::sShowXUINames && (getWidgetType() == WIDGET_TYPE_TEXT_BOX); + + if( !handled && (mMouseOpaque || showNamesTextBox) && pointInView( x, y ) && !tool_tip.empty()) + { + + msg = tool_tip; + + // Convert rect local to screen coordinates + localPointToScreen( + 0, 0, + &(sticky_rect_screen->mLeft), &(sticky_rect_screen->mBottom) ); + localPointToScreen( + mRect.getWidth(), mRect.getHeight(), + &(sticky_rect_screen->mRight), &(sticky_rect_screen->mTop) ); + + handled = TRUE; + } + } + + return handled; +} + + +BOOL LLView::handleKey(KEY key, MASK mask, BOOL called_from_parent) +{ + BOOL handled = FALSE; + + if( called_from_parent ) + { + // Downward traversal + if (getVisible() && mEnabled) + { + handled = childrenHandleKey( key, mask ) != NULL; + } + } + + if( !handled ) + { + // JC: Must pass to disabled views, since they could have + // keyboard focus, which requires the escape key to exit. + if (getVisible()) + { + handled = handleKeyHere( key, mask, called_from_parent ); + if (handled && LLView::sDebugKeys) + { + llinfos << "Key handled by " << getName() << llendl; + } + } + } + + if( !handled && !called_from_parent) + { + if (mIsFocusRoot) + { + // stop processing at focus root + handled = FALSE; + } + else if (mParentView) + { + // Upward traversal + handled = mParentView->handleKey( key, mask, FALSE ); + } + } + return handled; +} + +// Called from handleKey() +// Handles key in this object. Checking parents and children happens in handleKey() +BOOL LLView::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent) +{ + return FALSE; +} + + +BOOL LLView::handleUnicodeChar(llwchar uni_char, BOOL called_from_parent) +{ + BOOL handled = FALSE; + + /* + if( called_from_parent ) + { + // Downward traversal + if (getVisible() && mEnabled) + { + handled = childrenHandleKey( key, mask ) != NULL; + } + } + */ + + // JC: Must pass to disabled views, since they could have + // keyboard focus, which requires the escape key to exit. + if (getVisible()) + { + handled = handleUnicodeCharHere(uni_char, called_from_parent); + if (handled && LLView::sDebugKeys) + { + llinfos << "Unicode key handled by " << getName() << llendl; + } + } + + + if (!handled && !called_from_parent) + { + if (mIsFocusRoot) + { + // stop processing at focus root + handled = FALSE; + } + else if(mParentView) + { + // Upward traversal + handled = mParentView->handleUnicodeChar(uni_char, FALSE); + } + } + + return handled; +} + + +BOOL LLView::handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent ) +{ + return FALSE; +} + + +BOOL LLView::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, void* cargo_data, + EAcceptance* accept, + LLString& tooltip_msg) +{ + // CRO this is an experiment to allow drag and drop into object inventory based on the DragAndDrop tool's permissions rather than the parent + BOOL handled = childrenHandleDragAndDrop( x, y, mask, drop, + cargo_type, + cargo_data, + accept, + tooltip_msg) != NULL; + if( !handled && mMouseOpaque ) + { + *accept = ACCEPT_NO; + handled = TRUE; + lldebugst(LLERR_USER_INPUT) << "dragAndDrop handled by LLView " << getName() << llendl; + } + + return handled; +} + +LLView* LLView::childrenHandleDragAndDrop(S32 x, S32 y, MASK mask, + BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + LLString& tooltip_msg) +{ + LLView* handled_view = FALSE; + // CRO this is an experiment to allow drag and drop into object inventory based on the DragAndDrop tool's permissions rather than the parent + if( getVisible() ) +// if( getVisible() && mEnabled ) + { + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + S32 local_x = x - viewp->mRect.mLeft; + S32 local_y = y - viewp->mRect.mBottom; + if( viewp->pointInView(local_x, local_y) && + viewp->getVisible() && + viewp->mEnabled && + viewp->handleDragAndDrop(local_x, local_y, mask, drop, + cargo_type, + cargo_data, + accept, + tooltip_msg)) + { + handled_view = viewp; + break; + } + } + } + return handled_view; +} + + + +BOOL LLView::handleMouseUp(S32 x, S32 y, MASK mask) +{ + BOOL handled = childrenHandleMouseUp( x, y, mask ) != NULL; + if( !handled && mMouseOpaque ) + { + handled = TRUE; + } + return handled; +} + +BOOL LLView::handleMouseDown(S32 x, S32 y, MASK mask) +{ + LLView* handled_view = childrenHandleMouseDown( x, y, mask ); + BOOL handled = (handled_view != NULL); + if( !handled && mMouseOpaque ) + { + handled = TRUE; + handled_view = this; + } + + // HACK If we're editing UI, select the leaf view that ate the click. + if (sEditingUI && handled_view) + { + // need to find leaf views, big hack + EWidgetType type = handled_view->getWidgetType(); + if (type == WIDGET_TYPE_BUTTON + || type == WIDGET_TYPE_LINE_EDITOR + || type == WIDGET_TYPE_TEXT_EDITOR + || type == WIDGET_TYPE_TEXT_BOX) + { + sEditingUIView = handled_view; + } + } + + return handled; +} + +BOOL LLView::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + BOOL handled = childrenHandleDoubleClick( x, y, mask ) != NULL; + if( !handled && mMouseOpaque ) + { + handleMouseDown(x, y, mask); + handled = TRUE; + } + return handled; +} + +BOOL LLView::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + BOOL handled = FALSE; + if( getVisible() && mEnabled ) + { + handled = childrenHandleScrollWheel( x, y, clicks ) != NULL; + if( !handled && mMouseOpaque ) + { + handled = TRUE; + } + } + return handled; +} + +BOOL LLView::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + BOOL handled = childrenHandleRightMouseDown( x, y, mask ) != NULL; + if( !handled && mMouseOpaque ) + { + handled = TRUE; + } + return handled; +} + +BOOL LLView::handleRightMouseUp(S32 x, S32 y, MASK mask) +{ + BOOL handled = childrenHandleRightMouseUp( x, y, mask ) != NULL; + if( !handled && mMouseOpaque ) + { + handled = TRUE; + } + return handled; +} + +LLView* LLView::childrenHandleScrollWheel(S32 x, S32 y, S32 clicks) +{ + LLView* handled_view = NULL; + if (getVisible() && mEnabled ) + { + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + S32 local_x = x - viewp->mRect.mLeft; + S32 local_y = y - viewp->mRect.mBottom; + if (viewp->pointInView(local_x, local_y) && + viewp->handleScrollWheel( local_x, local_y, clicks )) + { + if (sDebugMouseHandling) + { + sMouseHandlerMessage = LLString("->") + viewp->mName.c_str() + sMouseHandlerMessage; + } + + handled_view = viewp; + break; + } + } + } + return handled_view; +} + +LLView* LLView::childrenHandleHover(S32 x, S32 y, MASK mask) +{ + LLView* handled_view = NULL; + if (getVisible() && mEnabled ) + { + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + S32 local_x = x - viewp->mRect.mLeft; + S32 local_y = y - viewp->mRect.mBottom; + if(viewp->pointInView(local_x, local_y) && + viewp->getVisible() && + viewp->getEnabled() && + viewp->handleHover(local_x, local_y, mask) ) + { + if (sDebugMouseHandling) + { + sMouseHandlerMessage = LLString("->") + viewp->mName.c_str() + sMouseHandlerMessage; + } + + handled_view = viewp; + break; + } + } + } + return handled_view; +} + +// Called during downward traversal +LLView* LLView::childrenHandleKey(KEY key, MASK mask) +{ + LLView* handled_view = NULL; + + if ( getVisible() && mEnabled ) + { + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + if (viewp->handleKey(key, mask, TRUE)) + { + if (LLView::sDebugKeys) + { + llinfos << "Key handled by " << viewp->getName() << llendl; + } + handled_view = viewp; + break; + } + } + } + + return handled_view; +} + + +LLView* LLView::childrenHandleMouseDown(S32 x, S32 y, MASK mask) +{ + LLView* handled_view = NULL; + + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + S32 local_x = x - viewp->mRect.mLeft; + S32 local_y = y - viewp->mRect.mBottom; + + if (viewp->pointInView(local_x, local_y) && + viewp->getVisible() && + viewp->mEnabled && + viewp->handleMouseDown( local_x, local_y, mask )) + { + if (sDebugMouseHandling) + { + sMouseHandlerMessage = LLString("->") + viewp->mName.c_str() + sMouseHandlerMessage; + } + handled_view = viewp; + break; + } + } + return handled_view; +} + +LLView* LLView::childrenHandleRightMouseDown(S32 x, S32 y, MASK mask) +{ + LLView* handled_view = NULL; + + if (getVisible() && mEnabled ) + { + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + S32 local_x = x - viewp->mRect.mLeft; + S32 local_y = y - viewp->mRect.mBottom; + if (viewp->pointInView(local_x, local_y) && + viewp->getVisible() && + viewp->mEnabled && + viewp->handleRightMouseDown( local_x, local_y, mask )) + { + if (sDebugMouseHandling) + { + sMouseHandlerMessage = LLString("->") + viewp->mName.c_str() + sMouseHandlerMessage; + } + handled_view = viewp; + break; + } + } + } + return handled_view; +} + +LLView* LLView::childrenHandleDoubleClick(S32 x, S32 y, MASK mask) +{ + LLView* handled_view = NULL; + + if (getVisible() && mEnabled ) + { + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + S32 local_x = x - viewp->mRect.mLeft; + S32 local_y = y - viewp->mRect.mBottom; + if (viewp->pointInView(local_x, local_y) && + viewp->getVisible() && + viewp->mEnabled && + viewp->handleDoubleClick( local_x, local_y, mask )) + { + if (sDebugMouseHandling) + { + sMouseHandlerMessage = LLString("->") + viewp->mName.c_str() + sMouseHandlerMessage; + } + handled_view = viewp; + break; + } + } + } + return handled_view; +} + +LLView* LLView::childrenHandleMouseUp(S32 x, S32 y, MASK mask) +{ + LLView* handled_view = NULL; + if( getVisible() && mEnabled ) + { + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + S32 local_x = x - viewp->mRect.mLeft; + S32 local_y = y - viewp->mRect.mBottom; + if (!viewp->pointInView(local_x, local_y)) + continue; + if (!viewp->getVisible()) + continue; + if (!viewp->mEnabled) + continue; + if (viewp->handleMouseUp( local_x, local_y, mask )) + { + if (sDebugMouseHandling) + { + sMouseHandlerMessage = LLString("->") + viewp->mName.c_str() + sMouseHandlerMessage; + } + handled_view = viewp; + break; + } + } + } + return handled_view; +} + +LLView* LLView::childrenHandleRightMouseUp(S32 x, S32 y, MASK mask) +{ + LLView* handled_view = NULL; + if( getVisible() && mEnabled ) + { + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + S32 local_x = x - viewp->mRect.mLeft; + S32 local_y = y - viewp->mRect.mBottom; + if (viewp->pointInView(local_x, local_y) && + viewp->getVisible() && + viewp->mEnabled && + viewp->handleRightMouseUp( local_x, local_y, mask )) + { + if (sDebugMouseHandling) + { + sMouseHandlerMessage = LLString("->") + viewp->mName.c_str() + sMouseHandlerMessage; + } + handled_view = viewp; + break; + } + } + } + return handled_view; +} + + +void LLView::draw() +{ + if (getVisible()) + { + if (sDebugRects) + { + drawDebugRect(); + + // Check for bogus rectangle + if (mRect.mRight <= mRect.mLeft + || mRect.mTop <= mRect.mBottom) + { + llwarns << "Bogus rectangle for " << getName() << " with " << mRect << llendl; + } + } + + LLRect rootRect = getRootView()->getRect(); + LLRect screenRect; + + // draw focused control on top of everything else + LLView* focus_view = gFocusMgr.getKeyboardFocus(); + if (focus_view && focus_view->getParent() != this) + { + focus_view = NULL; + } + + for (child_list_reverse_iter_t child_iter = mChildList.rbegin(); child_iter != mChildList.rend(); ++child_iter) + { + LLView *viewp = *child_iter; + ++sDepth; + + if (viewp->getVisible() && viewp != focus_view) + { + // Only draw views that are within the root view + localRectToScreen(viewp->getRect(),&screenRect); + if ( rootRect.rectInRect(&screenRect) ) + { + glMatrixMode(GL_MODELVIEW); + LLUI::pushMatrix(); + { + LLUI::translate((F32)viewp->getRect().mLeft, (F32)viewp->getRect().mBottom, 0.f); + viewp->draw(); + } + LLUI::popMatrix(); + } + } + + --sDepth; + } + + if (focus_view && focus_view->getVisible()) + { + drawChild(focus_view); + } + + // HACK + if (sEditingUI && this == sEditingUIView) + { + drawDebugRect(); + } + } +} + +//Draw a box for debugging. +void LLView::drawDebugRect() +{ + // drawing solids requires texturing be disabled + LLGLSNoTexture no_texture; + + // draw red rectangle for the border + LLColor4 border_color(0.f, 0.f, 0.f, 1.f); + if (sEditingUI) + { + border_color.mV[0] = 1.f; + } + else + { + border_color.mV[sDepth%3] = 1.f; + } + + glColor4fv( border_color.mV ); + + glBegin(GL_LINES); + glVertex2i(0, mRect.getHeight() - 1); + glVertex2i(0, 0); + + glVertex2i(0, 0); + glVertex2i(mRect.getWidth() - 1, 0); + + glVertex2i(mRect.getWidth() - 1, 0); + glVertex2i(mRect.getWidth() - 1, mRect.getHeight() - 1); + + glVertex2i(mRect.getWidth() - 1, mRect.getHeight() - 1); + glVertex2i(0, mRect.getHeight() - 1); + glEnd(); + + // Draw the name if it's not a leaf node + if (mChildList.size() && !sEditingUI) + { + //char temp[256]; + S32 x, y; + glColor4fv( border_color.mV ); + x = mRect.getWidth()/2; + y = mRect.getHeight()/2; + LLString debug_text = llformat("%s (%d x %d)", getName().c_str(), + mRect.getWidth(), mRect.getHeight()); + LLFontGL::sSansSerifSmall->renderUTF8(debug_text, 0, (F32)x, (F32)y, border_color, + LLFontGL::HCENTER, LLFontGL::BASELINE, LLFontGL::NORMAL, + S32_MAX, S32_MAX, NULL, FALSE); + } +} + +void LLView::drawChild(LLView* childp, S32 x_offset, S32 y_offset) +{ + if (childp && childp->getParent() == this) + { + ++sDepth; + + if (childp->getVisible()) + { + glMatrixMode(GL_MODELVIEW); + LLUI::pushMatrix(); + { + LLUI::translate((F32)childp->getRect().mLeft + x_offset, (F32)childp->getRect().mBottom + y_offset, 0.f); + childp->draw(); + } + LLUI::popMatrix(); + } + + --sDepth; + } +} + + +void LLView::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + // make sure this view contains all its children + updateRect(); + + // compute how much things changed and apply reshape logic to children + S32 delta_width = width - mRect.getWidth(); + S32 delta_height = height - mRect.getHeight(); + + if (delta_width || delta_height || sForceReshape) + { + // adjust our rectangle + mRect.mRight = mRect.mLeft + width; + mRect.mTop = mRect.mBottom + height; + + // move child views according to reshape flags + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + LLRect child_rect( viewp->mRect ); + + if (viewp->followsRight() && viewp->followsLeft()) + { + child_rect.mRight += delta_width; + } + else if (viewp->followsRight()) + { + child_rect.mLeft += delta_width; + child_rect.mRight += delta_width; + } + else if (viewp->followsLeft()) + { + // left is 0, don't need to adjust coords + } + else + { + // BUG what to do when we don't follow anyone? + // for now, same as followsLeft + } + + if (viewp->followsTop() && viewp->followsBottom()) + { + child_rect.mTop += delta_height; + } + else if (viewp->followsTop()) + { + child_rect.mTop += delta_height; + child_rect.mBottom += delta_height; + } + else if (viewp->followsBottom()) + { + // bottom is 0, so don't need to adjust coords + } + else + { + // BUG what to do when we don't follow? + // for now, same as bottom + } + + S32 delta_x = child_rect.mLeft - viewp->mRect.mLeft; + S32 delta_y = child_rect.mBottom - viewp->mRect.mBottom; + viewp->translate( delta_x, delta_y ); + viewp->reshape(child_rect.getWidth(), child_rect.getHeight()); + } + } + + if (!called_from_parent) + { + if (mParentView) + { + mParentView->reshape(mParentView->getRect().getWidth(), mParentView->getRect().getHeight(), FALSE); + } + } +} + +LLRect LLView::getRequiredRect() +{ + return mRect; +} + +const LLRect LLView::getScreenRect() const +{ + //FIXME: check for one-off error + LLRect screen_rect; + localPointToScreen(0, 0, &screen_rect.mLeft, &screen_rect.mBottom); + localPointToScreen(mRect.getWidth(), mRect.getHeight(), &screen_rect.mRight, &screen_rect.mTop); + return screen_rect; +} + +const LLRect LLView::getLocalRect() const +{ + LLRect local_rect(0, mRect.getHeight(), mRect.getWidth(), 0); + return local_rect; +} + +void LLView::updateRect() +{ + if (mSpanChildren && mChildList.size()) + { + LLView* first_child = (*mChildList.begin()); + LLRect child_spanning_rect = first_child->mRect; + + for ( child_list_iter_t child_it = ++mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + if (viewp->getVisible()) + { + child_spanning_rect |= viewp->mRect; + } + } + + S32 translate_x = llmin(0, child_spanning_rect.mLeft); + S32 translate_y = llmin(0, child_spanning_rect.mBottom); + S32 new_width = llmax(mRect.getWidth() + translate_x, child_spanning_rect.getWidth()); + S32 new_height = llmax(mRect.getHeight() + translate_y, child_spanning_rect.getHeight()); + + mRect.setOriginAndSize(mRect.mLeft + translate_x, mRect.mBottom + translate_y, new_width, new_height); + + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + viewp->mRect.translate(-translate_x, -translate_y); + } + } +} + +BOOL LLView::hasAncestor(LLView* parentp) +{ + if (!parentp) + { + return FALSE; + } + + LLView* viewp = getParent(); + while(viewp) + { + if (viewp == parentp) + { + return TRUE; + } + viewp = viewp->getParent(); + } + + return FALSE; +} + +//----------------------------------------------------------------------------- + +BOOL LLView::childHasKeyboardFocus( const LLString& childname ) const +{ + LLView *child = getChildByName(childname); + if (child) + { + return gFocusMgr.childHasKeyboardFocus(child); + } + else + { + return FALSE; + } +} + +//----------------------------------------------------------------------------- + +BOOL LLView::hasChild(const LLString& childname, BOOL recurse) const +{ + return getChildByName(childname, recurse) ? TRUE : FALSE; +} + +//----------------------------------------------------------------------------- +// getChildByName() +//----------------------------------------------------------------------------- +LLView* LLView::getChildByName(const LLString& name, BOOL recurse) const +{ + if(name.empty()) return NULL; + child_list_const_iter_t child_it; + // Look for direct children *first* + for ( child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* childp = *child_it; + if (childp->getName() == name) + { + return childp; + } + } + if (recurse) + { + // Look inside the child as well. + for ( child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* childp = *child_it; + LLView* viewp = childp->getChildByName(name, recurse); + if ( viewp ) + { + return viewp; + } + } + } + return NULL; +} + +// virtual +void LLView::onFocusLost() +{ +} + +// virtual +void LLView::onFocusReceived() +{ +} + +// virtual +void LLView::screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const +{ + *local_x = screen_x - mRect.mLeft; + *local_y = screen_y - mRect.mBottom; + + const LLView* cur = this; + while( cur->mParentView ) + { + cur = cur->mParentView; + *local_x -= cur->mRect.mLeft; + *local_y -= cur->mRect.mBottom; + } +} + +void LLView::localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const +{ + *screen_x = local_x + mRect.mLeft; + *screen_y = local_y + mRect.mBottom; + + const LLView* cur = this; + while( cur->mParentView ) + { + cur = cur->mParentView; + *screen_x += cur->mRect.mLeft; + *screen_y += cur->mRect.mBottom; + } +} + +void LLView::screenRectToLocal(const LLRect& screen, LLRect* local) const +{ + *local = screen; + local->translate( -mRect.mLeft, -mRect.mBottom ); + + const LLView* cur = this; + while( cur->mParentView ) + { + cur = cur->mParentView; + local->translate( -cur->mRect.mLeft, -cur->mRect.mBottom ); + } +} + +void LLView::localRectToScreen(const LLRect& local, LLRect* screen) const +{ + *screen = local; + screen->translate( mRect.mLeft, mRect.mBottom ); + + const LLView* cur = this; + while( cur->mParentView ) + { + cur = cur->mParentView; + screen->translate( cur->mRect.mLeft, cur->mRect.mBottom ); + } +} + +LLView* LLView::getRootMostFastFrameView() +{ + if (gFocusMgr.getTopView() == this) + { + return this; + } + + if (getParent()) + { + LLView* rootmost_view = getParent()->getRootMostFastFrameView(); + if (rootmost_view) + { + return rootmost_view; + } + } + + return mRenderInFastFrame ? this : NULL; +} + + +LLView* LLView::getRootView() +{ + LLView* view = this; + while( view->mParentView ) + { + view = view->mParentView; + } + return view; +} + +//static +LLWindow* LLView::getWindow(void) +{ + return LLUI::sWindow; +} + +// Moves the view so that it is entirely inside of constraint. +// If the view will not fit because it's too big, aligns with the top and left. +// (Why top and left? That's where the drag bars are for floaters.) +BOOL LLView::translateIntoRect(const LLRect& constraint, BOOL allow_partial_outside ) +{ + S32 delta_x = 0; + S32 delta_y = 0; + + if (allow_partial_outside) + { + const S32 KEEP_ONSCREEN_PIXELS = 16; + + if( mRect.mRight - KEEP_ONSCREEN_PIXELS < constraint.mLeft ) + { + delta_x = constraint.mLeft - (mRect.mRight - KEEP_ONSCREEN_PIXELS); + } + else + if( mRect.mLeft + KEEP_ONSCREEN_PIXELS > constraint.mRight ) + { + delta_x = constraint.mRight - (mRect.mLeft + KEEP_ONSCREEN_PIXELS); + delta_x += llmax( 0, mRect.getWidth() - constraint.getWidth() ); + } + + if( mRect.mTop > constraint.mTop ) + { + delta_y = constraint.mTop - mRect.mTop; + } + else + if( mRect.mTop - KEEP_ONSCREEN_PIXELS < constraint.mBottom ) + { + delta_y = constraint.mBottom - (mRect.mTop - KEEP_ONSCREEN_PIXELS); + delta_y -= llmax( 0, mRect.getHeight() - constraint.getHeight() ); + } + } + else + { + if( mRect.mLeft < constraint.mLeft ) + { + delta_x = constraint.mLeft - mRect.mLeft; + } + else + if( mRect.mRight > constraint.mRight ) + { + delta_x = constraint.mRight - mRect.mRight; + delta_x += llmax( 0, mRect.getWidth() - constraint.getWidth() ); + } + + if( mRect.mTop > constraint.mTop ) + { + delta_y = constraint.mTop - mRect.mTop; + } + else + if( mRect.mBottom < constraint.mBottom ) + { + delta_y = constraint.mBottom - mRect.mBottom; + delta_y -= llmax( 0, mRect.getHeight() - constraint.getHeight() ); + } + } + + if (delta_x != 0 || delta_y != 0) + { + translate(delta_x, delta_y); + return TRUE; + } + return FALSE; +} + +BOOL LLView::localPointToOtherView( S32 x, S32 y, S32 *other_x, S32 *other_y, LLView* other_view) +{ + LLView* cur_view = this; + LLView* root_view = NULL; + + while (cur_view) + { + if (cur_view == other_view) + { + *other_x = x; + *other_y = y; + return TRUE; + } + + x += cur_view->getRect().mLeft; + y += cur_view->getRect().mBottom; + + cur_view = cur_view->getParent(); + root_view = cur_view; + } + + // assuming common root between two views, chase other_view's parents up to root + cur_view = other_view; + while (cur_view) + { + x -= cur_view->getRect().mLeft; + y -= cur_view->getRect().mBottom; + + cur_view = cur_view->getParent(); + + if (cur_view == root_view) + { + *other_x = x; + *other_y = y; + return TRUE; + } + } + + *other_x = x; + *other_y = y; + return FALSE; +} + +BOOL LLView::localRectToOtherView( const LLRect& local, LLRect* other, LLView* other_view ) const +{ + LLRect cur_rect = local; + const LLView* cur_view = this; + const LLView* root_view = NULL; + + while (cur_view) + { + if (cur_view == other_view) + { + *other = cur_rect; + return TRUE; + } + + cur_rect.translate(cur_view->getRect().mLeft, cur_view->getRect().mBottom); + + cur_view = cur_view->getParent(); + root_view = cur_view; + } + + // assuming common root between two views, chase other_view's parents up to root + cur_view = other_view; + while (cur_view) + { + cur_rect.translate(-cur_view->getRect().mLeft, -cur_view->getRect().mBottom); + + cur_view = cur_view->getParent(); + + if (cur_view == root_view) + { + *other = cur_rect; + return TRUE; + } + } + + *other = cur_rect; + return FALSE; +} + +// virtual +LLXMLNodePtr LLView::getXML(bool save_children) const +{ + const LLString& type_name = getWidgetTag(); + + LLXMLNodePtr node = new LLXMLNode(type_name, FALSE); + + node->createChild("name", TRUE)->setStringValue(getName()); + node->createChild("width", TRUE)->setIntValue(getRect().getWidth()); + node->createChild("height", TRUE)->setIntValue(getRect().getHeight()); + + LLView* parent = getParent(); + S32 left = mRect.mLeft; + S32 bottom = mRect.mBottom; + if (parent) bottom -= parent->getRect().getHeight(); + + node->createChild("left", TRUE)->setIntValue(left); + node->createChild("bottom", TRUE)->setIntValue(bottom); + + U32 follows_flags = getFollows(); + if (follows_flags) + { + std::stringstream buffer; + bool pipe = false; + if (followsLeft()) + { + buffer << "left"; + pipe = true; + } + if (followsTop()) + { + if (pipe) buffer << "|"; + buffer << "top"; + pipe = true; + } + if (followsRight()) + { + if (pipe) buffer << "|"; + buffer << "right"; + pipe = true; + } + if (followsBottom()) + { + if (pipe) buffer << "|"; + buffer << "bottom"; + } + node->createChild("follows", TRUE)->setStringValue(buffer.str()); + } + // Export all widgets as enabled and visible - code must disable. + node->createChild("hidden", TRUE)->setBoolValue(mHidden); + node->createChild("mouse_opaque", TRUE)->setBoolValue(mMouseOpaque ); + if (!mToolTipMsg.empty()) + { + node->createChild("tool_tip", TRUE)->setStringValue(mToolTipMsg); + } + if (mSoundFlags != MOUSE_UP) + { + node->createChild("sound_flags", TRUE)->setIntValue((S32)mSoundFlags); + } + + node->createChild("enabled", TRUE)->setBoolValue(mEnabled); + + if (!mControlName.empty()) + { + node->createChild("control_name", TRUE)->setStringValue(mControlName); + } + return node; +} + +// static +void LLView::addColorXML(LLXMLNodePtr node, const LLColor4& color, + const LLString& xml_name, const LLString& control_name) +{ + if (color != LLUI::sColorsGroup->getColor(control_name)) + { + node->createChild(xml_name, TRUE)->setFloatValue(4, color.mV); + } +} + +// static +void LLView::saveColorToXML(std::ostream& out, const LLColor4& color, + const LLString& xml_name, const LLString& control_name, + const LLString& indent) +{ + if (color != LLUI::sColorsGroup->getColor(control_name)) + { + out << indent << xml_name << "=\"" + << color.mV[VRED] << ", " + << color.mV[VGREEN] << ", " + << color.mV[VBLUE] << ", " + << color.mV[VALPHA] << "\"\n"; + } +} + +//static +LLString LLView::escapeXML(const LLString& xml, LLString& indent) +{ + LLString ret = indent + "\"" + LLXMLNode::escapeXML(xml); + + //replace every newline with a close quote, new line, indent, open quote + size_t index = ret.size()-1; + size_t fnd; + + while ((fnd = ret.rfind("\n", index)) != std::string::npos) + { + ret.replace(fnd, 1, "\"\n" + indent + "\""); + index = fnd-1; + } + + //append close quote + ret.append("\""); + + return ret; +} + +// static +LLWString LLView::escapeXML(const LLWString& xml) +{ + LLWString out; + for (LLWString::size_type i = 0; i < xml.size(); ++i) + { + llwchar c = xml[i]; + switch(c) + { + case '"': out.append(utf8string_to_wstring(""")); break; + case '\'': out.append(utf8string_to_wstring("'")); break; + case '&': out.append(utf8string_to_wstring("&")); break; + case '<': out.append(utf8string_to_wstring("<")); break; + case '>': out.append(utf8string_to_wstring(">")); break; + default: out.push_back(c); break; + } + } + return out; +} + +// static +const LLCtrlQuery & LLView::getTabOrderQuery() +{ + static LLCtrlQuery query; + if(query.getPreFilters().size() == 0) { + query.addPreFilter(LLVisibleFilter::getInstance()); + query.addPreFilter(LLEnabledFilter::getInstance()); + query.addPreFilter(LLTabStopFilter::getInstance()); + query.addPostFilter(LLUICtrl::LLTabStopPostFilter::getInstance()); + } + return query; +} + +// static +const LLCtrlQuery & LLView::getFocusRootsQuery() +{ + static LLCtrlQuery query; + if(query.getPreFilters().size() == 0) { + query.addPreFilter(LLVisibleFilter::getInstance()); + query.addPreFilter(LLEnabledFilter::getInstance()); + query.addPreFilter(LLView::LLFocusRootsFilter::getInstance()); + } + return query; +} + + +LLView* LLView::findSnapRect(LLRect& new_rect, const LLCoordGL& mouse_dir, + LLView::ESnapType snap_type, S32 threshold, S32 padding) +{ + LLView* snap_view = NULL; + + if (!mParentView) + { + new_rect = mRect; + return snap_view; + } + + // If the view is near the edge of its parent, snap it to + // the edge. + LLRect test_rect = getSnapRect(); + LLRect view_rect = getSnapRect(); + test_rect.stretch(padding); + view_rect.stretch(padding); + + BOOL snapped_x = FALSE; + BOOL snapped_y = FALSE; + + LLRect parent_local_snap_rect = mParentView->getSnapRect(); + parent_local_snap_rect.translate(-mParentView->getRect().mLeft, -mParentView->getRect().mBottom); + + if (snap_type == SNAP_PARENT || snap_type == SNAP_PARENT_AND_SIBLINGS) + { + if (llabs(parent_local_snap_rect.mRight - test_rect.mRight) <= threshold && (parent_local_snap_rect.mRight - test_rect.mRight) * mouse_dir.mX >= 0) + { + view_rect.translate(parent_local_snap_rect.mRight - view_rect.mRight, 0); + snap_view = mParentView; + snapped_x = TRUE; + } + + if (llabs(test_rect.mLeft - parent_local_snap_rect.mLeft) <= threshold && test_rect.mLeft * mouse_dir.mX <= 0) + { + view_rect.translate(parent_local_snap_rect.mLeft - view_rect.mLeft, 0); + snap_view = mParentView; + snapped_x = TRUE; + } + + if (llabs(test_rect.mBottom - parent_local_snap_rect.mBottom) <= threshold && test_rect.mBottom * mouse_dir.mY <= 0) + { + view_rect.translate(0, parent_local_snap_rect.mBottom - view_rect.mBottom); + snap_view = mParentView; + snapped_y = TRUE; + } + + if (llabs(parent_local_snap_rect.mTop - test_rect.mTop) <= threshold && (parent_local_snap_rect.mTop - test_rect.mTop) * mouse_dir.mY >= 0) + { + view_rect.translate(0, parent_local_snap_rect.mTop - view_rect.mTop); + snap_view = mParentView; + snapped_y = TRUE; + } + } + if (snap_type == SNAP_SIBLINGS || snap_type == SNAP_PARENT_AND_SIBLINGS) + { + for ( child_list_const_iter_t child_it = mParentView->getChildList()->begin(); + child_it != mParentView->getChildList()->end(); ++child_it) + { + LLView* siblingp = *child_it; + // skip self + if (siblingp == this || !siblingp->getVisible() || !canSnapTo(siblingp)) + { + continue; + } + + LLRect sibling_rect = siblingp->getSnapRect(); + + if (!snapped_x && llabs(test_rect.mRight - sibling_rect.mLeft) <= threshold && (test_rect.mRight - sibling_rect.mLeft) * mouse_dir.mX <= 0) + { + view_rect.translate(sibling_rect.mLeft - view_rect.mRight, 0); + if (!snapped_y) + { + if (llabs(test_rect.mTop - sibling_rect.mTop) <= threshold && (test_rect.mTop - sibling_rect.mTop) * mouse_dir.mY <= 0) + { + view_rect.translate(0, sibling_rect.mTop - test_rect.mTop); + snapped_y = TRUE; + } + else if (llabs(test_rect.mBottom - sibling_rect.mBottom) <= threshold && (test_rect.mBottom - sibling_rect.mBottom) * mouse_dir.mY <= 0) + { + view_rect.translate(0, sibling_rect.mBottom - test_rect.mBottom); + snapped_y = TRUE; + } + } + snap_view = siblingp; + snapped_x = TRUE; + } + + if (!snapped_x && llabs(test_rect.mLeft - sibling_rect.mRight) <= threshold && (test_rect.mLeft - sibling_rect.mRight) * mouse_dir.mX <= 0) + { + view_rect.translate(sibling_rect.mRight - view_rect.mLeft, 0); + if (!snapped_y) + { + if (llabs(test_rect.mTop - sibling_rect.mTop) <= threshold && (test_rect.mTop - sibling_rect.mTop) * mouse_dir.mY <= 0) + { + view_rect.translate(0, sibling_rect.mTop - test_rect.mTop); + snapped_y = TRUE; + } + else if (llabs(test_rect.mBottom - sibling_rect.mBottom) <= threshold && (test_rect.mBottom - sibling_rect.mBottom) * mouse_dir.mY <= 0) + { + view_rect.translate(0, sibling_rect.mBottom - test_rect.mBottom); + snapped_y = TRUE; + } + } + snap_view = siblingp; + snapped_x = TRUE; + } + + if (!snapped_y && llabs(test_rect.mBottom - sibling_rect.mTop) <= threshold && (test_rect.mBottom - sibling_rect.mTop) * mouse_dir.mY <= 0) + { + view_rect.translate(0, sibling_rect.mTop - view_rect.mBottom); + if (!snapped_x) + { + if (llabs(test_rect.mLeft - sibling_rect.mLeft) <= threshold && (test_rect.mLeft - sibling_rect.mLeft) * mouse_dir.mX <= 0) + { + view_rect.translate(sibling_rect.mLeft - test_rect.mLeft, 0); + snapped_x = TRUE; + } + else if (llabs(test_rect.mRight - sibling_rect.mRight) <= threshold && (test_rect.mRight - sibling_rect.mRight) * mouse_dir.mX <= 0) + { + view_rect.translate(sibling_rect.mRight - test_rect.mRight, 0); + snapped_x = TRUE; + } + } + snap_view = siblingp; + snapped_y = TRUE; + } + + if (!snapped_y && llabs(test_rect.mTop - sibling_rect.mBottom) <= threshold && (test_rect.mTop - sibling_rect.mBottom) * mouse_dir.mY <= 0) + { + view_rect.translate(0, sibling_rect.mBottom - view_rect.mTop); + if (!snapped_x) + { + if (llabs(test_rect.mLeft - sibling_rect.mLeft) <= threshold && (test_rect.mLeft - sibling_rect.mLeft) * mouse_dir.mX <= 0) + { + view_rect.translate(sibling_rect.mLeft - test_rect.mLeft, 0); + snapped_x = TRUE; + } + else if (llabs(test_rect.mRight - sibling_rect.mRight) <= threshold && (test_rect.mRight - sibling_rect.mRight) * mouse_dir.mX <= 0) + { + view_rect.translate(sibling_rect.mRight - test_rect.mRight, 0); + snapped_x = TRUE; + } + } + snap_view = siblingp; + snapped_y = TRUE; + } + + if (snapped_x && snapped_y) + { + break; + } + } + } + + // shrink actual view rect back down + view_rect.stretch(-padding); + new_rect = view_rect; + return snap_view; +} + +LLView* LLView::findSnapEdge(S32& new_edge_val, const LLCoordGL& mouse_dir, ESnapEdge snap_edge, ESnapType snap_type, S32 threshold, S32 padding) +{ + LLRect snap_rect = getSnapRect(); + S32 snap_pos = 0; + switch(snap_edge) + { + case SNAP_LEFT: + snap_pos = snap_rect.mLeft; + break; + case SNAP_RIGHT: + snap_pos = snap_rect.mRight; + break; + case SNAP_TOP: + snap_pos = snap_rect.mTop; + break; + case SNAP_BOTTOM: + snap_pos = snap_rect.mBottom; + break; + } + + if (!mParentView) + { + new_edge_val = snap_pos; + return NULL; + } + + LLView* snap_view = NULL; + + // If the view is near the edge of its parent, snap it to + // the edge. + LLRect test_rect = snap_rect; + test_rect.stretch(padding); + + BOOL snapped_x = FALSE; + BOOL snapped_y = FALSE; + + LLRect parent_local_snap_rect = mParentView->getSnapRect(); + parent_local_snap_rect.translate(-mParentView->getRect().mLeft, -mParentView->getRect().mBottom); + + if (snap_type == SNAP_PARENT || snap_type == SNAP_PARENT_AND_SIBLINGS) + { + switch(snap_edge) + { + case SNAP_RIGHT: + if (llabs(parent_local_snap_rect.mRight - test_rect.mRight) <= threshold && (parent_local_snap_rect.mRight - test_rect.mRight) * mouse_dir.mX >= 0) + { + snap_pos = parent_local_snap_rect.mRight - padding; + snap_view = mParentView; + snapped_x = TRUE; + } + break; + case SNAP_LEFT: + if (llabs(test_rect.mLeft - parent_local_snap_rect.mLeft) <= threshold && test_rect.mLeft * mouse_dir.mX <= 0) + { + snap_pos = parent_local_snap_rect.mLeft + padding; + snap_view = mParentView; + snapped_x = TRUE; + } + break; + case SNAP_BOTTOM: + if (llabs(test_rect.mBottom - parent_local_snap_rect.mBottom) <= threshold && test_rect.mBottom * mouse_dir.mY <= 0) + { + snap_pos = parent_local_snap_rect.mBottom + padding; + snap_view = mParentView; + snapped_y = TRUE; + } + break; + case SNAP_TOP: + if (llabs(parent_local_snap_rect.mTop - test_rect.mTop) <= threshold && (parent_local_snap_rect.mTop - test_rect.mTop) * mouse_dir.mY >= 0) + { + snap_pos = parent_local_snap_rect.mTop - padding; + snap_view = mParentView; + snapped_y = TRUE; + } + break; + default: + llerrs << "Invalid snap edge" << llendl; + } + } + + if (snap_type == SNAP_SIBLINGS || snap_type == SNAP_PARENT_AND_SIBLINGS) + { + for ( child_list_const_iter_t child_it = mParentView->getChildList()->begin(); + child_it != mParentView->getChildList()->end(); ++child_it) + { + LLView* siblingp = *child_it; + // skip self + if (siblingp == this || !siblingp->getVisible() || !canSnapTo(siblingp)) + { + continue; + } + + LLRect sibling_rect = siblingp->getSnapRect(); + + switch(snap_edge) + { + case SNAP_RIGHT: + if (!snapped_x) + { + if (llabs(test_rect.mRight - sibling_rect.mLeft) <= threshold && (test_rect.mRight - sibling_rect.mLeft) * mouse_dir.mX <= 0) + { + snap_pos = sibling_rect.mLeft - padding; + snap_view = siblingp; + snapped_x = TRUE; + } + // if snapped with sibling along other axis, check for shared edge + else if (llabs(sibling_rect.mTop - (test_rect.mBottom - padding)) <= threshold || + llabs(sibling_rect.mBottom - (test_rect.mTop + padding)) <= threshold) + { + if (llabs(test_rect.mRight - sibling_rect.mRight) <= threshold && (test_rect.mRight - sibling_rect.mRight) * mouse_dir.mX <= 0) + { + snap_pos = sibling_rect.mRight; + snap_view = siblingp; + snapped_x = TRUE; + } + } + } + break; + case SNAP_LEFT: + if (!snapped_x) + { + if (llabs(test_rect.mLeft - sibling_rect.mRight) <= threshold && (test_rect.mLeft - sibling_rect.mRight) * mouse_dir.mX <= 0) + { + snap_pos = sibling_rect.mRight + padding; + snap_view = siblingp; + snapped_x = TRUE; + } + // if snapped with sibling along other axis, check for shared edge + else if (llabs(sibling_rect.mTop - (test_rect.mBottom - padding)) <= threshold || + llabs(sibling_rect.mBottom - (test_rect.mTop + padding)) <= threshold) + { + if (llabs(test_rect.mLeft - sibling_rect.mLeft) <= threshold && (test_rect.mLeft - sibling_rect.mLeft) * mouse_dir.mX <= 0) + { + snap_pos = sibling_rect.mLeft; + snap_view = siblingp; + snapped_x = TRUE; + } + } + } + break; + case SNAP_BOTTOM: + if (!snapped_y) + { + if (llabs(test_rect.mBottom - sibling_rect.mTop) <= threshold && (test_rect.mBottom - sibling_rect.mTop) * mouse_dir.mY <= 0) + { + snap_pos = sibling_rect.mTop + padding; + snap_view = siblingp; + snapped_y = TRUE; + } + // if snapped with sibling along other axis, check for shared edge + else if (llabs(sibling_rect.mRight - (test_rect.mLeft - padding)) <= threshold || + llabs(sibling_rect.mLeft - (test_rect.mRight + padding)) <= threshold) + { + if (llabs(test_rect.mBottom - sibling_rect.mBottom) <= threshold && (test_rect.mBottom - sibling_rect.mBottom) * mouse_dir.mY <= 0) + { + snap_pos = sibling_rect.mBottom; + snap_view = siblingp; + snapped_y = TRUE; + } + } + } + break; + case SNAP_TOP: + if (!snapped_y) + { + if (llabs(test_rect.mTop - sibling_rect.mBottom) <= threshold && (test_rect.mTop - sibling_rect.mBottom) * mouse_dir.mY <= 0) + { + snap_pos = sibling_rect.mBottom - padding; + snap_view = siblingp; + snapped_y = TRUE; + } + // if snapped with sibling along other axis, check for shared edge + else if (llabs(sibling_rect.mRight - (test_rect.mLeft - padding)) <= threshold || + llabs(sibling_rect.mLeft - (test_rect.mRight + padding)) <= threshold) + { + if (llabs(test_rect.mTop - sibling_rect.mTop) <= threshold && (test_rect.mTop - sibling_rect.mTop) * mouse_dir.mY <= 0) + { + snap_pos = sibling_rect.mTop; + snap_view = siblingp; + snapped_y = TRUE; + } + } + } + break; + default: + llerrs << "Invalid snap edge" << llendl; + } + if (snapped_x && snapped_y) + { + break; + } + } + } + + new_edge_val = snap_pos; + return snap_view; +} + +bool operator==(const LLViewHandle& lhs, const LLViewHandle& rhs) +{ + return lhs.mID == rhs.mID; +} + +bool operator!=(const LLViewHandle& lhs, const LLViewHandle& rhs) +{ + return lhs.mID != rhs.mID; +} + +bool operator<(const LLViewHandle &lhs, const LLViewHandle &rhs) +{ + return lhs.mID < rhs.mID; +} + +//----------------------------------------------------------------------------- +// Listener dispatch functions +//----------------------------------------------------------------------------- + +void LLView::registerEventListener(LLString name, LLSimpleListener* function) +{ + mDispatchList.insert(std::pair<LLString, LLSimpleListener*>(name, function)); +} + +void LLView::deregisterEventListener(LLString name) +{ + dispatch_list_t::iterator itor = mDispatchList.find(name); + if (itor != mDispatchList.end()) + { + delete itor->second; + mDispatchList.erase(itor); + } +} + +LLString LLView::findEventListener(LLSimpleListener *listener) const +{ + dispatch_list_t::const_iterator itor; + for (itor = mDispatchList.begin(); itor != mDispatchList.end(); ++itor) + { + if (itor->second == listener) + { + return itor->first; + } + } + if (mParentView) + { + return mParentView->findEventListener(listener); + } + return LLString::null; +} + +LLSimpleListener* LLView::getListenerByName(const LLString& callback_name) +{ + LLSimpleListener* callback = NULL; + dispatch_list_t::iterator itor = mDispatchList.find(callback_name); + if (itor != mDispatchList.end()) + { + callback = itor->second; + } + else if (mParentView) + { + callback = mParentView->getListenerByName(callback_name); + } + return callback; +} + +void LLView::addListenerToControl(LLEventDispatcher *dispatcher, const LLString& name, LLSD filter, LLSD userdata) +{ + LLSimpleListener* listener = getListenerByName(name); + if (listener) + { + dispatcher->addListener(listener, filter, userdata); + } +} + +LLControlBase *LLView::findControl(LLString name) +{ + control_map_t::iterator itor = mFloaterControls.find(name); + if (itor != mFloaterControls.end()) + { + return itor->second; + } + if (mParentView) + { + return mParentView->findControl(name); + } + return LLUI::sConfigGroup->getControl(name); +} + +const S32 FLOATER_H_MARGIN = 15; +const S32 MIN_WIDGET_HEIGHT = 10; +const S32 VPAD = 4; + +// static +U32 LLView::createRect(LLXMLNodePtr node, LLRect &rect, LLView* parent_view, const LLRect &required_rect) +{ + U32 follows = 0; + S32 x = FLOATER_H_MARGIN; + S32 y = 0; + S32 w = 0; + S32 h = 0; + + U32 last_x = 0; + U32 last_y = 0; + if (parent_view) + { + last_y = parent_view->getRect().getHeight(); + child_list_t::const_iterator itor = parent_view->getChildList()->begin(); + if (itor != parent_view->getChildList()->end()) + { + LLView *last_view = (*itor); + if (last_view->getSaveToXML()) + { + last_x = last_view->getRect().mLeft; + last_y = last_view->getRect().mBottom; + } + } + } + + LLString rect_control; + node->getAttributeString("rect_control", rect_control); + if (! rect_control.empty()) + { + LLRect rect = LLUI::sConfigGroup->getRect(rect_control); + x = rect.mLeft; + y = rect.mBottom; + w = rect.getWidth(); + h = rect.getHeight(); + } + + if (node->hasAttribute("left")) + { + node->getAttributeS32("left", x); + } + if (node->hasAttribute("bottom")) + { + node->getAttributeS32("bottom", y); + } + + // Make your width the width of the containing + // view if you don't specify a width. + if (parent_view) + { + w = llmax(required_rect.getWidth(), parent_view->getRect().getWidth() - (FLOATER_H_MARGIN) - x); + h = llmax(MIN_WIDGET_HEIGHT, required_rect.getHeight()); + } + + if (node->hasAttribute("width")) + { + node->getAttributeS32("width", w); + } + if (node->hasAttribute("height")) + { + node->getAttributeS32("height", h); + } + + if (parent_view) + { + if (node->hasAttribute("left_delta")) + { + S32 left_delta = 0; + node->getAttributeS32("left_delta", left_delta); + x = last_x + left_delta; + } + else if (node->hasAttribute("left") && node->hasAttribute("right")) + { + // compute width based on left and right + S32 right = 0; + node->getAttributeS32("right", right); + if (right < 0) + { + right = parent_view->getRect().getWidth() + right; + } + w = right - x; + } + else if (node->hasAttribute("left")) + { + if (x < 0) + { + x = parent_view->getRect().getWidth() + x; + follows |= FOLLOWS_RIGHT; + } + else + { + follows |= FOLLOWS_LEFT; + } + } + else if (node->hasAttribute("width") && node->hasAttribute("right")) + { + S32 right = 0; + node->getAttributeS32("right", right); + if (right < 0) + { + right = parent_view->getRect().getWidth() + right; + } + x = right - w; + } + else + { + // left not specified, same as last + x = last_x; + } + + if (node->hasAttribute("bottom_delta")) + { + S32 bottom_delta = 0; + node->getAttributeS32("bottom_delta", bottom_delta); + y = last_y + bottom_delta; + } + else if (node->hasAttribute("top")) + { + // compute height based on top + S32 top = 0; + node->getAttributeS32("top", top); + if (top < 0) + { + top = parent_view->getRect().getHeight() + top; + } + h = top - y; + } + else if (node->hasAttribute("bottom")) + { + if (y < 0) + { + y = parent_view->getRect().getHeight() + y; + follows |= FOLLOWS_TOP; + } + else + { + follows |= FOLLOWS_BOTTOM; + } + } + else + { + // if bottom not specified, generate automatically + if (last_y == 0) + { + // treat first child as "bottom" + y = parent_view->getRect().getHeight() - (h + VPAD); + follows |= FOLLOWS_TOP; + } + else + { + // treat subsequent children as "bottom_delta" + y = last_y - (h + VPAD); + } + } + } + else + { + x = llmax(x, 0); + y = llmax(y, 0); + follows = FOLLOWS_LEFT | FOLLOWS_TOP; + } + rect.setOriginAndSize(x, y, w, h); + + return follows; +} + +void LLView::initFromXML(LLXMLNodePtr node, LLView* parent) +{ + // create rect first, as this will supply initial follows flags + LLRect view_rect; + U32 follows_flags = createRect(node, view_rect, parent, getRequiredRect()); + // call reshape in case there are any child elements that need to be layed out + reshape(view_rect.getWidth(), view_rect.getHeight()); + setRect(view_rect); + setFollows(follows_flags); + + if (node->hasAttribute("follows")) + { + setFollowsNone(); + + LLString follows; + node->getAttributeString("follows", follows); + + typedef boost::tokenizer<boost::char_separator<char> > tokenizer; + boost::char_separator<char> sep("|"); + tokenizer tokens(follows, sep); + tokenizer::iterator token_iter = tokens.begin(); + + while(token_iter != tokens.end()) + { + const std::string& token_str = *token_iter; + if (token_str == "left") + { + setFollowsLeft(); + } + else if (token_str == "right") + { + setFollowsRight(); + } + else if (token_str == "top") + { + setFollowsTop(); + } + else if (token_str == "bottom") + { + setFollowsBottom(); + } + else if (token_str == "all") + { + setFollowsAll(); + } + ++token_iter; + } + } + + if (node->hasAttribute("control_name")) + { + LLString control_name; + node->getAttributeString("control_name", control_name); + setControlName(control_name, NULL); + } + + if (node->hasAttribute("tool_tip")) + { + LLString tool_tip_msg(""); + node->getAttributeString("tool_tip", tool_tip_msg); + setToolTip(tool_tip_msg); + } + + if (node->hasAttribute("enabled")) + { + BOOL enabled; + node->getAttributeBOOL("enabled", enabled); + setEnabled(enabled); + } + + if (node->hasAttribute("visible")) + { + BOOL visible; + node->getAttributeBOOL("visible", visible); + setVisible(visible); + } + + if (node->hasAttribute("hidden")) + { + BOOL hidden; + node->getAttributeBOOL("hidden", hidden); + setHidden(hidden); + } + + node->getAttributeS32("default_tab_group", mDefaultTabGroup); + + reshape(view_rect.getWidth(), view_rect.getHeight()); +} + +// static +LLFontGL* LLView::selectFont(LLXMLNodePtr node) +{ + LLFontGL* gl_font = NULL; + + if (node->hasAttribute("font")) + { + LLString font_name; + node->getAttributeString("font", font_name); + + gl_font = LLFontGL::fontFromName(font_name.c_str()); + } + return gl_font; +} + +// static +LLFontGL::HAlign LLView::selectFontHAlign(LLXMLNodePtr node) +{ + LLFontGL::HAlign gl_hfont_align = LLFontGL::LEFT; + + if (node->hasAttribute("halign")) + { + LLString horizontal_align_name; + node->getAttributeString("halign", horizontal_align_name); + gl_hfont_align = LLFontGL::hAlignFromName(horizontal_align_name); + } + return gl_hfont_align; +} + +// static +LLFontGL::VAlign LLView::selectFontVAlign(LLXMLNodePtr node) +{ + LLFontGL::VAlign gl_vfont_align = LLFontGL::BASELINE; + + if (node->hasAttribute("valign")) + { + LLString vert_align_name; + node->getAttributeString("valign", vert_align_name); + gl_vfont_align = LLFontGL::vAlignFromName(vert_align_name); + } + return gl_vfont_align; +} + +// static +LLFontGL::StyleFlags LLView::selectFontStyle(LLXMLNodePtr node) +{ + LLFontGL::StyleFlags gl_font_style = LLFontGL::NORMAL; + + if (node->hasAttribute("style")) + { + LLString style_flags_name; + node->getAttributeString("style", style_flags_name); + + if (style_flags_name == "normal") + { + gl_font_style = LLFontGL::NORMAL; + } + else if (style_flags_name == "bold") + { + gl_font_style = LLFontGL::BOLD; + } + else if (style_flags_name == "italic") + { + gl_font_style = LLFontGL::ITALIC; + } + else if (style_flags_name == "underline") + { + gl_font_style = LLFontGL::UNDERLINE; + } + //else leave left + } + return gl_font_style; +} + +void LLView::setControlValue(const LLSD& value) +{ + LLUI::sConfigGroup->setValue(getControlName(), value); +} + +//virtual +LLString LLView::getControlName() const +{ + return mControlName; +} + +//virtual +void LLView::setControlName(const LLString& control_name, LLView *context) +{ + if (context == NULL) + { + context = this; + } + + // Unregister from existing listeners + if (!mControlName.empty()) + { + clearDispatchers(); + } + + // Register new listener + if (!control_name.empty()) + { + LLControlBase *control = context->findControl(control_name); + if (control) + { + mControlName = control_name; + LLSD state = control->registerListener(this, "DEFAULT"); + setValue(state); + } + } +} + +// virtual +bool LLView::handleEvent(LLPointer<LLEvent> event, const LLSD& userdata) +{ + if (userdata.asString() == "DEFAULT" && event->desc() == "value_changed") + { + LLSD state = event->getValue(); + setValue(state); + return TRUE; + } + return FALSE; +} + +void LLView::setValue(const LLSD& value) +{ +} + + +void LLView::addBoolControl(LLString name, bool initial_value) +{ + mFloaterControls[name] = new LLControl(name, TYPE_BOOLEAN, initial_value, "Internal floater control"); +} + +LLControlBase *LLView::getControl(LLString name) +{ + control_map_t::iterator itor = mFloaterControls.find(name); + if (itor != mFloaterControls.end()) + { + return itor->second; + } + return NULL; +} diff --git a/indra/llui/llview.h b/indra/llui/llview.h new file mode 100644 index 0000000000..63d85fbcdc --- /dev/null +++ b/indra/llui/llview.h @@ -0,0 +1,489 @@ +/** + * @file llview.h + * @brief Container for other views, anything that draws. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLVIEW_H +#define LL_LLVIEW_H + +// A view is an area in a window that can draw. It might represent +// the HUD or a dialog box or a button. It can also contain sub-views +// and child widgets + +#include <iosfwd> +#include <list> + +#include "lluixmltags.h" +#include "llrect.h" +#include "llmousehandler.h" +#include "stdenums.h" +#include "llsd.h" +#include "llstring.h" +#include "llnametable.h" +#include "llcoord.h" +#include "llmortician.h" +#include "llxmlnode.h" +#include "llfontgl.h" +#include "llviewquery.h" + +#include "llui.h" + +class LLColor4; +class LLWindow; +class LLUICtrl; +class LLScrollListItem; + +const U32 FOLLOWS_NONE = 0x00; +const U32 FOLLOWS_LEFT = 0x01; +const U32 FOLLOWS_RIGHT = 0x02; +const U32 FOLLOWS_TOP = 0x10; +const U32 FOLLOWS_BOTTOM = 0x20; +const U32 FOLLOWS_ALL = 0x33; + +const BOOL MOUSE_OPAQUE = TRUE; +const BOOL NOT_MOUSE_OPAQUE = FALSE; + +const U32 GL_NAME_UI_RESERVED = 2; + +class LLSimpleListener; +class LLEventDispatcher; + +class LLViewHandle +{ +public: + LLViewHandle() { mID = 0; } + + void init() { mID = ++sNextID; } + void markDead() { mID = 0; } + BOOL isDead() { return (mID == 0); } + friend bool operator==(const LLViewHandle& lhs, const LLViewHandle& rhs); + friend bool operator!=(const LLViewHandle& lhs, const LLViewHandle& rhs); + friend bool operator<(const LLViewHandle &a, const LLViewHandle &b); + +public: + static LLViewHandle sDeadHandle; + +protected: + S32 mID; + + static S32 sNextID; +}; + +class LLView : public LLMouseHandler, public LLMortician, public LLSimpleListenerObservable +{ + +public: +#if LL_DEBUG + static BOOL sIsDrawing; +#endif + enum ESoundFlags + { + SILENT = 0, + MOUSE_DOWN = 1, + MOUSE_UP = 2 + }; + + enum ESnapType + { + SNAP_PARENT, + SNAP_SIBLINGS, + SNAP_PARENT_AND_SIBLINGS + }; + + enum ESnapEdge + { + SNAP_LEFT, + SNAP_TOP, + SNAP_RIGHT, + SNAP_BOTTOM + }; + + typedef std::list<LLView*> child_list_t; + typedef child_list_t::iterator child_list_iter_t; + typedef child_list_t::const_iterator child_list_const_iter_t; + typedef child_list_t::reverse_iterator child_list_reverse_iter_t; + typedef child_list_t::const_reverse_iterator child_list_const_reverse_iter_t; + + typedef std::vector<LLUICtrl *> ctrl_list_t; + + typedef std::pair<S32, S32> tab_order_t; + typedef std::pair<LLUICtrl *, tab_order_t> tab_order_pair_t; + // this structure primarily sorts by the tab group, secondarily by the insertion ordinal (lastly by the value of the pointer) + typedef std::map<const LLUICtrl*, tab_order_t> child_tab_order_t; + typedef child_tab_order_t::iterator child_tab_order_iter_t; + typedef child_tab_order_t::const_iterator child_tab_order_const_iter_t; + typedef child_tab_order_t::reverse_iterator child_tab_order_reverse_iter_t; + typedef child_tab_order_t::const_reverse_iterator child_tab_order_const_reverse_iter_t; + +private: + LLView* mParentView; + child_list_t mChildList; + +protected: + LLString mName; + // location in pixels, relative to surrounding structure, bottom,left=0,0 + LLRect mRect; + + U32 mReshapeFlags; + + child_tab_order_t mCtrlOrder; + S32 mDefaultTabGroup; + + BOOL mEnabled; // Enabled means "accepts input that has an effect on the state of the application." + // A disabled view, for example, may still have a scrollbar that responds to mouse events. + BOOL mMouseOpaque; // Opaque views handle all mouse events that are over their rect. + LLString mToolTipMsg; // isNull() is true if none. + + U8 mSoundFlags; + BOOL mSaveToXML; + + BOOL mIsFocusRoot; + +public: + LLViewHandle mViewHandle; + BOOL mLastVisible; + BOOL mRenderInFastFrame; + BOOL mSpanChildren; + +private: + BOOL mVisible; + BOOL mHidden; // Never show (generally for replacement text only) + + S32 mNextInsertionOrdinal; + +protected: + static LLWindow* sWindow; // All root views must know about their window. + +public: + static BOOL sDebugRects; // Draw debug rects behind everything. + static BOOL sDebugKeys; + static S32 sDepth; + static LLView* sFastFrameView; + static BOOL sDebugMouseHandling; + static LLString sMouseHandlerMessage; + static S32 sSelectID; + static BOOL sEditingUI; + static LLView* sEditingUIView; + static S32 sLastLeftXML; + static S32 sLastBottomXML; + static std::map<LLViewHandle,LLView*> sViewHandleMap; + static BOOL sForceReshape; + +public: + static LLView* getViewByHandle(LLViewHandle handle); + static BOOL deleteViewByHandle(LLViewHandle handle); + +public: + LLView(); + LLView(const LLString& name, BOOL mouse_opaque); + LLView(const LLString& name, const LLRect& rect, BOOL mouse_opaque, U32 follows=FOLLOWS_NONE); + + virtual ~LLView(); + + // Hack to support LLFocusMgr + virtual BOOL isView(); + + // Some UI widgets need to be added as controls. Others need to + // be added as regular view children. isCtrl should return TRUE + // if a widget needs to be added as a ctrl + virtual BOOL isCtrl() const; + + virtual BOOL isPanel(); + + // + // MANIPULATORS + // + void setMouseOpaque( BOOL b ); + void setToolTip( const LLString& msg ); + + virtual void setRect(const LLRect &rect); + void setFollows(U32 flags); + + // deprecated, use setFollows() with FOLLOWS_LEFT | FOLLOWS_TOP, etc. + void setFollowsNone(); + void setFollowsLeft(); + void setFollowsTop(); + void setFollowsRight(); + void setFollowsBottom(); + void setFollowsAll(); + + void setSoundFlags(U8 flags); + void setName(LLString name); + void setSpanChildren( BOOL span_children ); + + const LLString& getToolTip(); + + void sendChildToFront(LLView* child); + void sendChildToBack(LLView* child); + void moveChildToFrontOfTabGroup(LLUICtrl* child); + + void addChild(LLView* view, S32 tab_group = 0); + void addChildAtEnd(LLView* view, S32 tab_group = 0); + // remove the specified child from the view, and set it's parent to NULL. + void removeChild( LLView* view ); + + virtual void addCtrl( LLUICtrl* ctrl, S32 tab_group); + virtual void addCtrlAtEnd( LLUICtrl* ctrl, S32 tab_group); + virtual void removeCtrl( LLUICtrl* ctrl); + + child_tab_order_t getCtrlOrder() const { return mCtrlOrder; } + ctrl_list_t getCtrlList() const; + ctrl_list_t getCtrlListSorted() const; + S32 getDefaultTabGroup() const; + + BOOL isInVisibleChain() const; + BOOL isInEnabledChain() const; + + BOOL isFocusRoot() const; + LLView* findRootMostFocusRoot(); + virtual BOOL canFocusChildren() const; + + class LLFocusRootsFilter : public LLQueryFilter, public LLSingleton<LLFocusRootsFilter> + { + /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const + { + return filterResult_t(view->isCtrl() && view->isFocusRoot(), !view->isFocusRoot()); + } + }; + + virtual BOOL focusNextRoot(); + virtual BOOL focusPrevRoot(); + + virtual BOOL focusNextItem(BOOL text_entry_only); + virtual BOOL focusPrevItem(BOOL text_entry_only); + virtual BOOL focusFirstItem(BOOL prefer_text_fields = FALSE ); + virtual BOOL focusLastItem(BOOL prefer_text_fields = FALSE); + + // delete all children. Override this function if you need to + // perform any extra clean up such as cached pointers to selected + // children, etc. + virtual void deleteAllChildren(); + + // by default, does nothing + virtual void setTentative(BOOL b); + // by default, returns false + virtual BOOL getTentative() const; + virtual void setAllChildrenEnabled(BOOL b); + + virtual void setEnabled(BOOL enabled); + virtual void setVisible(BOOL visible); + virtual void setHidden(BOOL hidden); // Never show (replacement text) + + // by default, does nothing and returns false + virtual BOOL setLabelArg( const LLString& key, const LLString& text ); + + virtual void onVisibilityChange ( BOOL curVisibilityIn ); + + void pushVisible(BOOL visible) { mLastVisible = mVisible; setVisible(visible); } + void popVisible() { setVisible(mLastVisible); mLastVisible = TRUE; } + + // + // ACCESSORS + // + BOOL getMouseOpaque() const { return mMouseOpaque; } + + U32 getFollows() const { return mReshapeFlags; } + BOOL followsLeft() const { return mReshapeFlags & FOLLOWS_LEFT; } + BOOL followsRight() const { return mReshapeFlags & FOLLOWS_RIGHT; } + BOOL followsTop() const { return mReshapeFlags & FOLLOWS_TOP; } + BOOL followsBottom() const { return mReshapeFlags & FOLLOWS_BOTTOM; } + BOOL followsAll() const { return mReshapeFlags & FOLLOWS_ALL; } + + const LLRect& getRect() const { return mRect; } + const LLRect getScreenRect() const; + const LLRect getLocalRect() const; + virtual const LLRect getSnapRect() const { return mRect; } + + virtual LLRect getRequiredRect(); // Get required size for this object. 0 for width/height means don't care. + virtual void updateRect(); // apply procedural updates to own rectangle + + LLView* getRootView(); + LLView* getParent() const { return mParentView; } + LLView* getFirstChild() { return (mChildList.empty()) ? NULL : *(mChildList.begin()); } + S32 getChildCount() const { return (S32)mChildList.size(); } + template<class _Pr3> void sortChildren(_Pr3 _Pred) { mChildList.sort(_Pred); } + BOOL hasAncestor(LLView* parentp); + + BOOL hasChild(const LLString& childname, BOOL recurse = FALSE) const; + + BOOL childHasKeyboardFocus( const LLString& childname ) const; + + // + // UTILITIES + // + + // Default behavior is to use reshape flags to resize child views + virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); + + virtual void translate( S32 x, S32 y ); + BOOL translateIntoRect( const LLRect& constraint, BOOL allow_partial_outside ); + void setOrigin( S32 x, S32 y ) { mRect.translate( x - mRect.mLeft, y - mRect.mBottom ); } + LLView* findSnapRect(LLRect& new_rect, const LLCoordGL& mouse_dir, LLView::ESnapType snap_type, S32 threshold, S32 padding = 0); + LLView* findSnapEdge(S32& new_edge_val, const LLCoordGL& mouse_dir, ESnapEdge snap_edge, ESnapType snap_type, S32 threshold, S32 padding = 0); + + // Defaults to other_view->getVisible() + virtual BOOL canSnapTo(LLView* other_view); + + virtual void snappedTo(LLView* snap_view); + + virtual BOOL handleKey(KEY key, MASK mask, BOOL called_from_parent); + virtual BOOL handleUnicodeChar(llwchar uni_char, BOOL called_from_parent); + virtual BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + LLString& tooltip_msg); + + // LLMouseHandler functions + // Default behavior is to pass events to children + + /*virtual*/ BOOL handleHover(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleMouseUp(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleDoubleClick(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleScrollWheel(S32 x, S32 y, S32 clicks); + /*virtual*/ BOOL handleRightMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleRightMouseUp(S32 x, S32 y, MASK mask); + + // Default behavior is to pass the tooltip event to children, + // then display mToolTipMsg if no child handled it. + /*virtual*/ BOOL handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect); + + virtual void draw(); + + void drawDebugRect(); + void drawChild(LLView* childp, S32 x_offset = 0, S32 y_offset = 0); + + virtual const LLString& getName() const; + + virtual EWidgetType getWidgetType() const = 0; + virtual LLString getWidgetTag() const = 0; + virtual LLXMLNodePtr getXML(bool save_children = true) const; + + static U32 createRect(LLXMLNodePtr node, LLRect &rect, LLView* parent_view, const LLRect &required_rect = LLRect()); + virtual void initFromXML(LLXMLNodePtr node, LLView* parent); + + static LLFontGL* selectFont(LLXMLNodePtr node); + static LLFontGL::HAlign selectFontHAlign(LLXMLNodePtr node); + static LLFontGL::VAlign selectFontVAlign(LLXMLNodePtr node); + static LLFontGL::StyleFlags selectFontStyle(LLXMLNodePtr node); + + // Some widgets, like close box buttons, don't need to be saved + BOOL getSaveToXML() const { return mSaveToXML; } + void setSaveToXML(BOOL b) { mSaveToXML = b; } + + // Only saves color if different from default setting. + static void addColorXML(LLXMLNodePtr node, const LLColor4& color, + const LLString& xml_name, const LLString& control_name); + static void saveColorToXML(std::ostream& out, const LLColor4& color, + const LLString& xml_name, const LLString& control_name, + const LLString& indent); // DEPRECATED + // Escapes " (quot) ' (apos) & (amp) < (lt) > (gt) + //static LLString escapeXML(const LLString& xml); + static LLWString escapeXML(const LLWString& xml); + + //same as above, but wraps multiple lines in quotes and prepends + //indent as leading white space on each line + static LLString escapeXML(const LLString& xml, LLString& indent); + + // focuses the item in the list after the currently-focused item, wrapping if necessary + static BOOL focusNext(LLView::child_list_t & result); + // focuses the item in the list before the currently-focused item, wrapping if necessary + static BOOL focusPrev(LLView::child_list_t & result); + + // returns query for iterating over controls in tab order + static const LLCtrlQuery & getTabOrderQuery(); + // return query for iterating over focus roots in tab order + static const LLCtrlQuery & getFocusRootsQuery(); + + BOOL getEnabled() const { return mEnabled; } + BOOL getVisible() const { return mVisible && !mHidden; } + U8 getSoundFlags() const { return mSoundFlags; } + + // Default to no action + virtual void onFocusLost(); + virtual void onFocusReceived(); + + BOOL parentPointInView(S32 x, S32 y) const { return mRect.pointInRect( x, y ); } + BOOL pointInView(S32 x, S32 y) const { return mRect.localPointInRect( x, y ); } + virtual void screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const; + virtual void localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const; + virtual BOOL localPointToOtherView( S32 x, S32 y, S32 *other_x, S32 *other_y, LLView* other_view); + virtual void screenRectToLocal( const LLRect& screen, LLRect* local ) const; + virtual void localRectToScreen( const LLRect& local, LLRect* screen ) const; + virtual BOOL localRectToOtherView( const LLRect& local, LLRect* other, LLView* other_view ) const; + + void setRenderInFastFrame(BOOL render) { mRenderInFastFrame = render; } + virtual LLView* getRootMostFastFrameView(); + + static LLWindow* getWindow(void); + + // Listener dispatching functions (Dispatcher deletes pointers to listeners on deregistration or destruction) + LLSimpleListener* getListenerByName(const LLString &callback_name); + void registerEventListener(LLString name, LLSimpleListener* function); + void deregisterEventListener(LLString name); + LLString findEventListener(LLSimpleListener *listener) const; + void addListenerToControl(LLEventDispatcher *observer, const LLString& name, LLSD filter, LLSD userdata); + + virtual LLView* getChildByName(const LLString& name, BOOL recurse = FALSE) const; + + void addBoolControl(LLString name, bool initial_value); + LLControlBase *getControl(LLString name); + virtual LLControlBase *findControl(LLString name); + + void setControlValue(const LLSD& value); + virtual void setControlName(const LLString& control, LLView *context); + virtual LLString getControlName() const; + virtual bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata); + virtual void setValue(const LLSD& value); + const child_list_t* getChildList() const { return &mChildList; } + +protected: + virtual BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent); + virtual BOOL handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent); + + LLView* childrenHandleKey(KEY key, MASK mask); + LLView* childrenHandleDragAndDrop(S32 x, S32 y, MASK mask, + BOOL drop, + EDragAndDropType type, + void* data, + EAcceptance* accept, + LLString& tooltip_msg); + + LLView* childrenHandleHover(S32 x, S32 y, MASK mask); + LLView* childrenHandleMouseUp(S32 x, S32 y, MASK mask); + LLView* childrenHandleMouseDown(S32 x, S32 y, MASK mask); + LLView* childrenHandleDoubleClick(S32 x, S32 y, MASK mask); + LLView* childrenHandleScrollWheel(S32 x, S32 y, S32 clicks); + LLView* childrenHandleRightMouseDown(S32 x, S32 y, MASK mask); + LLView* childrenHandleRightMouseUp(S32 x, S32 y, MASK mask); + + typedef std::map<LLString, LLSimpleListener*> dispatch_list_t; + dispatch_list_t mDispatchList; + +protected: + typedef std::map<LLString, LLControlBase*> control_map_t; + control_map_t mFloaterControls; + + LLString mControlName; + friend class LLUICtrlFactory; +}; + + + + +class LLCompareByTabOrder +{ +public: + LLCompareByTabOrder(LLView::child_tab_order_t order); + virtual ~LLCompareByTabOrder() {} + bool operator() (const LLView* const a, const LLView* const b) const; +protected: + virtual bool compareTabOrders(const LLView::tab_order_t & a, const LLView::tab_order_t & b) const; + LLView::child_tab_order_t mTabOrder; +}; + +#endif diff --git a/indra/llui/llviewborder.cpp b/indra/llui/llviewborder.cpp new file mode 100644 index 0000000000..24cc57e709 --- /dev/null +++ b/indra/llui/llviewborder.cpp @@ -0,0 +1,337 @@ +/** + * @file llviewborder.cpp + * @brief LLViewBorder base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// A customizable decorative border. Does not interact with mouse events. + +#include "linden_common.h" + +#include "llviewborder.h" + +#include "llgl.h" +#include "llui.h" +#include "llimagegl.h" +//#include "llviewerimagelist.h" +#include "llcontrol.h" +#include "llglheaders.h" +#include "v2math.h" +#include "llfocusmgr.h" + +LLViewBorder::LLViewBorder( const LLString& name, const LLRect& rect, EBevel bevel, EStyle style, S32 width ) + : + LLView( name, rect, FALSE ), + mBevel( bevel ), + mStyle( style ), + mHighlightLight( LLUI::sColorsGroup->getColor( "DefaultHighlightLight" ) ), + mHighlightDark( LLUI::sColorsGroup->getColor( "DefaultHighlightDark" ) ), + mShadowLight( LLUI::sColorsGroup->getColor( "DefaultShadowLight" ) ), + mShadowDark( LLUI::sColorsGroup->getColor( "DefaultShadowDark" ) ), +// mKeyboardFocusColor(LLUI::sColorsGroup->getColor( "FocusColor" ) ), + mBorderWidth( width ), + mTexture( NULL ), + mHasKeyboardFocus( FALSE ) +{ + setFollowsAll(); +} + +// virtual +BOOL LLViewBorder::isCtrl() +{ + return FALSE; +} + +void LLViewBorder::setColors( const LLColor4& shadow_dark, const LLColor4& highlight_light ) +{ + mShadowDark = shadow_dark; + mHighlightLight = highlight_light; +} + +void LLViewBorder::setColorsExtended( const LLColor4& shadow_light, const LLColor4& shadow_dark, + const LLColor4& highlight_light, const LLColor4& highlight_dark ) +{ + mShadowDark = shadow_dark; + mShadowLight = shadow_light; + mHighlightLight = highlight_light; + mHighlightDark = highlight_dark; +} + +void LLViewBorder::setTexture( const LLUUID &image_id ) +{ + mTexture = LLUI::sImageProvider->getUIImageByID(image_id); +} + + +void LLViewBorder::draw() +{ + if( getVisible() ) + { + if( STYLE_LINE == mStyle ) + { + if( 0 == mBorderWidth ) + { + // no visible border + } + else + if( 1 == mBorderWidth ) + { + drawOnePixelLines(); + } + else + if( 2 == mBorderWidth ) + { + drawTwoPixelLines(); + } + else + { + llassert( FALSE ); // not implemented + } + } + else + if( STYLE_TEXTURE == mStyle ) + { + if( mTexture ) + { + drawTextures(); + } + } + + // draw the children + LLView::draw(); + } +} + +void LLViewBorder::drawOnePixelLines() +{ + LLGLSNoTexture uiNoTexture; + + LLColor4 top_color = mHighlightLight; + LLColor4 bottom_color = mHighlightLight; + switch( mBevel ) + { + case BEVEL_OUT: + top_color = mHighlightLight; + bottom_color = mShadowDark; + break; + case BEVEL_IN: + top_color = mShadowDark; + bottom_color = mHighlightLight; + break; + case BEVEL_NONE: + // use defaults + break; + default: + llassert(0); + } + + if( mHasKeyboardFocus ) + { + F32 lerp_amt = gFocusMgr.getFocusFlashAmt(); + top_color = gFocusMgr.getFocusColor(); + bottom_color = top_color; + + LLUI::setLineWidth(lerp(1.f, 3.f, lerp_amt)); + } + + S32 left = 0; + S32 top = mRect.getHeight(); + S32 right = mRect.getWidth(); + S32 bottom = 0; + + glColor4fv( top_color.mV ); + gl_line_2d(left, bottom, left, top); + gl_line_2d(left, top, right, top); + + glColor4fv( bottom_color.mV ); + gl_line_2d(right, top, right, bottom); + gl_line_2d(left, bottom, right, bottom); + + LLUI::setLineWidth(1.f); +} + +void LLViewBorder::drawTwoPixelLines() +{ + LLGLSNoTexture no_texture; + + LLColor4 focus_color = gFocusMgr.getFocusColor(); + + F32* top_in_color = mShadowDark.mV; + F32* top_out_color = mShadowDark.mV; + F32* bottom_in_color = mShadowDark.mV; + F32* bottom_out_color = mShadowDark.mV; + switch( mBevel ) + { + case BEVEL_OUT: + top_in_color = mHighlightLight.mV; + top_out_color = mHighlightDark.mV; + bottom_in_color = mShadowLight.mV; + bottom_out_color = mShadowDark.mV; + break; + case BEVEL_IN: + top_in_color = mShadowDark.mV; + top_out_color = mShadowLight.mV; + bottom_in_color = mHighlightDark.mV; + bottom_out_color = mHighlightLight.mV; + break; + case BEVEL_BRIGHT: + top_in_color = mHighlightLight.mV; + top_out_color = mHighlightLight.mV; + bottom_in_color = mHighlightLight.mV; + bottom_out_color = mHighlightLight.mV; + break; + case BEVEL_NONE: + // use defaults + break; + default: + llassert(0); + } + + if( mHasKeyboardFocus ) + { + top_out_color = focus_color.mV; + bottom_out_color = focus_color.mV; + } + + S32 left = 0; + S32 top = mRect.getHeight(); + S32 right = mRect.getWidth(); + S32 bottom = 0; + + // draw borders + glColor3fv( top_out_color ); + gl_line_2d(left, bottom, left, top-1); + gl_line_2d(left, top-1, right, top-1); + + glColor3fv( top_in_color ); + gl_line_2d(left+1, bottom+1, left+1, top-2); + gl_line_2d(left+1, top-2, right-1, top-2); + + glColor3fv( bottom_out_color ); + gl_line_2d(right-1, top-1, right-1, bottom); + gl_line_2d(left, bottom, right, bottom); + + glColor3fv( bottom_in_color ); + gl_line_2d(right-2, top-2, right-2, bottom+1); + gl_line_2d(left+1, bottom+1, right-1, bottom+1); +} + +void LLViewBorder::drawTextures() +{ + LLGLSUIDefault gls_ui; + + llassert( FALSE ); // TODO: finish implementing + + glColor4fv(UI_VERTEX_COLOR.mV); + + mTexture->bind(); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT ); + + drawTextureTrapezoid( 0.f, mBorderWidth, mRect.getWidth(), 0, 0 ); + drawTextureTrapezoid( 90.f, mBorderWidth, mRect.getHeight(), (F32)mRect.getWidth(),0 ); + drawTextureTrapezoid( 180.f, mBorderWidth, mRect.getWidth(), (F32)mRect.getWidth(),(F32)mRect.getHeight() ); + drawTextureTrapezoid( 270.f, mBorderWidth, mRect.getHeight(), 0, (F32)mRect.getHeight() ); +} + + +void LLViewBorder::drawTextureTrapezoid( F32 degrees, S32 width, S32 length, F32 start_x, F32 start_y ) +{ + glPushMatrix(); + { + glTranslatef(start_x, start_y, 0.f); + glRotatef( degrees, 0, 0, 1 ); + + glBegin(GL_QUADS); + { + // width, width /---------\ length-width, width // + // / \ // + // / \ // + // /---------------\ // + // 0,0 length, 0 // + + glTexCoord2f( 0, 0 ); + glVertex2i( 0, 0 ); + + glTexCoord2f( (GLfloat)length, 0 ); + glVertex2i( length, 0 ); + + glTexCoord2f( (GLfloat)(length - width), (GLfloat)width ); + glVertex2i( length - width, width ); + + glTexCoord2f( (GLfloat)width, (GLfloat)width ); + glVertex2i( width, width ); + } + glEnd(); + } + glPopMatrix(); +} + +bool LLViewBorder::getBevelFromAttribute(LLXMLNodePtr node, LLViewBorder::EBevel& bevel_style) +{ + if (node->hasAttribute("bevel_style")) + { + LLString bevel_string; + node->getAttributeString("bevel_style", bevel_string); + LLString::toLower(bevel_string); + + if (bevel_string == "none") + { + bevel_style = LLViewBorder::BEVEL_NONE; + } + else if (bevel_string == "in") + { + bevel_style = LLViewBorder::BEVEL_IN; + } + else if (bevel_string == "out") + { + bevel_style = LLViewBorder::BEVEL_OUT; + } + else if (bevel_string == "bright") + { + bevel_style = LLViewBorder::BEVEL_BRIGHT; + } + return true; + } + return false; +} + +void LLViewBorder::setValue(const LLSD& val) +{ + setRect(LLRect(val)); +} + +EWidgetType LLViewBorder::getWidgetType() const +{ + return WIDGET_TYPE_VIEW_BORDER; +} + +LLString LLViewBorder::getWidgetTag() const +{ + return LL_VIEW_BORDER_TAG; +} + +// static +LLView* LLViewBorder::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("view_border"); + node->getAttributeString("name", name); + + LLViewBorder::EBevel bevel_style = LLViewBorder::BEVEL_IN; + getBevelFromAttribute(node, bevel_style); + + S32 border_thickness = 1; + node->getAttributeS32("border_thickness", border_thickness); + + LLViewBorder* border = new LLViewBorder(name, + LLRect(), + bevel_style, + LLViewBorder::STYLE_LINE, + border_thickness); + + border->initFromXML(node, parent); + + return border; +} diff --git a/indra/llui/llviewborder.h b/indra/llui/llviewborder.h new file mode 100644 index 0000000000..984eee6e60 --- /dev/null +++ b/indra/llui/llviewborder.h @@ -0,0 +1,77 @@ +/** + * @file llviewborder.h + * @brief LLViewBorder base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// A customizable decorative border. Does not interact with mouse events. + +#ifndef LL_LLVIEWBORDER_H +#define LL_LLVIEWBORDER_H + +#include "llview.h" +#include "v4color.h" +#include "lluuid.h" +#include "llimagegl.h" +#include "llxmlnode.h" + +class LLUUID; + + +class LLViewBorder : public LLView +{ +public: + enum EBevel { BEVEL_IN, BEVEL_OUT, BEVEL_BRIGHT, BEVEL_NONE }; + + enum EStyle { STYLE_LINE, STYLE_TEXTURE }; + + LLViewBorder( const LLString& name, const LLRect& rect, EBevel bevel = BEVEL_OUT, EStyle style = STYLE_LINE, S32 width = 1 ); + + virtual void setValue(const LLSD& val); + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + virtual BOOL isCtrl(); + + // llview functionality + virtual void draw(); + + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + static bool getBevelFromAttribute(LLXMLNodePtr node, LLViewBorder::EBevel& bevel_style); + + void setBorderWidth(S32 width) { mBorderWidth = width; } + void setBevel(EBevel bevel) { mBevel = bevel; } + void setColors( const LLColor4& shadow_dark, const LLColor4& highlight_light ); + void setColorsExtended( const LLColor4& shadow_light, const LLColor4& shadow_dark, + const LLColor4& highlight_light, const LLColor4& highlight_dark ); + void setTexture( const LLUUID &image_id ); + + EBevel getBevel() const { return mBevel; } + EStyle getStyle() const { return mStyle; } + S32 getBorderWidth() const { return mBorderWidth; } + + void setKeyboardFocusHighlight( BOOL b ) { mHasKeyboardFocus = b; } + +protected: + void drawOnePixelLines(); + void drawTwoPixelLines(); + void drawTextures(); + void drawTextureTrapezoid( F32 degrees, S32 width, S32 length, F32 start_x, F32 start_y ); + +protected: + EBevel mBevel; + EStyle mStyle; + LLColor4 mHighlightLight; + LLColor4 mHighlightDark; + LLColor4 mShadowLight; + LLColor4 mShadowDark; + LLColor4 mBackgroundColor; + S32 mBorderWidth; + LLPointer<LLImageGL> mTexture; + BOOL mHasKeyboardFocus; +}; + +#endif // LL_LLVIEWBORDER_H + diff --git a/indra/llui/llviewquery.cpp b/indra/llui/llviewquery.cpp new file mode 100644 index 0000000000..416ca623bc --- /dev/null +++ b/indra/llui/llviewquery.cpp @@ -0,0 +1,126 @@ +/**
+ * @file llviewquery.cpp
+ * @brief Implementation of view query class.
+ *
+ * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc.
+ * $License$
+ */
+
+#include "llview.h"
+#include "lluictrl.h"
+#include "llviewquery.h"
+
+void LLQuerySorter::operator() (LLView * parent, viewList_t &children) const {}
+
+filterResult_t LLNoLeavesFilter::operator() (const LLView* const view, const viewList_t & children) const
+{
+ return filterResult_t(!(view->getChildList()->size() == 0), TRUE);
+}
+
+filterResult_t LLVisibleFilter::operator() (const LLView* const view, const viewList_t & children) const
+{
+ return filterResult_t(view->getVisible(), view->getVisible());
+}
+filterResult_t LLEnabledFilter::operator() (const LLView* const view, const viewList_t & children) const
+{
+ return filterResult_t(view->getEnabled(), view->getEnabled());
+}
+filterResult_t LLTabStopFilter::operator() (const LLView* const view, const viewList_t & children) const
+{
+ return filterResult_t(view->isCtrl() && static_cast<const LLUICtrl* const>(view)->hasTabStop(),
+ view->canFocusChildren());
+}
+
+// LLViewQuery
+
+LLViewQuery::LLViewQuery(): mPreFilters(), mPostFilters(), mSorterp()
+{
+}
+
+void LLViewQuery::addPreFilter(const LLQueryFilter* prefilter) { mPreFilters.push_back(prefilter); }
+
+void LLViewQuery::addPostFilter(const LLQueryFilter* postfilter) { mPostFilters.push_back(postfilter); }
+
+const LLViewQuery::filterList_t & LLViewQuery::getPreFilters() const { return mPreFilters; }
+
+const LLViewQuery::filterList_t & LLViewQuery::getPostFilters() const { return mPostFilters; }
+
+void LLViewQuery::setSorter(const LLQuerySorter* sorterp) { mSorterp = sorterp; }
+const LLQuerySorter* LLViewQuery::getSorter() const { return mSorterp; }
+
+viewList_t LLViewQuery::run(LLView * view) const
+{
+ viewList_t result;
+
+ filterResult_t pre = runFilters(view, viewList_t(), mPreFilters);
+ if(!pre.first && !pre.second)
+ {
+ // skip post filters completely if we're not including ourselves or the children
+ return result;
+ }
+ if(pre.second)
+ {
+ // run filters on children
+ viewList_t filtered_children;
+ filterChildren(view, filtered_children);
+ filterResult_t post = runFilters(view, filtered_children, mPostFilters);
+ if(pre.first && post.first)
+ {
+ result.push_back(view);
+ }
+ if(post.second)
+ {
+ result.insert(result.end(), filtered_children.begin(), filtered_children.end());
+ }
+ }
+ else
+ {
+ if(pre.first)
+ {
+ result.push_back(view);
+ }
+ }
+ return result;
+}
+
+void LLViewQuery::filterChildren(LLView * view, viewList_t & filtered_children) const
+{
+ LLView::child_list_t views(*(view->getChildList()));
+ (*mSorterp)(view, views); // sort the children per the sorter
+ for(LLView::child_list_iter_t iter = views.begin();
+ iter != views.end();
+ iter++)
+ {
+ viewList_t indiv_children = this->run(*iter);
+ filtered_children.insert(filtered_children.end(), indiv_children.begin(), indiv_children.end());
+ }
+}
+
+filterResult_t LLViewQuery::runFilters(LLView * view, const viewList_t children, const filterList_t filters) const
+{
+ filterResult_t result = filterResult_t(TRUE, TRUE);
+ for(filterList_const_iter_t iter = filters.begin();
+ iter != filters.end();
+ iter++)
+ {
+ filterResult_t filtered = (**iter)(view, children);
+ result.first = result.first && filtered.first;
+ result.second = result.second && filtered.second;
+ }
+ return result;
+}
+
+class SortByTabOrder : public LLQuerySorter, public LLSingleton<SortByTabOrder>
+{
+ /*virtual*/ void operator() (LLView * parent, LLView::child_list_t &children) const
+ {
+ children.sort(LLCompareByTabOrder(parent->getCtrlOrder()));
+ }
+};
+
+LLCtrlQuery::LLCtrlQuery() :
+ LLViewQuery()
+{
+ setSorter(SortByTabOrder::getInstance());
+}
+
diff --git a/indra/llui/llviewquery.h b/indra/llui/llviewquery.h new file mode 100644 index 0000000000..ba59965c59 --- /dev/null +++ b/indra/llui/llviewquery.h @@ -0,0 +1,89 @@ +/**
+ * @file llviewquery.h
+ * @brief Query algorithm for flattening and filtering the view hierarchy.
+ *
+ * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc.
+ * $License$
+ */
+
+#ifndef LL_LLVIEWQUERY_H
+#define LL_LLVIEWQUERY_H
+
+#include <list>
+
+#include "llmemory.h"
+
+class LLView;
+
+typedef std::list<LLView *> viewList_t;
+typedef std::pair<BOOL, BOOL> filterResult_t;
+
+// Abstract base class for all filters.
+class LLQueryFilter : public LLRefCount
+{
+public:
+ virtual filterResult_t operator() (const LLView* const view, const viewList_t & children) const =0;
+};
+
+class LLQuerySorter : public LLRefCount
+{
+public:
+ virtual void operator() (LLView * parent, viewList_t &children) const;
+};
+
+class LLNoLeavesFilter : public LLQueryFilter, public LLSingleton<LLNoLeavesFilter>
+{
+ /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const;
+};
+class LLVisibleFilter : public LLQueryFilter, public LLSingleton<LLVisibleFilter>
+{
+ /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const;
+};
+class LLEnabledFilter : public LLQueryFilter, public LLSingleton<LLEnabledFilter>
+{
+ /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const;
+};
+class LLTabStopFilter : public LLQueryFilter, public LLSingleton<LLTabStopFilter>
+{
+ /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const;
+};
+
+// Algorithm for flattening
+class LLViewQuery
+{
+public:
+ typedef std::list<const LLQueryFilter*> filterList_t;
+ typedef filterList_t::iterator filterList_iter_t;
+ typedef filterList_t::const_iterator filterList_const_iter_t;
+
+ LLViewQuery();
+ virtual ~LLViewQuery() {}
+
+ void addPreFilter(const LLQueryFilter* prefilter);
+ void addPostFilter(const LLQueryFilter* postfilter);
+ const filterList_t & getPreFilters() const;
+ const filterList_t & getPostFilters() const;
+
+ void setSorter(const LLQuerySorter* sorter);
+ const LLQuerySorter* getSorter() const;
+
+ viewList_t run(LLView * view) const;
+ // syntactic sugar
+ viewList_t operator () (LLView * view) const { return run(view); }
+protected:
+ // override this method to provide iteration over other types of children
+ virtual void filterChildren(LLView * view, viewList_t & filtered_children) const;
+ filterResult_t runFilters(LLView * view, const viewList_t children, const filterList_t filters) const;
+protected:
+ filterList_t mPreFilters;
+ filterList_t mPostFilters;
+ const LLQuerySorter* mSorterp;
+};
+
+class LLCtrlQuery : public LLViewQuery
+{
+public:
+ LLCtrlQuery();
+};
+
+#endif
|