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