|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim:set ts=2 sts=2 sw=2 et cin: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #ifdef MOZ_LOGGING |
|
8 #define FORCE_PR_LOG /* Allow logging in the release build */ |
|
9 #endif // MOZ_LOGGING |
|
10 #include "prlog.h" |
|
11 |
|
12 #include "nsIMM32Handler.h" |
|
13 #include "nsWindow.h" |
|
14 #include "nsWindowDefs.h" |
|
15 #include "WinUtils.h" |
|
16 #include "KeyboardLayout.h" |
|
17 #include <algorithm> |
|
18 |
|
19 #include "mozilla/MiscEvents.h" |
|
20 #include "mozilla/TextEvents.h" |
|
21 |
|
22 using namespace mozilla; |
|
23 using namespace mozilla::widget; |
|
24 |
|
25 static nsIMM32Handler* gIMM32Handler = nullptr; |
|
26 |
|
27 #ifdef PR_LOGGING |
|
28 PRLogModuleInfo* gIMM32Log = nullptr; |
|
29 #endif |
|
30 |
|
31 static UINT sWM_MSIME_MOUSE = 0; // mouse message for MSIME 98/2000 |
|
32 |
|
33 //------------------------------------------------------------------------- |
|
34 // |
|
35 // from http://download.microsoft.com/download/6/0/9/60908e9e-d2c1-47db-98f6-216af76a235f/msime.h |
|
36 // The document for this has been removed from MSDN... |
|
37 // |
|
38 //------------------------------------------------------------------------- |
|
39 |
|
40 #define RWM_MOUSE TEXT("MSIMEMouseOperation") |
|
41 |
|
42 #define IMEMOUSE_NONE 0x00 // no mouse button was pushed |
|
43 #define IMEMOUSE_LDOWN 0x01 |
|
44 #define IMEMOUSE_RDOWN 0x02 |
|
45 #define IMEMOUSE_MDOWN 0x04 |
|
46 #define IMEMOUSE_WUP 0x10 // wheel up |
|
47 #define IMEMOUSE_WDOWN 0x20 // wheel down |
|
48 |
|
49 UINT nsIMM32Handler::sCodePage = 0; |
|
50 DWORD nsIMM32Handler::sIMEProperty = 0; |
|
51 |
|
52 /* static */ void |
|
53 nsIMM32Handler::EnsureHandlerInstance() |
|
54 { |
|
55 if (!gIMM32Handler) { |
|
56 gIMM32Handler = new nsIMM32Handler(); |
|
57 } |
|
58 } |
|
59 |
|
60 /* static */ void |
|
61 nsIMM32Handler::Initialize() |
|
62 { |
|
63 #ifdef PR_LOGGING |
|
64 if (!gIMM32Log) |
|
65 gIMM32Log = PR_NewLogModule("nsIMM32HandlerWidgets"); |
|
66 #endif |
|
67 |
|
68 if (!sWM_MSIME_MOUSE) { |
|
69 sWM_MSIME_MOUSE = ::RegisterWindowMessage(RWM_MOUSE); |
|
70 } |
|
71 InitKeyboardLayout(::GetKeyboardLayout(0)); |
|
72 } |
|
73 |
|
74 /* static */ void |
|
75 nsIMM32Handler::Terminate() |
|
76 { |
|
77 if (!gIMM32Handler) |
|
78 return; |
|
79 delete gIMM32Handler; |
|
80 gIMM32Handler = nullptr; |
|
81 } |
|
82 |
|
83 /* static */ bool |
|
84 nsIMM32Handler::IsComposingOnOurEditor() |
|
85 { |
|
86 return gIMM32Handler && gIMM32Handler->mIsComposing; |
|
87 } |
|
88 |
|
89 /* static */ bool |
|
90 nsIMM32Handler::IsComposingOnPlugin() |
|
91 { |
|
92 return gIMM32Handler && gIMM32Handler->mIsComposingOnPlugin; |
|
93 } |
|
94 |
|
95 /* static */ bool |
|
96 nsIMM32Handler::IsComposingWindow(nsWindow* aWindow) |
|
97 { |
|
98 return gIMM32Handler && gIMM32Handler->mComposingWindow == aWindow; |
|
99 } |
|
100 |
|
101 /* static */ bool |
|
102 nsIMM32Handler::IsTopLevelWindowOfComposition(nsWindow* aWindow) |
|
103 { |
|
104 if (!gIMM32Handler || !gIMM32Handler->mComposingWindow) { |
|
105 return false; |
|
106 } |
|
107 HWND wnd = gIMM32Handler->mComposingWindow->GetWindowHandle(); |
|
108 return WinUtils::GetTopLevelHWND(wnd, true) == aWindow->GetWindowHandle(); |
|
109 } |
|
110 |
|
111 /* static */ bool |
|
112 nsIMM32Handler::ShouldDrawCompositionStringOurselves() |
|
113 { |
|
114 // If current IME has special UI or its composition window should not |
|
115 // positioned to caret position, we should now draw composition string |
|
116 // ourselves. |
|
117 return !(sIMEProperty & IME_PROP_SPECIAL_UI) && |
|
118 (sIMEProperty & IME_PROP_AT_CARET); |
|
119 } |
|
120 |
|
121 /* static */ void |
|
122 nsIMM32Handler::InitKeyboardLayout(HKL aKeyboardLayout) |
|
123 { |
|
124 WORD langID = LOWORD(aKeyboardLayout); |
|
125 ::GetLocaleInfoW(MAKELCID(langID, SORT_DEFAULT), |
|
126 LOCALE_IDEFAULTANSICODEPAGE | LOCALE_RETURN_NUMBER, |
|
127 (PWSTR)&sCodePage, sizeof(sCodePage) / sizeof(WCHAR)); |
|
128 sIMEProperty = ::ImmGetProperty(aKeyboardLayout, IGP_PROPERTY); |
|
129 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
130 ("IMM32: InitKeyboardLayout, aKeyboardLayout=%08x, sCodePage=%lu, " |
|
131 "sIMEProperty=%08x", |
|
132 aKeyboardLayout, sCodePage, sIMEProperty)); |
|
133 } |
|
134 |
|
135 /* static */ UINT |
|
136 nsIMM32Handler::GetKeyboardCodePage() |
|
137 { |
|
138 return sCodePage; |
|
139 } |
|
140 |
|
141 /* static */ |
|
142 nsIMEUpdatePreference |
|
143 nsIMM32Handler::GetIMEUpdatePreference() |
|
144 { |
|
145 return nsIMEUpdatePreference(nsIMEUpdatePreference::NOTIFY_POSITION_CHANGE); |
|
146 } |
|
147 |
|
148 // used for checking the lParam of WM_IME_COMPOSITION |
|
149 #define IS_COMPOSING_LPARAM(lParam) \ |
|
150 ((lParam) & (GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE | GCS_CURSORPOS)) |
|
151 #define IS_COMMITTING_LPARAM(lParam) ((lParam) & GCS_RESULTSTR) |
|
152 // Some IMEs (e.g., the standard IME for Korean) don't have caret position, |
|
153 // then, we should not set caret position to text event. |
|
154 #define NO_IME_CARET -1 |
|
155 |
|
156 nsIMM32Handler::nsIMM32Handler() : |
|
157 mComposingWindow(nullptr), mCursorPosition(NO_IME_CARET), mCompositionStart(0), |
|
158 mIsComposing(false), mIsComposingOnPlugin(false), |
|
159 mNativeCaretIsCreated(false) |
|
160 { |
|
161 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, ("IMM32: nsIMM32Handler is created\n")); |
|
162 } |
|
163 |
|
164 nsIMM32Handler::~nsIMM32Handler() |
|
165 { |
|
166 if (mIsComposing) { |
|
167 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
168 ("IMM32: ~nsIMM32Handler, ERROR, the instance is still composing\n")); |
|
169 } |
|
170 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, ("IMM32: nsIMM32Handler is destroyed\n")); |
|
171 } |
|
172 |
|
173 nsresult |
|
174 nsIMM32Handler::EnsureClauseArray(int32_t aCount) |
|
175 { |
|
176 NS_ENSURE_ARG_MIN(aCount, 0); |
|
177 mClauseArray.SetCapacity(aCount + 32); |
|
178 return NS_OK; |
|
179 } |
|
180 |
|
181 nsresult |
|
182 nsIMM32Handler::EnsureAttributeArray(int32_t aCount) |
|
183 { |
|
184 NS_ENSURE_ARG_MIN(aCount, 0); |
|
185 mAttributeArray.SetCapacity(aCount + 64); |
|
186 return NS_OK; |
|
187 } |
|
188 |
|
189 /* static */ void |
|
190 nsIMM32Handler::CommitComposition(nsWindow* aWindow, bool aForce) |
|
191 { |
|
192 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
193 ("IMM32: CommitComposition, aForce=%s, aWindow=%p, hWnd=%08x, mComposingWindow=%p%s\n", |
|
194 aForce ? "TRUE" : "FALSE", |
|
195 aWindow, aWindow->GetWindowHandle(), |
|
196 gIMM32Handler ? gIMM32Handler->mComposingWindow : nullptr, |
|
197 gIMM32Handler && gIMM32Handler->mComposingWindow ? |
|
198 IsComposingOnOurEditor() ? " (composing on editor)" : |
|
199 " (composing on plug-in)" : "")); |
|
200 if (!aForce && !IsComposingWindow(aWindow)) { |
|
201 return; |
|
202 } |
|
203 |
|
204 nsIMEContext IMEContext(aWindow->GetWindowHandle()); |
|
205 bool associated = IMEContext.AssociateDefaultContext(); |
|
206 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
207 ("IMM32: CommitComposition, associated=%s\n", |
|
208 associated ? "YES" : "NO")); |
|
209 |
|
210 if (IMEContext.IsValid()) { |
|
211 ::ImmNotifyIME(IMEContext.get(), NI_COMPOSITIONSTR, CPS_COMPLETE, 0); |
|
212 ::ImmNotifyIME(IMEContext.get(), NI_COMPOSITIONSTR, CPS_CANCEL, 0); |
|
213 } |
|
214 |
|
215 if (associated) { |
|
216 IMEContext.Disassociate(); |
|
217 } |
|
218 } |
|
219 |
|
220 /* static */ void |
|
221 nsIMM32Handler::CancelComposition(nsWindow* aWindow, bool aForce) |
|
222 { |
|
223 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
224 ("IMM32: CancelComposition, aForce=%s, aWindow=%p, hWnd=%08x, mComposingWindow=%p%s\n", |
|
225 aForce ? "TRUE" : "FALSE", |
|
226 aWindow, aWindow->GetWindowHandle(), |
|
227 gIMM32Handler ? gIMM32Handler->mComposingWindow : nullptr, |
|
228 gIMM32Handler && gIMM32Handler->mComposingWindow ? |
|
229 IsComposingOnOurEditor() ? " (composing on editor)" : |
|
230 " (composing on plug-in)" : "")); |
|
231 if (!aForce && !IsComposingWindow(aWindow)) { |
|
232 return; |
|
233 } |
|
234 |
|
235 nsIMEContext IMEContext(aWindow->GetWindowHandle()); |
|
236 bool associated = IMEContext.AssociateDefaultContext(); |
|
237 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
238 ("IMM32: CancelComposition, associated=%s\n", |
|
239 associated ? "YES" : "NO")); |
|
240 |
|
241 if (IMEContext.IsValid()) { |
|
242 ::ImmNotifyIME(IMEContext.get(), NI_COMPOSITIONSTR, CPS_CANCEL, 0); |
|
243 } |
|
244 |
|
245 if (associated) { |
|
246 IMEContext.Disassociate(); |
|
247 } |
|
248 } |
|
249 |
|
250 // static |
|
251 void |
|
252 nsIMM32Handler::OnUpdateComposition(nsWindow* aWindow) |
|
253 { |
|
254 if (!gIMM32Handler) { |
|
255 return; |
|
256 } |
|
257 |
|
258 if (aWindow->PluginHasFocus()) { |
|
259 return; |
|
260 } |
|
261 |
|
262 nsIMEContext IMEContext(aWindow->GetWindowHandle()); |
|
263 gIMM32Handler->SetIMERelatedWindowsPos(aWindow, IMEContext); |
|
264 } |
|
265 |
|
266 |
|
267 /* static */ bool |
|
268 nsIMM32Handler::ProcessInputLangChangeMessage(nsWindow* aWindow, |
|
269 WPARAM wParam, |
|
270 LPARAM lParam, |
|
271 MSGResult& aResult) |
|
272 { |
|
273 aResult.mResult = 0; |
|
274 aResult.mConsumed = false; |
|
275 // We don't need to create the instance of the handler here. |
|
276 if (gIMM32Handler) { |
|
277 gIMM32Handler->OnInputLangChange(aWindow, wParam, lParam, aResult); |
|
278 } |
|
279 InitKeyboardLayout(reinterpret_cast<HKL>(lParam)); |
|
280 // We can release the instance here, because the instance may be never |
|
281 // used. E.g., the new keyboard layout may not use IME, or it may use TSF. |
|
282 Terminate(); |
|
283 // Don't return as "processed", the messages should be processed on nsWindow |
|
284 // too. |
|
285 return false; |
|
286 } |
|
287 |
|
288 /* static */ bool |
|
289 nsIMM32Handler::ProcessMessage(nsWindow* aWindow, UINT msg, |
|
290 WPARAM &wParam, LPARAM &lParam, |
|
291 MSGResult& aResult) |
|
292 { |
|
293 // XXX We store the composing window in mComposingWindow. If IME messages are |
|
294 // sent to different window, we should commit the old transaction. And also |
|
295 // if the new window handle is not focused, probably, we should not start |
|
296 // the composition, however, such case should not be, it's just bad scenario. |
|
297 |
|
298 // When a plug-in has focus or compsition, we should dispatch the IME events |
|
299 // to the plug-in. |
|
300 if (aWindow->PluginHasFocus() || IsComposingOnPlugin()) { |
|
301 return ProcessMessageForPlugin(aWindow, msg, wParam, lParam, aResult); |
|
302 } |
|
303 |
|
304 aResult.mResult = 0; |
|
305 switch (msg) { |
|
306 case WM_LBUTTONDOWN: |
|
307 case WM_MBUTTONDOWN: |
|
308 case WM_RBUTTONDOWN: { |
|
309 // We don't need to create the instance of the handler here. |
|
310 if (!gIMM32Handler) { |
|
311 return false; |
|
312 } |
|
313 return gIMM32Handler->OnMouseEvent(aWindow, lParam, |
|
314 msg == WM_LBUTTONDOWN ? IMEMOUSE_LDOWN : |
|
315 msg == WM_MBUTTONDOWN ? IMEMOUSE_MDOWN : |
|
316 IMEMOUSE_RDOWN, aResult); |
|
317 } |
|
318 case WM_INPUTLANGCHANGE: |
|
319 return ProcessInputLangChangeMessage(aWindow, wParam, lParam, aResult); |
|
320 case WM_IME_STARTCOMPOSITION: |
|
321 EnsureHandlerInstance(); |
|
322 return gIMM32Handler->OnIMEStartComposition(aWindow, aResult); |
|
323 case WM_IME_COMPOSITION: |
|
324 EnsureHandlerInstance(); |
|
325 return gIMM32Handler->OnIMEComposition(aWindow, wParam, lParam, aResult); |
|
326 case WM_IME_ENDCOMPOSITION: |
|
327 EnsureHandlerInstance(); |
|
328 return gIMM32Handler->OnIMEEndComposition(aWindow, aResult); |
|
329 case WM_IME_CHAR: |
|
330 return OnIMEChar(aWindow, wParam, lParam, aResult); |
|
331 case WM_IME_NOTIFY: |
|
332 return OnIMENotify(aWindow, wParam, lParam, aResult); |
|
333 case WM_IME_REQUEST: |
|
334 EnsureHandlerInstance(); |
|
335 return gIMM32Handler->OnIMERequest(aWindow, wParam, lParam, aResult); |
|
336 case WM_IME_SELECT: |
|
337 return OnIMESelect(aWindow, wParam, lParam, aResult); |
|
338 case WM_IME_SETCONTEXT: |
|
339 return OnIMESetContext(aWindow, wParam, lParam, aResult); |
|
340 case WM_KEYDOWN: |
|
341 return OnKeyDownEvent(aWindow, wParam, lParam, aResult); |
|
342 case WM_CHAR: |
|
343 if (!gIMM32Handler) { |
|
344 return false; |
|
345 } |
|
346 return gIMM32Handler->OnChar(aWindow, wParam, lParam, aResult); |
|
347 default: |
|
348 return false; |
|
349 }; |
|
350 } |
|
351 |
|
352 /* static */ bool |
|
353 nsIMM32Handler::ProcessMessageForPlugin(nsWindow* aWindow, UINT msg, |
|
354 WPARAM &wParam, LPARAM &lParam, |
|
355 MSGResult& aResult) |
|
356 { |
|
357 aResult.mResult = 0; |
|
358 aResult.mConsumed = false; |
|
359 switch (msg) { |
|
360 case WM_INPUTLANGCHANGEREQUEST: |
|
361 case WM_INPUTLANGCHANGE: |
|
362 aWindow->DispatchPluginEvent(msg, wParam, lParam, false); |
|
363 return ProcessInputLangChangeMessage(aWindow, wParam, lParam, aResult); |
|
364 case WM_IME_COMPOSITION: |
|
365 EnsureHandlerInstance(); |
|
366 return gIMM32Handler->OnIMECompositionOnPlugin(aWindow, wParam, lParam, |
|
367 aResult); |
|
368 case WM_IME_STARTCOMPOSITION: |
|
369 EnsureHandlerInstance(); |
|
370 return gIMM32Handler->OnIMEStartCompositionOnPlugin(aWindow, wParam, |
|
371 lParam, aResult); |
|
372 case WM_IME_ENDCOMPOSITION: |
|
373 EnsureHandlerInstance(); |
|
374 return gIMM32Handler->OnIMEEndCompositionOnPlugin(aWindow, wParam, lParam, |
|
375 aResult); |
|
376 case WM_IME_CHAR: |
|
377 EnsureHandlerInstance(); |
|
378 return gIMM32Handler->OnIMECharOnPlugin(aWindow, wParam, lParam, aResult); |
|
379 case WM_IME_SETCONTEXT: |
|
380 return OnIMESetContextOnPlugin(aWindow, wParam, lParam, aResult); |
|
381 case WM_CHAR: |
|
382 if (!gIMM32Handler) { |
|
383 return false; |
|
384 } |
|
385 return gIMM32Handler->OnCharOnPlugin(aWindow, wParam, lParam, aResult); |
|
386 case WM_IME_COMPOSITIONFULL: |
|
387 case WM_IME_CONTROL: |
|
388 case WM_IME_KEYDOWN: |
|
389 case WM_IME_KEYUP: |
|
390 case WM_IME_REQUEST: |
|
391 case WM_IME_SELECT: |
|
392 aResult.mConsumed = |
|
393 aWindow->DispatchPluginEvent(msg, wParam, lParam, false); |
|
394 return true; |
|
395 } |
|
396 return false; |
|
397 } |
|
398 |
|
399 /**************************************************************************** |
|
400 * message handlers |
|
401 ****************************************************************************/ |
|
402 |
|
403 void |
|
404 nsIMM32Handler::OnInputLangChange(nsWindow* aWindow, |
|
405 WPARAM wParam, |
|
406 LPARAM lParam, |
|
407 MSGResult& aResult) |
|
408 { |
|
409 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
410 ("IMM32: OnInputLangChange, hWnd=%08x, wParam=%08x, lParam=%08x\n", |
|
411 aWindow->GetWindowHandle(), wParam, lParam)); |
|
412 |
|
413 aWindow->NotifyIME(REQUEST_TO_COMMIT_COMPOSITION); |
|
414 NS_ASSERTION(!mIsComposing, "ResetInputState failed"); |
|
415 |
|
416 if (mIsComposing) { |
|
417 HandleEndComposition(aWindow); |
|
418 } |
|
419 |
|
420 aResult.mConsumed = false; |
|
421 } |
|
422 |
|
423 bool |
|
424 nsIMM32Handler::OnIMEStartComposition(nsWindow* aWindow, |
|
425 MSGResult& aResult) |
|
426 { |
|
427 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
428 ("IMM32: OnIMEStartComposition, hWnd=%08x, mIsComposing=%s\n", |
|
429 aWindow->GetWindowHandle(), mIsComposing ? "TRUE" : "FALSE")); |
|
430 aResult.mConsumed = ShouldDrawCompositionStringOurselves(); |
|
431 if (mIsComposing) { |
|
432 NS_WARNING("Composition has been already started"); |
|
433 return true; |
|
434 } |
|
435 |
|
436 nsIMEContext IMEContext(aWindow->GetWindowHandle()); |
|
437 HandleStartComposition(aWindow, IMEContext); |
|
438 return true; |
|
439 } |
|
440 |
|
441 bool |
|
442 nsIMM32Handler::OnIMEComposition(nsWindow* aWindow, |
|
443 WPARAM wParam, |
|
444 LPARAM lParam, |
|
445 MSGResult& aResult) |
|
446 { |
|
447 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
448 ("IMM32: OnIMEComposition, hWnd=%08x, lParam=%08x, mIsComposing=%s\n", |
|
449 aWindow->GetWindowHandle(), lParam, mIsComposing ? "TRUE" : "FALSE")); |
|
450 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
451 ("IMM32: OnIMEComposition, GCS_RESULTSTR=%s, GCS_COMPSTR=%s, GCS_COMPATTR=%s, GCS_COMPCLAUSE=%s, GCS_CURSORPOS=%s\n", |
|
452 lParam & GCS_RESULTSTR ? "YES" : "no", |
|
453 lParam & GCS_COMPSTR ? "YES" : "no", |
|
454 lParam & GCS_COMPATTR ? "YES" : "no", |
|
455 lParam & GCS_COMPCLAUSE ? "YES" : "no", |
|
456 lParam & GCS_CURSORPOS ? "YES" : "no")); |
|
457 |
|
458 NS_PRECONDITION(!aWindow->PluginHasFocus(), |
|
459 "OnIMEComposition should not be called when a plug-in has focus"); |
|
460 |
|
461 nsIMEContext IMEContext(aWindow->GetWindowHandle()); |
|
462 aResult.mConsumed = HandleComposition(aWindow, IMEContext, lParam); |
|
463 return true; |
|
464 } |
|
465 |
|
466 bool |
|
467 nsIMM32Handler::OnIMEEndComposition(nsWindow* aWindow, |
|
468 MSGResult& aResult) |
|
469 { |
|
470 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
471 ("IMM32: OnIMEEndComposition, hWnd=%08x, mIsComposing=%s\n", |
|
472 aWindow->GetWindowHandle(), mIsComposing ? "TRUE" : "FALSE")); |
|
473 |
|
474 aResult.mConsumed = ShouldDrawCompositionStringOurselves(); |
|
475 if (!mIsComposing) { |
|
476 return true; |
|
477 } |
|
478 |
|
479 // Korean IME posts WM_IME_ENDCOMPOSITION first when we hit space during |
|
480 // composition. Then, we should ignore the message and commit the composition |
|
481 // string at following WM_IME_COMPOSITION. |
|
482 MSG compositionMsg; |
|
483 if (WinUtils::PeekMessage(&compositionMsg, aWindow->GetWindowHandle(), |
|
484 WM_IME_STARTCOMPOSITION, WM_IME_COMPOSITION, |
|
485 PM_NOREMOVE) && |
|
486 compositionMsg.message == WM_IME_COMPOSITION && |
|
487 IS_COMMITTING_LPARAM(compositionMsg.lParam)) { |
|
488 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
489 ("IMM32: OnIMEEndComposition, WM_IME_ENDCOMPOSITION is followed by " |
|
490 "WM_IME_COMPOSITION, ignoring the message...")); |
|
491 return true; |
|
492 } |
|
493 |
|
494 // Otherwise, e.g., ChangJie doesn't post WM_IME_COMPOSITION before |
|
495 // WM_IME_ENDCOMPOSITION when composition string becomes empty. |
|
496 // Then, we should dispatch a compositionupdate event, a text event and |
|
497 // a compositionend event. |
|
498 // XXX Shouldn't we dispatch the text event with actual or latest composition |
|
499 // string? |
|
500 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
501 ("IMM32: OnIMEEndComposition, mCompositionString=\"%s\"%s", |
|
502 NS_ConvertUTF16toUTF8(mCompositionString).get(), |
|
503 mCompositionString.IsEmpty() ? "" : ", but canceling it...")); |
|
504 |
|
505 mCompositionString.Truncate(); |
|
506 |
|
507 nsIMEContext IMEContext(aWindow->GetWindowHandle()); |
|
508 DispatchTextEvent(aWindow, IMEContext, false); |
|
509 |
|
510 HandleEndComposition(aWindow); |
|
511 |
|
512 return true; |
|
513 } |
|
514 |
|
515 /* static */ bool |
|
516 nsIMM32Handler::OnIMEChar(nsWindow* aWindow, |
|
517 WPARAM wParam, |
|
518 LPARAM lParam, |
|
519 MSGResult& aResult) |
|
520 { |
|
521 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
522 ("IMM32: OnIMEChar, hWnd=%08x, char=%08x\n", |
|
523 aWindow->GetWindowHandle(), wParam)); |
|
524 |
|
525 // We don't need to fire any text events from here. This method will be |
|
526 // called when the composition string of the current IME is not drawn by us |
|
527 // and some characters are committed. In that case, the committed string was |
|
528 // processed in nsWindow::OnIMEComposition already. |
|
529 |
|
530 // We need to consume the message so that Windows don't send two WM_CHAR msgs |
|
531 aResult.mConsumed = true; |
|
532 return true; |
|
533 } |
|
534 |
|
535 /* static */ bool |
|
536 nsIMM32Handler::OnIMECompositionFull(nsWindow* aWindow, |
|
537 MSGResult& aResult) |
|
538 { |
|
539 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
540 ("IMM32: OnIMECompositionFull, hWnd=%08x\n", |
|
541 aWindow->GetWindowHandle())); |
|
542 |
|
543 // not implement yet |
|
544 aResult.mConsumed = false; |
|
545 return true; |
|
546 } |
|
547 |
|
548 /* static */ bool |
|
549 nsIMM32Handler::OnIMENotify(nsWindow* aWindow, |
|
550 WPARAM wParam, |
|
551 LPARAM lParam, |
|
552 MSGResult& aResult) |
|
553 { |
|
554 #ifdef PR_LOGGING |
|
555 switch (wParam) { |
|
556 case IMN_CHANGECANDIDATE: |
|
557 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
558 ("IMM32: OnIMENotify, hWnd=%08x, IMN_CHANGECANDIDATE, lParam=%08x\n", |
|
559 aWindow->GetWindowHandle(), lParam)); |
|
560 break; |
|
561 case IMN_CLOSECANDIDATE: |
|
562 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
563 ("IMM32: OnIMENotify, hWnd=%08x, IMN_CLOSECANDIDATE, lParam=%08x\n", |
|
564 aWindow->GetWindowHandle(), lParam)); |
|
565 break; |
|
566 case IMN_CLOSESTATUSWINDOW: |
|
567 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
568 ("IMM32: OnIMENotify, hWnd=%08x, IMN_CLOSESTATUSWINDOW\n", |
|
569 aWindow->GetWindowHandle())); |
|
570 break; |
|
571 case IMN_GUIDELINE: |
|
572 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
573 ("IMM32: OnIMENotify, hWnd=%08x, IMN_GUIDELINE\n", |
|
574 aWindow->GetWindowHandle())); |
|
575 break; |
|
576 case IMN_OPENCANDIDATE: |
|
577 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
578 ("IMM32: OnIMENotify, hWnd=%08x, IMN_OPENCANDIDATE, lParam=%08x\n", |
|
579 aWindow->GetWindowHandle(), lParam)); |
|
580 break; |
|
581 case IMN_OPENSTATUSWINDOW: |
|
582 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
583 ("IMM32: OnIMENotify, hWnd=%08x, IMN_OPENSTATUSWINDOW\n", |
|
584 aWindow->GetWindowHandle())); |
|
585 break; |
|
586 case IMN_SETCANDIDATEPOS: |
|
587 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
588 ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETCANDIDATEPOS, lParam=%08x\n", |
|
589 aWindow->GetWindowHandle(), lParam)); |
|
590 break; |
|
591 case IMN_SETCOMPOSITIONFONT: |
|
592 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
593 ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETCOMPOSITIONFONT\n", |
|
594 aWindow->GetWindowHandle())); |
|
595 break; |
|
596 case IMN_SETCOMPOSITIONWINDOW: |
|
597 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
598 ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETCOMPOSITIONWINDOW\n", |
|
599 aWindow->GetWindowHandle())); |
|
600 break; |
|
601 case IMN_SETCONVERSIONMODE: |
|
602 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
603 ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETCONVERSIONMODE\n", |
|
604 aWindow->GetWindowHandle())); |
|
605 break; |
|
606 case IMN_SETOPENSTATUS: |
|
607 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
608 ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETOPENSTATUS\n", |
|
609 aWindow->GetWindowHandle())); |
|
610 break; |
|
611 case IMN_SETSENTENCEMODE: |
|
612 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
613 ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETSENTENCEMODE\n", |
|
614 aWindow->GetWindowHandle())); |
|
615 break; |
|
616 case IMN_SETSTATUSWINDOWPOS: |
|
617 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
618 ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETSTATUSWINDOWPOS\n", |
|
619 aWindow->GetWindowHandle())); |
|
620 break; |
|
621 case IMN_PRIVATE: |
|
622 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
623 ("IMM32: OnIMENotify, hWnd=%08x, IMN_PRIVATE\n", |
|
624 aWindow->GetWindowHandle())); |
|
625 break; |
|
626 } |
|
627 #endif // PR_LOGGING |
|
628 |
|
629 // not implement yet |
|
630 aResult.mConsumed = false; |
|
631 return true; |
|
632 } |
|
633 |
|
634 bool |
|
635 nsIMM32Handler::OnIMERequest(nsWindow* aWindow, |
|
636 WPARAM wParam, |
|
637 LPARAM lParam, |
|
638 MSGResult& aResult) |
|
639 { |
|
640 switch (wParam) { |
|
641 case IMR_RECONVERTSTRING: |
|
642 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
643 ("IMM32: OnIMERequest, hWnd=%08x, IMR_RECONVERTSTRING\n", |
|
644 aWindow->GetWindowHandle())); |
|
645 aResult.mConsumed = HandleReconvert(aWindow, lParam, &aResult.mResult); |
|
646 return true; |
|
647 case IMR_QUERYCHARPOSITION: |
|
648 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
649 ("IMM32: OnIMERequest, hWnd=%08x, IMR_QUERYCHARPOSITION\n", |
|
650 aWindow->GetWindowHandle())); |
|
651 aResult.mConsumed = |
|
652 HandleQueryCharPosition(aWindow, lParam, &aResult.mResult); |
|
653 return true; |
|
654 case IMR_DOCUMENTFEED: |
|
655 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
656 ("IMM32: OnIMERequest, hWnd=%08x, IMR_DOCUMENTFEED\n", |
|
657 aWindow->GetWindowHandle())); |
|
658 aResult.mConsumed = HandleDocumentFeed(aWindow, lParam, &aResult.mResult); |
|
659 return true; |
|
660 default: |
|
661 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
662 ("IMM32: OnIMERequest, hWnd=%08x, wParam=%08x\n", |
|
663 aWindow->GetWindowHandle(), wParam)); |
|
664 aResult.mConsumed = false; |
|
665 return true; |
|
666 } |
|
667 } |
|
668 |
|
669 /* static */ bool |
|
670 nsIMM32Handler::OnIMESelect(nsWindow* aWindow, |
|
671 WPARAM wParam, |
|
672 LPARAM lParam, |
|
673 MSGResult& aResult) |
|
674 { |
|
675 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
676 ("IMM32: OnIMESelect, hWnd=%08x, wParam=%08x, lParam=%08x\n", |
|
677 aWindow->GetWindowHandle(), wParam, lParam)); |
|
678 |
|
679 // not implement yet |
|
680 aResult.mConsumed = false; |
|
681 return true; |
|
682 } |
|
683 |
|
684 /* static */ bool |
|
685 nsIMM32Handler::OnIMESetContext(nsWindow* aWindow, |
|
686 WPARAM wParam, |
|
687 LPARAM lParam, |
|
688 MSGResult& aResult) |
|
689 { |
|
690 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
691 ("IMM32: OnIMESetContext, hWnd=%08x, %s, lParam=%08x\n", |
|
692 aWindow->GetWindowHandle(), wParam ? "Active" : "Deactive", lParam)); |
|
693 |
|
694 aResult.mConsumed = false; |
|
695 |
|
696 // NOTE: If the aWindow is top level window of the composing window because |
|
697 // when a window on deactive window gets focus, WM_IME_SETCONTEXT (wParam is |
|
698 // TRUE) is sent to the top level window first. After that, |
|
699 // WM_IME_SETCONTEXT (wParam is FALSE) is sent to the top level window. |
|
700 // Finally, WM_IME_SETCONTEXT (wParam is TRUE) is sent to the focused window. |
|
701 // The top level window never becomes composing window, so, we can ignore |
|
702 // the WM_IME_SETCONTEXT on the top level window. |
|
703 if (IsTopLevelWindowOfComposition(aWindow)) { |
|
704 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
705 ("IMM32: OnIMESetContext, hWnd=%08x is top level window\n")); |
|
706 return true; |
|
707 } |
|
708 |
|
709 // When IME context is activating on another window, |
|
710 // we should commit the old composition on the old window. |
|
711 bool cancelComposition = false; |
|
712 if (wParam && gIMM32Handler) { |
|
713 cancelComposition = |
|
714 gIMM32Handler->CommitCompositionOnPreviousWindow(aWindow); |
|
715 } |
|
716 |
|
717 if (wParam && (lParam & ISC_SHOWUICOMPOSITIONWINDOW) && |
|
718 ShouldDrawCompositionStringOurselves()) { |
|
719 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
720 ("IMM32: OnIMESetContext, ISC_SHOWUICOMPOSITIONWINDOW is removed\n")); |
|
721 lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW; |
|
722 } |
|
723 |
|
724 // We should sent WM_IME_SETCONTEXT to the DefWndProc here because the |
|
725 // ancestor windows shouldn't receive this message. If they receive the |
|
726 // message, we cannot know whether which window is the target of the message. |
|
727 aResult.mResult = ::DefWindowProc(aWindow->GetWindowHandle(), |
|
728 WM_IME_SETCONTEXT, wParam, lParam); |
|
729 |
|
730 // Cancel composition on the new window if we committed our composition on |
|
731 // another window. |
|
732 if (cancelComposition) { |
|
733 CancelComposition(aWindow, true); |
|
734 } |
|
735 |
|
736 aResult.mConsumed = true; |
|
737 return true; |
|
738 } |
|
739 |
|
740 bool |
|
741 nsIMM32Handler::OnChar(nsWindow* aWindow, |
|
742 WPARAM wParam, |
|
743 LPARAM lParam, |
|
744 MSGResult& aResult) |
|
745 { |
|
746 // The return value must be same as aResult.mConsumed because only when we |
|
747 // consume the message, the caller shouldn't do anything anymore but |
|
748 // otherwise, the caller should handle the message. |
|
749 aResult.mConsumed = false; |
|
750 if (IsIMECharRecordsEmpty()) { |
|
751 return aResult.mConsumed; |
|
752 } |
|
753 WPARAM recWParam; |
|
754 LPARAM recLParam; |
|
755 DequeueIMECharRecords(recWParam, recLParam); |
|
756 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
757 ("IMM32: OnChar, aWindow=%p, wParam=%08x, lParam=%08x,\n", |
|
758 aWindow->GetWindowHandle(), wParam, lParam)); |
|
759 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
760 (" recorded: wParam=%08x, lParam=%08x\n", |
|
761 recWParam, recLParam)); |
|
762 // If an unexpected char message comes, we should reset the records, |
|
763 // of course, this shouldn't happen. |
|
764 if (recWParam != wParam || recLParam != lParam) { |
|
765 ResetIMECharRecords(); |
|
766 return aResult.mConsumed; |
|
767 } |
|
768 // Eat the char message which is caused by WM_IME_CHAR because we should |
|
769 // have processed the IME messages, so, this message could be come from |
|
770 // a windowless plug-in. |
|
771 aResult.mConsumed = true; |
|
772 return aResult.mConsumed; |
|
773 } |
|
774 |
|
775 /**************************************************************************** |
|
776 * message handlers for plug-in |
|
777 ****************************************************************************/ |
|
778 |
|
779 bool |
|
780 nsIMM32Handler::OnIMEStartCompositionOnPlugin(nsWindow* aWindow, |
|
781 WPARAM wParam, |
|
782 LPARAM lParam, |
|
783 MSGResult& aResult) |
|
784 { |
|
785 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
786 ("IMM32: OnIMEStartCompositionOnPlugin, hWnd=%08x, mIsComposingOnPlugin=%s\n", |
|
787 aWindow->GetWindowHandle(), mIsComposingOnPlugin ? "TRUE" : "FALSE")); |
|
788 mIsComposingOnPlugin = true; |
|
789 mComposingWindow = aWindow; |
|
790 nsIMEContext IMEContext(aWindow->GetWindowHandle()); |
|
791 SetIMERelatedWindowsPosOnPlugin(aWindow, IMEContext); |
|
792 aResult.mConsumed = |
|
793 aWindow->DispatchPluginEvent(WM_IME_STARTCOMPOSITION, wParam, lParam, |
|
794 false); |
|
795 return true; |
|
796 } |
|
797 |
|
798 bool |
|
799 nsIMM32Handler::OnIMECompositionOnPlugin(nsWindow* aWindow, |
|
800 WPARAM wParam, |
|
801 LPARAM lParam, |
|
802 MSGResult& aResult) |
|
803 { |
|
804 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
805 ("IMM32: OnIMECompositionOnPlugin, hWnd=%08x, lParam=%08x, mIsComposingOnPlugin=%s\n", |
|
806 aWindow->GetWindowHandle(), lParam, |
|
807 mIsComposingOnPlugin ? "TRUE" : "FALSE")); |
|
808 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
809 ("IMM32: OnIMECompositionOnPlugin, GCS_RESULTSTR=%s, GCS_COMPSTR=%s, GCS_COMPATTR=%s, GCS_COMPCLAUSE=%s, GCS_CURSORPOS=%s\n", |
|
810 lParam & GCS_RESULTSTR ? "YES" : "no", |
|
811 lParam & GCS_COMPSTR ? "YES" : "no", |
|
812 lParam & GCS_COMPATTR ? "YES" : "no", |
|
813 lParam & GCS_COMPCLAUSE ? "YES" : "no", |
|
814 lParam & GCS_CURSORPOS ? "YES" : "no")); |
|
815 // We should end composition if there is a committed string. |
|
816 if (IS_COMMITTING_LPARAM(lParam)) { |
|
817 mIsComposingOnPlugin = false; |
|
818 mComposingWindow = nullptr; |
|
819 } |
|
820 // Continue composition if there is still a string being composed. |
|
821 if (IS_COMPOSING_LPARAM(lParam)) { |
|
822 mIsComposingOnPlugin = true; |
|
823 mComposingWindow = aWindow; |
|
824 nsIMEContext IMEContext(aWindow->GetWindowHandle()); |
|
825 SetIMERelatedWindowsPosOnPlugin(aWindow, IMEContext); |
|
826 } |
|
827 aResult.mConsumed = |
|
828 aWindow->DispatchPluginEvent(WM_IME_COMPOSITION, wParam, lParam, true); |
|
829 return true; |
|
830 } |
|
831 |
|
832 bool |
|
833 nsIMM32Handler::OnIMEEndCompositionOnPlugin(nsWindow* aWindow, |
|
834 WPARAM wParam, |
|
835 LPARAM lParam, |
|
836 MSGResult& aResult) |
|
837 { |
|
838 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
839 ("IMM32: OnIMEEndCompositionOnPlugin, hWnd=%08x, mIsComposingOnPlugin=%s\n", |
|
840 aWindow->GetWindowHandle(), mIsComposingOnPlugin ? "TRUE" : "FALSE")); |
|
841 |
|
842 mIsComposingOnPlugin = false; |
|
843 mComposingWindow = nullptr; |
|
844 |
|
845 if (mNativeCaretIsCreated) { |
|
846 ::DestroyCaret(); |
|
847 mNativeCaretIsCreated = false; |
|
848 } |
|
849 |
|
850 aResult.mConsumed = |
|
851 aWindow->DispatchPluginEvent(WM_IME_ENDCOMPOSITION, wParam, lParam, |
|
852 false); |
|
853 return true; |
|
854 } |
|
855 |
|
856 bool |
|
857 nsIMM32Handler::OnIMECharOnPlugin(nsWindow* aWindow, |
|
858 WPARAM wParam, |
|
859 LPARAM lParam, |
|
860 MSGResult& aResult) |
|
861 { |
|
862 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
863 ("IMM32: OnIMECharOnPlugin, hWnd=%08x, char=%08x, scancode=%08x\n", |
|
864 aWindow->GetWindowHandle(), wParam, lParam)); |
|
865 |
|
866 aResult.mConsumed = |
|
867 aWindow->DispatchPluginEvent(WM_IME_CHAR, wParam, lParam, true); |
|
868 |
|
869 if (!aResult.mConsumed) { |
|
870 // Record the WM_CHAR messages which are going to be coming. |
|
871 EnsureHandlerInstance(); |
|
872 EnqueueIMECharRecords(wParam, lParam); |
|
873 } |
|
874 return true; |
|
875 } |
|
876 |
|
877 /* static */ bool |
|
878 nsIMM32Handler::OnIMESetContextOnPlugin(nsWindow* aWindow, |
|
879 WPARAM wParam, |
|
880 LPARAM lParam, |
|
881 MSGResult& aResult) |
|
882 { |
|
883 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
884 ("IMM32: OnIMESetContextOnPlugin, hWnd=%08x, %s, lParam=%08x\n", |
|
885 aWindow->GetWindowHandle(), wParam ? "Active" : "Deactive", lParam)); |
|
886 |
|
887 // If the IME context becomes active on a plug-in, we should commit |
|
888 // our composition. And also we should cancel the composition on new |
|
889 // window. Note that if IsTopLevelWindowOfComposition(aWindow) returns |
|
890 // true, we should ignore the message here, see the comment in |
|
891 // OnIMESetContext() for the detail. |
|
892 if (wParam && gIMM32Handler && !IsTopLevelWindowOfComposition(aWindow)) { |
|
893 if (gIMM32Handler->CommitCompositionOnPreviousWindow(aWindow)) { |
|
894 CancelComposition(aWindow); |
|
895 } |
|
896 } |
|
897 |
|
898 // Dispatch message to the plug-in. |
|
899 // XXX When a windowless plug-in gets focus, we should send |
|
900 // WM_IME_SETCONTEXT |
|
901 aWindow->DispatchPluginEvent(WM_IME_SETCONTEXT, wParam, lParam, false); |
|
902 |
|
903 // We should send WM_IME_SETCONTEXT to the DefWndProc here. It shouldn't |
|
904 // be received on ancestor windows, see OnIMESetContext() for the detail. |
|
905 aResult.mResult = ::DefWindowProc(aWindow->GetWindowHandle(), |
|
906 WM_IME_SETCONTEXT, wParam, lParam); |
|
907 |
|
908 // Don't synchronously dispatch the pending events when we receive |
|
909 // WM_IME_SETCONTEXT because we get it during plugin destruction. |
|
910 // (bug 491848) |
|
911 aResult.mConsumed = true; |
|
912 return true; |
|
913 } |
|
914 |
|
915 bool |
|
916 nsIMM32Handler::OnCharOnPlugin(nsWindow* aWindow, |
|
917 WPARAM wParam, |
|
918 LPARAM lParam, |
|
919 MSGResult& aResult) |
|
920 { |
|
921 // We should never consume char message on windowless plugin. |
|
922 aResult.mConsumed = false; |
|
923 if (IsIMECharRecordsEmpty()) { |
|
924 return false; |
|
925 } |
|
926 |
|
927 WPARAM recWParam; |
|
928 LPARAM recLParam; |
|
929 DequeueIMECharRecords(recWParam, recLParam); |
|
930 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
931 ("IMM32: OnCharOnPlugin, aWindow=%p, wParam=%08x, lParam=%08x,\n", |
|
932 aWindow->GetWindowHandle(), wParam, lParam)); |
|
933 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
934 (" recorded: wParam=%08x, lParam=%08x\n", |
|
935 recWParam, recLParam)); |
|
936 // If an unexpected char message comes, we should reset the records, |
|
937 // of course, this shouldn't happen. |
|
938 if (recWParam != wParam || recLParam != lParam) { |
|
939 ResetIMECharRecords(); |
|
940 } |
|
941 // WM_CHAR on plug-in is always handled by nsWindow. |
|
942 return false; |
|
943 } |
|
944 |
|
945 /**************************************************************************** |
|
946 * others |
|
947 ****************************************************************************/ |
|
948 |
|
949 void |
|
950 nsIMM32Handler::HandleStartComposition(nsWindow* aWindow, |
|
951 const nsIMEContext &aIMEContext) |
|
952 { |
|
953 NS_PRECONDITION(!mIsComposing, |
|
954 "HandleStartComposition is called but mIsComposing is TRUE"); |
|
955 NS_PRECONDITION(!aWindow->PluginHasFocus(), |
|
956 "HandleStartComposition should not be called when a plug-in has focus"); |
|
957 |
|
958 WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, aWindow); |
|
959 nsIntPoint point(0, 0); |
|
960 aWindow->InitEvent(selection, &point); |
|
961 aWindow->DispatchWindowEvent(&selection); |
|
962 if (!selection.mSucceeded) { |
|
963 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
964 ("IMM32: HandleStartComposition, FAILED (NS_QUERY_SELECTED_TEXT)\n")); |
|
965 return; |
|
966 } |
|
967 |
|
968 mCompositionStart = selection.mReply.mOffset; |
|
969 mLastDispatchedCompositionString.Truncate(); |
|
970 |
|
971 WidgetCompositionEvent event(true, NS_COMPOSITION_START, aWindow); |
|
972 aWindow->InitEvent(event, &point); |
|
973 aWindow->DispatchWindowEvent(&event); |
|
974 |
|
975 mIsComposing = true; |
|
976 mComposingWindow = aWindow; |
|
977 |
|
978 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
979 ("IMM32: HandleStartComposition, START composition, mCompositionStart=%ld\n", |
|
980 mCompositionStart)); |
|
981 } |
|
982 |
|
983 bool |
|
984 nsIMM32Handler::HandleComposition(nsWindow* aWindow, |
|
985 const nsIMEContext &aIMEContext, |
|
986 LPARAM lParam) |
|
987 { |
|
988 NS_PRECONDITION(!aWindow->PluginHasFocus(), |
|
989 "HandleComposition should not be called when a plug-in has focus"); |
|
990 |
|
991 // for bug #60050 |
|
992 // MS-IME 95/97/98/2000 may send WM_IME_COMPOSITION with non-conversion |
|
993 // mode before it send WM_IME_STARTCOMPOSITION. |
|
994 // However, ATOK sends a WM_IME_COMPOSITION before WM_IME_STARTCOMPOSITION, |
|
995 // and if we access ATOK via some APIs, ATOK will sometimes fail to |
|
996 // initialize its state. If WM_IME_STARTCOMPOSITION is already in the |
|
997 // message queue, we should ignore the strange WM_IME_COMPOSITION message and |
|
998 // skip to the next. So, we should look for next composition message |
|
999 // (WM_IME_STARTCOMPOSITION or WM_IME_ENDCOMPOSITION or WM_IME_COMPOSITION), |
|
1000 // and if it's WM_IME_STARTCOMPOSITION, and one more next composition message |
|
1001 // is WM_IME_COMPOSITION, current IME is ATOK, probably. Otherwise, we |
|
1002 // should start composition forcibly. |
|
1003 if (!mIsComposing) { |
|
1004 MSG msg1, msg2; |
|
1005 HWND wnd = aWindow->GetWindowHandle(); |
|
1006 if (WinUtils::PeekMessage(&msg1, wnd, WM_IME_STARTCOMPOSITION, |
|
1007 WM_IME_COMPOSITION, PM_NOREMOVE) && |
|
1008 msg1.message == WM_IME_STARTCOMPOSITION && |
|
1009 WinUtils::PeekMessage(&msg2, wnd, WM_IME_ENDCOMPOSITION, |
|
1010 WM_IME_COMPOSITION, PM_NOREMOVE) && |
|
1011 msg2.message == WM_IME_COMPOSITION) { |
|
1012 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1013 ("IMM32: HandleComposition, Ignores due to find a WM_IME_STARTCOMPOSITION\n")); |
|
1014 return ShouldDrawCompositionStringOurselves(); |
|
1015 } |
|
1016 } |
|
1017 |
|
1018 bool startCompositionMessageHasBeenSent = mIsComposing; |
|
1019 |
|
1020 // |
|
1021 // This catches a fixed result |
|
1022 // |
|
1023 if (IS_COMMITTING_LPARAM(lParam)) { |
|
1024 if (!mIsComposing) { |
|
1025 HandleStartComposition(aWindow, aIMEContext); |
|
1026 } |
|
1027 |
|
1028 GetCompositionString(aIMEContext, GCS_RESULTSTR); |
|
1029 |
|
1030 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1031 ("IMM32: HandleComposition, GCS_RESULTSTR\n")); |
|
1032 |
|
1033 DispatchTextEvent(aWindow, aIMEContext, false); |
|
1034 HandleEndComposition(aWindow); |
|
1035 |
|
1036 if (!IS_COMPOSING_LPARAM(lParam)) { |
|
1037 return ShouldDrawCompositionStringOurselves(); |
|
1038 } |
|
1039 } |
|
1040 |
|
1041 |
|
1042 // |
|
1043 // This provides us with a composition string |
|
1044 // |
|
1045 if (!mIsComposing) { |
|
1046 HandleStartComposition(aWindow, aIMEContext); |
|
1047 } |
|
1048 |
|
1049 //-------------------------------------------------------- |
|
1050 // 1. Get GCS_COMPSTR |
|
1051 //-------------------------------------------------------- |
|
1052 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1053 ("IMM32: HandleComposition, GCS_COMPSTR\n")); |
|
1054 |
|
1055 GetCompositionString(aIMEContext, GCS_COMPSTR); |
|
1056 |
|
1057 if (!IS_COMPOSING_LPARAM(lParam)) { |
|
1058 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1059 ("IMM32: HandleComposition, lParam doesn't indicate composing, " |
|
1060 "mCompositionString=\"%s\", mLastDispatchedCompositionString=\"%s\"", |
|
1061 NS_ConvertUTF16toUTF8(mCompositionString).get(), |
|
1062 NS_ConvertUTF16toUTF8(mLastDispatchedCompositionString).get())); |
|
1063 |
|
1064 // If composition string isn't changed, we can trust the lParam. |
|
1065 // So, we need to do nothing. |
|
1066 if (mLastDispatchedCompositionString == mCompositionString) { |
|
1067 return ShouldDrawCompositionStringOurselves(); |
|
1068 } |
|
1069 |
|
1070 // IME may send WM_IME_COMPOSITION without composing lParam values |
|
1071 // when composition string becomes empty (e.g., using Backspace key). |
|
1072 // If composition string is empty, we should dispatch a text event with |
|
1073 // empty string. |
|
1074 if (mCompositionString.IsEmpty()) { |
|
1075 DispatchTextEvent(aWindow, aIMEContext, false); |
|
1076 return ShouldDrawCompositionStringOurselves(); |
|
1077 } |
|
1078 |
|
1079 // Otherwise, we cannot trust the lParam value. We might need to |
|
1080 // dispatch text event with the latest composition string information. |
|
1081 } |
|
1082 |
|
1083 // See https://bugzilla.mozilla.org/show_bug.cgi?id=296339 |
|
1084 if (mCompositionString.IsEmpty() && !startCompositionMessageHasBeenSent) { |
|
1085 // In this case, maybe, the sender is MSPinYin. That sends *only* |
|
1086 // WM_IME_COMPOSITION with GCS_COMP* and GCS_RESULT* when |
|
1087 // user inputted the Chinese full stop. So, that doesn't send |
|
1088 // WM_IME_STARTCOMPOSITION and WM_IME_ENDCOMPOSITION. |
|
1089 // If WM_IME_STARTCOMPOSITION was not sent and the composition |
|
1090 // string is null (it indicates the composition transaction ended), |
|
1091 // WM_IME_ENDCOMPOSITION may not be sent. If so, we cannot run |
|
1092 // HandleEndComposition() in other place. |
|
1093 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1094 ("IMM32: HandleComposition, Aborting GCS_COMPSTR\n")); |
|
1095 HandleEndComposition(aWindow); |
|
1096 return IS_COMMITTING_LPARAM(lParam); |
|
1097 } |
|
1098 |
|
1099 //-------------------------------------------------------- |
|
1100 // 2. Get GCS_COMPCLAUSE |
|
1101 //-------------------------------------------------------- |
|
1102 long clauseArrayLength = |
|
1103 ::ImmGetCompositionStringW(aIMEContext.get(), GCS_COMPCLAUSE, nullptr, 0); |
|
1104 clauseArrayLength /= sizeof(uint32_t); |
|
1105 |
|
1106 if (clauseArrayLength > 0) { |
|
1107 nsresult rv = EnsureClauseArray(clauseArrayLength); |
|
1108 NS_ENSURE_SUCCESS(rv, false); |
|
1109 |
|
1110 // Intelligent ABC IME (Simplified Chinese IME, the code page is 936) |
|
1111 // will crash in ImmGetCompositionStringW for GCS_COMPCLAUSE (bug 424663). |
|
1112 // See comment 35 of the bug for the detail. Therefore, we should use A |
|
1113 // API for it, however, we should not kill Unicode support on all IMEs. |
|
1114 bool useA_API = !(sIMEProperty & IME_PROP_UNICODE); |
|
1115 |
|
1116 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1117 ("IMM32: HandleComposition, GCS_COMPCLAUSE, useA_API=%s\n", |
|
1118 useA_API ? "TRUE" : "FALSE")); |
|
1119 |
|
1120 long clauseArrayLength2 = |
|
1121 useA_API ? |
|
1122 ::ImmGetCompositionStringA(aIMEContext.get(), GCS_COMPCLAUSE, |
|
1123 mClauseArray.Elements(), |
|
1124 mClauseArray.Capacity() * sizeof(uint32_t)) : |
|
1125 ::ImmGetCompositionStringW(aIMEContext.get(), GCS_COMPCLAUSE, |
|
1126 mClauseArray.Elements(), |
|
1127 mClauseArray.Capacity() * sizeof(uint32_t)); |
|
1128 clauseArrayLength2 /= sizeof(uint32_t); |
|
1129 |
|
1130 if (clauseArrayLength != clauseArrayLength2) { |
|
1131 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1132 ("IMM32: HandleComposition, GCS_COMPCLAUSE, clauseArrayLength=%ld but clauseArrayLength2=%ld\n", |
|
1133 clauseArrayLength, clauseArrayLength2)); |
|
1134 if (clauseArrayLength > clauseArrayLength2) |
|
1135 clauseArrayLength = clauseArrayLength2; |
|
1136 } |
|
1137 |
|
1138 if (useA_API) { |
|
1139 // Convert each values of sIMECompClauseArray. The values mean offset of |
|
1140 // the clauses in ANSI string. But we need the values in Unicode string. |
|
1141 nsAutoCString compANSIStr; |
|
1142 if (ConvertToANSIString(mCompositionString, GetKeyboardCodePage(), |
|
1143 compANSIStr)) { |
|
1144 uint32_t maxlen = compANSIStr.Length(); |
|
1145 mClauseArray[0] = 0; // first value must be 0 |
|
1146 for (int32_t i = 1; i < clauseArrayLength; i++) { |
|
1147 uint32_t len = std::min(mClauseArray[i], maxlen); |
|
1148 mClauseArray[i] = ::MultiByteToWideChar(GetKeyboardCodePage(), |
|
1149 MB_PRECOMPOSED, |
|
1150 (LPCSTR)compANSIStr.get(), |
|
1151 len, nullptr, 0); |
|
1152 } |
|
1153 } |
|
1154 } |
|
1155 } |
|
1156 // compClauseArrayLength may be negative. I.e., ImmGetCompositionStringW |
|
1157 // may return an error code. |
|
1158 mClauseArray.SetLength(std::max<long>(0, clauseArrayLength)); |
|
1159 |
|
1160 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1161 ("IMM32: HandleComposition, GCS_COMPCLAUSE, mClauseLength=%ld\n", |
|
1162 mClauseArray.Length())); |
|
1163 |
|
1164 //-------------------------------------------------------- |
|
1165 // 3. Get GCS_COMPATTR |
|
1166 //-------------------------------------------------------- |
|
1167 // This provides us with the attribute string necessary |
|
1168 // for doing hiliting |
|
1169 long attrArrayLength = |
|
1170 ::ImmGetCompositionStringW(aIMEContext.get(), GCS_COMPATTR, nullptr, 0); |
|
1171 attrArrayLength /= sizeof(uint8_t); |
|
1172 |
|
1173 if (attrArrayLength > 0) { |
|
1174 nsresult rv = EnsureAttributeArray(attrArrayLength); |
|
1175 NS_ENSURE_SUCCESS(rv, false); |
|
1176 attrArrayLength = |
|
1177 ::ImmGetCompositionStringW(aIMEContext.get(), GCS_COMPATTR, |
|
1178 mAttributeArray.Elements(), |
|
1179 mAttributeArray.Capacity() * sizeof(uint8_t)); |
|
1180 } |
|
1181 |
|
1182 // attrStrLen may be negative. I.e., ImmGetCompositionStringW may return an |
|
1183 // error code. |
|
1184 mAttributeArray.SetLength(std::max<long>(0, attrArrayLength)); |
|
1185 |
|
1186 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1187 ("IMM32: HandleComposition, GCS_COMPATTR, mAttributeLength=%ld\n", |
|
1188 mAttributeArray.Length())); |
|
1189 |
|
1190 //-------------------------------------------------------- |
|
1191 // 4. Get GCS_CURSOPOS |
|
1192 //-------------------------------------------------------- |
|
1193 // Some IMEs (e.g., the standard IME for Korean) don't have caret position. |
|
1194 if (lParam & GCS_CURSORPOS) { |
|
1195 mCursorPosition = |
|
1196 ::ImmGetCompositionStringW(aIMEContext.get(), GCS_CURSORPOS, nullptr, 0); |
|
1197 if (mCursorPosition < 0) { |
|
1198 mCursorPosition = NO_IME_CARET; // The result is error |
|
1199 } |
|
1200 } else { |
|
1201 mCursorPosition = NO_IME_CARET; |
|
1202 } |
|
1203 |
|
1204 NS_ASSERTION(mCursorPosition <= (long)mCompositionString.Length(), |
|
1205 "illegal pos"); |
|
1206 |
|
1207 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1208 ("IMM32: HandleComposition, GCS_CURSORPOS, mCursorPosition=%d\n", |
|
1209 mCursorPosition)); |
|
1210 |
|
1211 //-------------------------------------------------------- |
|
1212 // 5. Send the text event |
|
1213 //-------------------------------------------------------- |
|
1214 DispatchTextEvent(aWindow, aIMEContext); |
|
1215 |
|
1216 return ShouldDrawCompositionStringOurselves(); |
|
1217 } |
|
1218 |
|
1219 void |
|
1220 nsIMM32Handler::HandleEndComposition(nsWindow* aWindow) |
|
1221 { |
|
1222 NS_PRECONDITION(mIsComposing, |
|
1223 "HandleEndComposition is called but mIsComposing is FALSE"); |
|
1224 NS_PRECONDITION(!aWindow->PluginHasFocus(), |
|
1225 "HandleComposition should not be called when a plug-in has focus"); |
|
1226 |
|
1227 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1228 ("IMM32: HandleEndComposition\n")); |
|
1229 |
|
1230 WidgetCompositionEvent event(true, NS_COMPOSITION_END, aWindow); |
|
1231 nsIntPoint point(0, 0); |
|
1232 |
|
1233 if (mNativeCaretIsCreated) { |
|
1234 ::DestroyCaret(); |
|
1235 mNativeCaretIsCreated = false; |
|
1236 } |
|
1237 |
|
1238 aWindow->InitEvent(event, &point); |
|
1239 // The last dispatched composition string must be the committed string. |
|
1240 event.data = mLastDispatchedCompositionString; |
|
1241 aWindow->DispatchWindowEvent(&event); |
|
1242 mIsComposing = false; |
|
1243 mComposingWindow = nullptr; |
|
1244 mLastDispatchedCompositionString.Truncate(); |
|
1245 } |
|
1246 |
|
1247 static void |
|
1248 DumpReconvertString(RECONVERTSTRING* aReconv) |
|
1249 { |
|
1250 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1251 (" dwSize=%ld, dwVersion=%ld, dwStrLen=%ld, dwStrOffset=%ld\n", |
|
1252 aReconv->dwSize, aReconv->dwVersion, |
|
1253 aReconv->dwStrLen, aReconv->dwStrOffset)); |
|
1254 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1255 (" dwCompStrLen=%ld, dwCompStrOffset=%ld, dwTargetStrLen=%ld, dwTargetStrOffset=%ld\n", |
|
1256 aReconv->dwCompStrLen, aReconv->dwCompStrOffset, |
|
1257 aReconv->dwTargetStrLen, aReconv->dwTargetStrOffset)); |
|
1258 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1259 (" result str=\"%s\"\n", |
|
1260 NS_ConvertUTF16toUTF8( |
|
1261 nsAutoString((char16_t*)((char*)(aReconv) + aReconv->dwStrOffset), |
|
1262 aReconv->dwStrLen)).get())); |
|
1263 } |
|
1264 |
|
1265 bool |
|
1266 nsIMM32Handler::HandleReconvert(nsWindow* aWindow, |
|
1267 LPARAM lParam, |
|
1268 LRESULT *oResult) |
|
1269 { |
|
1270 *oResult = 0; |
|
1271 RECONVERTSTRING* pReconv = reinterpret_cast<RECONVERTSTRING*>(lParam); |
|
1272 |
|
1273 WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, aWindow); |
|
1274 nsIntPoint point(0, 0); |
|
1275 aWindow->InitEvent(selection, &point); |
|
1276 aWindow->DispatchWindowEvent(&selection); |
|
1277 if (!selection.mSucceeded) { |
|
1278 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1279 ("IMM32: HandleReconvert, FAILED (NS_QUERY_SELECTED_TEXT)\n")); |
|
1280 return false; |
|
1281 } |
|
1282 |
|
1283 uint32_t len = selection.mReply.mString.Length(); |
|
1284 uint32_t needSize = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR); |
|
1285 |
|
1286 if (!pReconv) { |
|
1287 // Return need size to reconvert. |
|
1288 if (len == 0) { |
|
1289 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1290 ("IMM32: HandleReconvert, There are not selected text\n")); |
|
1291 return false; |
|
1292 } |
|
1293 *oResult = needSize; |
|
1294 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1295 ("IMM32: HandleReconvert, SUCCEEDED result=%ld\n", |
|
1296 *oResult)); |
|
1297 return true; |
|
1298 } |
|
1299 |
|
1300 if (pReconv->dwSize < needSize) { |
|
1301 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1302 ("IMM32: HandleReconvert, FAILED pReconv->dwSize=%ld, needSize=%ld\n", |
|
1303 pReconv->dwSize, needSize)); |
|
1304 return false; |
|
1305 } |
|
1306 |
|
1307 *oResult = needSize; |
|
1308 |
|
1309 // Fill reconvert struct |
|
1310 pReconv->dwVersion = 0; |
|
1311 pReconv->dwStrLen = len; |
|
1312 pReconv->dwStrOffset = sizeof(RECONVERTSTRING); |
|
1313 pReconv->dwCompStrLen = len; |
|
1314 pReconv->dwCompStrOffset = 0; |
|
1315 pReconv->dwTargetStrLen = len; |
|
1316 pReconv->dwTargetStrOffset = 0; |
|
1317 |
|
1318 ::CopyMemory(reinterpret_cast<LPVOID>(lParam + sizeof(RECONVERTSTRING)), |
|
1319 selection.mReply.mString.get(), len * sizeof(WCHAR)); |
|
1320 |
|
1321 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1322 ("IMM32: HandleReconvert, SUCCEEDED result=%ld\n", |
|
1323 *oResult)); |
|
1324 DumpReconvertString(pReconv); |
|
1325 |
|
1326 return true; |
|
1327 } |
|
1328 |
|
1329 bool |
|
1330 nsIMM32Handler::HandleQueryCharPosition(nsWindow* aWindow, |
|
1331 LPARAM lParam, |
|
1332 LRESULT *oResult) |
|
1333 { |
|
1334 uint32_t len = mIsComposing ? mCompositionString.Length() : 0; |
|
1335 *oResult = false; |
|
1336 IMECHARPOSITION* pCharPosition = reinterpret_cast<IMECHARPOSITION*>(lParam); |
|
1337 if (!pCharPosition) { |
|
1338 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1339 ("IMM32: HandleQueryCharPosition, FAILED (pCharPosition is null)\n")); |
|
1340 return false; |
|
1341 } |
|
1342 if (pCharPosition->dwSize < sizeof(IMECHARPOSITION)) { |
|
1343 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1344 ("IMM32: HandleReconvert, FAILED, pCharPosition->dwSize=%ld, sizeof(IMECHARPOSITION)=%ld\n", |
|
1345 pCharPosition->dwSize, sizeof(IMECHARPOSITION))); |
|
1346 return false; |
|
1347 } |
|
1348 if (::GetFocus() != aWindow->GetWindowHandle()) { |
|
1349 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1350 ("IMM32: HandleReconvert, FAILED, ::GetFocus()=%08x, OurWindowHandle=%08x\n", |
|
1351 ::GetFocus(), aWindow->GetWindowHandle())); |
|
1352 return false; |
|
1353 } |
|
1354 if (pCharPosition->dwCharPos > len) { |
|
1355 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1356 ("IMM32: HandleQueryCharPosition, FAILED, pCharPosition->dwCharPos=%ld, len=%ld\n", |
|
1357 pCharPosition->dwCharPos, len)); |
|
1358 return false; |
|
1359 } |
|
1360 |
|
1361 nsIntRect r; |
|
1362 bool ret = |
|
1363 GetCharacterRectOfSelectedTextAt(aWindow, pCharPosition->dwCharPos, r); |
|
1364 NS_ENSURE_TRUE(ret, false); |
|
1365 |
|
1366 nsIntRect screenRect; |
|
1367 // We always need top level window that is owner window of the popup window |
|
1368 // even if the content of the popup window has focus. |
|
1369 ResolveIMECaretPos(aWindow->GetTopLevelWindow(false), |
|
1370 r, nullptr, screenRect); |
|
1371 pCharPosition->pt.x = screenRect.x; |
|
1372 pCharPosition->pt.y = screenRect.y; |
|
1373 |
|
1374 pCharPosition->cLineHeight = r.height; |
|
1375 |
|
1376 // XXX we should use NS_QUERY_EDITOR_RECT event here. |
|
1377 ::GetWindowRect(aWindow->GetWindowHandle(), &pCharPosition->rcDocument); |
|
1378 |
|
1379 *oResult = TRUE; |
|
1380 |
|
1381 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1382 ("IMM32: HandleQueryCharPosition, SUCCEEDED\n")); |
|
1383 return true; |
|
1384 } |
|
1385 |
|
1386 bool |
|
1387 nsIMM32Handler::HandleDocumentFeed(nsWindow* aWindow, |
|
1388 LPARAM lParam, |
|
1389 LRESULT *oResult) |
|
1390 { |
|
1391 *oResult = 0; |
|
1392 RECONVERTSTRING* pReconv = reinterpret_cast<RECONVERTSTRING*>(lParam); |
|
1393 |
|
1394 nsIntPoint point(0, 0); |
|
1395 |
|
1396 bool hasCompositionString = |
|
1397 mIsComposing && ShouldDrawCompositionStringOurselves(); |
|
1398 |
|
1399 int32_t targetOffset, targetLength; |
|
1400 if (!hasCompositionString) { |
|
1401 WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, aWindow); |
|
1402 aWindow->InitEvent(selection, &point); |
|
1403 aWindow->DispatchWindowEvent(&selection); |
|
1404 if (!selection.mSucceeded) { |
|
1405 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1406 ("IMM32: HandleDocumentFeed, FAILED (NS_QUERY_SELECTED_TEXT)\n")); |
|
1407 return false; |
|
1408 } |
|
1409 targetOffset = int32_t(selection.mReply.mOffset); |
|
1410 targetLength = int32_t(selection.mReply.mString.Length()); |
|
1411 } else { |
|
1412 targetOffset = int32_t(mCompositionStart); |
|
1413 targetLength = int32_t(mCompositionString.Length()); |
|
1414 } |
|
1415 |
|
1416 // XXX nsString::Find and nsString::RFind take int32_t for offset, so, |
|
1417 // we cannot support this message when the current offset is larger than |
|
1418 // INT32_MAX. |
|
1419 if (targetOffset < 0 || targetLength < 0 || |
|
1420 targetOffset + targetLength < 0) { |
|
1421 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1422 ("IMM32: HandleDocumentFeed, FAILED (The selection is out of range)\n")); |
|
1423 return false; |
|
1424 } |
|
1425 |
|
1426 // Get all contents of the focused editor. |
|
1427 WidgetQueryContentEvent textContent(true, NS_QUERY_TEXT_CONTENT, aWindow); |
|
1428 textContent.InitForQueryTextContent(0, UINT32_MAX); |
|
1429 aWindow->InitEvent(textContent, &point); |
|
1430 aWindow->DispatchWindowEvent(&textContent); |
|
1431 if (!textContent.mSucceeded) { |
|
1432 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1433 ("IMM32: HandleDocumentFeed, FAILED (NS_QUERY_TEXT_CONTENT)\n")); |
|
1434 return false; |
|
1435 } |
|
1436 |
|
1437 nsAutoString str(textContent.mReply.mString); |
|
1438 if (targetOffset > int32_t(str.Length())) { |
|
1439 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1440 ("IMM32: HandleDocumentFeed, FAILED (The caret offset is invalid)\n")); |
|
1441 return false; |
|
1442 } |
|
1443 |
|
1444 // Get the focused paragraph, we decide that it starts from the previous CRLF |
|
1445 // (or start of the editor) to the next one (or the end of the editor). |
|
1446 int32_t paragraphStart = str.RFind("\n", false, targetOffset, -1) + 1; |
|
1447 int32_t paragraphEnd = |
|
1448 str.Find("\r", false, targetOffset + targetLength, -1); |
|
1449 if (paragraphEnd < 0) { |
|
1450 paragraphEnd = str.Length(); |
|
1451 } |
|
1452 nsDependentSubstring paragraph(str, paragraphStart, |
|
1453 paragraphEnd - paragraphStart); |
|
1454 |
|
1455 uint32_t len = paragraph.Length(); |
|
1456 uint32_t needSize = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR); |
|
1457 |
|
1458 if (!pReconv) { |
|
1459 *oResult = needSize; |
|
1460 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1461 ("IMM32: HandleDocumentFeed, SUCCEEDED result=%ld\n", |
|
1462 *oResult)); |
|
1463 return true; |
|
1464 } |
|
1465 |
|
1466 if (pReconv->dwSize < needSize) { |
|
1467 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1468 ("IMM32: HandleDocumentFeed, FAILED pReconv->dwSize=%ld, needSize=%ld\n", |
|
1469 pReconv->dwSize, needSize)); |
|
1470 return false; |
|
1471 } |
|
1472 |
|
1473 // Fill reconvert struct |
|
1474 pReconv->dwVersion = 0; |
|
1475 pReconv->dwStrLen = len; |
|
1476 pReconv->dwStrOffset = sizeof(RECONVERTSTRING); |
|
1477 if (hasCompositionString) { |
|
1478 pReconv->dwCompStrLen = targetLength; |
|
1479 pReconv->dwCompStrOffset = |
|
1480 (targetOffset - paragraphStart) * sizeof(WCHAR); |
|
1481 // Set composition target clause information |
|
1482 uint32_t offset, length; |
|
1483 if (!GetTargetClauseRange(&offset, &length)) { |
|
1484 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1485 ("IMM32: HandleDocumentFeed, FAILED, by GetTargetClauseRange\n")); |
|
1486 return false; |
|
1487 } |
|
1488 pReconv->dwTargetStrLen = length; |
|
1489 pReconv->dwTargetStrOffset = (offset - paragraphStart) * sizeof(WCHAR); |
|
1490 } else { |
|
1491 pReconv->dwTargetStrLen = targetLength; |
|
1492 pReconv->dwTargetStrOffset = |
|
1493 (targetOffset - paragraphStart) * sizeof(WCHAR); |
|
1494 // There is no composition string, so, the length is zero but we should |
|
1495 // set the cursor offset to the composition str offset. |
|
1496 pReconv->dwCompStrLen = 0; |
|
1497 pReconv->dwCompStrOffset = pReconv->dwTargetStrOffset; |
|
1498 } |
|
1499 |
|
1500 *oResult = needSize; |
|
1501 ::CopyMemory(reinterpret_cast<LPVOID>(lParam + sizeof(RECONVERTSTRING)), |
|
1502 paragraph.BeginReading(), len * sizeof(WCHAR)); |
|
1503 |
|
1504 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1505 ("IMM32: HandleDocumentFeed, SUCCEEDED result=%ld\n", |
|
1506 *oResult)); |
|
1507 DumpReconvertString(pReconv); |
|
1508 |
|
1509 return true; |
|
1510 } |
|
1511 |
|
1512 bool |
|
1513 nsIMM32Handler::CommitCompositionOnPreviousWindow(nsWindow* aWindow) |
|
1514 { |
|
1515 if (!mComposingWindow || mComposingWindow == aWindow) { |
|
1516 return false; |
|
1517 } |
|
1518 |
|
1519 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1520 ("IMM32: CommitCompositionOnPreviousWindow, mIsComposing=%s, mIsComposingOnPlugin=%s\n", |
|
1521 mIsComposing ? "TRUE" : "FALSE", mIsComposingOnPlugin ? "TRUE" : "FALSE")); |
|
1522 |
|
1523 // If we have composition, we should dispatch composition events internally. |
|
1524 if (mIsComposing) { |
|
1525 nsIMEContext IMEContext(mComposingWindow->GetWindowHandle()); |
|
1526 NS_ASSERTION(IMEContext.IsValid(), "IME context must be valid"); |
|
1527 |
|
1528 DispatchTextEvent(mComposingWindow, IMEContext, false); |
|
1529 HandleEndComposition(mComposingWindow); |
|
1530 return true; |
|
1531 } |
|
1532 |
|
1533 // XXX When plug-in has composition, we should commit composition on the |
|
1534 // plug-in. However, we need some more work for that. |
|
1535 return mIsComposingOnPlugin; |
|
1536 } |
|
1537 |
|
1538 static uint32_t |
|
1539 PlatformToNSAttr(uint8_t aAttr) |
|
1540 { |
|
1541 switch (aAttr) |
|
1542 { |
|
1543 case ATTR_INPUT_ERROR: |
|
1544 // case ATTR_FIXEDCONVERTED: |
|
1545 case ATTR_INPUT: |
|
1546 return NS_TEXTRANGE_RAWINPUT; |
|
1547 case ATTR_CONVERTED: |
|
1548 return NS_TEXTRANGE_CONVERTEDTEXT; |
|
1549 case ATTR_TARGET_NOTCONVERTED: |
|
1550 return NS_TEXTRANGE_SELECTEDRAWTEXT; |
|
1551 case ATTR_TARGET_CONVERTED: |
|
1552 return NS_TEXTRANGE_SELECTEDCONVERTEDTEXT; |
|
1553 default: |
|
1554 NS_ASSERTION(false, "unknown attribute"); |
|
1555 return NS_TEXTRANGE_CARETPOSITION; |
|
1556 } |
|
1557 } |
|
1558 |
|
1559 #ifdef PR_LOGGING |
|
1560 static const char* |
|
1561 GetRangeTypeName(uint32_t aRangeType) |
|
1562 { |
|
1563 switch (aRangeType) { |
|
1564 case NS_TEXTRANGE_RAWINPUT: |
|
1565 return "NS_TEXTRANGE_RAWINPUT"; |
|
1566 case NS_TEXTRANGE_CONVERTEDTEXT: |
|
1567 return "NS_TEXTRANGE_CONVERTEDTEXT"; |
|
1568 case NS_TEXTRANGE_SELECTEDRAWTEXT: |
|
1569 return "NS_TEXTRANGE_SELECTEDRAWTEXT"; |
|
1570 case NS_TEXTRANGE_SELECTEDCONVERTEDTEXT: |
|
1571 return "NS_TEXTRANGE_SELECTEDCONVERTEDTEXT"; |
|
1572 case NS_TEXTRANGE_CARETPOSITION: |
|
1573 return "NS_TEXTRANGE_CARETPOSITION"; |
|
1574 default: |
|
1575 return "UNKNOWN SELECTION TYPE!!"; |
|
1576 } |
|
1577 } |
|
1578 #endif |
|
1579 |
|
1580 void |
|
1581 nsIMM32Handler::DispatchTextEvent(nsWindow* aWindow, |
|
1582 const nsIMEContext &aIMEContext, |
|
1583 bool aCheckAttr) |
|
1584 { |
|
1585 NS_ASSERTION(mIsComposing, "conflict state"); |
|
1586 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1587 ("IMM32: DispatchTextEvent, aCheckAttr=%s\n", |
|
1588 aCheckAttr ? "TRUE": "FALSE")); |
|
1589 |
|
1590 // If we don't need to draw composition string ourselves and this is not |
|
1591 // commit event (i.e., under composing), we don't need to fire text event |
|
1592 // during composing. |
|
1593 if (aCheckAttr && !ShouldDrawCompositionStringOurselves()) { |
|
1594 // But we need to adjust composition window pos and native caret pos, here. |
|
1595 SetIMERelatedWindowsPos(aWindow, aIMEContext); |
|
1596 return; |
|
1597 } |
|
1598 |
|
1599 nsRefPtr<nsWindow> kungFuDeathGrip(aWindow); |
|
1600 |
|
1601 nsIntPoint point(0, 0); |
|
1602 |
|
1603 if (mCompositionString != mLastDispatchedCompositionString) { |
|
1604 WidgetCompositionEvent compositionUpdate(true, NS_COMPOSITION_UPDATE, |
|
1605 aWindow); |
|
1606 aWindow->InitEvent(compositionUpdate, &point); |
|
1607 compositionUpdate.data = mCompositionString; |
|
1608 mLastDispatchedCompositionString = mCompositionString; |
|
1609 |
|
1610 aWindow->DispatchWindowEvent(&compositionUpdate); |
|
1611 |
|
1612 if (!mIsComposing || aWindow->Destroyed()) { |
|
1613 return; |
|
1614 } |
|
1615 SetIMERelatedWindowsPos(aWindow, aIMEContext); |
|
1616 } |
|
1617 |
|
1618 WidgetTextEvent event(true, NS_TEXT_TEXT, aWindow); |
|
1619 |
|
1620 aWindow->InitEvent(event, &point); |
|
1621 |
|
1622 if (aCheckAttr) { |
|
1623 event.mRanges = CreateTextRangeArray(); |
|
1624 } |
|
1625 |
|
1626 event.theText = mCompositionString.get(); |
|
1627 |
|
1628 aWindow->DispatchWindowEvent(&event); |
|
1629 |
|
1630 // Calling SetIMERelatedWindowsPos will be failure on e10s at this point. |
|
1631 // text event will notify NOTIFY_IME_OF_COMPOSITION_UPDATE, then |
|
1632 // it will call SetIMERelatedWindowsPos. |
|
1633 } |
|
1634 |
|
1635 already_AddRefed<TextRangeArray> |
|
1636 nsIMM32Handler::CreateTextRangeArray() |
|
1637 { |
|
1638 // Sogou (Simplified Chinese IME) returns contradictory values: The cursor |
|
1639 // position is actual cursor position. However, other values (composition |
|
1640 // string and attributes) are empty. So, if you want to remove following |
|
1641 // assertion, be careful. |
|
1642 NS_ASSERTION(ShouldDrawCompositionStringOurselves(), |
|
1643 "CreateTextRangeArray is called when we don't need to fire text event"); |
|
1644 |
|
1645 nsRefPtr<TextRangeArray> textRangeArray = new TextRangeArray(); |
|
1646 |
|
1647 TextRange range; |
|
1648 if (mClauseArray.Length() == 0) { |
|
1649 // Some IMEs don't return clause array information, then, we assume that |
|
1650 // all characters in the composition string are in one clause. |
|
1651 range.mStartOffset = 0; |
|
1652 range.mEndOffset = mCompositionString.Length(); |
|
1653 range.mRangeType = NS_TEXTRANGE_RAWINPUT; |
|
1654 textRangeArray->AppendElement(range); |
|
1655 |
|
1656 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1657 ("IMM32: CreateTextRangeArray, mClauseLength=0\n")); |
|
1658 } else { |
|
1659 // iterate over the attributes |
|
1660 uint32_t lastOffset = 0; |
|
1661 for (uint32_t i = 0; i < mClauseArray.Length() - 1; i++) { |
|
1662 uint32_t current = mClauseArray[i + 1]; |
|
1663 if (current > mCompositionString.Length()) { |
|
1664 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1665 ("IMM32: CreateTextRangeArray, mClauseArray[%ld]=%lu. " |
|
1666 "This is larger than mCompositionString.Length()=%lu\n", |
|
1667 i + 1, current, mCompositionString.Length())); |
|
1668 current = int32_t(mCompositionString.Length()); |
|
1669 } |
|
1670 |
|
1671 range.mRangeType = PlatformToNSAttr(mAttributeArray[lastOffset]); |
|
1672 range.mStartOffset = lastOffset; |
|
1673 range.mEndOffset = current; |
|
1674 textRangeArray->AppendElement(range); |
|
1675 |
|
1676 lastOffset = current; |
|
1677 |
|
1678 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1679 ("IMM32: CreateTextRangeArray, index=%ld, rangeType=%s, range=[%lu-%lu]\n", |
|
1680 i, GetRangeTypeName(range.mRangeType), range.mStartOffset, |
|
1681 range.mEndOffset)); |
|
1682 } |
|
1683 } |
|
1684 |
|
1685 if (mCursorPosition == NO_IME_CARET) { |
|
1686 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1687 ("IMM32: CreateTextRangeArray, no caret\n")); |
|
1688 return textRangeArray.forget(); |
|
1689 } |
|
1690 |
|
1691 int32_t cursor = mCursorPosition; |
|
1692 if (uint32_t(cursor) > mCompositionString.Length()) { |
|
1693 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1694 ("IMM32: CreateTextRangeArray, mCursorPosition=%ld. " |
|
1695 "This is larger than mCompositionString.Length()=%lu\n", |
|
1696 mCursorPosition, mCompositionString.Length())); |
|
1697 cursor = mCompositionString.Length(); |
|
1698 } |
|
1699 |
|
1700 range.mStartOffset = range.mEndOffset = cursor; |
|
1701 range.mRangeType = NS_TEXTRANGE_CARETPOSITION; |
|
1702 textRangeArray->AppendElement(range); |
|
1703 |
|
1704 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1705 ("IMM32: CreateTextRangeArray, caret position=%ld\n", |
|
1706 range.mStartOffset)); |
|
1707 |
|
1708 return textRangeArray.forget(); |
|
1709 } |
|
1710 |
|
1711 void |
|
1712 nsIMM32Handler::GetCompositionString(const nsIMEContext &aIMEContext, |
|
1713 DWORD aIndex) |
|
1714 { |
|
1715 // Retrieve the size of the required output buffer. |
|
1716 long lRtn = ::ImmGetCompositionStringW(aIMEContext.get(), aIndex, nullptr, 0); |
|
1717 if (lRtn < 0 || |
|
1718 !mCompositionString.SetLength((lRtn / sizeof(WCHAR)) + 1, mozilla::fallible_t())) { |
|
1719 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1720 ("IMM32: GetCompositionString, FAILED by OOM\n")); |
|
1721 return; // Error or out of memory. |
|
1722 } |
|
1723 |
|
1724 // Actually retrieve the composition string information. |
|
1725 lRtn = ::ImmGetCompositionStringW(aIMEContext.get(), aIndex, |
|
1726 (LPVOID)mCompositionString.BeginWriting(), |
|
1727 lRtn + sizeof(WCHAR)); |
|
1728 mCompositionString.SetLength(lRtn / sizeof(WCHAR)); |
|
1729 |
|
1730 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1731 ("IMM32: GetCompositionString, SUCCEEDED mCompositionString=\"%s\"\n", |
|
1732 NS_ConvertUTF16toUTF8(mCompositionString).get())); |
|
1733 } |
|
1734 |
|
1735 bool |
|
1736 nsIMM32Handler::GetTargetClauseRange(uint32_t *aOffset, uint32_t *aLength) |
|
1737 { |
|
1738 NS_ENSURE_TRUE(aOffset, false); |
|
1739 NS_ENSURE_TRUE(mIsComposing, false); |
|
1740 NS_ENSURE_TRUE(ShouldDrawCompositionStringOurselves(), false); |
|
1741 |
|
1742 bool found = false; |
|
1743 *aOffset = mCompositionStart; |
|
1744 for (uint32_t i = 0; i < mAttributeArray.Length(); i++) { |
|
1745 if (mAttributeArray[i] == ATTR_TARGET_NOTCONVERTED || |
|
1746 mAttributeArray[i] == ATTR_TARGET_CONVERTED) { |
|
1747 *aOffset = mCompositionStart + i; |
|
1748 found = true; |
|
1749 break; |
|
1750 } |
|
1751 } |
|
1752 |
|
1753 if (!aLength) { |
|
1754 return true; |
|
1755 } |
|
1756 |
|
1757 if (!found) { |
|
1758 // The all composition string is targetted when there is no ATTR_TARGET_* |
|
1759 // clause. E.g., there is only ATTR_INPUT |
|
1760 *aLength = mCompositionString.Length(); |
|
1761 return true; |
|
1762 } |
|
1763 |
|
1764 uint32_t offsetInComposition = *aOffset - mCompositionStart; |
|
1765 *aLength = mCompositionString.Length() - offsetInComposition; |
|
1766 for (uint32_t i = offsetInComposition; i < mAttributeArray.Length(); i++) { |
|
1767 if (mAttributeArray[i] != ATTR_TARGET_NOTCONVERTED && |
|
1768 mAttributeArray[i] != ATTR_TARGET_CONVERTED) { |
|
1769 *aLength = i - offsetInComposition; |
|
1770 break; |
|
1771 } |
|
1772 } |
|
1773 return true; |
|
1774 } |
|
1775 |
|
1776 bool |
|
1777 nsIMM32Handler::ConvertToANSIString(const nsAFlatString& aStr, UINT aCodePage, |
|
1778 nsACString& aANSIStr) |
|
1779 { |
|
1780 int len = ::WideCharToMultiByte(aCodePage, 0, |
|
1781 (LPCWSTR)aStr.get(), aStr.Length(), |
|
1782 nullptr, 0, nullptr, nullptr); |
|
1783 NS_ENSURE_TRUE(len >= 0, false); |
|
1784 |
|
1785 if (!aANSIStr.SetLength(len, mozilla::fallible_t())) { |
|
1786 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1787 ("IMM32: ConvertToANSIString, FAILED by OOM\n")); |
|
1788 return false; |
|
1789 } |
|
1790 ::WideCharToMultiByte(aCodePage, 0, (LPCWSTR)aStr.get(), aStr.Length(), |
|
1791 (LPSTR)aANSIStr.BeginWriting(), len, nullptr, nullptr); |
|
1792 return true; |
|
1793 } |
|
1794 |
|
1795 bool |
|
1796 nsIMM32Handler::GetCharacterRectOfSelectedTextAt(nsWindow* aWindow, |
|
1797 uint32_t aOffset, |
|
1798 nsIntRect &aCharRect) |
|
1799 { |
|
1800 nsIntPoint point(0, 0); |
|
1801 |
|
1802 WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, aWindow); |
|
1803 aWindow->InitEvent(selection, &point); |
|
1804 aWindow->DispatchWindowEvent(&selection); |
|
1805 if (!selection.mSucceeded) { |
|
1806 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1807 ("IMM32: GetCharacterRectOfSelectedTextAt, aOffset=%lu, FAILED (NS_QUERY_SELECTED_TEXT)\n", |
|
1808 aOffset)); |
|
1809 return false; |
|
1810 } |
|
1811 |
|
1812 uint32_t offset = selection.mReply.mOffset + aOffset; |
|
1813 bool useCaretRect = selection.mReply.mString.IsEmpty(); |
|
1814 if (useCaretRect && ShouldDrawCompositionStringOurselves() && |
|
1815 mIsComposing && !mCompositionString.IsEmpty()) { |
|
1816 // There is not a normal selection, but we have composition string. |
|
1817 // XXX mnakano - Should we implement NS_QUERY_IME_SELECTED_TEXT? |
|
1818 useCaretRect = false; |
|
1819 if (mCursorPosition != NO_IME_CARET) { |
|
1820 uint32_t cursorPosition = |
|
1821 std::min<uint32_t>(mCursorPosition, mCompositionString.Length()); |
|
1822 NS_ASSERTION(offset >= cursorPosition, "offset is less than cursorPosition!"); |
|
1823 offset -= cursorPosition; |
|
1824 } |
|
1825 } |
|
1826 |
|
1827 nsIntRect r; |
|
1828 if (!useCaretRect) { |
|
1829 WidgetQueryContentEvent charRect(true, NS_QUERY_TEXT_RECT, aWindow); |
|
1830 charRect.InitForQueryTextRect(offset, 1); |
|
1831 aWindow->InitEvent(charRect, &point); |
|
1832 aWindow->DispatchWindowEvent(&charRect); |
|
1833 if (charRect.mSucceeded) { |
|
1834 aCharRect = charRect.mReply.mRect; |
|
1835 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1836 ("IMM32: GetCharacterRectOfSelectedTextAt, aOffset=%lu, SUCCEEDED\n", |
|
1837 aOffset)); |
|
1838 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1839 ("IMM32: GetCharacterRectOfSelectedTextAt, aCharRect={ x: %ld, y: %ld, width: %ld, height: %ld }\n", |
|
1840 aCharRect.x, aCharRect.y, aCharRect.width, aCharRect.height)); |
|
1841 return true; |
|
1842 } |
|
1843 } |
|
1844 |
|
1845 return GetCaretRect(aWindow, aCharRect); |
|
1846 } |
|
1847 |
|
1848 bool |
|
1849 nsIMM32Handler::GetCaretRect(nsWindow* aWindow, nsIntRect &aCaretRect) |
|
1850 { |
|
1851 nsIntPoint point(0, 0); |
|
1852 |
|
1853 WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, aWindow); |
|
1854 aWindow->InitEvent(selection, &point); |
|
1855 aWindow->DispatchWindowEvent(&selection); |
|
1856 if (!selection.mSucceeded) { |
|
1857 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1858 ("IMM32: GetCaretRect, FAILED (NS_QUERY_SELECTED_TEXT)\n")); |
|
1859 return false; |
|
1860 } |
|
1861 |
|
1862 uint32_t offset = selection.mReply.mOffset; |
|
1863 |
|
1864 WidgetQueryContentEvent caretRect(true, NS_QUERY_CARET_RECT, aWindow); |
|
1865 caretRect.InitForQueryCaretRect(offset); |
|
1866 aWindow->InitEvent(caretRect, &point); |
|
1867 aWindow->DispatchWindowEvent(&caretRect); |
|
1868 if (!caretRect.mSucceeded) { |
|
1869 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1870 ("IMM32: GetCaretRect, FAILED (NS_QUERY_CARET_RECT)\n")); |
|
1871 return false; |
|
1872 } |
|
1873 aCaretRect = caretRect.mReply.mRect; |
|
1874 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1875 ("IMM32: GetCaretRect, SUCCEEDED, aCaretRect={ x: %ld, y: %ld, width: %ld, height: %ld }\n", |
|
1876 aCaretRect.x, aCaretRect.y, aCaretRect.width, aCaretRect.height)); |
|
1877 return true; |
|
1878 } |
|
1879 |
|
1880 bool |
|
1881 nsIMM32Handler::SetIMERelatedWindowsPos(nsWindow* aWindow, |
|
1882 const nsIMEContext &aIMEContext) |
|
1883 { |
|
1884 nsIntRect r; |
|
1885 // Get first character rect of current a normal selected text or a composing |
|
1886 // string. |
|
1887 bool ret = GetCharacterRectOfSelectedTextAt(aWindow, 0, r); |
|
1888 NS_ENSURE_TRUE(ret, false); |
|
1889 nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false); |
|
1890 nsIntRect firstSelectedCharRect; |
|
1891 ResolveIMECaretPos(toplevelWindow, r, aWindow, firstSelectedCharRect); |
|
1892 |
|
1893 // Set native caret size/position to our caret. Some IMEs honor it. E.g., |
|
1894 // "Intelligent ABC" (Simplified Chinese) and "MS PinYin 3.0" (Simplified |
|
1895 // Chinese) on XP. |
|
1896 nsIntRect caretRect(firstSelectedCharRect); |
|
1897 if (GetCaretRect(aWindow, r)) { |
|
1898 ResolveIMECaretPos(toplevelWindow, r, aWindow, caretRect); |
|
1899 } else { |
|
1900 NS_WARNING("failed to get caret rect"); |
|
1901 caretRect.width = 1; |
|
1902 } |
|
1903 if (!mNativeCaretIsCreated) { |
|
1904 mNativeCaretIsCreated = ::CreateCaret(aWindow->GetWindowHandle(), nullptr, |
|
1905 caretRect.width, caretRect.height); |
|
1906 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1907 ("IMM32: SetIMERelatedWindowsPos, mNativeCaretIsCreated=%s, width=%ld height=%ld\n", |
|
1908 mNativeCaretIsCreated ? "TRUE" : "FALSE", |
|
1909 caretRect.width, caretRect.height)); |
|
1910 } |
|
1911 ::SetCaretPos(caretRect.x, caretRect.y); |
|
1912 |
|
1913 if (ShouldDrawCompositionStringOurselves()) { |
|
1914 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1915 ("IMM32: SetIMERelatedWindowsPos, Set candidate window\n")); |
|
1916 |
|
1917 // Get a rect of first character in current target in composition string. |
|
1918 if (mIsComposing && !mCompositionString.IsEmpty()) { |
|
1919 // If there are no targetted selection, we should use it's first character |
|
1920 // rect instead. |
|
1921 uint32_t offset; |
|
1922 if (!GetTargetClauseRange(&offset)) { |
|
1923 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1924 ("IMM32: SetIMERelatedWindowsPos, FAILED, by GetTargetClauseRange\n")); |
|
1925 return false; |
|
1926 } |
|
1927 ret = GetCharacterRectOfSelectedTextAt(aWindow, |
|
1928 offset - mCompositionStart, r); |
|
1929 NS_ENSURE_TRUE(ret, false); |
|
1930 } else { |
|
1931 // If there are no composition string, we should use a first character |
|
1932 // rect. |
|
1933 ret = GetCharacterRectOfSelectedTextAt(aWindow, 0, r); |
|
1934 NS_ENSURE_TRUE(ret, false); |
|
1935 } |
|
1936 nsIntRect firstTargetCharRect; |
|
1937 ResolveIMECaretPos(toplevelWindow, r, aWindow, firstTargetCharRect); |
|
1938 |
|
1939 // Move the candidate window to first character position of the target. |
|
1940 CANDIDATEFORM candForm; |
|
1941 candForm.dwIndex = 0; |
|
1942 candForm.dwStyle = CFS_EXCLUDE; |
|
1943 candForm.ptCurrentPos.x = firstTargetCharRect.x; |
|
1944 candForm.ptCurrentPos.y = firstTargetCharRect.y; |
|
1945 candForm.rcArea.right = candForm.rcArea.left = candForm.ptCurrentPos.x; |
|
1946 candForm.rcArea.top = candForm.ptCurrentPos.y; |
|
1947 candForm.rcArea.bottom = candForm.ptCurrentPos.y + |
|
1948 firstTargetCharRect.height; |
|
1949 ::ImmSetCandidateWindow(aIMEContext.get(), &candForm); |
|
1950 } else { |
|
1951 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1952 ("IMM32: SetIMERelatedWindowsPos, Set composition window\n")); |
|
1953 |
|
1954 // Move the composition window to caret position (if selected some |
|
1955 // characters, we should use first character rect of them). |
|
1956 // And in this mode, IME adjusts the candidate window position |
|
1957 // automatically. So, we don't need to set it. |
|
1958 COMPOSITIONFORM compForm; |
|
1959 compForm.dwStyle = CFS_POINT; |
|
1960 compForm.ptCurrentPos.x = firstSelectedCharRect.x; |
|
1961 compForm.ptCurrentPos.y = firstSelectedCharRect.y; |
|
1962 ::ImmSetCompositionWindow(aIMEContext.get(), &compForm); |
|
1963 } |
|
1964 |
|
1965 return true; |
|
1966 } |
|
1967 |
|
1968 void |
|
1969 nsIMM32Handler::SetIMERelatedWindowsPosOnPlugin(nsWindow* aWindow, |
|
1970 const nsIMEContext& aIMEContext) |
|
1971 { |
|
1972 WidgetQueryContentEvent editorRectEvent(true, NS_QUERY_EDITOR_RECT, aWindow); |
|
1973 aWindow->InitEvent(editorRectEvent); |
|
1974 aWindow->DispatchWindowEvent(&editorRectEvent); |
|
1975 if (!editorRectEvent.mSucceeded) { |
|
1976 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
1977 ("IMM32: SetIMERelatedWindowsPosOnPlugin, " |
|
1978 "FAILED (NS_QUERY_EDITOR_RECT)")); |
|
1979 return; |
|
1980 } |
|
1981 |
|
1982 // Clip the plugin rect by the client rect of the window because composition |
|
1983 // window needs to be specified the position in the client area. |
|
1984 nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false); |
|
1985 nsIntRect pluginRectInScreen = |
|
1986 editorRectEvent.mReply.mRect + toplevelWindow->WidgetToScreenOffset(); |
|
1987 nsIntRect winRectInScreen; |
|
1988 aWindow->GetClientBounds(winRectInScreen); |
|
1989 // composition window cannot be positioned on the edge of client area. |
|
1990 winRectInScreen.width--; |
|
1991 winRectInScreen.height--; |
|
1992 nsIntRect clippedPluginRect; |
|
1993 clippedPluginRect.x = |
|
1994 std::min(std::max(pluginRectInScreen.x, winRectInScreen.x), |
|
1995 winRectInScreen.XMost()); |
|
1996 clippedPluginRect.y = |
|
1997 std::min(std::max(pluginRectInScreen.y, winRectInScreen.y), |
|
1998 winRectInScreen.YMost()); |
|
1999 int32_t xMost = std::min(pluginRectInScreen.XMost(), winRectInScreen.XMost()); |
|
2000 int32_t yMost = std::min(pluginRectInScreen.YMost(), winRectInScreen.YMost()); |
|
2001 clippedPluginRect.width = std::max(0, xMost - clippedPluginRect.x); |
|
2002 clippedPluginRect.height = std::max(0, yMost - clippedPluginRect.y); |
|
2003 clippedPluginRect -= aWindow->WidgetToScreenOffset(); |
|
2004 |
|
2005 // Cover the plugin with native caret. This prevents IME's window and plugin |
|
2006 // overlap. |
|
2007 if (mNativeCaretIsCreated) { |
|
2008 ::DestroyCaret(); |
|
2009 } |
|
2010 mNativeCaretIsCreated = |
|
2011 ::CreateCaret(aWindow->GetWindowHandle(), nullptr, |
|
2012 clippedPluginRect.width, clippedPluginRect.height); |
|
2013 ::SetCaretPos(clippedPluginRect.x, clippedPluginRect.y); |
|
2014 |
|
2015 // Set the composition window to bottom-left of the clipped plugin. |
|
2016 // As far as we know, there is no IME for RTL language. Therefore, this code |
|
2017 // must not need to take care of RTL environment. |
|
2018 COMPOSITIONFORM compForm; |
|
2019 compForm.dwStyle = CFS_POINT; |
|
2020 compForm.ptCurrentPos.x = clippedPluginRect.BottomLeft().x; |
|
2021 compForm.ptCurrentPos.y = clippedPluginRect.BottomLeft().y; |
|
2022 if (!::ImmSetCompositionWindow(aIMEContext.get(), &compForm)) { |
|
2023 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
2024 ("IMM32: SetIMERelatedWindowsPosOnPlugin, " |
|
2025 "FAILED to set composition window")); |
|
2026 return; |
|
2027 } |
|
2028 } |
|
2029 |
|
2030 void |
|
2031 nsIMM32Handler::ResolveIMECaretPos(nsIWidget* aReferenceWidget, |
|
2032 nsIntRect& aCursorRect, |
|
2033 nsIWidget* aNewOriginWidget, |
|
2034 nsIntRect& aOutRect) |
|
2035 { |
|
2036 aOutRect = aCursorRect; |
|
2037 |
|
2038 if (aReferenceWidget == aNewOriginWidget) |
|
2039 return; |
|
2040 |
|
2041 if (aReferenceWidget) |
|
2042 aOutRect.MoveBy(aReferenceWidget->WidgetToScreenOffset()); |
|
2043 |
|
2044 if (aNewOriginWidget) |
|
2045 aOutRect.MoveBy(-aNewOriginWidget->WidgetToScreenOffset()); |
|
2046 } |
|
2047 |
|
2048 bool |
|
2049 nsIMM32Handler::OnMouseEvent(nsWindow* aWindow, LPARAM lParam, int aAction, |
|
2050 MSGResult& aResult) |
|
2051 { |
|
2052 aResult.mConsumed = false; // always call next wndprc |
|
2053 |
|
2054 if (!sWM_MSIME_MOUSE || !mIsComposing || |
|
2055 !ShouldDrawCompositionStringOurselves()) { |
|
2056 return false; |
|
2057 } |
|
2058 |
|
2059 nsIntPoint cursor(LOWORD(lParam), HIWORD(lParam)); |
|
2060 WidgetQueryContentEvent charAtPt(true, NS_QUERY_CHARACTER_AT_POINT, aWindow); |
|
2061 aWindow->InitEvent(charAtPt, &cursor); |
|
2062 aWindow->DispatchWindowEvent(&charAtPt); |
|
2063 if (!charAtPt.mSucceeded || |
|
2064 charAtPt.mReply.mOffset == WidgetQueryContentEvent::NOT_FOUND || |
|
2065 charAtPt.mReply.mOffset < mCompositionStart || |
|
2066 charAtPt.mReply.mOffset > |
|
2067 mCompositionStart + mCompositionString.Length()) { |
|
2068 return false; |
|
2069 } |
|
2070 |
|
2071 // calcurate positioning and offset |
|
2072 // char : JCH1|JCH2|JCH3 |
|
2073 // offset: 0011 1122 2233 |
|
2074 // positioning: 2301 2301 2301 |
|
2075 nsIntRect cursorInTopLevel, cursorRect(cursor, nsIntSize(0, 0)); |
|
2076 ResolveIMECaretPos(aWindow, cursorRect, |
|
2077 aWindow->GetTopLevelWindow(false), cursorInTopLevel); |
|
2078 int32_t cursorXInChar = cursorInTopLevel.x - charAtPt.mReply.mRect.x; |
|
2079 // The event might hit to zero-width character, see bug 694913. |
|
2080 // The reason might be: |
|
2081 // * There are some zero-width characters are actually. |
|
2082 // * font-size is specified zero. |
|
2083 // But nobody reproduced this bug actually... |
|
2084 // We should assume that user clicked on right most of the zero-width |
|
2085 // character in such case. |
|
2086 int positioning = 1; |
|
2087 if (charAtPt.mReply.mRect.width > 0) { |
|
2088 positioning = cursorXInChar * 4 / charAtPt.mReply.mRect.width; |
|
2089 positioning = (positioning + 2) % 4; |
|
2090 } |
|
2091 |
|
2092 int offset = charAtPt.mReply.mOffset - mCompositionStart; |
|
2093 if (positioning < 2) { |
|
2094 offset++; |
|
2095 } |
|
2096 |
|
2097 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
2098 ("IMM32: OnMouseEvent, x,y=%ld,%ld, offset=%ld, positioning=%ld\n", |
|
2099 cursor.x, cursor.y, offset, positioning)); |
|
2100 |
|
2101 // send MS_MSIME_MOUSE message to default IME window. |
|
2102 HWND imeWnd = ::ImmGetDefaultIMEWnd(aWindow->GetWindowHandle()); |
|
2103 nsIMEContext IMEContext(aWindow->GetWindowHandle()); |
|
2104 return ::SendMessageW(imeWnd, sWM_MSIME_MOUSE, |
|
2105 MAKELONG(MAKEWORD(aAction, positioning), offset), |
|
2106 (LPARAM) IMEContext.get()) == 1; |
|
2107 } |
|
2108 |
|
2109 /* static */ bool |
|
2110 nsIMM32Handler::OnKeyDownEvent(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, |
|
2111 MSGResult& aResult) |
|
2112 { |
|
2113 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, |
|
2114 ("IMM32: OnKeyDownEvent, hWnd=%08x, wParam=%08x, lParam=%08x\n", |
|
2115 aWindow->GetWindowHandle(), wParam, lParam)); |
|
2116 aResult.mConsumed = false; |
|
2117 switch (wParam) { |
|
2118 case VK_TAB: |
|
2119 case VK_PRIOR: |
|
2120 case VK_NEXT: |
|
2121 case VK_END: |
|
2122 case VK_HOME: |
|
2123 case VK_LEFT: |
|
2124 case VK_UP: |
|
2125 case VK_RIGHT: |
|
2126 case VK_DOWN: |
|
2127 // If IME didn't process the key message (the virtual key code wasn't |
|
2128 // converted to VK_PROCESSKEY), and the virtual key code event causes |
|
2129 // to move caret, we should cancel the composition here. Then, this |
|
2130 // event will be dispatched. |
|
2131 // XXX I think that we should dispatch all key events during composition, |
|
2132 // and nsEditor should cancel/commit the composition if it *thinks* |
|
2133 // it's needed. |
|
2134 if (IsComposingOnOurEditor()) { |
|
2135 // NOTE: We don't need to cancel the composition on another window. |
|
2136 CancelComposition(aWindow, false); |
|
2137 } |
|
2138 return false; |
|
2139 default: |
|
2140 return false; |
|
2141 } |
|
2142 } |