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