diff options
Diffstat (limited to 'indra/llwindow')
-rw-r--r-- | indra/llwindow/llpreeditor.h | 81 | ||||
-rw-r--r-- | indra/llwindow/llwindow.h | 6 | ||||
-rw-r--r-- | indra/llwindow/llwindowmacosx.cpp | 382 | ||||
-rw-r--r-- | indra/llwindow/llwindowmacosx.h | 7 | ||||
-rw-r--r-- | indra/llwindow/llwindowwin32.cpp | 640 | ||||
-rw-r--r-- | indra/llwindow/llwindowwin32.h | 16 |
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, ¶m_type, sizeof(fix_len), NULL, &fix_len)) == noErr + && typeLongInteger == param_type + && (result = GetEventParameter(event, kEventParamTextInputSendText, + typeUnicodeText, ¶m_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, ¶m_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, + ¶m_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, + ¶m_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, ¶m_type, sizeof(range), NULL, &range)) == noErr + && typeCFRange == param_type + && (result = GetEventParameter(event, kEventParamTSMDocAccessSendCharactersPtr, + typePtr, ¶m_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; }; |