Cleanup: remove redundant doxygen \file argument
[blender.git] / intern / ghost / intern / GHOST_ImeWin32.cpp
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (c) 2010 The Chromium Authors. All rights reserved.
17  * All rights reserved.
18  *
19  * The Original Code is: some of this file.
20  */
21
22
23 /** \file \ingroup GHOST
24  */
25
26 #ifdef WITH_INPUT_IME
27
28 #include "GHOST_C-api.h"
29 #include "GHOST_ImeWin32.h"
30 #include "GHOST_WindowWin32.h"
31 #include "utfconv.h"
32
33
34 GHOST_ImeWin32::GHOST_ImeWin32()
35     : ime_status_(false),
36       input_language_id_(LANG_USER_DEFAULT),
37       is_composing_(false),
38       system_caret_(false),
39       caret_rect_(-1, -1, 0, 0),
40       is_first(true),
41       is_enable(true)
42 {
43 }
44
45
46 GHOST_ImeWin32::~GHOST_ImeWin32()
47 {
48 }
49
50
51 bool GHOST_ImeWin32::SetInputLanguage()
52 {
53         /**
54          * Retrieve the current keyboard layout from Windows and determine whether
55          * or not the current input context has IMEs.
56          * Also save its input language for language-specific operations required
57          * while composing a text.
58          */
59         HKL keyboard_layout = ::GetKeyboardLayout(0);
60         input_language_id_ = LOWORD(keyboard_layout);
61         ime_status_ = ::ImmIsIME(keyboard_layout);
62         return ime_status_;
63 }
64
65
66 void GHOST_ImeWin32::CreateImeWindow(HWND window_handle)
67 {
68         /**
69          * When a user disables TSF (Text Service Framework) and CUAS (Cicero
70          * Unaware Application Support), Chinese IMEs somehow ignore function calls
71          * to ::ImmSetCandidateWindow(), i.e. they do not move their candidate
72          * window to the position given as its parameters, and use the position
73          * of the current system caret instead, i.e. it uses ::GetCaretPos() to
74          * retrieve the position of their IME candidate window.
75          * Therefore, we create a temporary system caret for Chinese IMEs and use
76          * it during this input context.
77          * Since some third-party Japanese IME also uses ::GetCaretPos() to determine
78          * their window position, we also create a caret for Japanese IMEs.
79          */
80         if (PRIMARYLANGID(input_language_id_) == LANG_CHINESE ||
81                 PRIMARYLANGID(input_language_id_) == LANG_JAPANESE) {
82                 if (!system_caret_) {
83                         if (::CreateCaret(window_handle, NULL, 1, 1)) {
84                                 system_caret_ = true;
85                         }
86                 }
87         }
88         /* Restore the positions of the IME windows. */
89         UpdateImeWindow(window_handle);
90 }
91
92
93 void GHOST_ImeWin32::SetImeWindowStyle(HWND window_handle, UINT message, WPARAM wparam, LPARAM lparam, BOOL *handled)
94 {
95         /**
96          * To prevent the IMM (Input Method Manager) from displaying the IME
97          * composition window, Update the styles of the IME windows and EXPLICITLY
98          * call ::DefWindowProc() here.
99          * NOTE(hbono): We can NEVER let WTL call ::DefWindowProc() when we update
100          * the styles of IME windows because the 'lparam' variable is a local one
101          * and all its updates disappear in returning from this function, i.e. WTL
102          * does not call ::DefWindowProc() with our updated 'lparam' value but call
103          * the function with its original value and over-writes our window styles.
104          */
105         *handled = TRUE;
106         lparam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
107         ::DefWindowProc(window_handle, message, wparam, lparam);
108 }
109
110
111 void GHOST_ImeWin32::DestroyImeWindow(HWND window_handle)
112 {
113         /* Destroy the system caret if we have created for this IME input context. */
114         if (system_caret_) {
115                 ::DestroyCaret();
116                 system_caret_ = false;
117         }
118 }
119
120
121 void GHOST_ImeWin32::MoveImeWindow(HWND window_handle, HIMC imm_context)
122 {
123         int x = caret_rect_.m_l;
124         int y = caret_rect_.m_t;
125         const int kCaretMargin = 1;
126         /**
127          * As written in a comment in GHOST_ImeWin32::CreateImeWindow(),
128          * Chinese IMEs ignore function calls to ::ImmSetCandidateWindow()
129          * when a user disables TSF (Text Service Framework) and CUAS (Cicero
130          * Unaware Application Support).
131          * On the other hand, when a user enables TSF and CUAS, Chinese IMEs
132          * ignore the position of the current system caret and uses the
133          * parameters given to ::ImmSetCandidateWindow() with its 'dwStyle'
134          * parameter CFS_CANDIDATEPOS.
135          * Therefore, we do not only call ::ImmSetCandidateWindow() but also
136          * set the positions of the temporary system caret if it exists.
137          */
138         CANDIDATEFORM candidate_position = { 0, CFS_CANDIDATEPOS, { x, y },
139         { 0, 0, 0, 0 } };
140         ::ImmSetCandidateWindow(imm_context, &candidate_position);
141         if (system_caret_) {
142                 switch (PRIMARYLANGID(input_language_id_)) {
143                 case LANG_JAPANESE:
144                         ::SetCaretPos(x, y + caret_rect_.getHeight());
145                         break;
146                 default:
147                         ::SetCaretPos(x, y);
148                         break;
149                 }
150         }
151         if (PRIMARYLANGID(input_language_id_) == LANG_KOREAN) {
152                 /**
153                  * Chinese IMEs and Japanese IMEs require the upper-left corner of
154                  * the caret to move the position of their candidate windows.
155                  * On the other hand, Korean IMEs require the lower-left corner of the
156                  * caret to move their candidate windows.
157                  */
158                 y += kCaretMargin;
159         }
160         /**
161          * Japanese IMEs and Korean IMEs also use the rectangle given to
162          * ::ImmSetCandidateWindow() with its 'dwStyle' parameter CFS_EXCLUDE
163          * to move their candidate windows when a user disables TSF and CUAS.
164          * Therefore, we also set this parameter here.
165          */
166         CANDIDATEFORM exclude_rectangle = { 0, CFS_EXCLUDE, { x, y },
167         { x, y, x + caret_rect_.getWidth(), y + caret_rect_.getHeight() } };
168         ::ImmSetCandidateWindow(imm_context, &exclude_rectangle);
169 }
170
171
172 void GHOST_ImeWin32::UpdateImeWindow(HWND window_handle)
173 {
174         /* Just move the IME window attached to the given window. */
175         if (caret_rect_.m_l >= 0 && caret_rect_.m_t >= 0) {
176                 HIMC imm_context = ::ImmGetContext(window_handle);
177                 if (imm_context) {
178                         MoveImeWindow(window_handle, imm_context);
179                         ::ImmReleaseContext(window_handle, imm_context);
180                 }
181         }
182 }
183
184
185 void GHOST_ImeWin32::CleanupComposition(HWND window_handle)
186 {
187         /**
188          * Notify the IMM attached to the given window to complete the ongoing
189          * composition, (this case happens when the given window is de-activated
190          * while composing a text and re-activated), and reset the omposition status.
191          */
192         if (is_composing_) {
193                 HIMC imm_context = ::ImmGetContext(window_handle);
194                 if (imm_context) {
195                         ::ImmNotifyIME(imm_context, NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
196                         ::ImmReleaseContext(window_handle, imm_context);
197                 }
198                 ResetComposition(window_handle);
199         }
200 }
201
202
203 void GHOST_ImeWin32::CheckFirst(HWND window_handle)
204 {
205         if (is_first) {
206                 this->EndIME(window_handle);
207                 is_first = false;
208         }
209 }
210
211
212 void GHOST_ImeWin32::ResetComposition(HWND window_handle)
213 {
214         /* Currently, just reset the composition status. */
215         is_composing_ = false;
216 }
217
218
219 void GHOST_ImeWin32::CompleteComposition(HWND window_handle, HIMC imm_context)
220 {
221         /**
222          * We have to confirm there is an ongoing composition before completing it.
223          * This is for preventing some IMEs from getting confused while completing an
224          * ongoing composition even if they do not have any ongoing compositions.)
225          */
226         if (is_composing_) {
227                 ::ImmNotifyIME(imm_context, NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
228                 ResetComposition(window_handle);
229         }
230 }
231
232
233 void GHOST_ImeWin32::GetCaret(HIMC imm_context, LPARAM lparam, ImeComposition *composition)
234 {
235         /**
236          * This operation is optional and language-dependent because the caret
237          * style is depended on the language, e.g.:
238          *   * Korean IMEs: the caret is a blinking block,
239          *     (It contains only one hangul character);
240          *   * Chinese IMEs: the caret is a blinking line,
241          *     (i.e. they do not need to retrieve the target selection);
242          *   * Japanese IMEs: the caret is a selection (or underlined) block,
243          *     (which can contain one or more Japanese characters).
244          */
245         int target_start = -1;
246         int target_end = -1;
247         switch (PRIMARYLANGID(input_language_id_)) {
248         case LANG_KOREAN:
249                 if (lparam & CS_NOMOVECARET) {
250                         target_start = 0;
251                         target_end = 1;
252                 }
253                 break;
254         case LANG_CHINESE:
255         {
256                 int clause_size = ImmGetCompositionStringW(imm_context, GCS_COMPCLAUSE, NULL, 0);
257                 if (clause_size) {
258                         static std::vector<unsigned long> clauses;
259                         clause_size = clause_size / sizeof(clauses[0]);
260                         clauses.resize(clause_size);
261                         ImmGetCompositionStringW(imm_context, GCS_COMPCLAUSE, &clauses[0],
262                                 sizeof(clauses[0])  *clause_size);
263                         if (composition->cursor_position == composition->ime_string.size()) {
264                                 target_start = clauses[clause_size - 2];
265                                 target_end = clauses[clause_size - 1];
266                         }
267                         else {
268                                 for (int i = 0; i < clause_size - 1; i++) {
269                                         if (clauses[i] == composition->cursor_position) {
270                                                 target_start = clauses[i];
271                                                 target_end = clauses[i + 1];
272                                                 break;
273                                         }
274                                 }
275                         }
276                 }
277                 else {
278                         if (composition->cursor_position != -1) {
279                                 target_start = composition->cursor_position;
280                                 target_end = composition->ime_string.size();
281                         }
282                 }
283                 break;
284         }
285         case LANG_JAPANESE:
286
287                 /**
288                  * For Japanese IMEs, the robustest way to retrieve the caret
289                  * is scanning the attribute of the latest composition string and
290                  * retrieving the begining and the end of the target clause, i.e.
291                  * a clause being converted.
292                  */
293                 if (lparam & GCS_COMPATTR) {
294                         int attribute_size = ::ImmGetCompositionStringW(imm_context,
295                                 GCS_COMPATTR,
296                                 NULL, 0);
297                         if (attribute_size > 0) {
298                                 char *attribute_data = new char[attribute_size];
299                                 if (attribute_data) {
300                                         ::ImmGetCompositionStringW(imm_context, GCS_COMPATTR,
301                                                 attribute_data, attribute_size);
302                                         for (target_start = 0; target_start < attribute_size;
303                                                 ++target_start) {
304                                                 if (IsTargetAttribute(attribute_data[target_start]))
305                                                         break;
306                                         }
307                                         for (target_end = target_start; target_end < attribute_size;
308                                                 ++target_end) {
309                                                 if (!IsTargetAttribute(attribute_data[target_end]))
310                                                         break;
311                                         }
312                                         if (target_start == attribute_size) {
313                                                 /**
314                                                  * This composition clause does not contain any target clauses,
315                                                  * i.e. this clauses is an input clause.
316                                                  * We treat whole this clause as a target clause.
317                                                  */
318                                                 target_end = target_start;
319                                                 target_start = 0;
320                                         }
321                                         if (target_start != -1 && target_start < attribute_size &&
322                                                 attribute_data[target_start] == ATTR_TARGET_NOTCONVERTED)
323                                         {
324                                                 composition->cursor_position = target_start;
325                                         }
326                                 }
327                                 delete[] attribute_data;
328                         }
329                 }
330                 break;
331         }
332         composition->target_start = target_start;
333         composition->target_end = target_end;
334 }
335
336
337 bool GHOST_ImeWin32::GetString(HIMC imm_context, WPARAM lparam, int type, ImeComposition *composition)
338 {
339         bool result = false;
340         if (lparam & type) {
341                 int string_size = ::ImmGetCompositionStringW(imm_context, type, NULL, 0);
342                 if (string_size > 0) {
343                         int string_length = string_size / sizeof(wchar_t);
344                         wchar_t *string_data = new wchar_t[string_length + 1];
345                         string_data[string_length] = '\0';
346                         if (string_data) {
347                                 /* Fill the given ImeComposition object. */
348                                 ::ImmGetCompositionStringW(imm_context, type,
349                                         string_data, string_size);
350                                 composition->string_type = type;
351                                 composition->ime_string = string_data;
352                                 result = true;
353                         }
354                         delete[] string_data;
355                 }
356         }
357         return result;
358 }
359
360
361 bool GHOST_ImeWin32::GetResult(HWND window_handle, LPARAM lparam, ImeComposition *composition)
362 {
363         bool result = false;
364         HIMC imm_context = ::ImmGetContext(window_handle);
365         if (imm_context) {
366                 /* Copy the result string to the ImeComposition object. */
367                 result = GetString(imm_context, lparam, GCS_RESULTSTR, composition);
368                 /**
369                  * Reset all the other parameters because a result string does not
370                  * have composition attributes.
371                  */
372                 composition->cursor_position = -1;
373                 composition->target_start = -1;
374                 composition->target_end = -1;
375                 ::ImmReleaseContext(window_handle, imm_context);
376         }
377         return result;
378 }
379
380
381 bool GHOST_ImeWin32::GetComposition(HWND window_handle, LPARAM lparam, ImeComposition *composition)
382 {
383         bool result = false;
384         HIMC imm_context = ::ImmGetContext(window_handle);
385         if (imm_context) {
386                 /* Copy the composition string to the ImeComposition object. */
387                 result = GetString(imm_context, lparam, GCS_COMPSTR, composition);
388
389                 /* Retrieve the cursor position in the IME composition. */
390                 int cursor_position = ::ImmGetCompositionStringW(imm_context, GCS_CURSORPOS, NULL, 0);
391                 composition->cursor_position = cursor_position;
392                 composition->target_start = -1;
393                 composition->target_end = -1;
394
395                 /* Retrieve the target selection and Update the ImeComposition object. */
396                 GetCaret(imm_context, lparam, composition);
397
398                 /* Mark that there is an ongoing composition. */
399                 is_composing_ = true;
400
401                 ::ImmReleaseContext(window_handle, imm_context);
402         }
403         return result;
404 }
405
406
407 void GHOST_ImeWin32::EndIME(HWND window_handle)
408 {
409         /**
410          * A renderer process have moved its input focus to a password input
411          * when there is an ongoing composition, e.g. a user has clicked a
412          * mouse button and selected a password input while composing a text.
413          * For this case, we have to complete the ongoing composition and
414          * clean up the resources attached to this object BEFORE DISABLING THE IME.
415          */
416         if (!is_enable) return;
417         is_enable = false;
418         CleanupComposition(window_handle);
419         ::ImmAssociateContextEx(window_handle, NULL, 0);
420         eventImeData.composite_len = 0;
421 }
422
423
424 void GHOST_ImeWin32::BeginIME(HWND window_handle, const GHOST_Rect &caret_rect, bool complete)
425 {
426         if (is_enable && complete) return;
427         is_enable = true;
428         /**
429          * Load the default IME context.
430          * NOTE(hbono)
431          *   IMM ignores this call if the IME context is loaded. Therefore, we do
432          *   not have to check whether or not the IME context is loaded.
433          */
434         ::ImmAssociateContextEx(window_handle, NULL, IACE_DEFAULT);
435         /* Complete the ongoing composition and move the IME windows. */
436         HIMC imm_context = ::ImmGetContext(window_handle);
437         if (imm_context) {
438                 if (complete) {
439                         /**
440                          * A renderer process have moved its input focus to another edit
441                          * control when there is an ongoing composition, e.g. a user has
442                          * clicked a mouse button and selected another edit control while
443                          * composing a text.
444                          * For this case, we have to complete the ongoing composition and
445                          * hide the IME windows BEFORE MOVING THEM.
446                          */
447                         CompleteComposition(window_handle, imm_context);
448                 }
449                 /**
450                  * Save the caret position, and Update the position of the IME window.
451                  * This update is used for moving an IME window when a renderer process
452                  * resize/moves the input caret.
453                  */
454                 if (caret_rect.m_l >= 0 && caret_rect.m_t >= 0) {
455                         caret_rect_ = caret_rect;
456                         MoveImeWindow(window_handle, imm_context);
457                 }
458                 ::ImmReleaseContext(window_handle, imm_context);
459         }
460 }
461
462
463 static void convert_utf16_to_utf8_len(std::wstring s, int &len)
464 {
465         if (len >= 0 && len <= s.size())
466                 len = count_utf_8_from_16(s.substr(0, len).c_str()) - 1;
467         else
468                 len = -1;
469 }
470
471
472 static size_t updateUtf8Buf(ImeComposition &info)
473 {
474         size_t len = count_utf_8_from_16(info.ime_string.c_str());
475         info.utf8_buf.resize(len);
476         conv_utf_16_to_8(info.ime_string.c_str(), &info.utf8_buf[0], len);
477         convert_utf16_to_utf8_len(info.ime_string, info.cursor_position);
478         convert_utf16_to_utf8_len(info.ime_string, info.target_start);
479         convert_utf16_to_utf8_len(info.ime_string, info.target_end);
480         return len - 1;
481 }
482
483
484 void GHOST_ImeWin32::UpdateInfo(HWND window_handle)
485 {
486         int res = this->GetResult(window_handle, GCS_RESULTSTR, &resultInfo);
487         int comp = this->GetComposition(window_handle, GCS_COMPSTR | GCS_COMPATTR, &compInfo);
488         /* convert wchar to utf8 */
489         if (res) {
490                 eventImeData.result_len = (GHOST_TUserDataPtr)updateUtf8Buf(resultInfo);
491                 eventImeData.result = &resultInfo.utf8_buf[0];
492         }
493         else {
494                 eventImeData.result = 0;
495                 eventImeData.result_len = 0;
496         }
497         if (comp) {
498                 eventImeData.composite_len = (GHOST_TUserDataPtr)updateUtf8Buf(compInfo);
499                 eventImeData.composite = &compInfo.utf8_buf[0];
500                 eventImeData.cursor_position = compInfo.cursor_position;
501                 eventImeData.target_start = compInfo.target_start;
502                 eventImeData.target_end = compInfo.target_end;
503         }
504         else {
505                 eventImeData.composite = 0;
506                 eventImeData.composite_len = 0;
507                 eventImeData.cursor_position = -1;
508                 eventImeData.target_start = -1;
509                 eventImeData.target_end = -1;
510         }
511 }
512
513 #endif // WITH_INPUT_IME