summaryrefslogtreecommitdiff
path: root/indra/llui
diff options
context:
space:
mode:
authorDon Kjer <don@lindenlab.com>2007-12-05 23:43:56 +0000
committerDon Kjer <don@lindenlab.com>2007-12-05 23:43:56 +0000
commitfacf67ae3226105910c983a8fa8760414bf703e9 (patch)
treeb5f7cd6b79a79f769080a65b6fe2cb6b97c8b6fb /indra/llui
parent45057e8881c3166c7c0ef545c02bc177922af6fb (diff)
EFFECTIVE MERGE: svn merge -r 71520:73420 svn+ssh://svn/svn/linden/branches/maintenance-3 into release
ACTUAL MERGE: svn merge -r 75074:75114 svn+ssh://svn/svn/linden/qa/maintenance-3-merge-75067 into release
Diffstat (limited to 'indra/llui')
-rw-r--r--indra/llui/lllineeditor.cpp347
-rw-r--r--indra/llui/lllineeditor.h36
-rw-r--r--indra/llui/llmenugl.cpp4
-rw-r--r--indra/llui/lltextbox.cpp2
-rw-r--r--indra/llui/lltexteditor.cpp437
-rw-r--r--indra/llui/lltexteditor.h38
-rw-r--r--indra/llui/llui.cpp28
-rw-r--r--indra/llui/llui.h4
8 files changed, 815 insertions, 81 deletions
diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp
index 420970a38a..3c7cd17b92 100644
--- a/indra/llui/lllineeditor.cpp
+++ b/indra/llui/lllineeditor.cpp
@@ -72,6 +72,15 @@ const S32 SCROLL_INCREMENT_DEL = 4; // make space for baskspacing
const F32 AUTO_SCROLL_TIME = 0.05f;
const F32 LABEL_HPAD = 5.f;
+const F32 PREEDIT_MARKER_BRIGHTNESS = 0.4f;
+const S32 PREEDIT_MARKER_GAP = 1;
+const S32 PREEDIT_MARKER_POSITION = 2;
+const S32 PREEDIT_MARKER_THICKNESS = 1;
+const F32 PREEDIT_STANDOUT_BRIGHTNESS = 0.6f;
+const S32 PREEDIT_STANDOUT_GAP = 1;
+const S32 PREEDIT_STANDOUT_POSITION = 2;
+const S32 PREEDIT_STANDOUT_THICKNESS = 2;
+
// This is a friend class of and is only used by LLLineEditor
class LLLineEditorRollback
{
@@ -127,7 +136,6 @@ LLLineEditor::LLLineEditor(const LLString& name, const LLRect& rect,
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 ),
@@ -223,12 +231,19 @@ LLString LLLineEditor::getWidgetTag() const
return LL_LINE_EDITOR_TAG;
}
-void LLLineEditor::onFocusLost()
+void LLLineEditor::onFocusReceived()
{
- // Need to notify early when loosing focus.
- getWindow()->allowLanguageTextInput(FALSE);
+ LLUICtrl::onFocusReceived();
+ updateAllowingLanguageInput();
+}
- LLUICtrl::onFocusLost();
+void LLLineEditor::onFocusLost()
+{
+ // The call to updateAllowLanguageInput()
+ // when loosing the keyboard focus *may*
+ // indirectly invoke handleUnicodeCharHere(),
+ // so it must be called before onCommit.
+ updateAllowingLanguageInput();
if( mCommitOnFocusLost && mText.getString() != mPrevText)
{
@@ -241,6 +256,8 @@ void LLLineEditor::onFocusLost()
}
getWindow()->showCursorFromMouseMove();
+
+ LLUICtrl::onFocusLost();
}
void LLLineEditor::onCommit()
@@ -301,6 +318,7 @@ void LLLineEditor::setEnabled(BOOL enabled)
{
mReadOnly = !enabled;
setTabStop(!mReadOnly);
+ updateAllowingLanguageInput();
}
@@ -308,7 +326,6 @@ 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)
@@ -337,13 +354,13 @@ void LLLineEditor::setText(const LLStringExplicit &new_text)
BOOL allSelected = (len > 0) && (( mSelectionStart == 0 && mSelectionEnd == len )
|| ( mSelectionStart == len && mSelectionEnd == 0 ));
+ // Do safe truncation so we don't split multi-byte characters
LLString truncated_utf8 = new_text;
if (truncated_utf8.size() > (U32)mMaxLengthBytes)
- {
- utf8str_truncate(truncated_utf8, mMaxLengthBytes);
+ {
+ truncated_utf8 = utf8str_truncate(new_text, mMaxLengthBytes);
}
mText.assign(truncated_utf8);
- mText.truncate(mMaxLengthChars);
if (allSelected)
{
@@ -735,17 +752,12 @@ void LLLineEditor::addChar(const llwchar uni_char)
mText.erase(getCursor(), 1);
}
- S32 length_chars = mText.length();
- S32 cur_bytes = mText.getString().size();;
+ 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;
- }
+ // Check byte length limit
if ((new_bytes + cur_bytes) > mMaxLengthBytes)
{
allow_char = FALSE;
@@ -794,6 +806,12 @@ void LLLineEditor::setSelection(S32 start, S32 end)
setCursor(start);
}
+void LLLineEditor::setDrawAsterixes(BOOL b)
+{
+ mDrawAsterixes = b;
+ updateAllowingLanguageInput();
+}
+
S32 LLLineEditor::prevWordPos(S32 cursorPos) const
{
const LLWString& wtext = mText.getWString();
@@ -1022,13 +1040,11 @@ void LLLineEditor::paste()
// Insert the string
- //check to see that the size isn't going to be larger than the
- //max number of characters or bytes
+ // Check to see that the size isn't going to be larger than the max number of 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) )
- {
+ { // Doesn't all fit
llwchar current_symbol = clean_string[0];
U32 wchars_that_fit = 0;
U32 total_bytes = wchar_utf8_length(current_symbol);
@@ -1043,20 +1059,13 @@ void LLLineEditor::paste()
current_symbol = clean_string[++wchars_that_fit];
total_bytes += wchar_utf8_length(current_symbol);
}
-
+ // Truncate the clean string at the limit of what will fit
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()));
+ setCursor( getCursor() + (S32)clean_string.length() );
deselect();
// Validate new string and rollback the if needed.
@@ -1523,6 +1532,41 @@ void LLLineEditor::draw()
}
LLColor4 label_color = mTentativeFgColor;
+ if (hasPreeditString())
+ {
+ // Draw preedit markers. This needs to be before drawing letters.
+ for (U32 i = 0; i < mPreeditStandouts.size(); i++)
+ {
+ const S32 preedit_left = mPreeditPositions[i];
+ const S32 preedit_right = mPreeditPositions[i + 1];
+ if (preedit_right > mScrollHPos)
+ {
+ S32 preedit_pixels_left = findPixelNearestPos(llmax(preedit_left, mScrollHPos) - getCursor());
+ S32 preedit_pixels_right = llmin(findPixelNearestPos(preedit_right - getCursor()), background.mRight);
+ if (preedit_pixels_left >= background.mRight)
+ {
+ break;
+ }
+ if (mPreeditStandouts[i])
+ {
+ gl_rect_2d(preedit_pixels_left + PREEDIT_STANDOUT_GAP,
+ background.mBottom + PREEDIT_STANDOUT_POSITION,
+ preedit_pixels_right - PREEDIT_STANDOUT_GAP - 1,
+ background.mBottom + PREEDIT_STANDOUT_POSITION - PREEDIT_STANDOUT_THICKNESS,
+ (text_color * PREEDIT_STANDOUT_BRIGHTNESS + bg_color * (1 - PREEDIT_STANDOUT_BRIGHTNESS)).setAlpha(1.0f));
+ }
+ else
+ {
+ gl_rect_2d(preedit_pixels_left + PREEDIT_MARKER_GAP,
+ background.mBottom + PREEDIT_MARKER_POSITION,
+ preedit_pixels_right - PREEDIT_MARKER_GAP - 1,
+ background.mBottom + PREEDIT_MARKER_POSITION - PREEDIT_MARKER_THICKNESS,
+ (text_color * PREEDIT_MARKER_BRIGHTNESS + bg_color * (1 - PREEDIT_MARKER_BRIGHTNESS)).setAlpha(1.0f));
+ }
+ }
+ }
+ }
+
S32 rendered_text = 0;
F32 rendered_pixels_right = (F32)mMinHPixels;
F32 text_bottom = (F32)background.mBottom + (F32)UI_LINEEDITOR_V_PAD;
@@ -1677,7 +1721,7 @@ void LLLineEditor::draw()
// Returns the local screen space X coordinate associated with the text cursor position.
-S32 LLLineEditor::findPixelNearestPos(const S32 cursor_offset)
+S32 LLLineEditor::findPixelNearestPos(const S32 cursor_offset) const
{
S32 dpos = getCursor() - mScrollHPos + cursor_offset;
S32 result = mGLFont->getWidth(mText.getWString().c_str(), mScrollHPos, dpos) + mMinHPixels;
@@ -1715,7 +1759,7 @@ void LLLineEditor::setFocus( BOOL new_state )
if (!new_state)
{
- getWindow()->allowLanguageTextInput(FALSE);
+ getWindow()->allowLanguageTextInput(this, FALSE);
}
@@ -1757,7 +1801,7 @@ void LLLineEditor::setFocus( BOOL new_state )
// fine on 1.15.0.2, since all prevalidate func reject any
// non-ASCII characters. I'm not sure on future versions,
// however.
- getWindow()->allowLanguageTextInput(mPrevalidateFunc == NULL);
+ getWindow()->allowLanguageTextInput(this, mPrevalidateFunc == NULL);
}
}
@@ -1776,6 +1820,12 @@ void LLLineEditor::setRect(const LLRect& rect)
}
}
+void LLLineEditor::setPrevalidate(BOOL (*func)(const LLWString &))
+{
+ mPrevalidateFunc = func;
+ updateAllowingLanguageInput();
+}
+
// 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.
@@ -2336,6 +2386,239 @@ BOOL LLLineEditor::setLabelArg( const LLString& key, const LLStringExplicit& tex
return TRUE;
}
+
+void LLLineEditor::updateAllowingLanguageInput()
+{
+ // Allow Language Text Input only when this LineEditor has
+ // no prevalidate function attached (as long as other criteria
+ // common to LLTextEditor). This criterion works
+ // fine on 1.15.0.2, since all prevalidate func reject any
+ // non-ASCII characters. I'm not sure on future versions,
+ // however...
+ if (hasFocus() && !mReadOnly && !mDrawAsterixes && mPrevalidateFunc == NULL)
+ {
+ getWindow()->allowLanguageTextInput(this, TRUE);
+ }
+ else
+ {
+ getWindow()->allowLanguageTextInput(this, FALSE);
+ }
+}
+
+BOOL LLLineEditor::hasPreeditString() const
+{
+ return (mPreeditPositions.size() > 1);
+}
+
+void LLLineEditor::resetPreedit()
+{
+ if (hasPreeditString())
+ {
+ const S32 preedit_pos = mPreeditPositions.front();
+ mText.erase(preedit_pos, mPreeditPositions.back() - preedit_pos);
+ mText.insert(preedit_pos, mPreeditOverwrittenWString);
+ setCursor(preedit_pos);
+
+ mPreeditWString.clear();
+ mPreeditOverwrittenWString.clear();
+ mPreeditPositions.clear();
+
+ mKeystrokeTimer.reset();
+ if (mKeystrokeCallback)
+ {
+ mKeystrokeCallback(this, mCallbackUserData);
+ }
+ }
+}
+
+void LLLineEditor::updatePreedit(const LLWString &preedit_string,
+ const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position)
+{
+ // Just in case.
+ if (mReadOnly)
+ {
+ return;
+ }
+
+ if (hasSelection())
+ {
+ if (hasPreeditString())
+ {
+ llwarns << "Preedit and selection!" << llendl;
+ deselect();
+ }
+ else
+ {
+ deleteSelection();
+ }
+ }
+
+ S32 insert_preedit_at = getCursor();
+ if (hasPreeditString())
+ {
+ insert_preedit_at = mPreeditPositions.front();
+ //mText.replace(insert_preedit_at, mPreeditPositions.back() - insert_preedit_at, mPreeditOverwrittenWString);
+ mText.erase(insert_preedit_at, mPreeditPositions.back() - insert_preedit_at);
+ mText.insert(insert_preedit_at, mPreeditOverwrittenWString);
+ }
+
+ mPreeditWString = preedit_string;
+ mPreeditPositions.resize(preedit_segment_lengths.size() + 1);
+ S32 position = insert_preedit_at;
+ for (segment_lengths_t::size_type i = 0; i < preedit_segment_lengths.size(); i++)
+ {
+ mPreeditPositions[i] = position;
+ position += preedit_segment_lengths[i];
+ }
+ mPreeditPositions.back() = position;
+ if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode())
+ {
+ mPreeditOverwrittenWString.assign( LLWString( mText, insert_preedit_at, mPreeditWString.length() ) );
+ mText.erase(insert_preedit_at, mPreeditWString.length());
+ }
+ else
+ {
+ mPreeditOverwrittenWString.clear();
+ }
+ mText.insert(insert_preedit_at, mPreeditWString);
+
+ mPreeditStandouts = preedit_standouts;
+
+ setCursor(position);
+ setCursor(mPreeditPositions.front() + caret_position);
+
+ // Update of the preedit should be caused by some key strokes.
+ mKeystrokeTimer.reset();
+ if( mKeystrokeCallback )
+ {
+ mKeystrokeCallback( this, mCallbackUserData );
+ }
+}
+
+BOOL LLLineEditor::getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect *bounds, LLRect *control) const
+{
+ if (control)
+ {
+ LLRect control_rect_screen;
+ localRectToScreen(mRect, &control_rect_screen);
+ LLUI::screenRectToGL(control_rect_screen, control);
+ }
+
+ S32 preedit_left_column, preedit_right_column;
+ if (hasPreeditString())
+ {
+ preedit_left_column = mPreeditPositions.front();
+ preedit_right_column = mPreeditPositions.back();
+ }
+ else
+ {
+ preedit_left_column = preedit_right_column = getCursor();
+ }
+ if (preedit_right_column < mScrollHPos)
+ {
+ // This should not occure...
+ return FALSE;
+ }
+
+ const S32 query = (query_offset >= 0 ? preedit_left_column + query_offset : getCursor());
+ if (query < mScrollHPos || query < preedit_left_column || query > preedit_right_column)
+ {
+ return FALSE;
+ }
+
+ if (coord)
+ {
+ S32 query_local = findPixelNearestPos(query - getCursor());
+ S32 query_screen_x, query_screen_y;
+ localPointToScreen(query_local, mRect.getHeight() / 2, &query_screen_x, &query_screen_y);
+ LLUI::screenPointToGL(query_screen_x, query_screen_y, &coord->mX, &coord->mY);
+ }
+
+ if (bounds)
+ {
+ S32 preedit_left_local = findPixelNearestPos(llmax(preedit_left_column, mScrollHPos) - getCursor());
+ S32 preedit_right_local = llmin(findPixelNearestPos(preedit_right_column - getCursor()), mRect.getWidth() - mBorderThickness);
+ if (preedit_left_local > preedit_right_local)
+ {
+ // Is this condition possible?
+ preedit_right_local = preedit_left_local;
+ }
+
+ LLRect preedit_rect_local(preedit_left_local, mRect.getHeight(), preedit_right_local, 0);
+ LLRect preedit_rect_screen;
+ localRectToScreen(preedit_rect_local, &preedit_rect_screen);
+ LLUI::screenRectToGL(preedit_rect_screen, bounds);
+ }
+
+ return TRUE;
+}
+
+void LLLineEditor::getPreeditRange(S32 *position, S32 *length) const
+{
+ if (hasPreeditString())
+ {
+ *position = mPreeditPositions.front();
+ *length = mPreeditPositions.back() - mPreeditPositions.front();
+ }
+ else
+ {
+ *position = mCursorPos;
+ *length = 0;
+ }
+}
+
+void LLLineEditor::getSelectionRange(S32 *position, S32 *length) const
+{
+ if (hasSelection())
+ {
+ *position = llmin(mSelectionStart, mSelectionEnd);
+ *length = llabs(mSelectionStart - mSelectionEnd);
+ }
+ else
+ {
+ *position = mCursorPos;
+ *length = 0;
+ }
+}
+
+void LLLineEditor::markAsPreedit(S32 position, S32 length)
+{
+ deselect();
+ setCursor(position);
+ if (hasPreeditString())
+ {
+ llwarns << "markAsPreedit invoked when hasPreeditString is true." << llendl;
+ }
+ mPreeditWString.assign( LLWString( mText.getWString(), position, length ) );
+ if (length > 0)
+ {
+ mPreeditPositions.resize(2);
+ mPreeditPositions[0] = position;
+ mPreeditPositions[1] = position + length;
+ mPreeditStandouts.resize(1);
+ mPreeditStandouts[0] = FALSE;
+ }
+ else
+ {
+ mPreeditPositions.clear();
+ mPreeditStandouts.clear();
+ }
+ if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode())
+ {
+ mPreeditOverwrittenWString = mPreeditWString;
+ }
+ else
+ {
+ mPreeditOverwrittenWString.clear();
+ }
+}
+
+S32 LLLineEditor::getPreeditFontSize() const
+{
+ return llround(mGLFont->getLineHeight() * LLUI::sGLScaleFactor.mV[VY]);
+}
+
+
LLSearchEditor::LLSearchEditor(const LLString& name,
const LLRect& rect,
S32 max_length_bytes,
diff --git a/indra/llui/lllineeditor.h b/indra/llui/lllineeditor.h
index f1b9fbe33e..a019353856 100644
--- a/indra/llui/lllineeditor.h
+++ b/indra/llui/lllineeditor.h
@@ -53,6 +53,8 @@
#include "lluistring.h"
#include "llviewborder.h"
+#include "llpreeditor.h"
+
class LLFontGL;
class LLLineEditorRollback;
class LLButton;
@@ -63,7 +65,7 @@ typedef BOOL (*LLLinePrevalidateFunc)(const LLWString &wstr);
// Classes
//
class LLLineEditor
-: public LLUICtrl, public LLEditMenuHandler
+: public LLUICtrl, public LLEditMenuHandler, protected LLPreeditor
{
friend class LLLineEditorRollback;
@@ -120,6 +122,7 @@ public:
// view overrides
virtual void draw();
virtual void reshape(S32 width,S32 height,BOOL called_from_parent=TRUE);
+ virtual void onFocusReceived();
virtual void onFocusLost();
virtual void setEnabled(BOOL enabled);
@@ -146,7 +149,7 @@ public:
const LLWString& getWText() const { return mText.getWString(); }
S32 getLength() const { return mText.length(); }
- S32 getCursor() { return mCursorPos; }
+ S32 getCursor() const { return mCursorPos; }
void setCursor( S32 pos );
void setCursorToEnd();
@@ -177,13 +180,13 @@ public:
void setIgnoreTab(BOOL b) { mIgnoreTab = b; }
void setPassDelete(BOOL b) { mPassDelete = b; }
- void setDrawAsterixes(BOOL b) { mDrawAsterixes = b; }
+ void setDrawAsterixes(BOOL 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); }
+ BOOL hasSelection() const { return (mSelectionStart != mSelectionEnd); }
void startSelection();
void endSelection();
void extendSelection(S32 new_cursor_pos);
@@ -199,7 +202,7 @@ public:
static BOOL isPartOfWord(llwchar c);
// Prevalidation controls which keystrokes can affect the editor
- void setPrevalidate( BOOL (*func)(const LLWString &) ) { mPrevalidateFunc = func; }
+ void setPrevalidate( BOOL (*func)(const LLWString &) );
static BOOL prevalidateFloat(const LLWString &str );
static BOOL prevalidateInt(const LLWString &str );
static BOOL prevalidatePositiveS32(const LLWString &str);
@@ -221,7 +224,7 @@ protected:
void addChar(const llwchar c);
void setCursorAtLocalPos(S32 local_mouse_x);
- S32 findPixelNearestPos(S32 cursor_offset = 0);
+ S32 findPixelNearestPos(S32 cursor_offset = 0) const;
void reportBadKeystroke();
BOOL handleSpecialKey(KEY key, MASK mask);
@@ -230,6 +233,19 @@ protected:
S32 handleCommitKey(KEY key, MASK mask);
protected:
+ void updateAllowingLanguageInput();
+ BOOL hasPreeditString() const;
+ // Implementation (overrides) of LLPreeditor
+ virtual void resetPreedit();
+ virtual void updatePreedit(const LLWString &preedit_string,
+ const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position);
+ virtual void markAsPreedit(S32 position, S32 length);
+ virtual void getPreeditRange(S32 *position, S32 *length) const;
+ virtual void getSelectionRange(S32 *position, S32 *length) const;
+ virtual BOOL getPreeditLocation(S32 query_position, LLCoordGL *coord, LLRect *bounds, LLRect *control) const;
+ virtual S32 getPreeditFontSize() const;
+
+protected:
LLUIString mText; // The string being edited.
LLString mPrevText; // Saved string for 'ESC' revert
LLUIString mLabel; // text label that is visible when no user text provided
@@ -241,8 +257,7 @@ protected:
LLViewBorder* mBorder;
const LLFontGL* mGLFont;
- S32 mMaxLengthChars; // Max number of characters
- S32 mMaxLengthBytes; // Max length of the UTF8 string.
+ S32 mMaxLengthBytes; // Max length of the UTF8 string in bytes
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;
@@ -288,6 +303,11 @@ protected:
BOOL mPassDelete;
BOOL mReadOnly;
+
+ LLWString mPreeditWString;
+ LLWString mPreeditOverwrittenWString;
+ std::vector<S32> mPreeditPositions;
+ LLPreeditor::standouts_t mPreeditStandouts;
};
diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp
index d150f8954e..46f9f515d7 100644
--- a/indra/llui/llmenugl.cpp
+++ b/indra/llui/llmenugl.cpp
@@ -632,7 +632,7 @@ public:
};
LLMenuItemSeparatorGL::LLMenuItemSeparatorGL( const LLString &name ) :
- LLMenuItemGL( SEPARATOR_NAME, SEPARATOR_LABEL )
+ LLMenuItemGL( name, SEPARATOR_LABEL )
{
}
@@ -2832,7 +2832,7 @@ LLMenuItemGL* LLMenuGL::highlightNextItem(LLMenuItemGL* cur_item, BOOL skip_disa
while(1)
{
// skip separators and disabled items
- if ((*next_item_iter)->getEnabled() && (*next_item_iter)->getName() != SEPARATOR_NAME)
+ if ((*next_item_iter)->getEnabled() && (*next_item_iter)->getType() != SEPARATOR_NAME)
{
if (cur_item)
{
diff --git a/indra/llui/lltextbox.cpp b/indra/llui/lltextbox.cpp
index efd42455e5..8bd7b1509f 100644
--- a/indra/llui/lltextbox.cpp
+++ b/indra/llui/lltextbox.cpp
@@ -174,7 +174,7 @@ BOOL LLTextBox::handleHover(S32 x, S32 y, MASK mask)
mHasHover = TRUE; // This should be set every frame during a hover.
return TRUE;
}
- return FALSE;
+ return LLView::handleHover(x,y,mask);
}
void LLTextBox::setText(const LLStringExplicit& text)
diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp
index d08997c3ed..5c8b7c7281 100644
--- a/indra/llui/lltexteditor.cpp
+++ b/indra/llui/lltexteditor.cpp
@@ -79,6 +79,15 @@ const F32 CURSOR_FLASH_DELAY = 1.0f; // in seconds
const S32 CURSOR_THICKNESS = 2;
const S32 SPACES_PER_TAB = 4;
+const F32 PREEDIT_MARKER_BRIGHTNESS = 0.4f;
+const S32 PREEDIT_MARKER_GAP = 1;
+const S32 PREEDIT_MARKER_POSITION = 2;
+const S32 PREEDIT_MARKER_THICKNESS = 1;
+const F32 PREEDIT_STANDOUT_BRIGHTNESS = 0.6f;
+const S32 PREEDIT_STANDOUT_GAP = 1;
+const S32 PREEDIT_STANDOUT_POSITION = 2;
+const S32 PREEDIT_STANDOUT_THICKNESS = 2;
+
LLColor4 LLTextEditor::mLinkColor = LLColor4::blue;
void (* LLTextEditor::mURLcallback)(const char*) = NULL;
bool (* LLTextEditor::mSecondlifeURLcallback)(const std::string&) = NULL;
@@ -274,14 +283,14 @@ private:
LLTextEditor::LLTextEditor(
const LLString& name,
const LLRect& rect,
- S32 max_length,
+ S32 max_length, // In bytes
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 ),
+ mMaxTextByteLength( max_length ),
mBaseDocIsPristine(TRUE),
mPristineCmd( NULL ),
mLastCmd( NULL ),
@@ -510,13 +519,27 @@ BOOL LLTextEditor::isPartOfWord(llwchar c) { return (c == '_') || isalnum(c); }
-void LLTextEditor::truncate()
+BOOL LLTextEditor::truncate()
{
- if (mWText.size() > (size_t)mMaxTextLength)
- {
- LLWString::truncate(mWText, mMaxTextLength);
- mTextIsUpToDate = FALSE;
+ BOOL did_truncate = FALSE;
+
+ // First rough check - if we're less than 1/4th the size, we're OK
+ if (mWText.size() >= (size_t) (mMaxTextByteLength / 4))
+ {
+ // Have to check actual byte size
+ S32 utf8_byte_size = wstring_utf8_length( mWText );
+ if ( utf8_byte_size > mMaxTextByteLength )
+ {
+ // Truncate safely in UTF-8
+ std::string temp_utf8_text = wstring_to_utf8str( mWText );
+ temp_utf8_text = utf8str_truncate( temp_utf8_text, mMaxTextByteLength );
+ mWText = utf8str_to_wstring( temp_utf8_text );
+ mTextIsUpToDate = FALSE;
+ did_truncate = TRUE;
+ }
}
+
+ return did_truncate;
}
void LLTextEditor::setText(const LLStringExplicit &utf8str)
@@ -750,12 +773,12 @@ S32 LLTextEditor::nextWordPos(S32 cursorPos) const
return cursorPos;
}
-S32 LLTextEditor::getLineCount()
+S32 LLTextEditor::getLineCount() const
{
return mLineStartList.size();
}
-S32 LLTextEditor::getLineStart( S32 line )
+S32 LLTextEditor::getLineStart( S32 line ) const
{
S32 num_lines = getLineCount();
if (num_lines == 0)
@@ -1604,7 +1627,7 @@ void LLTextEditor::removeChar()
// Add a single character to the text
S32 LLTextEditor::addChar(S32 pos, llwchar wc)
{
- if ((S32)mWText.length() == mMaxTextLength)
+ if ( (wstring_utf8_length( mWText ) + wchar_utf8_length( wc )) >= mMaxTextByteLength)
{
make_ui_sound("UISndBadKeystroke");
return 0;
@@ -2490,11 +2513,16 @@ void LLTextEditor::redo()
}
}
+void LLTextEditor::onFocusReceived()
+{
+ LLUICtrl::onFocusReceived();
+ updateAllowingLanguageInput();
+}
// virtual, from LLView
void LLTextEditor::onFocusLost()
{
- getWindow()->allowLanguageTextInput(FALSE);
+ updateAllowingLanguageInput();
// Route menu back to the default
if( gEditMenuHandler == this )
@@ -2521,6 +2549,7 @@ void LLTextEditor::setEnabled(BOOL enabled)
{
mReadOnly = read_only;
updateSegments();
+ updateAllowingLanguageInput();
}
}
@@ -2825,6 +2854,100 @@ void LLTextEditor::drawCursor()
}
}
+void LLTextEditor::drawPreeditMarker()
+{
+ if (!hasPreeditString())
+ {
+ return;
+ }
+
+ const llwchar *text = mWText.c_str();
+ const S32 text_len = getLength();
+ const S32 num_lines = getLineCount();
+
+ S32 cur_line = mScrollbar->getDocPos();
+ if (cur_line >= num_lines)
+ {
+ return;
+ }
+
+ const S32 line_height = llround( mGLFont->getLineHeight() );
+
+ S32 line_start = getLineStart(cur_line);
+ S32 line_y = mTextRect.mTop - line_height;
+ while((mTextRect.mBottom <= line_y) && (num_lines > cur_line))
+ {
+ 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;
+ }
+
+ // Does this line contain preedits?
+ if (line_start >= mPreeditPositions.back())
+ {
+ // We have passed the preedits.
+ break;
+ }
+ if (line_end > mPreeditPositions.front())
+ {
+ for (U32 i = 0; i < mPreeditStandouts.size(); i++)
+ {
+ S32 left = mPreeditPositions[i];
+ S32 right = mPreeditPositions[i + 1];
+ if (right <= line_start || left >= line_end)
+ {
+ continue;
+ }
+
+ S32 preedit_left = mTextRect.mLeft;
+ if (left > line_start)
+ {
+ preedit_left += mGLFont->getWidth(text, line_start, left - line_start, mAllowEmbeddedItems);
+ }
+ S32 preedit_right = mTextRect.mLeft;
+ if (right < line_end)
+ {
+ preedit_right += mGLFont->getWidth(text, line_start, right - line_start, mAllowEmbeddedItems);
+ }
+ else
+ {
+ preedit_right += mGLFont->getWidth(text, line_start, line_end - line_start, mAllowEmbeddedItems);
+ }
+
+ if (mPreeditStandouts[i])
+ {
+ gl_rect_2d(preedit_left + PREEDIT_STANDOUT_GAP,
+ line_y + PREEDIT_STANDOUT_POSITION,
+ preedit_right - PREEDIT_STANDOUT_GAP - 1,
+ line_y + PREEDIT_STANDOUT_POSITION - PREEDIT_STANDOUT_THICKNESS,
+ (mCursorColor * PREEDIT_STANDOUT_BRIGHTNESS + mWriteableBgColor * (1 - PREEDIT_STANDOUT_BRIGHTNESS)).setAlpha(1.0f));
+ }
+ else
+ {
+ gl_rect_2d(preedit_left + PREEDIT_MARKER_GAP,
+ line_y + PREEDIT_MARKER_POSITION,
+ preedit_right - PREEDIT_MARKER_GAP - 1,
+ line_y + PREEDIT_MARKER_POSITION - PREEDIT_MARKER_THICKNESS,
+ (mCursorColor * PREEDIT_MARKER_BRIGHTNESS + mWriteableBgColor * (1 - PREEDIT_MARKER_BRIGHTNESS)).setAlpha(1.0f));
+ }
+ }
+ }
+
+ // move down one line
+ line_y -= line_height;
+ line_start = next_start;
+ cur_line++;
+ }
+}
+
void LLTextEditor::drawText()
{
@@ -3025,6 +3148,7 @@ void LLTextEditor::draw()
drawBackground();
drawSelectionBackground();
+ drawPreeditMarker();
drawText();
drawCursor();
@@ -3067,10 +3191,10 @@ void LLTextEditor::setFocus( BOOL new_state )
// Don't change anything if the focus state didn't change
if (new_state == old_state) return;
- // Notify early if we are loosing focus.
+ // Notify early if we are losing focus.
if (!new_state)
{
- getWindow()->allowLanguageTextInput(FALSE);
+ getWindow()->allowLanguageTextInput(this, FALSE);
}
LLUICtrl::setFocus( new_state );
@@ -3093,12 +3217,6 @@ void LLTextEditor::setFocus( BOOL new_state )
endSelection();
}
-
- // Notify late if we are gaining focus.
- if (new_state && !mReadOnly)
- {
- getWindow()->allowLanguageTextInput(TRUE);
- }
}
BOOL LLTextEditor::acceptsTextInput() const
@@ -3540,22 +3658,20 @@ void LLTextEditor::removeTextFromEnd(S32 num_chars)
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;
+ S32 old_len = mWText.length(); // length() returns character length
+ S32 insert_len = wstr.length();
+ mWText.insert(pos, wstr);
+ mTextIsUpToDate = FALSE;
+
+ if ( truncate() )
+ {
// The user's not getting everything he's hoping for
make_ui_sound("UISndBadKeystroke");
+ insert_len = mWText.length() - old_len;
}
- mWText.insert(pos, wstr);
- mTextIsUpToDate = FALSE;
- truncate();
-
- return new_len - len;
+ return insert_len;
}
S32 LLTextEditor::removeStringNoUndo(S32 pos, S32 length)
@@ -3920,7 +4036,7 @@ BOOL LLTextEditor::importBuffer(const LLString& buffer )
return FALSE;
}
- if( text_len > mMaxTextLength )
+ if( text_len > mMaxTextByteLength )
{
llwarns << "Invalid Linden text length: " << text_len << llendl;
return FALSE;
@@ -4281,3 +4397,262 @@ BOOL LLTextEditor::findHTML(const LLString &line, S32 *begin, S32 *end)
}
return matched;
}
+
+
+
+void LLTextEditor::updateAllowingLanguageInput()
+{
+ if (hasFocus() && !mReadOnly)
+ {
+ getWindow()->allowLanguageTextInput(this, TRUE);
+ }
+ else
+ {
+ getWindow()->allowLanguageTextInput(this, FALSE);
+ }
+}
+
+// Preedit is managed off the undo/redo command stack.
+
+BOOL LLTextEditor::hasPreeditString() const
+{
+ return (mPreeditPositions.size() > 1);
+}
+
+void LLTextEditor::resetPreedit()
+{
+ if (hasPreeditString())
+ {
+ mCursorPos = mPreeditPositions.front();
+ removeStringNoUndo(mCursorPos, mPreeditPositions.back() - mCursorPos);
+ insertStringNoUndo(mCursorPos, mPreeditOverwrittenWString);
+
+ mPreeditWString.clear();
+ mPreeditOverwrittenWString.clear();
+ mPreeditPositions.clear();
+
+ updateLineStartList();
+ setCursorPos(mCursorPos);
+ // updateScrollFromCursor();
+ }
+}
+
+void LLTextEditor::updatePreedit(const LLWString &preedit_string,
+ const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position)
+{
+ // Just in case.
+ if (mReadOnly)
+ {
+ return;
+ }
+
+ if (hasSelection())
+ {
+ if (hasPreeditString())
+ {
+ llwarns << "Preedit and selection!" << llendl;
+ deselect();
+ }
+ else
+ {
+ deleteSelection(TRUE);
+ }
+ }
+
+ getWindow()->hideCursorUntilMouseMove();
+
+ S32 insert_preedit_at = mCursorPos;
+ if (hasPreeditString())
+ {
+ insert_preedit_at = mPreeditPositions.front();
+ removeStringNoUndo(insert_preedit_at, mPreeditPositions.back() - insert_preedit_at);
+ insertStringNoUndo(insert_preedit_at, mPreeditOverwrittenWString);
+ }
+
+ mPreeditWString = preedit_string;
+ mPreeditPositions.resize(preedit_segment_lengths.size() + 1);
+ S32 position = insert_preedit_at;
+ for (segment_lengths_t::size_type i = 0; i < preedit_segment_lengths.size(); i++)
+ {
+ mPreeditPositions[i] = position;
+ position += preedit_segment_lengths[i];
+ }
+ mPreeditPositions.back() = position;
+
+ if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode())
+ {
+ mPreeditOverwrittenWString = getWSubString(insert_preedit_at, mPreeditWString.length());
+ removeStringNoUndo(insert_preedit_at, mPreeditWString.length());
+ }
+ else
+ {
+ mPreeditOverwrittenWString.clear();
+ }
+ insertStringNoUndo(insert_preedit_at, mPreeditWString);
+
+ mPreeditStandouts = preedit_standouts;
+
+ updateLineStartList();
+ setCursorPos(insert_preedit_at + caret_position);
+ // updateScrollFromCursor();
+
+ // Update of the preedit should be caused by some key strokes.
+ mKeystrokeTimer.reset();
+}
+
+BOOL LLTextEditor::getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect *bounds, LLRect *control) const
+{
+ if (control)
+ {
+ LLRect control_rect_screen;
+ localRectToScreen(mTextRect, &control_rect_screen);
+ LLUI::screenRectToGL(control_rect_screen, control);
+ }
+
+ S32 preedit_left_position, preedit_right_position;
+ if (hasPreeditString())
+ {
+ preedit_left_position = mPreeditPositions.front();
+ preedit_right_position = mPreeditPositions.back();
+ }
+ else
+ {
+ preedit_left_position = preedit_right_position = mCursorPos;
+ }
+
+ const S32 query = (query_offset >= 0 ? preedit_left_position + query_offset : mCursorPos);
+ if (query < preedit_left_position || query > preedit_right_position)
+ {
+ return FALSE;
+ }
+
+ const S32 first_visible_line = mScrollbar->getDocPos();
+ if (query < getLineStart(first_visible_line))
+ {
+ return FALSE;
+ }
+
+ S32 current_line = first_visible_line;
+ S32 current_line_start, current_line_end;
+ for (;;)
+ {
+ current_line_start = getLineStart(current_line);
+ current_line_end = getLineStart(current_line + 1);
+ if (query >= current_line_start && query < current_line_end)
+ {
+ break;
+ }
+ if (current_line_start == current_line_end)
+ {
+ // We have reached on the last line. The query position must be here.
+ break;
+ }
+ current_line++;
+ }
+
+ const llwchar * const text = mWText.c_str();
+ const S32 line_height = llround(mGLFont->getLineHeight());
+
+ if (coord)
+ {
+ const S32 query_x = mTextRect.mLeft + mGLFont->getWidth(text, current_line_start, query - current_line_start, mAllowEmbeddedItems);
+ const S32 query_y = mTextRect.mTop - (current_line - first_visible_line) * line_height - line_height / 2;
+ S32 query_screen_x, query_screen_y;
+ localPointToScreen(query_x, query_y, &query_screen_x, &query_screen_y);
+ LLUI::screenPointToGL(query_screen_x, query_screen_y, &coord->mX, &coord->mY);
+ }
+
+ if (bounds)
+ {
+ S32 preedit_left = mTextRect.mLeft;
+ if (preedit_left_position > current_line_start)
+ {
+ preedit_left += mGLFont->getWidth(text, current_line_start, preedit_left_position - current_line_start, mAllowEmbeddedItems);
+ }
+
+ S32 preedit_right = mTextRect.mLeft;
+ if (preedit_right_position < current_line_end)
+ {
+ preedit_right += mGLFont->getWidth(text, current_line_start, preedit_right_position - current_line_start, mAllowEmbeddedItems);
+ }
+ else
+ {
+ preedit_right += mGLFont->getWidth(text, current_line_start, current_line_end - current_line_start, mAllowEmbeddedItems);
+ }
+
+ const S32 preedit_top = mTextRect.mTop - (current_line - first_visible_line) * line_height;
+ const S32 preedit_bottom = preedit_top - line_height;
+
+ const LLRect preedit_rect_local(preedit_left, preedit_top, preedit_right, preedit_bottom);
+ LLRect preedit_rect_screen;
+ localRectToScreen(preedit_rect_local, &preedit_rect_screen);
+ LLUI::screenRectToGL(preedit_rect_screen, bounds);
+ }
+
+ return TRUE;
+}
+
+void LLTextEditor::getSelectionRange(S32 *position, S32 *length) const
+{
+ if (hasSelection())
+ {
+ *position = llmin(mSelectionStart, mSelectionEnd);
+ *length = llabs(mSelectionStart - mSelectionEnd);
+ }
+ else
+ {
+ *position = mCursorPos;
+ *length = 0;
+ }
+}
+
+void LLTextEditor::getPreeditRange(S32 *position, S32 *length) const
+{
+ if (hasPreeditString())
+ {
+ *position = mPreeditPositions.front();
+ *length = mPreeditPositions.back() - mPreeditPositions.front();
+ }
+ else
+ {
+ *position = mCursorPos;
+ *length = 0;
+ }
+}
+
+void LLTextEditor::markAsPreedit(S32 position, S32 length)
+{
+ deselect();
+ setCursorPos(position);
+ if (hasPreeditString())
+ {
+ llwarns << "markAsPreedit invoked when hasPreeditString is true." << llendl;
+ }
+ mPreeditWString = LLWString( mWText, position, length );
+ if (length > 0)
+ {
+ mPreeditPositions.resize(2);
+ mPreeditPositions[0] = position;
+ mPreeditPositions[1] = position + length;
+ mPreeditStandouts.resize(1);
+ mPreeditStandouts[0] = FALSE;
+ }
+ else
+ {
+ mPreeditPositions.clear();
+ mPreeditStandouts.clear();
+ }
+ if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode())
+ {
+ mPreeditOverwrittenWString = mPreeditWString;
+ }
+ else
+ {
+ mPreeditOverwrittenWString.clear();
+ }
+}
+
+S32 LLTextEditor::getPreeditFontSize() const
+{
+ return llround(mGLFont->getLineHeight() * LLUI::sGLScaleFactor.mV[VY]);
+}
diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h
index d38accca8f..7049de7b89 100644
--- a/indra/llui/lltexteditor.h
+++ b/indra/llui/lltexteditor.h
@@ -43,6 +43,8 @@
#include "lleditmenuhandler.h"
#include "lldarray.h"
+#include "llpreeditor.h"
+
class LLFontGL;
class LLScrollbar;
class LLViewBorder;
@@ -64,7 +66,7 @@ const S32 MAX_EMBEDDED_ITEMS = LAST_EMBEDDED_CHAR - FIRST_EMBEDDED_CHAR + 1;
class LLTextSegment;
class LLTextCmd;
-class LLTextEditor : public LLUICtrl, LLEditMenuHandler
+class LLTextEditor : public LLUICtrl, LLEditMenuHandler, protected LLPreeditor
{
friend class LLTextCmd;
public:
@@ -104,6 +106,7 @@ public:
// view overrides
virtual void reshape(S32 width, S32 height, BOOL called_from_parent);
virtual void draw();
+ virtual void onFocusReceived();
virtual void onFocusLost();
virtual void setEnabled(BOOL enabled);
@@ -234,7 +237,8 @@ public:
void setText(const LLStringExplicit &utf8str);
void setWText(const LLWString &wtext);
- S32 getMaxLength() const { return mMaxTextLength; }
+ // Returns byte length limit
+ S32 getMaxLength() const { return mMaxTextByteLength; }
// Change cursor
void startOfLine();
@@ -259,6 +263,7 @@ protected:
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 drawPreeditMarker();
void updateLineStartList(S32 startpos = 0);
void updateScrollFromCursor();
@@ -267,7 +272,7 @@ protected:
void pruneSegments();
void assignEmbedded(const LLString &s);
- void truncate();
+ BOOL truncate(); // Returns true if truncation occurs
static BOOL isPartOfWord(llwchar c);
@@ -291,7 +296,7 @@ protected:
BOOL handleControlKey(const KEY key, const MASK mask);
BOOL handleEditKey(const KEY key, const MASK mask);
- BOOL hasSelection() { return (mSelectionStart !=mSelectionEnd); }
+ BOOL hasSelection() const { return (mSelectionStart !=mSelectionEnd); }
BOOL selectionContainsLineBreaks();
void startSelection();
void endSelection();
@@ -300,8 +305,8 @@ protected:
S32 prevWordPos(S32 cursorPos) const;
S32 nextWordPos(S32 cursorPos) const;
- S32 getLineCount();
- S32 getLineStart( S32 line );
+ S32 getLineCount() const;
+ S32 getLineStart( S32 line ) const;
void getLineAndOffset(S32 pos, S32* linep, S32* offsetp);
S32 getPos(S32 line, S32 offset);
@@ -338,6 +343,20 @@ protected:
S32 removeStringNoUndo(S32 pos, S32 length);
S32 overwriteCharNoUndo(S32 pos, llwchar wc);
+protected:
+ void updateAllowingLanguageInput();
+ BOOL hasPreeditString() const;
+
+ // Overrides LLPreeditor
+ virtual void resetPreedit();
+ virtual void updatePreedit(const LLWString &preedit_string,
+ const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position);
+ virtual void markAsPreedit(S32 position, S32 length);
+ virtual void getPreeditRange(S32 *position, S32 *length) const;
+ virtual void getSelectionRange(S32 *position, S32 *length) const;
+ virtual BOOL getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect *bounds, LLRect *control) const;
+ virtual S32 getPreeditFontSize() const;
+
public:
LLKeywords mKeywords;
static LLColor4 mLinkColor;
@@ -349,7 +368,7 @@ protected:
mutable LLString mUTF8Text;
mutable BOOL mTextIsUpToDate;
- S32 mMaxTextLength; // Maximum length mText is allowed to be
+ S32 mMaxTextByteLength; // Maximum length mText is allowed to be in bytes
const LLFontGL* mGLFont;
@@ -439,6 +458,11 @@ protected:
BOOL mParseHTML;
LLString mHTML;
+
+ LLWString mPreeditWString;
+ LLWString mPreeditOverwrittenWString;
+ std::vector<S32> mPreeditPositions;
+ std::vector<BOOL> mPreeditStandouts;
};
class LLTextSegment
diff --git a/indra/llui/llui.cpp b/indra/llui/llui.cpp
index 7af0d726cb..00a230dff3 100644
--- a/indra/llui/llui.cpp
+++ b/indra/llui/llui.cpp
@@ -1696,6 +1696,34 @@ LLVector2 LLUI::getWindowSize()
}
//static
+void LLUI::screenPointToGL(S32 screen_x, S32 screen_y, S32 *gl_x, S32 *gl_y)
+{
+ *gl_x = llround((F32)screen_x * sGLScaleFactor.mV[VX]);
+ *gl_y = llround((F32)screen_y * sGLScaleFactor.mV[VY]);
+}
+
+//static
+void LLUI::glPointToScreen(S32 gl_x, S32 gl_y, S32 *screen_x, S32 *screen_y)
+{
+ *screen_x = llround((F32)gl_x / sGLScaleFactor.mV[VX]);
+ *screen_y = llround((F32)gl_y / sGLScaleFactor.mV[VY]);
+}
+
+//static
+void LLUI::screenRectToGL(const LLRect& screen, LLRect *gl)
+{
+ screenPointToGL(screen.mLeft, screen.mTop, &gl->mLeft, &gl->mTop);
+ screenPointToGL(screen.mRight, screen.mBottom, &gl->mRight, &gl->mBottom);
+}
+
+//static
+void LLUI::glRectToScreen(const LLRect& gl, LLRect *screen)
+{
+ glPointToScreen(gl.mLeft, gl.mTop, &screen->mLeft, &screen->mTop);
+ glPointToScreen(gl.mRight, gl.mBottom, &screen->mRight, &screen->mBottom);
+}
+
+//static
LLUUID LLUI::findAssetUUIDByName(const LLString &asset_name)
{
if(asset_name == LLString::null) return LLUUID::null;
diff --git a/indra/llui/llui.h b/indra/llui/llui.h
index b78b046a8c..b98f4d5de2 100644
--- a/indra/llui/llui.h
+++ b/indra/llui/llui.h
@@ -174,6 +174,10 @@ public:
static void setLineWidth(F32 width);
static LLUUID findAssetUUIDByName(const LLString& name);
static LLVector2 getWindowSize();
+ static void screenPointToGL(S32 screen_x, S32 screen_y, S32 *gl_x, S32 *gl_y);
+ static void glPointToScreen(S32 gl_x, S32 gl_y, S32 *screen_x, S32 *screen_y);
+ static void screenRectToGL(const LLRect& screen, LLRect *gl);
+ static void glRectToScreen(const LLRect& gl, LLRect *screen);
static void setHtmlHelp(LLHtmlHelp* html_help);
private: