summaryrefslogtreecommitdiff
path: root/indra/llwindow
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llwindow')
-rw-r--r--indra/llwindow/llpreeditor.h81
-rw-r--r--indra/llwindow/llwindow.h6
-rw-r--r--indra/llwindow/llwindowmacosx.cpp382
-rw-r--r--indra/llwindow/llwindowmacosx.h7
-rw-r--r--indra/llwindow/llwindowwin32.cpp640
-rw-r--r--indra/llwindow/llwindowwin32.h16
6 files changed, 1118 insertions, 14 deletions
diff --git a/indra/llwindow/llpreeditor.h b/indra/llwindow/llpreeditor.h
new file mode 100644
index 0000000000..63e64ab781
--- /dev/null
+++ b/indra/llwindow/llpreeditor.h
@@ -0,0 +1,81 @@
+/**
+ * @file llpreeditor.h
+ * @brief abstract class that defines interface for components to feedback preedits to users.
+ *
+ * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc.
+ * $License$
+ */
+
+#ifndef LL_PREEDITOR
+#define LL_PREEDITOR
+
+class LLPreeditor
+{
+public:
+
+ typedef std::vector<S32> segment_lengths_t;
+ typedef std::vector<BOOL> standouts_t;
+
+ // We don't delete against LLPreeditor, but compilers complain without this...
+
+ virtual ~LLPreeditor() {};
+
+ // Discard any preedit info. on this preeditor.
+
+ virtual void resetPreedit() = 0;
+
+ // Update the preedit feedback using specified details.
+ // Existing preedit is discarded and replaced with the new one. (I.e., updatePreedit is not cumulative.)
+ // All arguments are IN.
+ // preedit_count is the number of elements in arrays preedit_list and preedit_standouts.
+ // preedit list is an array of preedit texts (clauses.)
+ // preedit_standouts indicates whether each preedit text should be shown as standout clause.
+ // caret_position is the preedit-local position of text editing caret, in # of llwchar.
+
+ virtual void updatePreedit(const LLWString &preedit_string,
+ const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position) = 0;
+
+ // Turn the specified sub-contents into an active preedit.
+ // Both position and length are IN and count with UTF-32 (llwchar) characters.
+ // This method primarily facilitates reconversion.
+
+ virtual void markAsPreedit(S32 position, S32 length) = 0;
+
+ // Get the position and the length of the active preedit in the contents.
+ // Both position and length are OUT and count with UTF-32 (llwchar) characters.
+ // When this preeditor has no active preedit, position receives
+ // the caret position, and length receives 0.
+
+ virtual void getPreeditRange(S32 *position, S32 *length) const = 0;
+
+ // Get the position and the length of the current selection in the contents.
+ // Both position and length are OUT and count with UTF-32 (llwchar) characters.
+ // When this preeditor has no selection, position receives
+ // the caret position, and length receives 0.
+
+ virtual void getSelectionRange(S32 *position, S32 *length) const = 0;
+
+ // Get the locations where the preedit and related UI elements are displayed.
+ // Locations are relative to the app window and measured in GL coordinate space (before scaling.)
+ // query_position is IN argument, and other three are OUT.
+
+ virtual BOOL getPreeditLocation(S32 query_position, LLCoordGL *coord, LLRect *bounds, LLRect *control) const = 0;
+
+ // Get the size (height) of the current font used in this preeditor.
+
+ virtual S32 getPreeditFontSize() const = 0;
+
+ // Get the contents of this preeditor as a LLWString. If there is an active preedit,
+ // the returned LLWString contains it.
+
+ virtual const LLWString & getWText() const = 0;
+
+ // Handle a UTF-32 char on this preeditor, i.e., add the character
+ // to the contents.
+ // This is a back door of the method of same name of LLWindowCallback.
+ // called_from_parent should be set to FALSE if calling through LLPreeditor.
+
+ virtual BOOL handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent) = 0;
+};
+
+#endif
diff --git a/indra/llwindow/llwindow.h b/indra/llwindow/llwindow.h
index 4db2c8105f..9a3542d3fb 100644
--- a/indra/llwindow/llwindow.h
+++ b/indra/llwindow/llwindow.h
@@ -81,6 +81,8 @@ class LLSplashScreen;
class LLWindow;
+class LLPreeditor;
+
class LLWindowCallbacks
{
public:
@@ -222,8 +224,10 @@ public:
virtual void *getPlatformWindow() = 0;
// control platform's Language Text Input mechanisms.
- virtual void allowLanguageTextInput( BOOL b ) {};
+ virtual void allowLanguageTextInput(LLPreeditor *preeditor, BOOL b) {}
virtual void setLanguageTextInput( const LLCoordGL & pos ) {};
+ virtual void updateLanguageTextInputArea() {}
+ virtual void interruptLanguageTextInput() {}
protected:
LLWindow(BOOL fullscreen, U32 flags);
diff --git a/indra/llwindow/llwindowmacosx.cpp b/indra/llwindow/llwindowmacosx.cpp
index 2724cb56e4..943b98e9d5 100644
--- a/indra/llwindow/llwindowmacosx.cpp
+++ b/indra/llwindow/llwindowmacosx.cpp
@@ -48,6 +48,7 @@
#include "indra_constants.h"
#include "llwindowmacosx-objc.h"
+#include "llpreeditor.h"
extern BOOL gDebugWindowProc;
@@ -172,8 +173,22 @@ static EventTypeSpec WindowHandlerEventList[] =
{ kEventClassKeyboard, kEventRawKeyModifiersChanged },
// Text input events
- { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent }
-
+ { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent },
+ { kEventClassTextInput, kEventTextInputUpdateActiveInputArea },
+ { kEventClassTextInput, kEventTextInputOffsetToPos },
+ { kEventClassTextInput, kEventTextInputPosToOffset },
+ { kEventClassTextInput, kEventTextInputShowHideBottomWindow },
+ { kEventClassTextInput, kEventTextInputGetSelectedText },
+ { kEventClassTextInput, kEventTextInputFilterText },
+
+ // TSM Document Access events (advanced input method support)
+ { kEventClassTSMDocumentAccess, kEventTSMDocumentAccessGetLength },
+ { kEventClassTSMDocumentAccess, kEventTSMDocumentAccessGetSelectedRange },
+ { kEventClassTSMDocumentAccess, kEventTSMDocumentAccessGetCharacters },
+ { kEventClassTSMDocumentAccess, kEventTSMDocumentAccessGetFont },
+ { kEventClassTSMDocumentAccess, kEventTSMDocumentAccessGetGlyphInfo },
+ { kEventClassTSMDocumentAccess, kEventTSMDocumentAccessLockDocument },
+ { kEventClassTSMDocumentAccess, kEventTSMDocumentAccessUnlockDocument }
};
static EventTypeSpec GlobalHandlerEventList[] =
@@ -195,7 +210,22 @@ static EventTypeSpec GlobalHandlerEventList[] =
{ kEventClassKeyboard, kEventRawKeyModifiersChanged },
// Text input events
- { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent }
+ { kEventClassTextInput, kEventTextInputUpdateActiveInputArea },
+ { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent },
+ { kEventClassTextInput, kEventTextInputOffsetToPos },
+ { kEventClassTextInput, kEventTextInputPosToOffset },
+ { kEventClassTextInput, kEventTextInputShowHideBottomWindow },
+ { kEventClassTextInput, kEventTextInputGetSelectedText },
+ { kEventClassTextInput, kEventTextInputFilterText },
+
+ // TSM Document Access events (advanced input method support)
+ { kEventClassTSMDocumentAccess, kEventTSMDocumentAccessGetLength },
+ { kEventClassTSMDocumentAccess, kEventTSMDocumentAccessGetSelectedRange },
+ { kEventClassTSMDocumentAccess, kEventTSMDocumentAccessGetCharacters },
+ { kEventClassTSMDocumentAccess, kEventTSMDocumentAccessGetFont },
+ { kEventClassTSMDocumentAccess, kEventTSMDocumentAccessGetGlyphInfo },
+ { kEventClassTSMDocumentAccess, kEventTSMDocumentAccessLockDocument },
+ { kEventClassTSMDocumentAccess, kEventTSMDocumentAccessUnlockDocument }
};
static EventTypeSpec CommandHandlerEventList[] =
@@ -246,6 +276,7 @@ LLWindowMacOSX::LLWindowMacOSX(char *title, char *name, S32 x, S32 y, S32 width,
mLanguageTextInputAllowed = FALSE;
mTSMScriptCode = 0;
mTSMLangCode = 0;
+ mPreeditor = NULL;
// For reasons that aren't clear to me, LLTimers seem to be created in the "started" state.
// Since the started state of this one is used to track whether the NMRec has been installed, it wants to start out in the "stopped" state.
@@ -497,15 +528,16 @@ BOOL LLWindowMacOSX::createContext(int x, int y, int width, int height, int bits
mTSMDocument = NULL;
}
static InterfaceTypeList types = { kUnicodeDocument };
- OSErr err = NewTSMDocument(1, types, &mTSMDocument, 0);
+ err = NewTSMDocument(1, types, &mTSMDocument, 0);
if (err != noErr)
{
llwarns << "createContext: couldn't create a TSMDocument (" << err << ")" << llendl;
}
if (mTSMDocument)
{
- UseInputWindow(mTSMDocument, TRUE);
ActivateTSMDocument(mTSMDocument);
+ UseInputWindow(mTSMDocument, FALSE);
+ allowLanguageTextInput(NULL, FALSE);
}
}
@@ -1949,6 +1981,141 @@ OSStatus LLWindowMacOSX::eventHandler (EventHandlerCallRef myHandler, EventRef e
{
switch (evtKind)
{
+ case kEventTextInputUpdateActiveInputArea:
+ {
+ EventParamType param_type;
+
+ long fix_len;
+ UInt32 text_len;
+ if (mPreeditor
+ && (result = GetEventParameter(event, kEventParamTextInputSendFixLen,
+ typeLongInteger, &param_type, sizeof(fix_len), NULL, &fix_len)) == noErr
+ && typeLongInteger == param_type
+ && (result = GetEventParameter(event, kEventParamTextInputSendText,
+ typeUnicodeText, &param_type, 0, &text_len, NULL)) == noErr
+ && typeUnicodeText == param_type)
+ {
+ // Handle an optional (but essential to facilitate TSMDA) ReplaceRange param.
+ CFRange range;
+ if (GetEventParameter(event, kEventParamTextInputSendReplaceRange,
+ typeCFRange, &param_type, sizeof(range), NULL, &range) == noErr
+ && typeCFRange == param_type)
+ {
+ // Although the spec. is unclear, replace range should
+ // not present when there is an active preedit. We just
+ // ignore the case. markAsPreedit will detect the case and warn it.
+ const LLWString & text = mPreeditor->getWText();
+ const S32 location = wstring_wstring_length_from_utf16_length(text, 0, range.location);
+ const S32 length = wstring_wstring_length_from_utf16_length(text, location, range.length);
+ mPreeditor->markAsPreedit(location, length);
+ }
+ mPreeditor->resetPreedit();
+
+ // Receive the text from input method.
+ U16 *const text = new U16[text_len / sizeof(U16)];
+ GetEventParameter(event, kEventParamTextInputSendText, typeUnicodeText, NULL, text_len, NULL, text);
+ if (fix_len < 0)
+ {
+ // Do we still need this? Seems obsolete...
+ fix_len = text_len;
+ }
+ const LLWString fix_string
+ = utf16str_to_wstring(llutf16string(text, fix_len / sizeof(U16)));
+ const LLWString preedit_string
+ = utf16str_to_wstring(llutf16string(text + fix_len / sizeof(U16), (text_len - fix_len) / sizeof(U16)));
+ delete[] text;
+
+ // Handle fixed (comitted) string.
+ if (fix_string.length() > 0)
+ {
+ for (LLWString::const_iterator i = fix_string.begin(); i != fix_string.end(); i++)
+ {
+ mPreeditor->handleUnicodeCharHere(*i, FALSE);
+ }
+ }
+
+ // Receive the segment info and caret position.
+ LLPreeditor::segment_lengths_t preedit_segment_lengths;
+ LLPreeditor::standouts_t preedit_standouts;
+ S32 caret_position = preedit_string.length();
+ UInt32 text_range_array_size;
+ if (GetEventParameter(event, kEventParamTextInputSendHiliteRng, typeTextRangeArray,
+ &param_type, 0, &text_range_array_size, NULL) == noErr
+ && typeTextRangeArray == param_type
+ && text_range_array_size > sizeof(TextRangeArray))
+ {
+ // TextRangeArray is a variable-length struct.
+ TextRangeArray * const text_range_array = (TextRangeArray *) new char[text_range_array_size];
+ GetEventParameter(event, kEventParamTextInputSendHiliteRng, typeTextRangeArray,
+ NULL, text_range_array_size, NULL, text_range_array);
+
+ // WARNING: We assume ranges are in ascending order,
+ // although the condition is undocumented. It seems
+ // OK to assume this. I also assumed
+ // the ranges are contiguous in previous versions, but I
+ // have heard a rumore that older versions os ATOK may
+ // return ranges with some _gap_. I don't know whether
+ // it is true, but I'm preparing my code for the case.
+
+ const S32 ranges = text_range_array->fNumOfRanges;
+ preedit_segment_lengths.reserve(ranges);
+ preedit_standouts.reserve(ranges);
+
+ S32 last_bytes = 0;
+ S32 last_utf32 = 0;
+ for (S32 i = 0; i < ranges; i++)
+ {
+ const TextRange &range = text_range_array->fRange[i];
+ if (range.fStart > last_bytes)
+ {
+ const S32 length_utf16 = (range.fStart - last_bytes) / sizeof(U16);
+ const S32 length_utf32 = wstring_wstring_length_from_utf16_length(preedit_string, last_utf32, length_utf16);
+ preedit_segment_lengths.push_back(length_utf32);
+ preedit_standouts.push_back(FALSE);
+ last_utf32 += length_utf32;
+ }
+ if (range.fEnd > range.fStart)
+ {
+ const S32 length_utf16 = (range.fEnd - range.fStart) / sizeof(U16);
+ const S32 length_utf32 = wstring_wstring_length_from_utf16_length(preedit_string, last_utf32, length_utf16);
+ preedit_segment_lengths.push_back(length_utf32);
+ preedit_standouts.push_back(
+ kTSMHiliteSelectedRawText == range.fHiliteStyle
+ || kTSMHiliteSelectedConvertedText == range.fHiliteStyle
+ || kTSMHiliteSelectedText == range.fHiliteStyle);
+ last_utf32 += length_utf32;
+ }
+ if (kTSMHiliteCaretPosition == range.fHiliteStyle)
+ {
+ caret_position = last_utf32;
+ }
+ last_bytes = range.fEnd;
+ }
+ if (preedit_string.length() > last_utf32)
+ {
+ preedit_segment_lengths.push_back(preedit_string.length() - last_utf32);
+ preedit_standouts.push_back(FALSE);
+ }
+
+ delete[] (char *) text_range_array;
+ }
+
+ // Handle preedit string.
+ if (preedit_string.length() > 0)
+ {
+ if (preedit_segment_lengths.size() == 0)
+ {
+ preedit_segment_lengths.push_back(preedit_string.length());
+ preedit_standouts.push_back(FALSE);
+ }
+ mPreeditor->updatePreedit(preedit_string, preedit_segment_lengths, preedit_standouts, caret_position);
+ }
+
+ result = noErr;
+ }
+ }
+ break;
+
case kEventTextInputUnicodeForKeyEvent:
{
UInt32 modifiers = 0;
@@ -2021,6 +2188,63 @@ OSStatus LLWindowMacOSX::eventHandler (EventHandlerCallRef myHandler, EventRef e
result = err;
}
break;
+
+ case kEventTextInputOffsetToPos:
+ {
+ EventParamType param_type;
+ long offset;
+ if (mPreeditor
+ && GetEventParameter(event, kEventParamTextInputSendTextOffset, typeLongInteger,
+ &param_type, sizeof(offset), NULL, &offset) == noErr
+ && typeLongInteger == param_type)
+ {
+ S32 preedit, preedit_length;
+ mPreeditor->getPreeditRange(&preedit, &preedit_length);
+ const LLWString & text = mPreeditor->getWText();
+
+ LLCoordGL caret_coord;
+ LLRect preedit_bounds;
+ if (0 <= offset
+ && mPreeditor->getPreeditLocation(wstring_wstring_length_from_utf16_length(text, preedit, offset / sizeof(U16)),
+ &caret_coord, &preedit_bounds, NULL))
+ {
+ LLCoordGL caret_base_coord(caret_coord.mX, preedit_bounds.mBottom);
+ LLCoordScreen caret_base_coord_screen;
+ convertCoords(caret_base_coord, &caret_base_coord_screen);
+ Point qd_point;
+ qd_point.h = caret_base_coord_screen.mX;
+ qd_point.v = caret_base_coord_screen.mY;
+ SetEventParameter(event, kEventParamTextInputReplyPoint, typeQDPoint, sizeof(qd_point), &qd_point);
+
+ short line_height = (short) preedit_bounds.getHeight();
+ SetEventParameter(event, kEventParamTextInputReplyLineHeight, typeShortInteger, sizeof(line_height), &line_height);
+
+ result = noErr;
+ }
+ else
+ {
+ result = errOffsetInvalid;
+ }
+ }
+ }
+ break;
+
+ case kEventTextInputGetSelectedText:
+ {
+ if (mPreeditor)
+ {
+ S32 selection, selection_length;
+ mPreeditor->getSelectionRange(&selection, &selection_length);
+ if (selection_length)
+ {
+ const LLWString text = mPreeditor->getWText().substr(selection, selection_length);
+ const llutf16string text_utf16 = wstring_to_utf16str(text);
+ result = SetEventParameter(event, kEventParamTextInputReplyText, typeUnicodeText,
+ text_utf16.length() * sizeof(U16), text_utf16.c_str());
+ }
+ }
+ }
+ break;
}
}
break;
@@ -2194,6 +2418,13 @@ OSStatus LLWindowMacOSX::eventHandler (EventHandlerCallRef myHandler, EventRef e
switch (evtKind)
{
case kEventMouseDown:
+ if (mLanguageTextInputAllowed)
+ {
+ // We need to interrupt before handling mouse events,
+ // so that the fixed string from IM are delivered to
+ // the currently focused UI component.
+ interruptLanguageTextInput();
+ }
switch(button)
{
case kEventMouseButtonPrimary:
@@ -2287,6 +2518,10 @@ OSStatus LLWindowMacOSX::eventHandler (EventHandlerCallRef myHandler, EventRef e
mCallbacks->handleFocus(this);
break;
case kEventWindowDeactivated:
+ if (mTSMDocument)
+ {
+ DeactivateTSMDocument(mTSMDocument);
+ }
mCallbacks->handleFocusLost(this);
break;
case kEventWindowBoundsChanging:
@@ -2359,6 +2594,109 @@ OSStatus LLWindowMacOSX::eventHandler (EventHandlerCallRef myHandler, EventRef e
// BringToFront(mWindow);
// result = noErr;
break;
+
+ }
+ break;
+
+ case kEventClassTSMDocumentAccess:
+ if (mPreeditor)
+ {
+ switch(evtKind)
+ {
+
+ case kEventTSMDocumentAccessGetLength:
+ {
+ // Return the number of UTF-16 units in the text, excluding those for preedit.
+
+ S32 preedit, preedit_length;
+ mPreeditor->getPreeditRange(&preedit, &preedit_length);
+ const LLWString & text = mPreeditor->getWText();
+ const CFIndex length = wstring_utf16_length(text, 0, preedit)
+ + wstring_utf16_length(text, preedit + preedit_length, text.length());
+ result = SetEventParameter(event, kEventParamTSMDocAccessCharacterCount, typeCFIndex, sizeof(length), &length);
+ }
+ break;
+
+ case kEventTSMDocumentAccessGetSelectedRange:
+ {
+ // Return the selected range, excluding preedit.
+ // In our preeditor, preedit and selection are exclusive, so,
+ // when it has a preedit, there is no selection and the
+ // insertion point is on the preedit that corrupses into the
+ // beginning of the preedit when the preedit was removed.
+
+ S32 preedit, preedit_length;
+ mPreeditor->getPreeditRange(&preedit, &preedit_length);
+ const LLWString & text = mPreeditor->getWText();
+
+ CFRange range;
+ if (preedit_length)
+ {
+ range.location = wstring_utf16_length(text, 0, preedit);
+ range.length = 0;
+ }
+ else
+ {
+ S32 selection, selection_length;
+ mPreeditor->getSelectionRange(&selection, &selection_length);
+ range.location = wstring_utf16_length(text, 0, selection);
+ range.length = wstring_utf16_length(text, selection, selection_length);
+ }
+
+ result = SetEventParameter(event, kEventParamTSMDocAccessReplyCharacterRange, typeCFRange, sizeof(range), &range);
+ }
+ break;
+
+ case kEventTSMDocumentAccessGetCharacters:
+ {
+ UniChar *target_pointer;
+ CFRange range;
+ EventParamType param_type;
+ if ((result = GetEventParameter(event, kEventParamTSMDocAccessSendCharacterRange,
+ typeCFRange, &param_type, sizeof(range), NULL, &range)) == noErr
+ && typeCFRange == param_type
+ && (result = GetEventParameter(event, kEventParamTSMDocAccessSendCharactersPtr,
+ typePtr, &param_type, sizeof(target_pointer), NULL, &target_pointer)) == noErr
+ && typePtr == param_type)
+ {
+ S32 preedit, preedit_length;
+ mPreeditor->getPreeditRange(&preedit, &preedit_length);
+ const LLWString & text = mPreeditor->getWText();
+
+ // The GetCharacters event of TSMDA has a fundamental flaw;
+ // An input method need to decide the starting offset and length
+ // *before* it actually see the contents, so it is impossible
+ // to guarantee the character-aligned access. The event reply
+ // has no way to indicate a condition something like "Request
+ // was not fulfilled due to unaligned access. Please retry."
+ // Any error sent back to the input method stops use of TSMDA
+ // entirely during the session...
+ // We need to simulate very strictly the behaviour as if the
+ // underlying *text engine* holds the contents in UTF-16.
+ // I guess this is the reason why Apple repeats saying "all
+ // text handling application should use UTF-16." They are
+ // trying to _fix_ the flaw by changing the appliations...
+ // ... or, domination of UTF-16 in the industry may be a part
+ // of the company vision, and Apple is trying to force third
+ // party developers to obey their vision. Remember that use
+ // of 16 bits per _a_character_ was one of the very fundamental
+ // Unicode design policy on its early days (during late 80s)
+ // and the original Unicode design was by two Apple employees...
+
+ const llutf16string text_utf16
+ = wstring_to_utf16str(text, preedit)
+ + wstring_to_utf16str(text.substr(preedit + preedit_length));
+
+ llassert_always(sizeof(U16) == sizeof(UniChar));
+ llassert(0 <= range.location && 0 <= range.length && range.location + range.length <= text_utf16.length());
+ memcpy(target_pointer, text_utf16.c_str() + range.location, range.length * sizeof(UniChar));
+
+ // Note that result has already been set above.
+ }
+ }
+ break;
+
+ }
}
break;
}
@@ -2995,10 +3333,34 @@ static long getDictLong (CFDictionaryRef refDict, CFStringRef key)
return int_value; // otherwise return the long value
}
-void LLWindowMacOSX::allowLanguageTextInput(BOOL b)
+void LLWindowMacOSX::allowLanguageTextInput(LLPreeditor *preeditor, BOOL b)
{
ScriptLanguageRecord script_language;
+ if (preeditor != mPreeditor && !b)
+ {
+ // This condition may occur by a call to
+ // setEnabled(BOOL) against LLTextEditor or LLLineEditor
+ // when the control is not focused.
+ // We need to silently ignore the case so that
+ // the language input status of the focused control
+ // is not disturbed.
+ return;
+ }
+
+ // Take care of old and new preeditors.
+ if (preeditor != mPreeditor || !b)
+ {
+ // We need to interrupt before updating mPreeditor,
+ // so that the fix string from input method goes to
+ // the old preeditor.
+ if (mLanguageTextInputAllowed)
+ {
+ interruptLanguageTextInput();
+ }
+ mPreeditor = (b ? preeditor : NULL);
+ }
+
if (b == mLanguageTextInputAllowed)
{
return;
@@ -3028,4 +3390,12 @@ void LLWindowMacOSX::allowLanguageTextInput(BOOL b)
}
}
+void LLWindowMacOSX::interruptLanguageTextInput()
+{
+ if (mTSMDocument)
+ {
+ FixTSMDocument(mTSMDocument);
+ }
+}
+
#endif // LL_DARWIN
diff --git a/indra/llwindow/llwindowmacosx.h b/indra/llwindow/llwindowmacosx.h
index ee3019bd21..2a4cf97308 100644
--- a/indra/llwindow/llwindowmacosx.h
+++ b/indra/llwindow/llwindowmacosx.h
@@ -111,8 +111,10 @@ public:
/*virtual*/ void *getPlatformWindow();
/*virtual*/ void bringToFront() {};
- /*virtual*/ void allowLanguageTextInput(BOOL b);
-
+ /*virtual*/ void allowLanguageTextInput(LLPreeditor *preeditor, BOOL b);
+ /*virtual*/ void updateLanguageTextInputArea(const LLCoordGL& caret, const LLRect& bounds);
+ /*virtual*/ void interruptLanguageTextInput();
+
protected:
LLWindowMacOSX(
char *title, char *name, int x, int y, int width, int height, U32 flags,
@@ -193,6 +195,7 @@ protected:
BOOL mLanguageTextInputAllowed;
ScriptCode mTSMScriptCode;
LangCode mTSMLangCode;
+ LLPreeditor* mPreeditor;
friend class LLWindowManager;
};
diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp
index e5fd0f7360..4e4bed6485 100644
--- a/indra/llwindow/llwindowwin32.cpp
+++ b/indra/llwindow/llwindowwin32.cpp
@@ -57,6 +57,8 @@
#include "indra_constants.h"
+#include "llpreeditor.h"
+
// culled from winuser.h
#ifndef WM_MOUSEWHEEL /* Added to be compatible with later SDK's */
const S32 WM_MOUSEWHEEL = 0x020A;
@@ -112,6 +114,7 @@ public:
public:
// Wrappers for IMM API.
static BOOL isIME(HKL hkl);
+ static HWND getDefaultIMEWnd(HWND hwnd);
static HIMC getContext(HWND hwnd);
static BOOL releaseContext(HWND hwnd, HIMC himc);
static BOOL getOpenStatus(HIMC himc);
@@ -120,6 +123,11 @@ public:
static BOOL setConversionStatus(HIMC himc, DWORD conversion, DWORD sentence);
static BOOL getCompositionWindow(HIMC himc, LPCOMPOSITIONFORM form);
static BOOL setCompositionWindow(HIMC himc, LPCOMPOSITIONFORM form);
+ static LONG getCompositionString(HIMC himc, DWORD index, LPVOID data, DWORD length);
+ static BOOL setCompositionString(HIMC himc, DWORD index, LPVOID pComp, DWORD compLength, LPVOID pRead, DWORD readLength);
+ static BOOL setCompositionFont(HIMC himc, LPLOGFONTW logfont);
+ static BOOL setCandidateWindow(HIMC himc, LPCANDIDATEFORM candidate_form);
+ static BOOL notifyIME(HIMC himc, DWORD action, DWORD index, DWORD value);
private:
LLWinImm();
@@ -128,6 +136,7 @@ private:
private:
// Pointers to IMM API.
BOOL (WINAPI *mImmIsIME)(HKL);
+ HWND (WINAPI *mImmGetDefaultIMEWnd)(HWND);
HIMC (WINAPI *mImmGetContext)(HWND);
BOOL (WINAPI *mImmReleaseContext)(HWND, HIMC);
BOOL (WINAPI *mImmGetOpenStatus)(HIMC);
@@ -136,6 +145,11 @@ private:
BOOL (WINAPI *mImmSetConversionStatus)(HIMC, DWORD, DWORD);
BOOL (WINAPI *mImmGetCompostitionWindow)(HIMC, LPCOMPOSITIONFORM);
BOOL (WINAPI *mImmSetCompostitionWindow)(HIMC, LPCOMPOSITIONFORM);
+ LONG (WINAPI *mImmGetCompositionString)(HIMC, DWORD, LPVOID, DWORD);
+ BOOL (WINAPI *mImmSetCompositionString)(HIMC, DWORD, LPVOID, DWORD, LPVOID, DWORD);
+ BOOL (WINAPI *mImmSetCompositionFont)(HIMC, LPLOGFONTW);
+ BOOL (WINAPI *mImmSetCandidateWindow)(HIMC, LPCANDIDATEFORM);
+ BOOL (WINAPI *mImmNotifyIME)(HIMC, DWORD, DWORD, DWORD);
private:
HMODULE mHImmDll;
@@ -155,6 +169,7 @@ LLWinImm::LLWinImm() : mHImmDll(NULL)
if (mHImmDll != NULL)
{
mImmIsIME = (BOOL (WINAPI *)(HKL)) GetProcAddress(mHImmDll, "ImmIsIME");
+ mImmGetDefaultIMEWnd = (HWND (WINAPI *)(HWND)) GetProcAddress(mHImmDll, "ImmGetDefaultIMEWnd");
mImmGetContext = (HIMC (WINAPI *)(HWND)) GetProcAddress(mHImmDll, "ImmGetContext");
mImmReleaseContext = (BOOL (WINAPI *)(HWND, HIMC)) GetProcAddress(mHImmDll, "ImmReleaseContext");
mImmGetOpenStatus = (BOOL (WINAPI *)(HIMC)) GetProcAddress(mHImmDll, "ImmGetOpenStatus");
@@ -163,8 +178,14 @@ LLWinImm::LLWinImm() : mHImmDll(NULL)
mImmSetConversionStatus = (BOOL (WINAPI *)(HIMC, DWORD, DWORD)) GetProcAddress(mHImmDll, "ImmSetConversionStatus");
mImmGetCompostitionWindow = (BOOL (WINAPI *)(HIMC, LPCOMPOSITIONFORM)) GetProcAddress(mHImmDll, "ImmGetCompositionWindow");
mImmSetCompostitionWindow = (BOOL (WINAPI *)(HIMC, LPCOMPOSITIONFORM)) GetProcAddress(mHImmDll, "ImmSetCompositionWindow");
+ mImmGetCompositionString= (LONG (WINAPI *)(HIMC, DWORD, LPVOID, DWORD)) GetProcAddress(mHImmDll, "ImmGetCompositionStringW");
+ mImmSetCompositionString= (BOOL (WINAPI *)(HIMC, DWORD, LPVOID, DWORD, LPVOID, DWORD)) GetProcAddress(mHImmDll, "ImmSetCompositionStringW");
+ mImmSetCompositionFont = (BOOL (WINAPI *)(HIMC, LPLOGFONTW)) GetProcAddress(mHImmDll, "ImmSetCompositionFontW");
+ mImmSetCandidateWindow = (BOOL (WINAPI *)(HIMC, LPCANDIDATEFORM)) GetProcAddress(mHImmDll, "ImmSetCandidateWindow");
+ mImmNotifyIME = (BOOL (WINAPI *)(HIMC, DWORD, DWORD, DWORD)) GetProcAddress(mHImmDll, "ImmNotifyIME");
if (mImmIsIME == NULL ||
+ mImmGetDefaultIMEWnd == NULL ||
mImmGetContext == NULL ||
mImmReleaseContext == NULL ||
mImmGetOpenStatus == NULL ||
@@ -172,7 +193,12 @@ LLWinImm::LLWinImm() : mHImmDll(NULL)
mImmGetConversionStatus == NULL ||
mImmSetConversionStatus == NULL ||
mImmGetCompostitionWindow == NULL ||
- mImmSetCompostitionWindow == NULL)
+ mImmSetCompostitionWindow == NULL ||
+ mImmGetCompositionString == NULL ||
+ mImmSetCompositionString == NULL ||
+ mImmSetCompositionFont == NULL ||
+ mImmSetCandidateWindow == NULL ||
+ mImmNotifyIME == NULL)
{
// If any of the above API entires are not found, we can't use IMM API.
// So, turn off the IMM support. We should log some warning message in
@@ -186,6 +212,7 @@ LLWinImm::LLWinImm() : mHImmDll(NULL)
// If we unload the library, make sure all the function pointers are cleared
mImmIsIME = NULL;
+ mImmGetDefaultIMEWnd = NULL;
mImmGetContext = NULL;
mImmReleaseContext = NULL;
mImmGetOpenStatus = NULL;
@@ -194,6 +221,11 @@ LLWinImm::LLWinImm() : mHImmDll(NULL)
mImmSetConversionStatus = NULL;
mImmGetCompostitionWindow = NULL;
mImmSetCompostitionWindow = NULL;
+ mImmGetCompositionString = NULL;
+ mImmSetCompositionString = NULL;
+ mImmSetCompositionFont = NULL;
+ mImmSetCandidateWindow = NULL;
+ mImmNotifyIME = NULL;
}
}
}
@@ -272,6 +304,50 @@ BOOL LLWinImm::setCompositionWindow(HIMC himc, LPCOMPOSITIONFORM form)
}
+// static
+LONG LLWinImm::getCompositionString(HIMC himc, DWORD index, LPVOID data, DWORD length)
+{
+ if ( sTheInstance.mImmGetCompositionString )
+ return sTheInstance.mImmGetCompositionString(himc, index, data, length);
+ return FALSE;
+}
+
+
+// static
+BOOL LLWinImm::setCompositionString(HIMC himc, DWORD index, LPVOID pComp, DWORD compLength, LPVOID pRead, DWORD readLength)
+{
+ if ( sTheInstance.mImmSetCompositionString )
+ return sTheInstance.mImmSetCompositionString(himc, index, pComp, compLength, pRead, readLength);
+ return FALSE;
+}
+
+// static
+BOOL LLWinImm::setCompositionFont(HIMC himc, LPLOGFONTW pFont)
+{
+ if ( sTheInstance.mImmSetCompositionFont )
+ return sTheInstance.mImmSetCompositionFont(himc, pFont);
+ return FALSE;
+}
+
+// static
+BOOL LLWinImm::setCandidateWindow(HIMC himc, LPCANDIDATEFORM form)
+{
+ if ( sTheInstance.mImmSetCandidateWindow )
+ return sTheInstance.mImmSetCandidateWindow(himc, form);
+ return FALSE;
+}
+
+// static
+BOOL LLWinImm::notifyIME(HIMC himc, DWORD action, DWORD index, DWORD value)
+{
+ if ( sTheInstance.mImmNotifyIME )
+ return sTheInstance.mImmNotifyIME(himc, action, index, value);
+ return FALSE;
+}
+
+
+
+
// ----------------------------------------------------------------------------------------
LLWinImm::~LLWinImm()
{
@@ -304,13 +380,14 @@ LLWindowWin32::LLWindowWin32(char *title, char *name, S32 x, S32 y, S32 width,
mNativeAspectRatio = 0.f;
mMousePositionModified = FALSE;
mInputProcessingPaused = FALSE;
+ mPreeditor = NULL;
// Initialize the keyboard
gKeyboard = new LLKeyboardWin32();
// Initialize (boot strap) the Language text input management,
// based on the system's (user's) default settings.
- allowLanguageTextInput(FALSE);
+ allowLanguageTextInput(mPreeditor, FALSE);
GLuint pixel_format;
WNDCLASS wc;
@@ -938,6 +1015,10 @@ LLWindowWin32::LLWindowWin32(char *title, char *name, S32 x, S32 y, S32 width,
initCursors();
setCursor( UI_CURSOR_ARROW );
+ // Initialize (boot strap) the Language text input management,
+ // based on the system's (or user's) default settings.
+ allowLanguageTextInput(NULL, FALSE);
+
// Direct Input
HRESULT hr;
@@ -2035,6 +2116,11 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
BOOL minimized = BOOL(HIWORD(w_param));
+ if (!activating && LLWinImm::isAvailable() && window_imp->mPreeditor)
+ {
+ window_imp->interruptLanguageTextInput();
+ }
+
// JC - I'm not sure why, but if we don't report that we handled the
// WM_ACTIVATE message, the WM_ACTIVATEAPP messages don't work
// properly when we run fullscreen.
@@ -2127,6 +2213,47 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
// pass on to windows
break;
+ case WM_IME_SETCONTEXT:
+ if (LLWinImm::isAvailable() && window_imp->mPreeditor)
+ {
+ l_param &= ~ISC_SHOWUICOMPOSITIONWINDOW;
+ // Invoke DefWinProc with the modified LPARAM.
+ }
+ break;
+
+ case WM_IME_STARTCOMPOSITION:
+ if (LLWinImm::isAvailable() && window_imp->mPreeditor)
+ {
+ window_imp->handleStartCompositionMessage();
+ return 0;
+ }
+ break;
+
+ case WM_IME_ENDCOMPOSITION:
+ if (LLWinImm::isAvailable() && window_imp->mPreeditor)
+ {
+ return 0;
+ }
+ break;
+
+ case WM_IME_COMPOSITION:
+ if (LLWinImm::isAvailable() && window_imp->mPreeditor)
+ {
+ window_imp->handleCompositionMessage(l_param);
+ return 0;
+ }
+ break;
+
+ case WM_IME_REQUEST:
+ if (LLWinImm::isAvailable() && window_imp->mPreeditor)
+ {
+ LRESULT result = 0;
+ if (window_imp->handleImeRequests(w_param, l_param, &result))
+ {
+ return result;
+ }
+ }
+ break;
case WM_CHAR:
// Should really use WM_UNICHAR eventually, but it requires a specific Windows version and I need
@@ -2154,6 +2281,11 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
case WM_LBUTTONDOWN:
{
+ if (LLWinImm::isAvailable() && window_imp->mPreeditor)
+ {
+ window_imp->interruptLanguageTextInput();
+ }
+
// Because we move the cursor position in the app, we need to query
// to find out where the cursor at the time the event is handled.
// If we don't do this, many clicks could get buffered up, and if the
@@ -2236,6 +2368,11 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
case WM_RBUTTONDBLCLK:
case WM_RBUTTONDOWN:
{
+ if (LLWinImm::isAvailable() && window_imp->mPreeditor)
+ {
+ window_imp->interruptLanguageTextInput();
+ }
+
// Because we move the cursor position in tllviewerhe app, we need to query
// to find out where the cursor at the time the event is handled.
// If we don't do this, many clicks could get buffered up, and if the
@@ -2287,6 +2424,11 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
case WM_MBUTTONDOWN:
// case WM_MBUTTONDBLCLK:
{
+ if (LLWinImm::isAvailable() && window_imp->mPreeditor)
+ {
+ window_imp->interruptLanguageTextInput();
+ }
+
// Because we move the cursor position in tllviewerhe app, we need to query
// to find out where the cursor at the time the event is handled.
// If we don't do this, many clicks could get buffered up, and if the
@@ -3324,15 +3466,37 @@ void LLWindowWin32::focusClient()
SetFocus ( mWindowHandle );
}
-void LLWindowWin32::allowLanguageTextInput(BOOL b)
+void LLWindowWin32::allowLanguageTextInput(LLPreeditor *preeditor, BOOL b)
{
if (b == sLanguageTextInputAllowed || !LLWinImm::isAvailable())
{
return;
}
+
+ if (preeditor != mPreeditor && !b)
+ {
+ // This condition may occur with a call to
+ // setEnabled(BOOL) from LLTextEditor or LLLineEditor
+ // when the control is not focused.
+ // We need to silently ignore the case so that
+ // the language input status of the focused control
+ // is not disturbed.
+ return;
+ }
+
+ // Take care of old and new preeditors.
+ if (preeditor != mPreeditor || !b)
+ {
+ if (sLanguageTextInputAllowed)
+ {
+ interruptLanguageTextInput();
+ }
+ mPreeditor = (b ? preeditor : NULL);
+ }
+
sLanguageTextInputAllowed = b;
- if (b)
+ if ( sLanguageTextInputAllowed )
{
// Allowing: Restore the previous IME status, so that the user has a feeling that the previous
// text input continues naturally. Be careful, however, the IME status is meaningful only during the user keeps
@@ -3368,7 +3532,24 @@ void LLWindowWin32::allowLanguageTextInput(BOOL b)
LLWinImm::releaseContext(mWindowHandle, himc);
}
}
+}
+void LLWindowWin32::fillCandidateForm(const LLCoordGL& caret, const LLRect& bounds,
+ CANDIDATEFORM *form)
+{
+ LLCoordWindow caret_coord, top_left, bottom_right;
+ convertCoords(caret, &caret_coord);
+ convertCoords(LLCoordGL(bounds.mLeft, bounds.mTop), &top_left);
+ convertCoords(LLCoordGL(bounds.mRight, bounds.mBottom), &bottom_right);
+
+ memset(form, 0, sizeof(CANDIDATEFORM));
+ form->dwStyle = CFS_EXCLUDE;
+ form->ptCurrentPos.x = caret_coord.mX;
+ form->ptCurrentPos.y = caret_coord.mY;
+ form->rcArea.left = top_left.mX;
+ form->rcArea.top = top_left.mY;
+ form->rcArea.right = bottom_right.mX;
+ form->rcArea.bottom = bottom_right.mY;
}
@@ -3416,4 +3597,455 @@ void LLWindowWin32::setLanguageTextInput( const LLCoordGL & position )
}
}
+
+void LLWindowWin32::fillCharPosition(const LLCoordGL& caret, const LLRect& bounds, const LLRect& control,
+ IMECHARPOSITION *char_position)
+{
+ LLCoordScreen caret_coord, top_left, bottom_right;
+ convertCoords(caret, &caret_coord);
+ convertCoords(LLCoordGL(bounds.mLeft, bounds.mTop), &top_left);
+ convertCoords(LLCoordGL(bounds.mRight, bounds.mBottom), &bottom_right);
+
+ char_position->pt.x = caret_coord.mX;
+ char_position->pt.y = top_left.mY; // Windows wants the coordinate of upper left corner of a character...
+ char_position->cLineHeight = bottom_right.mY - top_left.mY;
+ char_position->rcDocument.left = top_left.mX;
+ char_position->rcDocument.top = top_left.mY;
+ char_position->rcDocument.right = bottom_right.mX;
+ char_position->rcDocument.bottom = bottom_right.mY;
+}
+
+void LLWindowWin32::fillCompositionLogfont(LOGFONT *logfont)
+{
+ // Our font is a list of FreeType recognized font files that may
+ // not have a corresponding ones in Windows' fonts. Hence, we
+ // can't simply tell Windows which font we are using. We will
+ // notify a _standard_ font for a current input locale instead.
+ // We use a hard-coded knowledge about the Windows' standard
+ // configuration to do so...
+
+ memset(logfont, 0, sizeof(LOGFONT));
+
+ const WORD lang_id = LOWORD(GetKeyboardLayout(0));
+ switch (PRIMARYLANGID(lang_id))
+ {
+ case LANG_CHINESE:
+ // We need to identify one of two Chinese fonts.
+ switch (SUBLANGID(lang_id))
+ {
+ case SUBLANG_CHINESE_SIMPLIFIED:
+ case SUBLANG_CHINESE_SINGAPORE:
+ logfont->lfCharSet = GB2312_CHARSET;
+ lstrcpy(logfont->lfFaceName, TEXT("SimHei"));
+ break;
+ case SUBLANG_CHINESE_TRADITIONAL:
+ case SUBLANG_CHINESE_HONGKONG:
+ case SUBLANG_CHINESE_MACAU:
+ default:
+ logfont->lfCharSet = CHINESEBIG5_CHARSET;
+ lstrcpy(logfont->lfFaceName, TEXT("MingLiU"));
+ break;
+ }
+ break;
+ case LANG_JAPANESE:
+ logfont->lfCharSet = SHIFTJIS_CHARSET;
+ lstrcpy(logfont->lfFaceName, TEXT("MS Gothic"));
+ break;
+ case LANG_KOREAN:
+ logfont->lfCharSet = HANGUL_CHARSET;
+ lstrcpy(logfont->lfFaceName, TEXT("Gulim"));
+ break;
+ default:
+ logfont->lfCharSet = ANSI_CHARSET;
+ lstrcpy(logfont->lfFaceName, TEXT("Tahoma"));
+ break;
+ }
+
+ logfont->lfHeight = mPreeditor->getPreeditFontSize();
+ logfont->lfWeight = FW_NORMAL;
+}
+
+U32 LLWindowWin32::fillReconvertString(const LLWString &text,
+ S32 focus, S32 focus_length, RECONVERTSTRING *reconvert_string)
+{
+ const llutf16string text_utf16 = wstring_to_utf16str(text);
+ const DWORD required_size = sizeof(RECONVERTSTRING) + (text_utf16.length() + 1) * sizeof(WCHAR);
+ if (reconvert_string && reconvert_string->dwSize >= required_size)
+ {
+ const DWORD focus_utf16_at = wstring_utf16_length(text, 0, focus);
+ const DWORD focus_utf16_length = wstring_utf16_length(text, focus, focus_length);
+
+ reconvert_string->dwVersion = 0;
+ reconvert_string->dwStrLen = text_utf16.length();
+ reconvert_string->dwStrOffset = sizeof(RECONVERTSTRING);
+ reconvert_string->dwCompStrLen = focus_utf16_length;
+ reconvert_string->dwCompStrOffset = focus_utf16_at * sizeof(WCHAR);
+ reconvert_string->dwTargetStrLen = 0;
+ reconvert_string->dwTargetStrOffset = focus_utf16_at * sizeof(WCHAR);
+
+ const LPWSTR text = (LPWSTR)((BYTE *)reconvert_string + sizeof(RECONVERTSTRING));
+ memcpy(text, text_utf16.c_str(), (text_utf16.length() + 1) * sizeof(WCHAR));
+ }
+ return required_size;
+}
+
+void LLWindowWin32::updateLanguageTextInputArea()
+{
+ if (!mPreeditor || !LLWinImm::isAvailable())
+ {
+ return;
+ }
+
+ LLCoordGL caret_coord;
+ LLRect preedit_bounds;
+ if (mPreeditor->getPreeditLocation(-1, &caret_coord, &preedit_bounds, NULL))
+ {
+ mLanguageTextInputPointGL = caret_coord;
+ mLanguageTextInputAreaGL = preedit_bounds;
+
+ CANDIDATEFORM candidate_form;
+ fillCandidateForm(caret_coord, preedit_bounds, &candidate_form);
+
+ HIMC himc = LLWinImm::getContext(mWindowHandle);
+ // Win32 document says there may be up to 4 candidate windows.
+ // This magic number 4 appears only in the document, and
+ // there are no constant/macro for the value...
+ for (int i = 3; i >= 0; --i)
+ {
+ candidate_form.dwIndex = i;
+ LLWinImm::setCandidateWindow(himc, &candidate_form);
+ }
+ LLWinImm::releaseContext(mWindowHandle, himc);
+ }
+}
+
+void LLWindowWin32::interruptLanguageTextInput()
+{
+ if (mPreeditor)
+ {
+ if (LLWinImm::isAvailable())
+ {
+ HIMC himc = LLWinImm::getContext(mWindowHandle);
+ LLWinImm::notifyIME(himc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
+ LLWinImm::releaseContext(mWindowHandle, himc);
+ }
+ mPreeditor->resetPreedit();
+ }
+}
+
+void LLWindowWin32::handleStartCompositionMessage()
+{
+ // Let IME know the font to use in feedback UI.
+ LOGFONT logfont;
+ fillCompositionLogfont(&logfont);
+ HIMC himc = LLWinImm::getContext(mWindowHandle);
+ LLWinImm::setCompositionFont(himc, &logfont);
+ LLWinImm::releaseContext(mWindowHandle, himc);
+}
+
+// Handle WM_IME_COMPOSITION message.
+
+void LLWindowWin32::handleCompositionMessage(const U32 indexes)
+{
+ BOOL needs_update = FALSE;
+ LLWString result_string;
+ LLWString preedit_string;
+ S32 preedit_string_utf16_length = 0;
+ LLPreeditor::segment_lengths_t preedit_segment_lengths;
+ LLPreeditor::standouts_t preedit_standouts;
+
+ // Step I: Receive details of preedits from IME.
+
+ HIMC himc = LLWinImm::getContext(mWindowHandle);
+
+ if (indexes & GCS_RESULTSTR)
+ {
+ LONG size = LLWinImm::getCompositionString(himc, GCS_RESULTSTR, NULL, 0);
+ if (size >= 0)
+ {
+ const LPWSTR data = new WCHAR[size / sizeof(WCHAR) + 1];
+ size = LLWinImm::getCompositionString(himc, GCS_RESULTSTR, data, size);
+ if (size > 0)
+ {
+ result_string = utf16str_to_wstring(llutf16string(data, size / sizeof(WCHAR)));
+ }
+ delete[] data;
+ needs_update = TRUE;
+ }
+ }
+
+ if (indexes & GCS_COMPSTR)
+ {
+ LONG size = LLWinImm::getCompositionString(himc, GCS_COMPSTR, NULL, 0);
+ if (size >= 0)
+ {
+ const LPWSTR data = new WCHAR[size / sizeof(WCHAR) + 1];
+ size = LLWinImm::getCompositionString(himc, GCS_COMPSTR, data, size);
+ if (size > 0)
+ {
+ preedit_string_utf16_length = size / sizeof(WCHAR);
+ preedit_string = utf16str_to_wstring(llutf16string(data, size / sizeof(WCHAR)));
+ }
+ delete[] data;
+ needs_update = TRUE;
+ }
+ }
+
+ if ((indexes & GCS_COMPCLAUSE) && preedit_string.length() > 0)
+ {
+ LONG size = LLWinImm::getCompositionString(himc, GCS_COMPCLAUSE, NULL, 0);
+ if (size > 0)
+ {
+ const LPDWORD data = new DWORD[size / sizeof(DWORD)];
+ size = LLWinImm::getCompositionString(himc, GCS_COMPCLAUSE, data, size);
+ if (size >= sizeof(DWORD) * 2
+ && data[0] == 0 && data[size / sizeof(DWORD) - 1] == preedit_string_utf16_length)
+ {
+ preedit_segment_lengths.resize(size / sizeof(DWORD) - 1);
+ S32 offset = 0;
+ for (U32 i = 0; i < preedit_segment_lengths.size(); i++)
+ {
+ const S32 length = wstring_wstring_length_from_utf16_length(preedit_string, offset, data[i + 1] - data[i]);
+ preedit_segment_lengths[i] = length;
+ offset += length;
+ }
+ }
+ delete[] data;
+ }
+ }
+
+ if ((indexes & GCS_COMPATTR) && preedit_segment_lengths.size() > 1)
+ {
+ LONG size = LLWinImm::getCompositionString(himc, GCS_COMPATTR, NULL, 0);
+ if (size > 0)
+ {
+ const LPBYTE data = new BYTE[size / sizeof(BYTE)];
+ size = LLWinImm::getCompositionString(himc, GCS_COMPATTR, data, size);
+ if (size == preedit_string_utf16_length)
+ {
+ preedit_standouts.assign(preedit_segment_lengths.size(), FALSE);
+ S32 offset = 0;
+ for (U32 i = 0; i < preedit_segment_lengths.size(); i++)
+ {
+ if (ATTR_TARGET_CONVERTED == data[offset] || ATTR_TARGET_NOTCONVERTED == data[offset])
+ {
+ preedit_standouts[i] = TRUE;
+ }
+ offset += wstring_utf16_length(preedit_string, offset, preedit_segment_lengths[i]);
+ }
+ }
+ delete[] data;
+ }
+ }
+
+ S32 caret_position = preedit_string.length();
+ if (indexes & GCS_CURSORPOS)
+ {
+ const S32 caret_position_utf16 = LLWinImm::getCompositionString(himc, GCS_CURSORPOS, NULL, 0);
+ if (caret_position_utf16 >= 0 && caret_position <= preedit_string_utf16_length)
+ {
+ caret_position = wstring_wstring_length_from_utf16_length(preedit_string, 0, caret_position_utf16);
+ }
+ }
+
+ if (indexes == 0)
+ {
+ // I'm not sure this condition really happens, but
+ // Windows SDK document says it is an indication
+ // of "reset everything."
+ needs_update = TRUE;
+ }
+
+ LLWinImm::releaseContext(mWindowHandle, himc);
+
+ // Step II: Update the active preeditor.
+
+ if (needs_update)
+ {
+ mPreeditor->resetPreedit();
+
+ if (result_string.length() > 0)
+ {
+ for (LLWString::const_iterator i = result_string.begin(); i != result_string.end(); i++)
+ {
+ mPreeditor->handleUnicodeCharHere(*i, FALSE);
+ }
+ }
+
+ if (preedit_string.length() > 0)
+ {
+ if (preedit_segment_lengths.size() == 0)
+ {
+ preedit_segment_lengths.assign(1, preedit_string.length());
+ }
+ if (preedit_standouts.size() == 0)
+ {
+ preedit_standouts.assign(preedit_segment_lengths.size(), FALSE);
+ }
+ mPreeditor->updatePreedit(preedit_string, preedit_segment_lengths, preedit_standouts, caret_position);
+ }
+
+ // Some IME doesn't query char position after WM_IME_COMPOSITION,
+ // so we need to update them actively.
+ updateLanguageTextInputArea();
+ }
+}
+
+// Given a text and a focus range, find_context finds and returns a
+// surrounding context of the focused subtext. A variable pointed
+// to by offset receives the offset in llwchars of the beginning of
+// the returned context string in the given wtext.
+
+static LLWString find_context(const LLWString & wtext, S32 focus, S32 focus_length, S32 *offset)
+{
+ static const S32 CONTEXT_EXCESS = 30; // This value is by experiences.
+
+ const S32 e = llmin((S32) wtext.length(), focus + focus_length + CONTEXT_EXCESS);
+ S32 end = focus + focus_length;
+ while (end < e && '\n' != wtext[end])
+ {
+ end++;
+ }
+
+ const S32 s = llmax(0, focus - CONTEXT_EXCESS);
+ S32 start = focus;
+ while (start > s && '\n' != wtext[start - 1])
+ {
+ --start;
+ }
+
+ *offset = start;
+ return wtext.substr(start, end - start);
+}
+
+// Handle WM_IME_REQUEST message.
+// If it handled the message, returns TRUE. Otherwise, FALSE.
+// When it handled the message, the value to be returned from
+// the Window Procedure is set to *result.
+
+BOOL LLWindowWin32::handleImeRequests(U32 request, U32 param, LRESULT *result)
+{
+ if ( mPreeditor )
+ {
+ switch (request)
+ {
+ case IMR_CANDIDATEWINDOW: // http://msdn2.microsoft.com/en-us/library/ms776080.aspx
+ {
+ LLCoordGL caret_coord;
+ LLRect preedit_bounds;
+ mPreeditor->getPreeditLocation(-1, &caret_coord, &preedit_bounds, NULL);
+
+ CANDIDATEFORM *const form = (CANDIDATEFORM *)param;
+ DWORD const dwIndex = form->dwIndex;
+ fillCandidateForm(caret_coord, preedit_bounds, form);
+ form->dwIndex = dwIndex;
+
+ *result = 1;
+ return TRUE;
+ }
+ case IMR_QUERYCHARPOSITION:
+ {
+ IMECHARPOSITION *const char_position = (IMECHARPOSITION *)param;
+
+ // char_position->dwCharPos counts in number of
+ // WCHARs, i.e., UTF-16 encoding units, so we can't simply pass the
+ // number to getPreeditLocation.
+
+ const LLWString & wtext = mPreeditor->getWText();
+ S32 preedit, preedit_length;
+ mPreeditor->getPreeditRange(&preedit, &preedit_length);
+ LLCoordGL caret_coord;
+ LLRect preedit_bounds, text_control;
+ const S32 position = wstring_wstring_length_from_utf16_length(wtext, preedit, char_position->dwCharPos);
+
+ if (!mPreeditor->getPreeditLocation(position, &caret_coord, &preedit_bounds, &text_control))
+ {
+ llwarns << "*** IMR_QUERYCHARPOSITON called but getPreeditLocation failed." << llendl;
+ return FALSE;
+ }
+ fillCharPosition(caret_coord, preedit_bounds, text_control, char_position);
+
+ *result = 1;
+ return TRUE;
+ }
+ case IMR_COMPOSITIONFONT:
+ {
+ fillCompositionLogfont((LOGFONT *)param);
+
+ *result = 1;
+ return TRUE;
+ }
+ case IMR_RECONVERTSTRING:
+ {
+ mPreeditor->resetPreedit();
+ const LLWString & wtext = mPreeditor->getWText();
+ S32 select, select_length;
+ mPreeditor->getSelectionRange(&select, &select_length);
+
+ S32 context_offset;
+ const LLWString context = find_context(wtext, select, select_length, &context_offset);
+
+ RECONVERTSTRING * const reconvert_string = (RECONVERTSTRING *)param;
+ const U32 size = fillReconvertString(context, select - context_offset, select_length, reconvert_string);
+ if (reconvert_string)
+ {
+ if (select_length == 0)
+ {
+ // Let the IME to decide the reconversion range, and
+ // adjust the reconvert_string structure accordingly.
+ HIMC himc = LLWinImm::getContext(mWindowHandle);
+ const BOOL adjusted = LLWinImm::setCompositionString(himc,
+ SCS_QUERYRECONVERTSTRING, reconvert_string, size, NULL, 0);
+ LLWinImm::releaseContext(mWindowHandle, himc);
+ if (adjusted)
+ {
+ const llutf16string & text_utf16 = wstring_to_utf16str(context);
+ const S32 new_preedit_start = reconvert_string->dwCompStrOffset / sizeof(WCHAR);
+ const S32 new_preedit_end = new_preedit_start + reconvert_string->dwCompStrLen;
+ select = utf16str_wstring_length(text_utf16, new_preedit_start);
+ select_length = utf16str_wstring_length(text_utf16, new_preedit_end) - select;
+ select += context_offset;
+ }
+ }
+ mPreeditor->markAsPreedit(select, select_length);
+ }
+
+ *result = size;
+ return TRUE;
+ }
+ case IMR_CONFIRMRECONVERTSTRING:
+ {
+ *result = FALSE;
+ return TRUE;
+ }
+ case IMR_DOCUMENTFEED:
+ {
+ const LLWString & wtext = mPreeditor->getWText();
+ S32 preedit, preedit_length;
+ mPreeditor->getPreeditRange(&preedit, &preedit_length);
+
+ S32 context_offset;
+ LLWString context = find_context(wtext, preedit, preedit_length, &context_offset);
+ preedit -= context_offset;
+ if (preedit_length)
+ {
+ // IMR_DOCUMENTFEED may be called when we have an active preedit.
+ // We should pass the context string *excluding* the preedit string.
+ // Otherwise, some IME are confused.
+ context.erase(preedit, preedit_length);
+ }
+
+ RECONVERTSTRING *reconvert_string = (RECONVERTSTRING *)param;
+ *result = fillReconvertString(context, preedit, 0, reconvert_string);
+ return TRUE;
+ }
+ default:
+ return FALSE;
+ }
+ }
+
+ return FALSE;
+}
+
+
#endif // LL_WINDOWS
diff --git a/indra/llwindow/llwindowwin32.h b/indra/llwindow/llwindowwin32.h
index 602e06600f..9ad99b0201 100644
--- a/indra/llwindow/llwindowwin32.h
+++ b/indra/llwindow/llwindowwin32.h
@@ -109,8 +109,10 @@ public:
/*virtual*/ void bringToFront();
/*virtual*/ void focusClient();
- /*virtual*/ void allowLanguageTextInput(BOOL b);
+ /*virtual*/ void allowLanguageTextInput(LLPreeditor *preeditor, BOOL b);
/*virtual*/ void setLanguageTextInput( const LLCoordGL & pos );
+ /*virtual*/ void updateLanguageTextInputArea();
+ /*virtual*/ void interruptLanguageTextInput();
protected:
LLWindowWin32(
@@ -139,6 +141,14 @@ protected:
BOOL shouldPostQuit() { return mPostQuit; }
+ void fillCompositionForm(const LLRect& bounds, COMPOSITIONFORM *form);
+ void fillCandidateForm(const LLCoordGL& caret, const LLRect& bounds, CANDIDATEFORM *form);
+ void fillCharPosition(const LLCoordGL& caret, const LLRect& bounds, const LLRect& control, IMECHARPOSITION *char_position);
+ void fillCompositionLogfont(LOGFONT *logfont);
+ U32 fillReconvertString(const LLWString &text, S32 focus, S32 focus_length, RECONVERTSTRING *reconvert_string);
+ void handleStartCompositionMessage();
+ void handleCompositionMessage(U32 indexes);
+ BOOL handleImeRequests(U32 request, U32 param, LRESULT *result);
protected:
//
@@ -189,6 +199,10 @@ protected:
static DWORD sWinIMEConversionMode;
static DWORD sWinIMESentenceMode;
static LLCoordWindow sWinIMEWindowPosition;
+ LLCoordGL mLanguageTextInputPointGL;
+ LLRect mLanguageTextInputAreaGL;
+
+ LLPreeditor *mPreeditor;
friend class LLWindowManager;
};