Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | #ifndef nsIMM32Handler_h__ |
michael@0 | 7 | #define nsIMM32Handler_h__ |
michael@0 | 8 | |
michael@0 | 9 | #include "nscore.h" |
michael@0 | 10 | #include <windows.h> |
michael@0 | 11 | #include "nsCOMPtr.h" |
michael@0 | 12 | #include "nsString.h" |
michael@0 | 13 | #include "nsTArray.h" |
michael@0 | 14 | #include "nsIWidget.h" |
michael@0 | 15 | #include "mozilla/EventForwards.h" |
michael@0 | 16 | |
michael@0 | 17 | class nsWindow; |
michael@0 | 18 | struct nsIntRect; |
michael@0 | 19 | |
michael@0 | 20 | namespace mozilla { |
michael@0 | 21 | namespace widget { |
michael@0 | 22 | |
michael@0 | 23 | struct MSGResult; |
michael@0 | 24 | |
michael@0 | 25 | } // namespace widget |
michael@0 | 26 | } // namespace mozilla |
michael@0 | 27 | |
michael@0 | 28 | class nsIMEContext |
michael@0 | 29 | { |
michael@0 | 30 | public: |
michael@0 | 31 | nsIMEContext(HWND aWnd) : mWnd(aWnd) |
michael@0 | 32 | { |
michael@0 | 33 | mIMC = ::ImmGetContext(mWnd); |
michael@0 | 34 | } |
michael@0 | 35 | |
michael@0 | 36 | ~nsIMEContext() |
michael@0 | 37 | { |
michael@0 | 38 | if (mIMC) { |
michael@0 | 39 | ::ImmReleaseContext(mWnd, mIMC); |
michael@0 | 40 | mIMC = nullptr; |
michael@0 | 41 | } |
michael@0 | 42 | } |
michael@0 | 43 | |
michael@0 | 44 | HIMC get() const |
michael@0 | 45 | { |
michael@0 | 46 | return mIMC; |
michael@0 | 47 | } |
michael@0 | 48 | |
michael@0 | 49 | bool IsValid() const |
michael@0 | 50 | { |
michael@0 | 51 | return !!mIMC; |
michael@0 | 52 | } |
michael@0 | 53 | |
michael@0 | 54 | void SetOpenState(bool aOpen) const |
michael@0 | 55 | { |
michael@0 | 56 | if (!mIMC) { |
michael@0 | 57 | return; |
michael@0 | 58 | } |
michael@0 | 59 | ::ImmSetOpenStatus(mIMC, aOpen); |
michael@0 | 60 | } |
michael@0 | 61 | |
michael@0 | 62 | bool GetOpenState() const |
michael@0 | 63 | { |
michael@0 | 64 | if (!mIMC) { |
michael@0 | 65 | return false; |
michael@0 | 66 | } |
michael@0 | 67 | return (::ImmGetOpenStatus(mIMC) != FALSE); |
michael@0 | 68 | } |
michael@0 | 69 | |
michael@0 | 70 | bool AssociateDefaultContext() |
michael@0 | 71 | { |
michael@0 | 72 | // We assume that there is only default IMC, no new IMC has been created. |
michael@0 | 73 | if (mIMC) { |
michael@0 | 74 | return false; |
michael@0 | 75 | } |
michael@0 | 76 | if (!::ImmAssociateContextEx(mWnd, nullptr, IACE_DEFAULT)) { |
michael@0 | 77 | return false; |
michael@0 | 78 | } |
michael@0 | 79 | mIMC = ::ImmGetContext(mWnd); |
michael@0 | 80 | return (mIMC != nullptr); |
michael@0 | 81 | } |
michael@0 | 82 | |
michael@0 | 83 | bool Disassociate() |
michael@0 | 84 | { |
michael@0 | 85 | if (!mIMC) { |
michael@0 | 86 | return false; |
michael@0 | 87 | } |
michael@0 | 88 | if (!::ImmAssociateContextEx(mWnd, nullptr, 0)) { |
michael@0 | 89 | return false; |
michael@0 | 90 | } |
michael@0 | 91 | ::ImmReleaseContext(mWnd, mIMC); |
michael@0 | 92 | mIMC = nullptr; |
michael@0 | 93 | return true; |
michael@0 | 94 | } |
michael@0 | 95 | |
michael@0 | 96 | protected: |
michael@0 | 97 | nsIMEContext() |
michael@0 | 98 | { |
michael@0 | 99 | NS_ERROR("Don't create nsIMEContext without window handle"); |
michael@0 | 100 | } |
michael@0 | 101 | |
michael@0 | 102 | nsIMEContext(const nsIMEContext &aSrc) : mWnd(nullptr), mIMC(nullptr) |
michael@0 | 103 | { |
michael@0 | 104 | NS_ERROR("Don't copy nsIMEContext"); |
michael@0 | 105 | } |
michael@0 | 106 | |
michael@0 | 107 | HWND mWnd; |
michael@0 | 108 | HIMC mIMC; |
michael@0 | 109 | }; |
michael@0 | 110 | |
michael@0 | 111 | class nsIMM32Handler |
michael@0 | 112 | { |
michael@0 | 113 | typedef mozilla::widget::MSGResult MSGResult; |
michael@0 | 114 | public: |
michael@0 | 115 | static void Initialize(); |
michael@0 | 116 | static void Terminate(); |
michael@0 | 117 | |
michael@0 | 118 | // If Process*() returns true, the caller shouldn't do anything anymore. |
michael@0 | 119 | static bool ProcessMessage(nsWindow* aWindow, UINT msg, |
michael@0 | 120 | WPARAM& wParam, LPARAM& lParam, |
michael@0 | 121 | MSGResult& aResult); |
michael@0 | 122 | static bool IsComposing() |
michael@0 | 123 | { |
michael@0 | 124 | return IsComposingOnOurEditor() || IsComposingOnPlugin(); |
michael@0 | 125 | } |
michael@0 | 126 | static bool IsComposingOn(nsWindow* aWindow) |
michael@0 | 127 | { |
michael@0 | 128 | return IsComposing() && IsComposingWindow(aWindow); |
michael@0 | 129 | } |
michael@0 | 130 | |
michael@0 | 131 | #ifdef DEBUG |
michael@0 | 132 | /** |
michael@0 | 133 | * IsIMEAvailable() returns TRUE when current keyboard layout has IME. |
michael@0 | 134 | * Otherwise, FALSE. |
michael@0 | 135 | */ |
michael@0 | 136 | static bool IsIMEAvailable() { return !!::ImmIsIME(::GetKeyboardLayout(0)); } |
michael@0 | 137 | #endif |
michael@0 | 138 | |
michael@0 | 139 | // If aForce is TRUE, these methods doesn't check whether we have composition |
michael@0 | 140 | // or not. If you don't set it to TRUE, these method doesn't commit/cancel |
michael@0 | 141 | // the composition on uexpected window. |
michael@0 | 142 | static void CommitComposition(nsWindow* aWindow, bool aForce = false); |
michael@0 | 143 | static void CancelComposition(nsWindow* aWindow, bool aForce = false); |
michael@0 | 144 | static void OnUpdateComposition(nsWindow* aWindow); |
michael@0 | 145 | |
michael@0 | 146 | static nsIMEUpdatePreference GetIMEUpdatePreference(); |
michael@0 | 147 | |
michael@0 | 148 | protected: |
michael@0 | 149 | static void EnsureHandlerInstance(); |
michael@0 | 150 | |
michael@0 | 151 | static bool IsComposingOnOurEditor(); |
michael@0 | 152 | static bool IsComposingOnPlugin(); |
michael@0 | 153 | static bool IsComposingWindow(nsWindow* aWindow); |
michael@0 | 154 | |
michael@0 | 155 | static bool ShouldDrawCompositionStringOurselves(); |
michael@0 | 156 | static void InitKeyboardLayout(HKL aKeyboardLayout); |
michael@0 | 157 | static UINT GetKeyboardCodePage(); |
michael@0 | 158 | |
michael@0 | 159 | /** |
michael@0 | 160 | * Checks whether the window is top level window of the composing window. |
michael@0 | 161 | * In this method, the top level window means in all windows, not only in all |
michael@0 | 162 | * OUR windows. I.e., if the aWindow is embedded, this always returns FALSE. |
michael@0 | 163 | */ |
michael@0 | 164 | static bool IsTopLevelWindowOfComposition(nsWindow* aWindow); |
michael@0 | 165 | |
michael@0 | 166 | static bool ProcessInputLangChangeMessage(nsWindow* aWindow, |
michael@0 | 167 | WPARAM wParam, |
michael@0 | 168 | LPARAM lParam, |
michael@0 | 169 | MSGResult& aResult); |
michael@0 | 170 | static bool ProcessMessageForPlugin(nsWindow* aWindow, UINT msg, |
michael@0 | 171 | WPARAM &wParam, LPARAM &lParam, |
michael@0 | 172 | MSGResult& aResult); |
michael@0 | 173 | |
michael@0 | 174 | nsIMM32Handler(); |
michael@0 | 175 | ~nsIMM32Handler(); |
michael@0 | 176 | |
michael@0 | 177 | // On*() methods return true if the caller of message handler shouldn't do |
michael@0 | 178 | // anything anymore. Otherwise, false. |
michael@0 | 179 | bool OnMouseEvent(nsWindow* aWindow, LPARAM lParam, int aAction, |
michael@0 | 180 | MSGResult& aResult); |
michael@0 | 181 | static bool OnKeyDownEvent(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, |
michael@0 | 182 | MSGResult& aResult); |
michael@0 | 183 | |
michael@0 | 184 | bool OnIMEStartComposition(nsWindow* aWindow, MSGResult& aResult); |
michael@0 | 185 | bool OnIMEStartCompositionOnPlugin(nsWindow* aWindow, |
michael@0 | 186 | WPARAM wParam, LPARAM lParam, |
michael@0 | 187 | MSGResult& aResult); |
michael@0 | 188 | bool OnIMEComposition(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, |
michael@0 | 189 | MSGResult& aResult); |
michael@0 | 190 | bool OnIMECompositionOnPlugin(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, |
michael@0 | 191 | MSGResult& aResult); |
michael@0 | 192 | bool OnIMEEndComposition(nsWindow* aWindow, MSGResult& aResult); |
michael@0 | 193 | bool OnIMEEndCompositionOnPlugin(nsWindow* aWindow, WPARAM wParam, |
michael@0 | 194 | LPARAM lParam, MSGResult& aResult); |
michael@0 | 195 | bool OnIMERequest(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, |
michael@0 | 196 | MSGResult& aResult); |
michael@0 | 197 | bool OnIMECharOnPlugin(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, |
michael@0 | 198 | MSGResult& aResult); |
michael@0 | 199 | bool OnChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, |
michael@0 | 200 | MSGResult& aResult); |
michael@0 | 201 | bool OnCharOnPlugin(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, |
michael@0 | 202 | MSGResult& aResult); |
michael@0 | 203 | void OnInputLangChange(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, |
michael@0 | 204 | MSGResult& aResult); |
michael@0 | 205 | |
michael@0 | 206 | // These message handlers don't use instance members, we should not create |
michael@0 | 207 | // the instance by the messages. So, they should be static. |
michael@0 | 208 | static bool OnIMEChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, |
michael@0 | 209 | MSGResult& aResult); |
michael@0 | 210 | static bool OnIMESetContext(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, |
michael@0 | 211 | MSGResult& aResult); |
michael@0 | 212 | static bool OnIMESetContextOnPlugin(nsWindow* aWindow, |
michael@0 | 213 | WPARAM wParam, LPARAM lParam, |
michael@0 | 214 | MSGResult& aResult); |
michael@0 | 215 | static bool OnIMECompositionFull(nsWindow* aWindow, MSGResult& aResult); |
michael@0 | 216 | static bool OnIMENotify(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, |
michael@0 | 217 | MSGResult& aResult); |
michael@0 | 218 | static bool OnIMESelect(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, |
michael@0 | 219 | MSGResult& aResult); |
michael@0 | 220 | |
michael@0 | 221 | // The result of Handle* method mean "Processed" when it's TRUE. |
michael@0 | 222 | void HandleStartComposition(nsWindow* aWindow, |
michael@0 | 223 | const nsIMEContext &aIMEContext); |
michael@0 | 224 | bool HandleComposition(nsWindow* aWindow, const nsIMEContext &aIMEContext, |
michael@0 | 225 | LPARAM lParam); |
michael@0 | 226 | void HandleEndComposition(nsWindow* aWindow); |
michael@0 | 227 | bool HandleReconvert(nsWindow* aWindow, LPARAM lParam, LRESULT *oResult); |
michael@0 | 228 | bool HandleQueryCharPosition(nsWindow* aWindow, LPARAM lParam, |
michael@0 | 229 | LRESULT *oResult); |
michael@0 | 230 | bool HandleDocumentFeed(nsWindow* aWindow, LPARAM lParam, LRESULT *oResult); |
michael@0 | 231 | |
michael@0 | 232 | /** |
michael@0 | 233 | * When a window's IME context is activating but we have composition on |
michael@0 | 234 | * another window, we should commit our composition because IME context is |
michael@0 | 235 | * shared by all our windows (including plug-ins). |
michael@0 | 236 | * @param aWindow is a new activated window. |
michael@0 | 237 | * If aWindow is our composing window, this method does nothing. |
michael@0 | 238 | * Otherwise, this commits the composition on the previous window. |
michael@0 | 239 | * If this method did commit a composition, this returns TRUE. |
michael@0 | 240 | */ |
michael@0 | 241 | bool CommitCompositionOnPreviousWindow(nsWindow* aWindow); |
michael@0 | 242 | |
michael@0 | 243 | /** |
michael@0 | 244 | * ResolveIMECaretPos |
michael@0 | 245 | * Convert the caret rect of a composition event to another widget's |
michael@0 | 246 | * coordinate system. |
michael@0 | 247 | * |
michael@0 | 248 | * @param aReferenceWidget The origin widget of aCursorRect. |
michael@0 | 249 | * Typically, this is mReferenceWidget of the |
michael@0 | 250 | * composing events. If the aCursorRect is in screen |
michael@0 | 251 | * coordinates, set nullptr. |
michael@0 | 252 | * @param aCursorRect The cursor rect. |
michael@0 | 253 | * @param aNewOriginWidget aOutRect will be in this widget's coordinates. If |
michael@0 | 254 | * this is nullptr, aOutRect will be in screen |
michael@0 | 255 | * coordinates. |
michael@0 | 256 | * @param aOutRect The converted cursor rect. |
michael@0 | 257 | */ |
michael@0 | 258 | void ResolveIMECaretPos(nsIWidget* aReferenceWidget, |
michael@0 | 259 | nsIntRect& aCursorRect, |
michael@0 | 260 | nsIWidget* aNewOriginWidget, |
michael@0 | 261 | nsIntRect& aOutRect); |
michael@0 | 262 | |
michael@0 | 263 | bool ConvertToANSIString(const nsAFlatString& aStr, |
michael@0 | 264 | UINT aCodePage, |
michael@0 | 265 | nsACString& aANSIStr); |
michael@0 | 266 | |
michael@0 | 267 | bool SetIMERelatedWindowsPos(nsWindow* aWindow, |
michael@0 | 268 | const nsIMEContext& aIMEContext); |
michael@0 | 269 | void SetIMERelatedWindowsPosOnPlugin(nsWindow* aWindow, |
michael@0 | 270 | const nsIMEContext& aIMEContext); |
michael@0 | 271 | bool GetCharacterRectOfSelectedTextAt(nsWindow* aWindow, |
michael@0 | 272 | uint32_t aOffset, |
michael@0 | 273 | nsIntRect &aCharRect); |
michael@0 | 274 | bool GetCaretRect(nsWindow* aWindow, nsIntRect &aCaretRect); |
michael@0 | 275 | void GetCompositionString(const nsIMEContext &aIMEContext, DWORD aIndex); |
michael@0 | 276 | /** |
michael@0 | 277 | * Get the current target clause of composition string. |
michael@0 | 278 | * If there are one or more characters whose attribute is ATTR_TARGET_*, |
michael@0 | 279 | * this returns the first character's offset and its length. |
michael@0 | 280 | * Otherwise, e.g., the all characters are ATTR_INPUT, this returns |
michael@0 | 281 | * the composition string range because the all is the current target. |
michael@0 | 282 | * |
michael@0 | 283 | * aLength can be null (default), but aOffset must not be null. |
michael@0 | 284 | * |
michael@0 | 285 | * The aOffset value is offset in the contents. So, when you need offset |
michael@0 | 286 | * in the composition string, you need to subtract mCompositionStart from it. |
michael@0 | 287 | */ |
michael@0 | 288 | bool GetTargetClauseRange(uint32_t *aOffset, uint32_t *aLength = nullptr); |
michael@0 | 289 | void DispatchTextEvent(nsWindow* aWindow, const nsIMEContext &aIMEContext, |
michael@0 | 290 | bool aCheckAttr = true); |
michael@0 | 291 | already_AddRefed<mozilla::TextRangeArray> CreateTextRangeArray(); |
michael@0 | 292 | |
michael@0 | 293 | nsresult EnsureClauseArray(int32_t aCount); |
michael@0 | 294 | nsresult EnsureAttributeArray(int32_t aCount); |
michael@0 | 295 | |
michael@0 | 296 | /** |
michael@0 | 297 | * When WM_IME_CHAR is received and passed to DefWindowProc, we need to |
michael@0 | 298 | * record the messages. In other words, we should record the messages |
michael@0 | 299 | * when we receive WM_IME_CHAR on windowless plug-in (if we have focus, |
michael@0 | 300 | * we always eat them). When focus is moved from a windowless plug-in to |
michael@0 | 301 | * our window during composition, WM_IME_CHAR messages were received when |
michael@0 | 302 | * the plug-in has focus. However, WM_CHAR messages are received after the |
michael@0 | 303 | * plug-in lost focus. So, we need to ignore the WM_CHAR messages because |
michael@0 | 304 | * they make unexpected text input events on us. |
michael@0 | 305 | */ |
michael@0 | 306 | nsTArray<MSG> mPassedIMEChar; |
michael@0 | 307 | |
michael@0 | 308 | bool IsIMECharRecordsEmpty() |
michael@0 | 309 | { |
michael@0 | 310 | return mPassedIMEChar.IsEmpty(); |
michael@0 | 311 | } |
michael@0 | 312 | void ResetIMECharRecords() |
michael@0 | 313 | { |
michael@0 | 314 | mPassedIMEChar.Clear(); |
michael@0 | 315 | } |
michael@0 | 316 | void DequeueIMECharRecords(WPARAM &wParam, LPARAM &lParam) |
michael@0 | 317 | { |
michael@0 | 318 | MSG msg = mPassedIMEChar.ElementAt(0); |
michael@0 | 319 | wParam = msg.wParam; |
michael@0 | 320 | lParam = msg.lParam; |
michael@0 | 321 | mPassedIMEChar.RemoveElementAt(0); |
michael@0 | 322 | } |
michael@0 | 323 | void EnqueueIMECharRecords(WPARAM wParam, LPARAM lParam) |
michael@0 | 324 | { |
michael@0 | 325 | MSG msg; |
michael@0 | 326 | msg.wParam = wParam; |
michael@0 | 327 | msg.lParam = lParam; |
michael@0 | 328 | mPassedIMEChar.AppendElement(msg); |
michael@0 | 329 | } |
michael@0 | 330 | |
michael@0 | 331 | nsWindow* mComposingWindow; |
michael@0 | 332 | nsString mCompositionString; |
michael@0 | 333 | nsString mLastDispatchedCompositionString; |
michael@0 | 334 | InfallibleTArray<uint32_t> mClauseArray; |
michael@0 | 335 | InfallibleTArray<uint8_t> mAttributeArray; |
michael@0 | 336 | |
michael@0 | 337 | int32_t mCursorPosition; |
michael@0 | 338 | uint32_t mCompositionStart; |
michael@0 | 339 | |
michael@0 | 340 | bool mIsComposing; |
michael@0 | 341 | bool mIsComposingOnPlugin; |
michael@0 | 342 | bool mNativeCaretIsCreated; |
michael@0 | 343 | |
michael@0 | 344 | static UINT sCodePage; |
michael@0 | 345 | static DWORD sIMEProperty; |
michael@0 | 346 | }; |
michael@0 | 347 | |
michael@0 | 348 | #endif // nsIMM32Handler_h__ |