summaryrefslogtreecommitdiff
path: root/indra/llui
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llui')
-rw-r--r--indra/llui/llbutton.cpp1012
-rw-r--r--indra/llui/llbutton.h261
-rw-r--r--indra/llui/llcallbackmap.h36
-rw-r--r--indra/llui/llcheckboxctrl.cpp315
-rw-r--r--indra/llui/llcheckboxctrl.h112
-rw-r--r--indra/llui/llclipboard.cpp71
-rw-r--r--indra/llui/llclipboard.h41
-rw-r--r--indra/llui/llcombobox.cpp1133
-rw-r--r--indra/llui/llcombobox.h178
-rw-r--r--indra/llui/llctrlselectioninterface.cpp44
-rw-r--r--indra/llui/llctrlselectioninterface.h84
-rw-r--r--indra/llui/lldraghandle.cpp353
-rw-r--r--indra/llui/lldraghandle.h98
-rw-r--r--indra/llui/lleditmenuhandler.cpp107
-rw-r--r--indra/llui/lleditmenuhandler.h50
-rw-r--r--indra/llui/llfloater.cpp2933
-rw-r--r--indra/llui/llfloater.h399
-rw-r--r--indra/llui/llfocusmgr.cpp369
-rw-r--r--indra/llui/llfocusmgr.h102
-rw-r--r--indra/llui/lliconctrl.cpp139
-rw-r--r--indra/llui/lliconctrl.h55
-rw-r--r--indra/llui/llkeywords.cpp502
-rw-r--r--indra/llui/llkeywords.h91
-rw-r--r--indra/llui/lllineeditor.cpp2301
-rw-r--r--indra/llui/lllineeditor.h298
-rw-r--r--indra/llui/llmenugl.cpp4341
-rw-r--r--indra/llui/llmenugl.h728
-rw-r--r--indra/llui/llmodaldialog.cpp288
-rw-r--r--indra/llui/llmodaldialog.h60
-rw-r--r--indra/llui/llpanel.cpp1030
-rw-r--r--indra/llui/llpanel.h229
-rw-r--r--indra/llui/llradiogroup.cpp440
-rw-r--r--indra/llui/llradiogroup.h102
-rw-r--r--indra/llui/llresizebar.cpp257
-rw-r--r--indra/llui/llresizebar.h47
-rw-r--r--indra/llui/llresizehandle.cpp321
-rw-r--r--indra/llui/llresizehandle.h56
-rw-r--r--indra/llui/llresmgr.cpp447
-rw-r--r--indra/llui/llresmgr.h147
-rw-r--r--indra/llui/llscrollbar.cpp618
-rw-r--r--indra/llui/llscrollbar.h123
-rw-r--r--indra/llui/llscrollcontainer.cpp772
-rw-r--r--indra/llui/llscrollcontainer.h109
-rw-r--r--indra/llui/llscrollingpanellist.cpp150
-rw-r--r--indra/llui/llscrollingpanellist.h53
-rw-r--r--indra/llui/llscrolllistctrl.cpp2673
-rw-r--r--indra/llui/llscrolllistctrl.h527
-rw-r--r--indra/llui/llslider.cpp352
-rw-r--r--indra/llui/llslider.h79
-rw-r--r--indra/llui/llsliderctrl.cpp538
-rw-r--r--indra/llui/llsliderctrl.h124
-rw-r--r--indra/llui/llspinctrl.cpp509
-rw-r--r--indra/llui/llspinctrl.h121
-rw-r--r--indra/llui/llstyle.cpp223
-rw-r--r--indra/llui/llstyle.h75
-rw-r--r--indra/llui/lltabcontainer.cpp1469
-rw-r--r--indra/llui/lltabcontainer.h242
-rw-r--r--indra/llui/lltextbox.cpp438
-rw-r--r--indra/llui/lltextbox.h106
-rw-r--r--indra/llui/lltexteditor.cpp4144
-rw-r--r--indra/llui/lltexteditor.h483
-rw-r--r--indra/llui/llui.cpp1764
-rw-r--r--indra/llui/llui.h255
-rw-r--r--indra/llui/lluiconstants.h32
-rw-r--r--indra/llui/lluictrl.cpp341
-rw-r--r--indra/llui/lluictrl.h153
-rw-r--r--indra/llui/lluictrlfactory.cpp722
-rw-r--r--indra/llui/lluictrlfactory.h142
-rwxr-xr-xindra/llui/lluistring.cpp87
-rwxr-xr-xindra/llui/lluistring.h87
-rw-r--r--indra/llui/llundo.cpp161
-rw-r--r--indra/llui/llundo.h52
-rw-r--r--indra/llui/llview.cpp2924
-rw-r--r--indra/llui/llview.h489
-rw-r--r--indra/llui/llviewborder.cpp337
-rw-r--r--indra/llui/llviewborder.h77
-rw-r--r--indra/llui/llviewquery.cpp126
-rw-r--r--indra/llui/llviewquery.h89
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 &copy_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, &center_x, &center_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("&quot;")); break;
+ case '\'': out.append(utf8string_to_wstring("&apos;")); break;
+ case '&': out.append(utf8string_to_wstring("&amp;")); break;
+ case '<': out.append(utf8string_to_wstring("&lt;")); break;
+ case '>': out.append(utf8string_to_wstring("&gt;")); 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