Mercurial > vim
comparison src/gui_xim.c @ 20637:6c5b11458f31 v8.2.0872
patch 8.2.0872: XIM code is mixed with multi-byte code
Commit: https://github.com/vim/vim/commit/f15c8b6eb32fcfea88fd9ca42ef87bbee2c8fe2b
Author: Bram Moolenaar <Bram@vim.org>
Date: Mon Jun 1 14:34:43 2020 +0200
patch 8.2.0872: XIM code is mixed with multi-byte code
Problem: XIM code is mixed with multi-byte code.
Solution: Move the XIM code to a separate file. (Yegappan Lakshmanan,
closes #6177)
author | Bram Moolenaar <Bram@vim.org> |
---|---|
date | Mon, 01 Jun 2020 14:45:05 +0200 |
parents | |
children | ccdd80a6dad7 |
comparison
equal
deleted
inserted
replaced
20636:7226685d849a | 20637:6c5b11458f31 |
---|---|
1 /* vi:set ts=8 sts=4 sw=4 noet: | |
2 * | |
3 * VIM - Vi IMproved by Bram Moolenaar | |
4 * | |
5 * Do ":help uganda" in Vim to read copying and usage conditions. | |
6 * Do ":help credits" in Vim to see a list of people who contributed. | |
7 * See README.txt for an overview of the Vim source code. | |
8 */ | |
9 | |
10 /* | |
11 * gui_xim.c: functions for the X Input Method | |
12 */ | |
13 | |
14 #include "vim.h" | |
15 | |
16 #if defined(FEAT_GUI_GTK) && defined(FEAT_XIM) | |
17 # if GTK_CHECK_VERSION(3,0,0) | |
18 # include <gdk/gdkkeysyms-compat.h> | |
19 # else | |
20 # include <gdk/gdkkeysyms.h> | |
21 # endif | |
22 # ifdef MSWIN | |
23 # include <gdk/gdkwin32.h> | |
24 # else | |
25 # include <gdk/gdkx.h> | |
26 # endif | |
27 #endif | |
28 | |
29 /* | |
30 * XIM often causes trouble. Define XIM_DEBUG to get a log of XIM callbacks | |
31 * in the "xim.log" file. | |
32 */ | |
33 // #define XIM_DEBUG | |
34 #ifdef XIM_DEBUG | |
35 static void | |
36 xim_log(char *s, ...) | |
37 { | |
38 va_list arglist; | |
39 static FILE *fd = NULL; | |
40 | |
41 if (fd == (FILE *)-1) | |
42 return; | |
43 if (fd == NULL) | |
44 { | |
45 fd = mch_fopen("xim.log", "w"); | |
46 if (fd == NULL) | |
47 { | |
48 emsg("Cannot open xim.log"); | |
49 fd = (FILE *)-1; | |
50 return; | |
51 } | |
52 } | |
53 | |
54 va_start(arglist, s); | |
55 vfprintf(fd, s, arglist); | |
56 va_end(arglist); | |
57 } | |
58 #endif | |
59 | |
60 #ifdef FEAT_GUI | |
61 # define USE_IMACTIVATEFUNC (!gui.in_use && *p_imaf != NUL) | |
62 # define USE_IMSTATUSFUNC (!gui.in_use && *p_imsf != NUL) | |
63 #else | |
64 # define USE_IMACTIVATEFUNC (*p_imaf != NUL) | |
65 # define USE_IMSTATUSFUNC (*p_imsf != NUL) | |
66 #endif | |
67 | |
68 #if defined(FEAT_EVAL) && \ | |
69 (defined(FEAT_XIM) || defined(IME_WITHOUT_XIM) || defined(VIMDLL)) | |
70 static void | |
71 call_imactivatefunc(int active) | |
72 { | |
73 typval_T argv[2]; | |
74 | |
75 argv[0].v_type = VAR_NUMBER; | |
76 argv[0].vval.v_number = active ? 1 : 0; | |
77 argv[1].v_type = VAR_UNKNOWN; | |
78 (void)call_func_retnr(p_imaf, 1, argv); | |
79 } | |
80 | |
81 static int | |
82 call_imstatusfunc(void) | |
83 { | |
84 int is_active; | |
85 | |
86 // FIXME: Don't execute user function in unsafe situation. | |
87 if (exiting || is_autocmd_blocked()) | |
88 return FALSE; | |
89 // FIXME: :py print 'xxx' is shown duplicate result. | |
90 // Use silent to avoid it. | |
91 ++msg_silent; | |
92 is_active = call_func_retnr(p_imsf, 0, NULL); | |
93 --msg_silent; | |
94 return (is_active > 0); | |
95 } | |
96 #endif | |
97 | |
98 #if defined(FEAT_XIM) || defined(PROTO) | |
99 | |
100 # if defined(FEAT_GUI_GTK) || defined(PROTO) | |
101 static int xim_has_preediting INIT(= FALSE); // IM current status | |
102 | |
103 /* | |
104 * Set preedit_start_col to the current cursor position. | |
105 */ | |
106 static void | |
107 init_preedit_start_col(void) | |
108 { | |
109 if (State & CMDLINE) | |
110 preedit_start_col = cmdline_getvcol_cursor(); | |
111 else if (curwin != NULL && curwin->w_buffer != NULL) | |
112 getvcol(curwin, &curwin->w_cursor, &preedit_start_col, NULL, NULL); | |
113 // Prevent that preediting marks the buffer as changed. | |
114 xim_changed_while_preediting = curbuf->b_changed; | |
115 } | |
116 | |
117 static int im_is_active = FALSE; // IM is enabled for current mode | |
118 static int preedit_is_active = FALSE; | |
119 static int im_preedit_cursor = 0; // cursor offset in characters | |
120 static int im_preedit_trailing = 0; // number of characters after cursor | |
121 | |
122 static unsigned long im_commit_handler_id = 0; | |
123 static unsigned int im_activatekey_keyval = GDK_VoidSymbol; | |
124 static unsigned int im_activatekey_state = 0; | |
125 | |
126 static GtkWidget *preedit_window = NULL; | |
127 static GtkWidget *preedit_label = NULL; | |
128 | |
129 static void im_preedit_window_set_position(void); | |
130 | |
131 void | |
132 im_set_active(int active) | |
133 { | |
134 int was_active; | |
135 | |
136 was_active = !!im_get_status(); | |
137 im_is_active = (active && !p_imdisable); | |
138 | |
139 if (im_is_active != was_active) | |
140 xim_reset(); | |
141 } | |
142 | |
143 void | |
144 xim_set_focus(int focus) | |
145 { | |
146 if (xic != NULL) | |
147 { | |
148 if (focus) | |
149 gtk_im_context_focus_in(xic); | |
150 else | |
151 gtk_im_context_focus_out(xic); | |
152 } | |
153 } | |
154 | |
155 void | |
156 im_set_position(int row, int col) | |
157 { | |
158 if (xic != NULL) | |
159 { | |
160 GdkRectangle area; | |
161 | |
162 area.x = FILL_X(col); | |
163 area.y = FILL_Y(row); | |
164 area.width = gui.char_width * (mb_lefthalve(row, col) ? 2 : 1); | |
165 area.height = gui.char_height; | |
166 | |
167 gtk_im_context_set_cursor_location(xic, &area); | |
168 | |
169 if (p_imst == IM_OVER_THE_SPOT) | |
170 im_preedit_window_set_position(); | |
171 } | |
172 } | |
173 | |
174 # if 0 || defined(PROTO) // apparently only used in gui_x11.c | |
175 void | |
176 xim_set_preedit(void) | |
177 { | |
178 im_set_position(gui.row, gui.col); | |
179 } | |
180 # endif | |
181 | |
182 static void | |
183 im_add_to_input(char_u *str, int len) | |
184 { | |
185 // Convert from 'termencoding' (always "utf-8") to 'encoding' | |
186 if (input_conv.vc_type != CONV_NONE) | |
187 { | |
188 str = string_convert(&input_conv, str, &len); | |
189 g_return_if_fail(str != NULL); | |
190 } | |
191 | |
192 add_to_input_buf_csi(str, len); | |
193 | |
194 if (input_conv.vc_type != CONV_NONE) | |
195 vim_free(str); | |
196 | |
197 if (p_mh) // blank out the pointer if necessary | |
198 gui_mch_mousehide(TRUE); | |
199 } | |
200 | |
201 static void | |
202 im_preedit_window_set_position(void) | |
203 { | |
204 int x, y, width, height; | |
205 int screen_x, screen_y, screen_width, screen_height; | |
206 | |
207 if (preedit_window == NULL) | |
208 return; | |
209 | |
210 gui_gtk_get_screen_geom_of_win(gui.drawarea, | |
211 &screen_x, &screen_y, &screen_width, &screen_height); | |
212 gdk_window_get_origin(gtk_widget_get_window(gui.drawarea), &x, &y); | |
213 gtk_window_get_size(GTK_WINDOW(preedit_window), &width, &height); | |
214 x = x + FILL_X(gui.col); | |
215 y = y + FILL_Y(gui.row); | |
216 if (x + width > screen_x + screen_width) | |
217 x = screen_x + screen_width - width; | |
218 if (y + height > screen_y + screen_height) | |
219 y = screen_y + screen_height - height; | |
220 gtk_window_move(GTK_WINDOW(preedit_window), x, y); | |
221 } | |
222 | |
223 static void | |
224 im_preedit_window_open() | |
225 { | |
226 char *preedit_string; | |
227 #if !GTK_CHECK_VERSION(3,16,0) | |
228 char buf[8]; | |
229 #endif | |
230 PangoAttrList *attr_list; | |
231 PangoLayout *layout; | |
232 #if GTK_CHECK_VERSION(3,0,0) | |
233 # if !GTK_CHECK_VERSION(3,16,0) | |
234 GdkRGBA color; | |
235 # endif | |
236 #else | |
237 GdkColor color; | |
238 #endif | |
239 gint w, h; | |
240 | |
241 if (preedit_window == NULL) | |
242 { | |
243 preedit_window = gtk_window_new(GTK_WINDOW_POPUP); | |
244 gtk_window_set_transient_for(GTK_WINDOW(preedit_window), | |
245 GTK_WINDOW(gui.mainwin)); | |
246 preedit_label = gtk_label_new(""); | |
247 gtk_widget_set_name(preedit_label, "vim-gui-preedit-area"); | |
248 gtk_container_add(GTK_CONTAINER(preedit_window), preedit_label); | |
249 } | |
250 | |
251 #if GTK_CHECK_VERSION(3,16,0) | |
252 { | |
253 GtkStyleContext * const context | |
254 = gtk_widget_get_style_context(gui.drawarea); | |
255 GtkCssProvider * const provider = gtk_css_provider_new(); | |
256 gchar *css = NULL; | |
257 const char * const fontname | |
258 = pango_font_description_get_family(gui.norm_font); | |
259 gint fontsize | |
260 = pango_font_description_get_size(gui.norm_font) / PANGO_SCALE; | |
261 gchar *fontsize_propval = NULL; | |
262 | |
263 if (!pango_font_description_get_size_is_absolute(gui.norm_font)) | |
264 { | |
265 // fontsize was given in points. Convert it into that in pixels | |
266 // to use with CSS. | |
267 GdkScreen * const screen | |
268 = gdk_window_get_screen(gtk_widget_get_window(gui.mainwin)); | |
269 const gdouble dpi = gdk_screen_get_resolution(screen); | |
270 fontsize = dpi * fontsize / 72; | |
271 } | |
272 if (fontsize > 0) | |
273 fontsize_propval = g_strdup_printf("%dpx", fontsize); | |
274 else | |
275 fontsize_propval = g_strdup_printf("inherit"); | |
276 | |
277 css = g_strdup_printf( | |
278 "widget#vim-gui-preedit-area {\n" | |
279 " font-family: %s,monospace;\n" | |
280 " font-size: %s;\n" | |
281 " color: #%.2lx%.2lx%.2lx;\n" | |
282 " background-color: #%.2lx%.2lx%.2lx;\n" | |
283 "}\n", | |
284 fontname != NULL ? fontname : "inherit", | |
285 fontsize_propval, | |
286 (gui.norm_pixel >> 16) & 0xff, | |
287 (gui.norm_pixel >> 8) & 0xff, | |
288 gui.norm_pixel & 0xff, | |
289 (gui.back_pixel >> 16) & 0xff, | |
290 (gui.back_pixel >> 8) & 0xff, | |
291 gui.back_pixel & 0xff); | |
292 | |
293 gtk_css_provider_load_from_data(provider, css, -1, NULL); | |
294 gtk_style_context_add_provider(context, | |
295 GTK_STYLE_PROVIDER(provider), G_MAXUINT); | |
296 | |
297 g_free(css); | |
298 g_free(fontsize_propval); | |
299 g_object_unref(provider); | |
300 } | |
301 #elif GTK_CHECK_VERSION(3,0,0) | |
302 gtk_widget_override_font(preedit_label, gui.norm_font); | |
303 | |
304 vim_snprintf(buf, sizeof(buf), "#%06X", gui.norm_pixel); | |
305 gdk_rgba_parse(&color, buf); | |
306 gtk_widget_override_color(preedit_label, GTK_STATE_FLAG_NORMAL, &color); | |
307 | |
308 vim_snprintf(buf, sizeof(buf), "#%06X", gui.back_pixel); | |
309 gdk_rgba_parse(&color, buf); | |
310 gtk_widget_override_background_color(preedit_label, GTK_STATE_FLAG_NORMAL, | |
311 &color); | |
312 #else | |
313 gtk_widget_modify_font(preedit_label, gui.norm_font); | |
314 | |
315 vim_snprintf(buf, sizeof(buf), "#%06X", (unsigned)gui.norm_pixel); | |
316 gdk_color_parse(buf, &color); | |
317 gtk_widget_modify_fg(preedit_label, GTK_STATE_NORMAL, &color); | |
318 | |
319 vim_snprintf(buf, sizeof(buf), "#%06X", (unsigned)gui.back_pixel); | |
320 gdk_color_parse(buf, &color); | |
321 gtk_widget_modify_bg(preedit_window, GTK_STATE_NORMAL, &color); | |
322 #endif | |
323 | |
324 gtk_im_context_get_preedit_string(xic, &preedit_string, &attr_list, NULL); | |
325 | |
326 if (preedit_string[0] != NUL) | |
327 { | |
328 gtk_label_set_text(GTK_LABEL(preedit_label), preedit_string); | |
329 gtk_label_set_attributes(GTK_LABEL(preedit_label), attr_list); | |
330 | |
331 layout = gtk_label_get_layout(GTK_LABEL(preedit_label)); | |
332 pango_layout_get_pixel_size(layout, &w, &h); | |
333 h = MAX(h, gui.char_height); | |
334 gtk_window_resize(GTK_WINDOW(preedit_window), w, h); | |
335 | |
336 gtk_widget_show_all(preedit_window); | |
337 | |
338 im_preedit_window_set_position(); | |
339 } | |
340 | |
341 g_free(preedit_string); | |
342 pango_attr_list_unref(attr_list); | |
343 } | |
344 | |
345 static void | |
346 im_preedit_window_close() | |
347 { | |
348 if (preedit_window != NULL) | |
349 gtk_widget_hide(preedit_window); | |
350 } | |
351 | |
352 static void | |
353 im_show_preedit() | |
354 { | |
355 im_preedit_window_open(); | |
356 | |
357 if (p_mh) // blank out the pointer if necessary | |
358 gui_mch_mousehide(TRUE); | |
359 } | |
360 | |
361 static void | |
362 im_delete_preedit(void) | |
363 { | |
364 char_u bskey[] = {CSI, 'k', 'b'}; | |
365 char_u delkey[] = {CSI, 'k', 'D'}; | |
366 | |
367 if (p_imst == IM_OVER_THE_SPOT) | |
368 { | |
369 im_preedit_window_close(); | |
370 return; | |
371 } | |
372 | |
373 if (State & NORMAL | |
374 #ifdef FEAT_TERMINAL | |
375 && !term_use_loop() | |
376 #endif | |
377 ) | |
378 { | |
379 im_preedit_cursor = 0; | |
380 return; | |
381 } | |
382 for (; im_preedit_cursor > 0; --im_preedit_cursor) | |
383 add_to_input_buf(bskey, (int)sizeof(bskey)); | |
384 | |
385 for (; im_preedit_trailing > 0; --im_preedit_trailing) | |
386 add_to_input_buf(delkey, (int)sizeof(delkey)); | |
387 } | |
388 | |
389 /* | |
390 * Move the cursor left by "num_move_back" characters. | |
391 * Note that ins_left() checks im_is_preediting() to avoid breaking undo for | |
392 * these K_LEFT keys. | |
393 */ | |
394 static void | |
395 im_correct_cursor(int num_move_back) | |
396 { | |
397 char_u backkey[] = {CSI, 'k', 'l'}; | |
398 | |
399 if (State & NORMAL) | |
400 return; | |
401 # ifdef FEAT_RIGHTLEFT | |
402 if ((State & CMDLINE) == 0 && curwin != NULL && curwin->w_p_rl) | |
403 backkey[2] = 'r'; | |
404 # endif | |
405 for (; num_move_back > 0; --num_move_back) | |
406 add_to_input_buf(backkey, (int)sizeof(backkey)); | |
407 } | |
408 | |
409 static int xim_expected_char = NUL; | |
410 static int xim_ignored_char = FALSE; | |
411 | |
412 /* | |
413 * Update the mode and cursor while in an IM callback. | |
414 */ | |
415 static void | |
416 im_show_info(void) | |
417 { | |
418 int old_vgetc_busy; | |
419 | |
420 old_vgetc_busy = vgetc_busy; | |
421 vgetc_busy = TRUE; | |
422 showmode(); | |
423 vgetc_busy = old_vgetc_busy; | |
424 if ((State & NORMAL) || (State & INSERT)) | |
425 setcursor(); | |
426 out_flush(); | |
427 } | |
428 | |
429 /* | |
430 * Callback invoked when the user finished preediting. | |
431 * Put the final string into the input buffer. | |
432 */ | |
433 static void | |
434 im_commit_cb(GtkIMContext *context UNUSED, | |
435 const gchar *str, | |
436 gpointer data UNUSED) | |
437 { | |
438 int slen = (int)STRLEN(str); | |
439 int add_to_input = TRUE; | |
440 int clen; | |
441 int len = slen; | |
442 int commit_with_preedit = TRUE; | |
443 char_u *im_str; | |
444 | |
445 #ifdef XIM_DEBUG | |
446 xim_log("im_commit_cb(): %s\n", str); | |
447 #endif | |
448 | |
449 if (p_imst == IM_ON_THE_SPOT) | |
450 { | |
451 // The imhangul module doesn't reset the preedit string before | |
452 // committing. Call im_delete_preedit() to work around that. | |
453 im_delete_preedit(); | |
454 | |
455 // Indicate that preediting has finished. | |
456 if (preedit_start_col == MAXCOL) | |
457 { | |
458 init_preedit_start_col(); | |
459 commit_with_preedit = FALSE; | |
460 } | |
461 | |
462 // The thing which setting "preedit_start_col" to MAXCOL means that | |
463 // "preedit_start_col" will be set forcedly when calling | |
464 // preedit_changed_cb() next time. | |
465 // "preedit_start_col" should not reset with MAXCOL on this part. Vim | |
466 // is simulating the preediting by using add_to_input_str(). when | |
467 // preedit begin immediately before committed, the typebuf is not | |
468 // flushed to screen, then it can't get correct "preedit_start_col". | |
469 // Thus, it should calculate the cells by adding cells of the committed | |
470 // string. | |
471 if (input_conv.vc_type != CONV_NONE) | |
472 { | |
473 im_str = string_convert(&input_conv, (char_u *)str, &len); | |
474 g_return_if_fail(im_str != NULL); | |
475 } | |
476 else | |
477 im_str = (char_u *)str; | |
478 | |
479 clen = mb_string2cells(im_str, len); | |
480 | |
481 if (input_conv.vc_type != CONV_NONE) | |
482 vim_free(im_str); | |
483 preedit_start_col += clen; | |
484 } | |
485 | |
486 // Is this a single character that matches a keypad key that's just | |
487 // been pressed? If so, we don't want it to be entered as such - let | |
488 // us carry on processing the raw keycode so that it may be used in | |
489 // mappings as <kSomething>. | |
490 if (xim_expected_char != NUL) | |
491 { | |
492 // We're currently processing a keypad or other special key | |
493 if (slen == 1 && str[0] == xim_expected_char) | |
494 { | |
495 // It's a match - don't do it here | |
496 xim_ignored_char = TRUE; | |
497 add_to_input = FALSE; | |
498 } | |
499 else | |
500 { | |
501 // Not a match | |
502 xim_ignored_char = FALSE; | |
503 } | |
504 } | |
505 | |
506 if (add_to_input) | |
507 im_add_to_input((char_u *)str, slen); | |
508 | |
509 if (p_imst == IM_ON_THE_SPOT) | |
510 { | |
511 // Inserting chars while "im_is_active" is set does not cause a | |
512 // change of buffer. When the chars are committed the buffer must be | |
513 // marked as changed. | |
514 if (!commit_with_preedit) | |
515 preedit_start_col = MAXCOL; | |
516 | |
517 // This flag is used in changed() at next call. | |
518 xim_changed_while_preediting = TRUE; | |
519 } | |
520 | |
521 if (gtk_main_level() > 0) | |
522 gtk_main_quit(); | |
523 } | |
524 | |
525 /* | |
526 * Callback invoked after start to the preedit. | |
527 */ | |
528 static void | |
529 im_preedit_start_cb(GtkIMContext *context UNUSED, gpointer data UNUSED) | |
530 { | |
531 #ifdef XIM_DEBUG | |
532 xim_log("im_preedit_start_cb()\n"); | |
533 #endif | |
534 | |
535 im_is_active = TRUE; | |
536 preedit_is_active = TRUE; | |
537 gui_update_cursor(TRUE, FALSE); | |
538 im_show_info(); | |
539 } | |
540 | |
541 /* | |
542 * Callback invoked after end to the preedit. | |
543 */ | |
544 static void | |
545 im_preedit_end_cb(GtkIMContext *context UNUSED, gpointer data UNUSED) | |
546 { | |
547 #ifdef XIM_DEBUG | |
548 xim_log("im_preedit_end_cb()\n"); | |
549 #endif | |
550 im_delete_preedit(); | |
551 | |
552 // Indicate that preediting has finished | |
553 if (p_imst == IM_ON_THE_SPOT) | |
554 preedit_start_col = MAXCOL; | |
555 xim_has_preediting = FALSE; | |
556 | |
557 #if 0 | |
558 // Removal of this line suggested by Takuhiro Nishioka. Fixes that IM was | |
559 // switched off unintentionally. We now use preedit_is_active (added by | |
560 // SungHyun Nam). | |
561 im_is_active = FALSE; | |
562 #endif | |
563 preedit_is_active = FALSE; | |
564 gui_update_cursor(TRUE, FALSE); | |
565 im_show_info(); | |
566 } | |
567 | |
568 /* | |
569 * Callback invoked after changes to the preedit string. If the preedit | |
570 * string was empty before, remember the preedit start column so we know | |
571 * where to apply feedback attributes. Delete the previous preedit string | |
572 * if there was one, save the new preedit cursor offset, and put the new | |
573 * string into the input buffer. | |
574 * | |
575 * TODO: The pragmatic "put into input buffer" approach used here has | |
576 * several fundamental problems: | |
577 * | |
578 * - The characters in the preedit string are subject to remapping. | |
579 * That's broken, only the finally committed string should be remapped. | |
580 * | |
581 * - There is a race condition involved: The retrieved value for the | |
582 * current cursor position will be wrong if any unprocessed characters | |
583 * are still queued in the input buffer. | |
584 * | |
585 * - Due to the lack of synchronization between the file buffer in memory | |
586 * and any typed characters, it's practically impossible to implement the | |
587 * "retrieve_surrounding" and "delete_surrounding" signals reliably. IM | |
588 * modules for languages such as Thai are likely to rely on this feature | |
589 * for proper operation. | |
590 * | |
591 * Conclusions: I think support for preediting needs to be moved to the | |
592 * core parts of Vim. Ideally, until it has been committed, the preediting | |
593 * string should only be displayed and not affect the buffer content at all. | |
594 * The question how to deal with the synchronization issue still remains. | |
595 * Circumventing the input buffer is probably not desirable. Anyway, I think | |
596 * implementing "retrieve_surrounding" is the only hard problem. | |
597 * | |
598 * One way to solve all of this in a clean manner would be to queue all key | |
599 * press/release events "as is" in the input buffer, and apply the IM filtering | |
600 * at the receiving end of the queue. This, however, would have a rather large | |
601 * impact on the code base. If there is an easy way to force processing of all | |
602 * remaining input from within the "retrieve_surrounding" signal handler, this | |
603 * might not be necessary. Gotta ask on vim-dev for opinions. | |
604 */ | |
605 static void | |
606 im_preedit_changed_cb(GtkIMContext *context, gpointer data UNUSED) | |
607 { | |
608 char *preedit_string = NULL; | |
609 int cursor_index = 0; | |
610 int num_move_back = 0; | |
611 char_u *str; | |
612 char_u *p; | |
613 int i; | |
614 | |
615 if (p_imst == IM_ON_THE_SPOT) | |
616 gtk_im_context_get_preedit_string(context, | |
617 &preedit_string, NULL, | |
618 &cursor_index); | |
619 else | |
620 gtk_im_context_get_preedit_string(context, | |
621 &preedit_string, NULL, | |
622 NULL); | |
623 | |
624 #ifdef XIM_DEBUG | |
625 xim_log("im_preedit_changed_cb(): %s\n", preedit_string); | |
626 #endif | |
627 | |
628 g_return_if_fail(preedit_string != NULL); // just in case | |
629 | |
630 if (p_imst == IM_OVER_THE_SPOT) | |
631 { | |
632 if (preedit_string[0] == NUL) | |
633 { | |
634 xim_has_preediting = FALSE; | |
635 im_delete_preedit(); | |
636 } | |
637 else | |
638 { | |
639 xim_has_preediting = TRUE; | |
640 im_show_preedit(); | |
641 } | |
642 } | |
643 else | |
644 { | |
645 // If preedit_start_col is MAXCOL set it to the current cursor position. | |
646 if (preedit_start_col == MAXCOL && preedit_string[0] != '\0') | |
647 { | |
648 xim_has_preediting = TRUE; | |
649 | |
650 // Urgh, this breaks if the input buffer isn't empty now | |
651 init_preedit_start_col(); | |
652 } | |
653 else if (cursor_index == 0 && preedit_string[0] == '\0') | |
654 { | |
655 xim_has_preediting = FALSE; | |
656 | |
657 // If at the start position (after typing backspace) | |
658 // preedit_start_col must be reset. | |
659 preedit_start_col = MAXCOL; | |
660 } | |
661 | |
662 im_delete_preedit(); | |
663 | |
664 /* | |
665 * Compute the end of the preediting area: "preedit_end_col". | |
666 * According to the documentation of gtk_im_context_get_preedit_string(), | |
667 * the cursor_pos output argument returns the offset in bytes. This is | |
668 * unfortunately not true -- real life shows the offset is in characters, | |
669 * and the GTK+ source code agrees with me. Will file a bug later. | |
670 */ | |
671 if (preedit_start_col != MAXCOL) | |
672 preedit_end_col = preedit_start_col; | |
673 str = (char_u *)preedit_string; | |
674 for (p = str, i = 0; *p != NUL; p += utf_byte2len(*p), ++i) | |
675 { | |
676 int is_composing; | |
677 | |
678 is_composing = ((*p & 0x80) != 0 && utf_iscomposing(utf_ptr2char(p))); | |
679 /* | |
680 * These offsets are used as counters when generating <BS> and <Del> | |
681 * to delete the preedit string. So don't count composing characters | |
682 * unless 'delcombine' is enabled. | |
683 */ | |
684 if (!is_composing || p_deco) | |
685 { | |
686 if (i < cursor_index) | |
687 ++im_preedit_cursor; | |
688 else | |
689 ++im_preedit_trailing; | |
690 } | |
691 if (!is_composing && i >= cursor_index) | |
692 { | |
693 // This is essentially the same as im_preedit_trailing, except | |
694 // composing characters are not counted even if p_deco is set. | |
695 ++num_move_back; | |
696 } | |
697 if (preedit_start_col != MAXCOL) | |
698 preedit_end_col += utf_ptr2cells(p); | |
699 } | |
700 | |
701 if (p > str) | |
702 { | |
703 im_add_to_input(str, (int)(p - str)); | |
704 im_correct_cursor(num_move_back); | |
705 } | |
706 } | |
707 | |
708 g_free(preedit_string); | |
709 | |
710 if (gtk_main_level() > 0) | |
711 gtk_main_quit(); | |
712 } | |
713 | |
714 /* | |
715 * Translate the Pango attributes at iter to Vim highlighting attributes. | |
716 * Ignore attributes not supported by Vim highlighting. This shouldn't have | |
717 * too much impact -- right now we handle even more attributes than necessary | |
718 * for the IM modules I tested with. | |
719 */ | |
720 static int | |
721 translate_pango_attributes(PangoAttrIterator *iter) | |
722 { | |
723 PangoAttribute *attr; | |
724 int char_attr = HL_NORMAL; | |
725 | |
726 attr = pango_attr_iterator_get(iter, PANGO_ATTR_UNDERLINE); | |
727 if (attr != NULL && ((PangoAttrInt *)attr)->value | |
728 != (int)PANGO_UNDERLINE_NONE) | |
729 char_attr |= HL_UNDERLINE; | |
730 | |
731 attr = pango_attr_iterator_get(iter, PANGO_ATTR_WEIGHT); | |
732 if (attr != NULL && ((PangoAttrInt *)attr)->value >= (int)PANGO_WEIGHT_BOLD) | |
733 char_attr |= HL_BOLD; | |
734 | |
735 attr = pango_attr_iterator_get(iter, PANGO_ATTR_STYLE); | |
736 if (attr != NULL && ((PangoAttrInt *)attr)->value | |
737 != (int)PANGO_STYLE_NORMAL) | |
738 char_attr |= HL_ITALIC; | |
739 | |
740 attr = pango_attr_iterator_get(iter, PANGO_ATTR_BACKGROUND); | |
741 if (attr != NULL) | |
742 { | |
743 const PangoColor *color = &((PangoAttrColor *)attr)->color; | |
744 | |
745 // Assume inverse if black background is requested | |
746 if ((color->red | color->green | color->blue) == 0) | |
747 char_attr |= HL_INVERSE; | |
748 } | |
749 | |
750 return char_attr; | |
751 } | |
752 | |
753 /* | |
754 * Retrieve the highlighting attributes at column col in the preedit string. | |
755 * Return -1 if not in preediting mode or if col is out of range. | |
756 */ | |
757 int | |
758 im_get_feedback_attr(int col) | |
759 { | |
760 char *preedit_string = NULL; | |
761 PangoAttrList *attr_list = NULL; | |
762 int char_attr = -1; | |
763 | |
764 if (xic == NULL) | |
765 return char_attr; | |
766 | |
767 gtk_im_context_get_preedit_string(xic, &preedit_string, &attr_list, NULL); | |
768 | |
769 if (preedit_string != NULL && attr_list != NULL) | |
770 { | |
771 int idx; | |
772 | |
773 // Get the byte index as used by PangoAttrIterator | |
774 for (idx = 0; col > 0 && preedit_string[idx] != '\0'; --col) | |
775 idx += utfc_ptr2len((char_u *)preedit_string + idx); | |
776 | |
777 if (preedit_string[idx] != '\0') | |
778 { | |
779 PangoAttrIterator *iter; | |
780 int start, end; | |
781 | |
782 char_attr = HL_NORMAL; | |
783 iter = pango_attr_list_get_iterator(attr_list); | |
784 | |
785 // Extract all relevant attributes from the list. | |
786 do | |
787 { | |
788 pango_attr_iterator_range(iter, &start, &end); | |
789 | |
790 if (idx >= start && idx < end) | |
791 char_attr |= translate_pango_attributes(iter); | |
792 } | |
793 while (pango_attr_iterator_next(iter)); | |
794 | |
795 pango_attr_iterator_destroy(iter); | |
796 } | |
797 } | |
798 | |
799 if (attr_list != NULL) | |
800 pango_attr_list_unref(attr_list); | |
801 g_free(preedit_string); | |
802 | |
803 return char_attr; | |
804 } | |
805 | |
806 void | |
807 xim_init(void) | |
808 { | |
809 #ifdef XIM_DEBUG | |
810 xim_log("xim_init()\n"); | |
811 #endif | |
812 | |
813 g_return_if_fail(gui.drawarea != NULL); | |
814 g_return_if_fail(gtk_widget_get_window(gui.drawarea) != NULL); | |
815 | |
816 xic = gtk_im_multicontext_new(); | |
817 g_object_ref(xic); | |
818 | |
819 im_commit_handler_id = g_signal_connect(G_OBJECT(xic), "commit", | |
820 G_CALLBACK(&im_commit_cb), NULL); | |
821 g_signal_connect(G_OBJECT(xic), "preedit_changed", | |
822 G_CALLBACK(&im_preedit_changed_cb), NULL); | |
823 g_signal_connect(G_OBJECT(xic), "preedit_start", | |
824 G_CALLBACK(&im_preedit_start_cb), NULL); | |
825 g_signal_connect(G_OBJECT(xic), "preedit_end", | |
826 G_CALLBACK(&im_preedit_end_cb), NULL); | |
827 | |
828 gtk_im_context_set_client_window(xic, gtk_widget_get_window(gui.drawarea)); | |
829 } | |
830 | |
831 void | |
832 im_shutdown(void) | |
833 { | |
834 #ifdef XIM_DEBUG | |
835 xim_log("im_shutdown()\n"); | |
836 #endif | |
837 | |
838 if (xic != NULL) | |
839 { | |
840 gtk_im_context_focus_out(xic); | |
841 g_object_unref(xic); | |
842 xic = NULL; | |
843 } | |
844 im_is_active = FALSE; | |
845 im_commit_handler_id = 0; | |
846 if (p_imst == IM_ON_THE_SPOT) | |
847 preedit_start_col = MAXCOL; | |
848 xim_has_preediting = FALSE; | |
849 } | |
850 | |
851 /* | |
852 * Convert the string argument to keyval and state for GdkEventKey. | |
853 * If str is valid return TRUE, otherwise FALSE. | |
854 * | |
855 * See 'imactivatekey' for documentation of the format. | |
856 */ | |
857 static int | |
858 im_string_to_keyval(const char *str, unsigned int *keyval, unsigned int *state) | |
859 { | |
860 const char *mods_end; | |
861 unsigned tmp_keyval; | |
862 unsigned tmp_state = 0; | |
863 | |
864 mods_end = strrchr(str, '-'); | |
865 mods_end = (mods_end != NULL) ? mods_end + 1 : str; | |
866 | |
867 // Parse modifier keys | |
868 while (str < mods_end) | |
869 switch (*str++) | |
870 { | |
871 case '-': break; | |
872 case 'S': case 's': tmp_state |= (unsigned)GDK_SHIFT_MASK; break; | |
873 case 'L': case 'l': tmp_state |= (unsigned)GDK_LOCK_MASK; break; | |
874 case 'C': case 'c': tmp_state |= (unsigned)GDK_CONTROL_MASK;break; | |
875 case '1': tmp_state |= (unsigned)GDK_MOD1_MASK; break; | |
876 case '2': tmp_state |= (unsigned)GDK_MOD2_MASK; break; | |
877 case '3': tmp_state |= (unsigned)GDK_MOD3_MASK; break; | |
878 case '4': tmp_state |= (unsigned)GDK_MOD4_MASK; break; | |
879 case '5': tmp_state |= (unsigned)GDK_MOD5_MASK; break; | |
880 default: | |
881 return FALSE; | |
882 } | |
883 | |
884 tmp_keyval = gdk_keyval_from_name(str); | |
885 | |
886 if (tmp_keyval == 0 || tmp_keyval == GDK_VoidSymbol) | |
887 return FALSE; | |
888 | |
889 if (keyval != NULL) | |
890 *keyval = tmp_keyval; | |
891 if (state != NULL) | |
892 *state = tmp_state; | |
893 | |
894 return TRUE; | |
895 } | |
896 | |
897 /* | |
898 * Return TRUE if p_imak is valid, otherwise FALSE. As a special case, an | |
899 * empty string is also regarded as valid. | |
900 * | |
901 * Note: The numerical key value of p_imak is cached if it was valid; thus | |
902 * boldly assuming im_xim_isvalid_imactivate() will always be called whenever | |
903 * 'imak' changes. This is currently the case but not obvious -- should | |
904 * probably rename the function for clarity. | |
905 */ | |
906 int | |
907 im_xim_isvalid_imactivate(void) | |
908 { | |
909 if (p_imak[0] == NUL) | |
910 { | |
911 im_activatekey_keyval = GDK_VoidSymbol; | |
912 im_activatekey_state = 0; | |
913 return TRUE; | |
914 } | |
915 | |
916 return im_string_to_keyval((const char *)p_imak, | |
917 &im_activatekey_keyval, | |
918 &im_activatekey_state); | |
919 } | |
920 | |
921 static void | |
922 im_synthesize_keypress(unsigned int keyval, unsigned int state) | |
923 { | |
924 GdkEventKey *event; | |
925 | |
926 event = (GdkEventKey *)gdk_event_new(GDK_KEY_PRESS); | |
927 g_object_ref(gtk_widget_get_window(gui.drawarea)); | |
928 // unreffed by gdk_event_free() | |
929 event->window = gtk_widget_get_window(gui.drawarea); | |
930 event->send_event = TRUE; | |
931 event->time = GDK_CURRENT_TIME; | |
932 event->state = state; | |
933 event->keyval = keyval; | |
934 event->hardware_keycode = // needed for XIM | |
935 XKeysymToKeycode(GDK_WINDOW_XDISPLAY(event->window), (KeySym)keyval); | |
936 event->length = 0; | |
937 event->string = NULL; | |
938 | |
939 gtk_im_context_filter_keypress(xic, event); | |
940 | |
941 // For consistency, also send the corresponding release event. | |
942 event->type = GDK_KEY_RELEASE; | |
943 event->send_event = FALSE; | |
944 gtk_im_context_filter_keypress(xic, event); | |
945 | |
946 gdk_event_free((GdkEvent *)event); | |
947 } | |
948 | |
949 void | |
950 xim_reset(void) | |
951 { | |
952 # ifdef FEAT_EVAL | |
953 if (USE_IMACTIVATEFUNC) | |
954 call_imactivatefunc(im_is_active); | |
955 else | |
956 # endif | |
957 if (xic != NULL) | |
958 { | |
959 gtk_im_context_reset(xic); | |
960 | |
961 if (p_imdisable) | |
962 im_shutdown(); | |
963 else | |
964 { | |
965 xim_set_focus(gui.in_focus); | |
966 | |
967 if (im_activatekey_keyval != GDK_VoidSymbol) | |
968 { | |
969 if (im_is_active) | |
970 { | |
971 g_signal_handler_block(xic, im_commit_handler_id); | |
972 im_synthesize_keypress(im_activatekey_keyval, | |
973 im_activatekey_state); | |
974 g_signal_handler_unblock(xic, im_commit_handler_id); | |
975 } | |
976 } | |
977 else | |
978 { | |
979 im_shutdown(); | |
980 xim_init(); | |
981 xim_set_focus(gui.in_focus); | |
982 } | |
983 } | |
984 } | |
985 | |
986 if (p_imst == IM_ON_THE_SPOT) | |
987 preedit_start_col = MAXCOL; | |
988 xim_has_preediting = FALSE; | |
989 } | |
990 | |
991 int | |
992 xim_queue_key_press_event(GdkEventKey *event, int down) | |
993 { | |
994 if (down) | |
995 { | |
996 /* | |
997 * Workaround GTK2 XIM 'feature' that always converts keypad keys to | |
998 * chars., even when not part of an IM sequence (ref. feature of | |
999 * gdk/gdkkeyuni.c). | |
1000 * Flag any keypad keys that might represent a single char. | |
1001 * If this (on its own - i.e., not part of an IM sequence) is | |
1002 * committed while we're processing one of these keys, we can ignore | |
1003 * that commit and go ahead & process it ourselves. That way we can | |
1004 * still distinguish keypad keys for use in mappings. | |
1005 * Also add GDK_space to make <S-Space> work. | |
1006 */ | |
1007 switch (event->keyval) | |
1008 { | |
1009 case GDK_KP_Add: xim_expected_char = '+'; break; | |
1010 case GDK_KP_Subtract: xim_expected_char = '-'; break; | |
1011 case GDK_KP_Divide: xim_expected_char = '/'; break; | |
1012 case GDK_KP_Multiply: xim_expected_char = '*'; break; | |
1013 case GDK_KP_Decimal: xim_expected_char = '.'; break; | |
1014 case GDK_KP_Equal: xim_expected_char = '='; break; | |
1015 case GDK_KP_0: xim_expected_char = '0'; break; | |
1016 case GDK_KP_1: xim_expected_char = '1'; break; | |
1017 case GDK_KP_2: xim_expected_char = '2'; break; | |
1018 case GDK_KP_3: xim_expected_char = '3'; break; | |
1019 case GDK_KP_4: xim_expected_char = '4'; break; | |
1020 case GDK_KP_5: xim_expected_char = '5'; break; | |
1021 case GDK_KP_6: xim_expected_char = '6'; break; | |
1022 case GDK_KP_7: xim_expected_char = '7'; break; | |
1023 case GDK_KP_8: xim_expected_char = '8'; break; | |
1024 case GDK_KP_9: xim_expected_char = '9'; break; | |
1025 case GDK_space: xim_expected_char = ' '; break; | |
1026 default: xim_expected_char = NUL; | |
1027 } | |
1028 xim_ignored_char = FALSE; | |
1029 } | |
1030 | |
1031 /* | |
1032 * When typing fFtT, XIM may be activated. Thus it must pass | |
1033 * gtk_im_context_filter_keypress() in Normal mode. | |
1034 * And while doing :sh too. | |
1035 */ | |
1036 if (xic != NULL && !p_imdisable | |
1037 && (State & (INSERT | CMDLINE | NORMAL | EXTERNCMD)) != 0) | |
1038 { | |
1039 /* | |
1040 * Filter 'imactivatekey' and map it to CTRL-^. This way, Vim is | |
1041 * always aware of the current status of IM, and can even emulate | |
1042 * the activation key for modules that don't support one. | |
1043 */ | |
1044 if (event->keyval == im_activatekey_keyval | |
1045 && (event->state & im_activatekey_state) == im_activatekey_state) | |
1046 { | |
1047 unsigned int state_mask; | |
1048 | |
1049 // Require the state of the 3 most used modifiers to match exactly. | |
1050 // Otherwise e.g. <S-C-space> would be unusable for other purposes | |
1051 // if the IM activate key is <S-space>. | |
1052 state_mask = im_activatekey_state; | |
1053 state_mask |= ((int)GDK_SHIFT_MASK | (int)GDK_CONTROL_MASK | |
1054 | (int)GDK_MOD1_MASK); | |
1055 | |
1056 if ((event->state & state_mask) != im_activatekey_state) | |
1057 return FALSE; | |
1058 | |
1059 // Don't send it a second time on GDK_KEY_RELEASE. | |
1060 if (event->type != GDK_KEY_PRESS) | |
1061 return TRUE; | |
1062 | |
1063 if (map_to_exists_mode((char_u *)"", LANGMAP, FALSE)) | |
1064 { | |
1065 im_set_active(FALSE); | |
1066 | |
1067 // ":lmap" mappings exists, toggle use of mappings. | |
1068 State ^= LANGMAP; | |
1069 if (State & LANGMAP) | |
1070 { | |
1071 curbuf->b_p_iminsert = B_IMODE_NONE; | |
1072 State &= ~LANGMAP; | |
1073 } | |
1074 else | |
1075 { | |
1076 curbuf->b_p_iminsert = B_IMODE_LMAP; | |
1077 State |= LANGMAP; | |
1078 } | |
1079 return TRUE; | |
1080 } | |
1081 | |
1082 return gtk_im_context_filter_keypress(xic, event); | |
1083 } | |
1084 | |
1085 // Don't filter events through the IM context if IM isn't active | |
1086 // right now. Unlike with GTK+ 1.2 we cannot rely on the IM module | |
1087 // not doing anything before the activation key was sent. | |
1088 if (im_activatekey_keyval == GDK_VoidSymbol || im_is_active) | |
1089 { | |
1090 int imresult = gtk_im_context_filter_keypress(xic, event); | |
1091 | |
1092 if (p_imst == IM_ON_THE_SPOT) | |
1093 { | |
1094 // Some XIM send following sequence: | |
1095 // 1. preedited string. | |
1096 // 2. committed string. | |
1097 // 3. line changed key. | |
1098 // 4. preedited string. | |
1099 // 5. remove preedited string. | |
1100 // if 3, Vim can't move back the above line for 5. | |
1101 // thus, this part should not parse the key. | |
1102 if (!imresult && preedit_start_col != MAXCOL | |
1103 && event->keyval == GDK_Return) | |
1104 { | |
1105 im_synthesize_keypress(GDK_Return, 0U); | |
1106 return FALSE; | |
1107 } | |
1108 } | |
1109 | |
1110 // If XIM tried to commit a keypad key as a single char., | |
1111 // ignore it so we can use the keypad key 'raw', for mappings. | |
1112 if (xim_expected_char != NUL && xim_ignored_char) | |
1113 // We had a keypad key, and XIM tried to thieve it | |
1114 return FALSE; | |
1115 | |
1116 // This is supposed to fix a problem with iBus, that space | |
1117 // characters don't work in input mode. | |
1118 xim_expected_char = NUL; | |
1119 | |
1120 // Normal processing | |
1121 return imresult; | |
1122 } | |
1123 } | |
1124 | |
1125 return FALSE; | |
1126 } | |
1127 | |
1128 int | |
1129 im_get_status(void) | |
1130 { | |
1131 # ifdef FEAT_EVAL | |
1132 if (USE_IMSTATUSFUNC) | |
1133 return call_imstatusfunc(); | |
1134 # endif | |
1135 return im_is_active; | |
1136 } | |
1137 | |
1138 int | |
1139 preedit_get_status(void) | |
1140 { | |
1141 return preedit_is_active; | |
1142 } | |
1143 | |
1144 int | |
1145 im_is_preediting(void) | |
1146 { | |
1147 return xim_has_preediting; | |
1148 } | |
1149 | |
1150 # else // !FEAT_GUI_GTK | |
1151 | |
1152 static int xim_is_active = FALSE; // XIM should be active in the current | |
1153 // mode | |
1154 static int xim_has_focus = FALSE; // XIM is really being used for Vim | |
1155 # ifdef FEAT_GUI_X11 | |
1156 static XIMStyle input_style; | |
1157 static int status_area_enabled = TRUE; | |
1158 # endif | |
1159 | |
1160 /* | |
1161 * Switch using XIM on/off. This is used by the code that changes "State". | |
1162 * When 'imactivatefunc' is defined use that function instead. | |
1163 */ | |
1164 void | |
1165 im_set_active(int active_arg) | |
1166 { | |
1167 int active = active_arg; | |
1168 | |
1169 // If 'imdisable' is set, XIM is never active. | |
1170 if (p_imdisable) | |
1171 active = FALSE; | |
1172 else if (input_style & XIMPreeditPosition) | |
1173 // There is a problem in switching XIM off when preediting is used, | |
1174 // and it is not clear how this can be solved. For now, keep XIM on | |
1175 // all the time, like it was done in Vim 5.8. | |
1176 active = TRUE; | |
1177 | |
1178 # if defined(FEAT_EVAL) | |
1179 if (USE_IMACTIVATEFUNC) | |
1180 { | |
1181 if (active != im_get_status()) | |
1182 { | |
1183 call_imactivatefunc(active); | |
1184 xim_has_focus = active; | |
1185 } | |
1186 return; | |
1187 } | |
1188 # endif | |
1189 | |
1190 if (xic == NULL) | |
1191 return; | |
1192 | |
1193 // Remember the active state, it is needed when Vim gets keyboard focus. | |
1194 xim_is_active = active; | |
1195 xim_set_preedit(); | |
1196 } | |
1197 | |
1198 /* | |
1199 * Adjust using XIM for gaining or losing keyboard focus. Also called when | |
1200 * "xim_is_active" changes. | |
1201 */ | |
1202 void | |
1203 xim_set_focus(int focus) | |
1204 { | |
1205 if (xic == NULL) | |
1206 return; | |
1207 | |
1208 /* | |
1209 * XIM only gets focus when the Vim window has keyboard focus and XIM has | |
1210 * been set active for the current mode. | |
1211 */ | |
1212 if (focus && xim_is_active) | |
1213 { | |
1214 if (!xim_has_focus) | |
1215 { | |
1216 xim_has_focus = TRUE; | |
1217 XSetICFocus(xic); | |
1218 } | |
1219 } | |
1220 else | |
1221 { | |
1222 if (xim_has_focus) | |
1223 { | |
1224 xim_has_focus = FALSE; | |
1225 XUnsetICFocus(xic); | |
1226 } | |
1227 } | |
1228 } | |
1229 | |
1230 void | |
1231 im_set_position(int row UNUSED, int col UNUSED) | |
1232 { | |
1233 xim_set_preedit(); | |
1234 } | |
1235 | |
1236 /* | |
1237 * Set the XIM to the current cursor position. | |
1238 */ | |
1239 void | |
1240 xim_set_preedit(void) | |
1241 { | |
1242 XVaNestedList attr_list; | |
1243 XRectangle spot_area; | |
1244 XPoint over_spot; | |
1245 int line_space; | |
1246 | |
1247 if (xic == NULL) | |
1248 return; | |
1249 | |
1250 xim_set_focus(TRUE); | |
1251 | |
1252 if (!xim_has_focus) | |
1253 { | |
1254 // hide XIM cursor | |
1255 over_spot.x = 0; | |
1256 over_spot.y = -100; // arbitrary invisible position | |
1257 attr_list = (XVaNestedList) XVaCreateNestedList(0, | |
1258 XNSpotLocation, | |
1259 &over_spot, | |
1260 NULL); | |
1261 XSetICValues(xic, XNPreeditAttributes, attr_list, NULL); | |
1262 XFree(attr_list); | |
1263 return; | |
1264 } | |
1265 | |
1266 if (input_style & XIMPreeditPosition) | |
1267 { | |
1268 if (xim_fg_color == INVALCOLOR) | |
1269 { | |
1270 xim_fg_color = gui.def_norm_pixel; | |
1271 xim_bg_color = gui.def_back_pixel; | |
1272 } | |
1273 over_spot.x = TEXT_X(gui.col); | |
1274 over_spot.y = TEXT_Y(gui.row); | |
1275 spot_area.x = 0; | |
1276 spot_area.y = 0; | |
1277 spot_area.height = gui.char_height * Rows; | |
1278 spot_area.width = gui.char_width * Columns; | |
1279 line_space = gui.char_height; | |
1280 attr_list = (XVaNestedList) XVaCreateNestedList(0, | |
1281 XNSpotLocation, &over_spot, | |
1282 XNForeground, (Pixel) xim_fg_color, | |
1283 XNBackground, (Pixel) xim_bg_color, | |
1284 XNArea, &spot_area, | |
1285 XNLineSpace, line_space, | |
1286 NULL); | |
1287 if (XSetICValues(xic, XNPreeditAttributes, attr_list, NULL)) | |
1288 emsg(_("E284: Cannot set IC values")); | |
1289 XFree(attr_list); | |
1290 } | |
1291 } | |
1292 | |
1293 # if defined(FEAT_GUI_X11) | |
1294 static char e_xim[] = N_("E285: Failed to create input context"); | |
1295 # endif | |
1296 | |
1297 # if defined(FEAT_GUI_X11) || defined(PROTO) | |
1298 # if defined(XtSpecificationRelease) && XtSpecificationRelease >= 6 && !defined(SUN_SYSTEM) | |
1299 # define USE_X11R6_XIM | |
1300 # endif | |
1301 | |
1302 static int xim_real_init(Window x11_window, Display *x11_display); | |
1303 | |
1304 | |
1305 # ifdef USE_X11R6_XIM | |
1306 static void | |
1307 xim_instantiate_cb( | |
1308 Display *display, | |
1309 XPointer client_data UNUSED, | |
1310 XPointer call_data UNUSED) | |
1311 { | |
1312 Window x11_window; | |
1313 Display *x11_display; | |
1314 | |
1315 # ifdef XIM_DEBUG | |
1316 xim_log("xim_instantiate_cb()\n"); | |
1317 # endif | |
1318 | |
1319 gui_get_x11_windis(&x11_window, &x11_display); | |
1320 if (display != x11_display) | |
1321 return; | |
1322 | |
1323 xim_real_init(x11_window, x11_display); | |
1324 gui_set_shellsize(FALSE, FALSE, RESIZE_BOTH); | |
1325 if (xic != NULL) | |
1326 XUnregisterIMInstantiateCallback(x11_display, NULL, NULL, NULL, | |
1327 xim_instantiate_cb, NULL); | |
1328 } | |
1329 | |
1330 static void | |
1331 xim_destroy_cb( | |
1332 XIM im UNUSED, | |
1333 XPointer client_data UNUSED, | |
1334 XPointer call_data UNUSED) | |
1335 { | |
1336 Window x11_window; | |
1337 Display *x11_display; | |
1338 | |
1339 # ifdef XIM_DEBUG | |
1340 xim_log("xim_destroy_cb()\n"); | |
1341 #endif | |
1342 gui_get_x11_windis(&x11_window, &x11_display); | |
1343 | |
1344 xic = NULL; | |
1345 status_area_enabled = FALSE; | |
1346 | |
1347 gui_set_shellsize(FALSE, FALSE, RESIZE_BOTH); | |
1348 | |
1349 XRegisterIMInstantiateCallback(x11_display, NULL, NULL, NULL, | |
1350 xim_instantiate_cb, NULL); | |
1351 } | |
1352 # endif | |
1353 | |
1354 void | |
1355 xim_init(void) | |
1356 { | |
1357 Window x11_window; | |
1358 Display *x11_display; | |
1359 | |
1360 # ifdef XIM_DEBUG | |
1361 xim_log("xim_init()\n"); | |
1362 # endif | |
1363 | |
1364 gui_get_x11_windis(&x11_window, &x11_display); | |
1365 | |
1366 xic = NULL; | |
1367 | |
1368 if (xim_real_init(x11_window, x11_display)) | |
1369 return; | |
1370 | |
1371 gui_set_shellsize(FALSE, FALSE, RESIZE_BOTH); | |
1372 | |
1373 # ifdef USE_X11R6_XIM | |
1374 XRegisterIMInstantiateCallback(x11_display, NULL, NULL, NULL, | |
1375 xim_instantiate_cb, NULL); | |
1376 # endif | |
1377 } | |
1378 | |
1379 static int | |
1380 xim_real_init(Window x11_window, Display *x11_display) | |
1381 { | |
1382 int i; | |
1383 char *p, | |
1384 *s, | |
1385 *ns, | |
1386 *end, | |
1387 tmp[1024]; | |
1388 # define IMLEN_MAX 40 | |
1389 char buf[IMLEN_MAX + 7]; | |
1390 XIM xim = NULL; | |
1391 XIMStyles *xim_styles; | |
1392 XIMStyle this_input_style = 0; | |
1393 Boolean found; | |
1394 XPoint over_spot; | |
1395 XVaNestedList preedit_list, status_list; | |
1396 | |
1397 input_style = 0; | |
1398 status_area_enabled = FALSE; | |
1399 | |
1400 if (xic != NULL) | |
1401 return FALSE; | |
1402 | |
1403 if (gui.rsrc_input_method != NULL && *gui.rsrc_input_method != NUL) | |
1404 { | |
1405 strcpy(tmp, gui.rsrc_input_method); | |
1406 for (ns = s = tmp; ns != NULL && *s != NUL;) | |
1407 { | |
1408 s = (char *)skipwhite((char_u *)s); | |
1409 if (*s == NUL) | |
1410 break; | |
1411 if ((ns = end = strchr(s, ',')) == NULL) | |
1412 end = s + strlen(s); | |
1413 while (isspace(((char_u *)end)[-1])) | |
1414 end--; | |
1415 *end = NUL; | |
1416 | |
1417 if (strlen(s) <= IMLEN_MAX) | |
1418 { | |
1419 strcpy(buf, "@im="); | |
1420 strcat(buf, s); | |
1421 if ((p = XSetLocaleModifiers(buf)) != NULL && *p != NUL | |
1422 && (xim = XOpenIM(x11_display, NULL, NULL, NULL)) | |
1423 != NULL) | |
1424 break; | |
1425 } | |
1426 | |
1427 s = ns + 1; | |
1428 } | |
1429 } | |
1430 | |
1431 if (xim == NULL && (p = XSetLocaleModifiers("")) != NULL && *p != NUL) | |
1432 xim = XOpenIM(x11_display, NULL, NULL, NULL); | |
1433 | |
1434 // This is supposed to be useful to obtain characters through | |
1435 // XmbLookupString() without really using a XIM. | |
1436 if (xim == NULL && (p = XSetLocaleModifiers("@im=none")) != NULL | |
1437 && *p != NUL) | |
1438 xim = XOpenIM(x11_display, NULL, NULL, NULL); | |
1439 | |
1440 if (xim == NULL) | |
1441 { | |
1442 // Only give this message when verbose is set, because too many people | |
1443 // got this message when they didn't want to use a XIM. | |
1444 if (p_verbose > 0) | |
1445 { | |
1446 verbose_enter(); | |
1447 emsg(_("E286: Failed to open input method")); | |
1448 verbose_leave(); | |
1449 } | |
1450 return FALSE; | |
1451 } | |
1452 | |
1453 # ifdef USE_X11R6_XIM | |
1454 { | |
1455 XIMCallback destroy_cb; | |
1456 | |
1457 destroy_cb.callback = xim_destroy_cb; | |
1458 destroy_cb.client_data = NULL; | |
1459 if (XSetIMValues(xim, XNDestroyCallback, &destroy_cb, NULL)) | |
1460 emsg(_("E287: Warning: Could not set destroy callback to IM")); | |
1461 } | |
1462 # endif | |
1463 | |
1464 if (XGetIMValues(xim, XNQueryInputStyle, &xim_styles, NULL) || !xim_styles) | |
1465 { | |
1466 emsg(_("E288: input method doesn't support any style")); | |
1467 XCloseIM(xim); | |
1468 return FALSE; | |
1469 } | |
1470 | |
1471 found = False; | |
1472 strcpy(tmp, gui.rsrc_preedit_type_name); | |
1473 for (s = tmp; s && !found; ) | |
1474 { | |
1475 while (*s && isspace((unsigned char)*s)) | |
1476 s++; | |
1477 if (!*s) | |
1478 break; | |
1479 if ((ns = end = strchr(s, ',')) != 0) | |
1480 ns++; | |
1481 else | |
1482 end = s + strlen(s); | |
1483 while (isspace((unsigned char)*end)) | |
1484 end--; | |
1485 *end = '\0'; | |
1486 | |
1487 if (!strcmp(s, "OverTheSpot")) | |
1488 this_input_style = (XIMPreeditPosition | XIMStatusArea); | |
1489 else if (!strcmp(s, "OffTheSpot")) | |
1490 this_input_style = (XIMPreeditArea | XIMStatusArea); | |
1491 else if (!strcmp(s, "Root")) | |
1492 this_input_style = (XIMPreeditNothing | XIMStatusNothing); | |
1493 | |
1494 for (i = 0; (unsigned short)i < xim_styles->count_styles; i++) | |
1495 { | |
1496 if (this_input_style == xim_styles->supported_styles[i]) | |
1497 { | |
1498 found = True; | |
1499 break; | |
1500 } | |
1501 } | |
1502 if (!found) | |
1503 for (i = 0; (unsigned short)i < xim_styles->count_styles; i++) | |
1504 { | |
1505 if ((xim_styles->supported_styles[i] & this_input_style) | |
1506 == (this_input_style & ~XIMStatusArea)) | |
1507 { | |
1508 this_input_style &= ~XIMStatusArea; | |
1509 found = True; | |
1510 break; | |
1511 } | |
1512 } | |
1513 | |
1514 s = ns; | |
1515 } | |
1516 XFree(xim_styles); | |
1517 | |
1518 if (!found) | |
1519 { | |
1520 // Only give this message when verbose is set, because too many people | |
1521 // got this message when they didn't want to use a XIM. | |
1522 if (p_verbose > 0) | |
1523 { | |
1524 verbose_enter(); | |
1525 emsg(_("E289: input method doesn't support my preedit type")); | |
1526 verbose_leave(); | |
1527 } | |
1528 XCloseIM(xim); | |
1529 return FALSE; | |
1530 } | |
1531 | |
1532 over_spot.x = TEXT_X(gui.col); | |
1533 over_spot.y = TEXT_Y(gui.row); | |
1534 input_style = this_input_style; | |
1535 | |
1536 // A crash was reported when trying to pass gui.norm_font as XNFontSet, | |
1537 // thus that has been removed. Hopefully the default works... | |
1538 # ifdef FEAT_XFONTSET | |
1539 if (gui.fontset != NOFONTSET) | |
1540 { | |
1541 preedit_list = XVaCreateNestedList(0, | |
1542 XNSpotLocation, &over_spot, | |
1543 XNForeground, (Pixel)gui.def_norm_pixel, | |
1544 XNBackground, (Pixel)gui.def_back_pixel, | |
1545 XNFontSet, (XFontSet)gui.fontset, | |
1546 NULL); | |
1547 status_list = XVaCreateNestedList(0, | |
1548 XNForeground, (Pixel)gui.def_norm_pixel, | |
1549 XNBackground, (Pixel)gui.def_back_pixel, | |
1550 XNFontSet, (XFontSet)gui.fontset, | |
1551 NULL); | |
1552 } | |
1553 else | |
1554 # endif | |
1555 { | |
1556 preedit_list = XVaCreateNestedList(0, | |
1557 XNSpotLocation, &over_spot, | |
1558 XNForeground, (Pixel)gui.def_norm_pixel, | |
1559 XNBackground, (Pixel)gui.def_back_pixel, | |
1560 NULL); | |
1561 status_list = XVaCreateNestedList(0, | |
1562 XNForeground, (Pixel)gui.def_norm_pixel, | |
1563 XNBackground, (Pixel)gui.def_back_pixel, | |
1564 NULL); | |
1565 } | |
1566 | |
1567 xic = XCreateIC(xim, | |
1568 XNInputStyle, input_style, | |
1569 XNClientWindow, x11_window, | |
1570 XNFocusWindow, gui.wid, | |
1571 XNPreeditAttributes, preedit_list, | |
1572 XNStatusAttributes, status_list, | |
1573 NULL); | |
1574 XFree(status_list); | |
1575 XFree(preedit_list); | |
1576 if (xic != NULL) | |
1577 { | |
1578 if (input_style & XIMStatusArea) | |
1579 { | |
1580 xim_set_status_area(); | |
1581 status_area_enabled = TRUE; | |
1582 } | |
1583 else | |
1584 gui_set_shellsize(FALSE, FALSE, RESIZE_BOTH); | |
1585 } | |
1586 else | |
1587 { | |
1588 if (!is_not_a_term()) | |
1589 emsg(_(e_xim)); | |
1590 XCloseIM(xim); | |
1591 return FALSE; | |
1592 } | |
1593 | |
1594 return TRUE; | |
1595 } | |
1596 | |
1597 # endif // FEAT_GUI_X11 | |
1598 | |
1599 /* | |
1600 * Get IM status. When IM is on, return TRUE. Else return FALSE. | |
1601 * FIXME: This doesn't work correctly: Having focus doesn't always mean XIM is | |
1602 * active, when not having focus XIM may still be active (e.g., when using a | |
1603 * tear-off menu item). | |
1604 */ | |
1605 int | |
1606 im_get_status(void) | |
1607 { | |
1608 # ifdef FEAT_EVAL | |
1609 if (USE_IMSTATUSFUNC) | |
1610 return call_imstatusfunc(); | |
1611 # endif | |
1612 return xim_has_focus; | |
1613 } | |
1614 | |
1615 # endif // !FEAT_GUI_GTK | |
1616 | |
1617 # if !defined(FEAT_GUI_GTK) || defined(PROTO) | |
1618 /* | |
1619 * Set up the status area. | |
1620 * | |
1621 * This should use a separate Widget, but that seems not possible, because | |
1622 * preedit_area and status_area should be set to the same window as for the | |
1623 * text input. Unfortunately this means the status area pollutes the text | |
1624 * window... | |
1625 */ | |
1626 void | |
1627 xim_set_status_area(void) | |
1628 { | |
1629 XVaNestedList preedit_list = 0, status_list = 0, list = 0; | |
1630 XRectangle pre_area, status_area; | |
1631 | |
1632 if (xic == NULL) | |
1633 return; | |
1634 | |
1635 if (input_style & XIMStatusArea) | |
1636 { | |
1637 if (input_style & XIMPreeditArea) | |
1638 { | |
1639 XRectangle *needed_rect; | |
1640 | |
1641 // to get status_area width | |
1642 status_list = XVaCreateNestedList(0, XNAreaNeeded, | |
1643 &needed_rect, NULL); | |
1644 XGetICValues(xic, XNStatusAttributes, status_list, NULL); | |
1645 XFree(status_list); | |
1646 | |
1647 status_area.width = needed_rect->width; | |
1648 } | |
1649 else | |
1650 status_area.width = gui.char_width * Columns; | |
1651 | |
1652 status_area.x = 0; | |
1653 status_area.y = gui.char_height * Rows + gui.border_offset; | |
1654 if (gui.which_scrollbars[SBAR_BOTTOM]) | |
1655 status_area.y += gui.scrollbar_height; | |
1656 #ifdef FEAT_MENU | |
1657 if (gui.menu_is_active) | |
1658 status_area.y += gui.menu_height; | |
1659 #endif | |
1660 status_area.height = gui.char_height; | |
1661 status_list = XVaCreateNestedList(0, XNArea, &status_area, NULL); | |
1662 } | |
1663 else | |
1664 { | |
1665 status_area.x = 0; | |
1666 status_area.y = gui.char_height * Rows + gui.border_offset; | |
1667 if (gui.which_scrollbars[SBAR_BOTTOM]) | |
1668 status_area.y += gui.scrollbar_height; | |
1669 #ifdef FEAT_MENU | |
1670 if (gui.menu_is_active) | |
1671 status_area.y += gui.menu_height; | |
1672 #endif | |
1673 status_area.width = 0; | |
1674 status_area.height = gui.char_height; | |
1675 } | |
1676 | |
1677 if (input_style & XIMPreeditArea) // off-the-spot | |
1678 { | |
1679 pre_area.x = status_area.x + status_area.width; | |
1680 pre_area.y = gui.char_height * Rows + gui.border_offset; | |
1681 pre_area.width = gui.char_width * Columns - pre_area.x; | |
1682 if (gui.which_scrollbars[SBAR_BOTTOM]) | |
1683 pre_area.y += gui.scrollbar_height; | |
1684 #ifdef FEAT_MENU | |
1685 if (gui.menu_is_active) | |
1686 pre_area.y += gui.menu_height; | |
1687 #endif | |
1688 pre_area.height = gui.char_height; | |
1689 preedit_list = XVaCreateNestedList(0, XNArea, &pre_area, NULL); | |
1690 } | |
1691 else if (input_style & XIMPreeditPosition) // over-the-spot | |
1692 { | |
1693 pre_area.x = 0; | |
1694 pre_area.y = 0; | |
1695 pre_area.height = gui.char_height * Rows; | |
1696 pre_area.width = gui.char_width * Columns; | |
1697 preedit_list = XVaCreateNestedList(0, XNArea, &pre_area, NULL); | |
1698 } | |
1699 | |
1700 if (preedit_list && status_list) | |
1701 list = XVaCreateNestedList(0, XNPreeditAttributes, preedit_list, | |
1702 XNStatusAttributes, status_list, NULL); | |
1703 else if (preedit_list) | |
1704 list = XVaCreateNestedList(0, XNPreeditAttributes, preedit_list, | |
1705 NULL); | |
1706 else if (status_list) | |
1707 list = XVaCreateNestedList(0, XNStatusAttributes, status_list, | |
1708 NULL); | |
1709 else | |
1710 list = NULL; | |
1711 | |
1712 if (list) | |
1713 { | |
1714 XSetICValues(xic, XNVaNestedList, list, NULL); | |
1715 XFree(list); | |
1716 } | |
1717 if (status_list) | |
1718 XFree(status_list); | |
1719 if (preedit_list) | |
1720 XFree(preedit_list); | |
1721 } | |
1722 | |
1723 int | |
1724 xim_get_status_area_height(void) | |
1725 { | |
1726 if (status_area_enabled) | |
1727 return gui.char_height; | |
1728 return 0; | |
1729 } | |
1730 # endif | |
1731 | |
1732 #else // !defined(FEAT_XIM) | |
1733 | |
1734 # if defined(IME_WITHOUT_XIM) || defined(VIMDLL) || defined(PROTO) | |
1735 static int im_was_set_active = FALSE; | |
1736 | |
1737 int | |
1738 # ifdef VIMDLL | |
1739 mbyte_im_get_status(void) | |
1740 # else | |
1741 im_get_status(void) | |
1742 # endif | |
1743 { | |
1744 # if defined(FEAT_EVAL) | |
1745 if (USE_IMSTATUSFUNC) | |
1746 return call_imstatusfunc(); | |
1747 # endif | |
1748 return im_was_set_active; | |
1749 } | |
1750 | |
1751 void | |
1752 # ifdef VIMDLL | |
1753 mbyte_im_set_active(int active_arg) | |
1754 # else | |
1755 im_set_active(int active_arg) | |
1756 # endif | |
1757 { | |
1758 # if defined(FEAT_EVAL) | |
1759 int active = !p_imdisable && active_arg; | |
1760 | |
1761 if (USE_IMACTIVATEFUNC && active != im_get_status()) | |
1762 { | |
1763 call_imactivatefunc(active); | |
1764 im_was_set_active = active; | |
1765 } | |
1766 # endif | |
1767 } | |
1768 | |
1769 # if defined(FEAT_GUI) && !defined(FEAT_GUI_HAIKU) && !defined(VIMDLL) | |
1770 void | |
1771 im_set_position(int row UNUSED, int col UNUSED) | |
1772 { | |
1773 } | |
1774 # endif | |
1775 # endif | |
1776 | |
1777 #endif // FEAT_XIM |