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