1/* GTK - The GIMP Toolkit
2 * Copyright (C) 2000 Red Hat, Inc.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "config.h"
19
20#include <string.h>
21#include <locale.h>
22
23#include "gtkimmulticontext.h"
24#include "gtkimmoduleprivate.h"
25#include "gtkintl.h"
26#include "gtklabel.h"
27#include "gtkmain.h"
28#include "gtkprivate.h"
29#include "gtksettings.h"
30
31
32/**
33 * GtkIMMulticontext:
34 *
35 * `GtkIMMulticontext` is an input method context supporting multiple,
36 * switchable input methods.
37 *
38 * Text widgets such as `GtkText` or `GtkTextView` use a `GtkIMMultiContext`
39 * to implement their `im-module` property for switching between different
40 * input methods.
41 */
42
43
44struct _GtkIMMulticontextPrivate
45{
46 GtkIMContext *delegate;
47
48 GtkWidget *client_widget;
49 GdkRectangle cursor_location;
50
51 char *context_id;
52 char *context_id_aux;
53
54 guint use_preedit : 1;
55 guint have_cursor_location : 1;
56 guint focus_in : 1;
57};
58
59static void gtk_im_multicontext_notify (GObject *object,
60 GParamSpec *pspec);
61static void gtk_im_multicontext_finalize (GObject *object);
62
63static void gtk_im_multicontext_set_delegate (GtkIMMulticontext *multicontext,
64 GtkIMContext *delegate,
65 gboolean finalizing);
66
67static void gtk_im_multicontext_set_client_widget (GtkIMContext *context,
68 GtkWidget *widget);
69static void gtk_im_multicontext_get_preedit_string (GtkIMContext *context,
70 char **str,
71 PangoAttrList **attrs,
72 int *cursor_pos);
73static gboolean gtk_im_multicontext_filter_keypress (GtkIMContext *context,
74 GdkEvent *event);
75static void gtk_im_multicontext_focus_in (GtkIMContext *context);
76static void gtk_im_multicontext_focus_out (GtkIMContext *context);
77static void gtk_im_multicontext_reset (GtkIMContext *context);
78static void gtk_im_multicontext_set_cursor_location (GtkIMContext *context,
79 GdkRectangle *area);
80static void gtk_im_multicontext_set_use_preedit (GtkIMContext *context,
81 gboolean use_preedit);
82static gboolean gtk_im_multicontext_get_surrounding_with_selection
83 (GtkIMContext *context,
84 char **text,
85 int *cursor_index,
86 int *anchor_index);
87static void gtk_im_multicontext_set_surrounding_with_selection
88 (GtkIMContext *context,
89 const char *text,
90 int len,
91 int cursor_index,
92 int anchor_index);
93
94static void gtk_im_multicontext_preedit_start_cb (GtkIMContext *delegate,
95 GtkIMMulticontext *multicontext);
96static void gtk_im_multicontext_preedit_end_cb (GtkIMContext *delegate,
97 GtkIMMulticontext *multicontext);
98static void gtk_im_multicontext_preedit_changed_cb (GtkIMContext *delegate,
99 GtkIMMulticontext *multicontext);
100static void gtk_im_multicontext_commit_cb (GtkIMContext *delegate,
101 const char *str,
102 GtkIMMulticontext *multicontext);
103static gboolean gtk_im_multicontext_retrieve_surrounding_cb (GtkIMContext *delegate,
104 GtkIMMulticontext *multicontext);
105static gboolean gtk_im_multicontext_delete_surrounding_cb (GtkIMContext *delegate,
106 int offset,
107 int n_chars,
108 GtkIMMulticontext *multicontext);
109
110static void propagate_purpose (GtkIMMulticontext *context);
111
112G_DEFINE_TYPE_WITH_PRIVATE (GtkIMMulticontext, gtk_im_multicontext, GTK_TYPE_IM_CONTEXT)
113
114static void
115gtk_im_multicontext_class_init (GtkIMMulticontextClass *class)
116{
117 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
118 GtkIMContextClass *im_context_class = GTK_IM_CONTEXT_CLASS (class);
119
120 gobject_class->notify = gtk_im_multicontext_notify;
121
122 im_context_class->set_client_widget = gtk_im_multicontext_set_client_widget;
123 im_context_class->get_preedit_string = gtk_im_multicontext_get_preedit_string;
124 im_context_class->filter_keypress = gtk_im_multicontext_filter_keypress;
125 im_context_class->focus_in = gtk_im_multicontext_focus_in;
126 im_context_class->focus_out = gtk_im_multicontext_focus_out;
127 im_context_class->reset = gtk_im_multicontext_reset;
128 im_context_class->set_cursor_location = gtk_im_multicontext_set_cursor_location;
129 im_context_class->set_use_preedit = gtk_im_multicontext_set_use_preedit;
130 im_context_class->set_surrounding_with_selection = gtk_im_multicontext_set_surrounding_with_selection;
131 im_context_class->get_surrounding_with_selection = gtk_im_multicontext_get_surrounding_with_selection;
132
133 gobject_class->finalize = gtk_im_multicontext_finalize;
134}
135
136static void
137gtk_im_multicontext_init (GtkIMMulticontext *multicontext)
138{
139 GtkIMMulticontextPrivate *priv;
140
141 multicontext->priv = gtk_im_multicontext_get_instance_private (self: multicontext);
142 priv = multicontext->priv;
143
144 priv->delegate = NULL;
145 priv->use_preedit = TRUE;
146 priv->have_cursor_location = FALSE;
147 priv->focus_in = FALSE;
148}
149
150/**
151 * gtk_im_multicontext_new:
152 *
153 * Creates a new `GtkIMMulticontext`.
154 *
155 * Returns: a new `GtkIMMulticontext`.
156 */
157GtkIMContext *
158gtk_im_multicontext_new (void)
159{
160 return g_object_new (GTK_TYPE_IM_MULTICONTEXT, NULL);
161}
162
163static void
164gtk_im_multicontext_finalize (GObject *object)
165{
166 GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT (object);
167 GtkIMMulticontextPrivate *priv = multicontext->priv;
168
169 gtk_im_multicontext_set_delegate (multicontext, NULL, TRUE);
170 g_free (mem: priv->context_id);
171 g_free (mem: priv->context_id_aux);
172
173 G_OBJECT_CLASS (gtk_im_multicontext_parent_class)->finalize (object);
174}
175
176static void
177gtk_im_multicontext_set_delegate (GtkIMMulticontext *multicontext,
178 GtkIMContext *delegate,
179 gboolean finalizing)
180{
181 GtkIMMulticontextPrivate *priv = multicontext->priv;
182 gboolean need_preedit_changed = FALSE;
183
184 if (priv->delegate)
185 {
186 if (!finalizing)
187 gtk_im_context_reset (context: priv->delegate);
188
189 g_signal_handlers_disconnect_by_func (priv->delegate,
190 gtk_im_multicontext_preedit_start_cb,
191 multicontext);
192 g_signal_handlers_disconnect_by_func (priv->delegate,
193 gtk_im_multicontext_preedit_end_cb,
194 multicontext);
195 g_signal_handlers_disconnect_by_func (priv->delegate,
196 gtk_im_multicontext_preedit_changed_cb,
197 multicontext);
198 g_signal_handlers_disconnect_by_func (priv->delegate,
199 gtk_im_multicontext_commit_cb,
200 multicontext);
201 g_signal_handlers_disconnect_by_func (priv->delegate,
202 gtk_im_multicontext_retrieve_surrounding_cb,
203 multicontext);
204 g_signal_handlers_disconnect_by_func (priv->delegate,
205 gtk_im_multicontext_delete_surrounding_cb,
206 multicontext);
207
208 if (priv->client_widget)
209 gtk_im_context_set_client_widget (context: priv->delegate, NULL);
210
211 g_object_unref (object: priv->delegate);
212 priv->delegate = NULL;
213
214 if (!finalizing)
215 need_preedit_changed = TRUE;
216 }
217
218 priv->delegate = delegate;
219
220 if (priv->delegate)
221 {
222 g_object_ref (priv->delegate);
223
224 propagate_purpose (context: multicontext);
225
226 g_signal_connect (priv->delegate, "preedit-start",
227 G_CALLBACK (gtk_im_multicontext_preedit_start_cb),
228 multicontext);
229 g_signal_connect (priv->delegate, "preedit-end",
230 G_CALLBACK (gtk_im_multicontext_preedit_end_cb),
231 multicontext);
232 g_signal_connect (priv->delegate, "preedit-changed",
233 G_CALLBACK (gtk_im_multicontext_preedit_changed_cb),
234 multicontext);
235 g_signal_connect (priv->delegate, "commit",
236 G_CALLBACK (gtk_im_multicontext_commit_cb),
237 multicontext);
238 g_signal_connect (priv->delegate, "retrieve-surrounding",
239 G_CALLBACK (gtk_im_multicontext_retrieve_surrounding_cb),
240 multicontext);
241 g_signal_connect (priv->delegate, "delete-surrounding",
242 G_CALLBACK (gtk_im_multicontext_delete_surrounding_cb),
243 multicontext);
244
245 if (!priv->use_preedit) /* Default is TRUE */
246 gtk_im_context_set_use_preedit (context: delegate, FALSE);
247 if (priv->client_widget)
248 gtk_im_context_set_client_widget (context: delegate, widget: priv->client_widget);
249 if (priv->have_cursor_location)
250 gtk_im_context_set_cursor_location (context: delegate, area: &priv->cursor_location);
251 if (priv->focus_in)
252 gtk_im_context_focus_in (context: delegate);
253 }
254
255 if (need_preedit_changed)
256 g_signal_emit_by_name (instance: multicontext, detailed_signal: "preedit-changed");
257}
258
259static const char *
260get_effective_context_id (GtkIMMulticontext *multicontext)
261{
262 GtkIMMulticontextPrivate *priv = multicontext->priv;
263 GdkDisplay *display;
264
265 if (priv->context_id_aux)
266 return priv->context_id_aux;
267
268 if (priv->client_widget)
269 display = gtk_widget_get_display (widget: priv->client_widget);
270 else
271 display = gdk_display_get_default ();
272
273 return _gtk_im_module_get_default_context_id (display);
274}
275
276static GtkIMContext *
277gtk_im_multicontext_get_delegate (GtkIMMulticontext *multicontext)
278{
279 GtkIMMulticontextPrivate *priv = multicontext->priv;
280
281 if (!priv->delegate)
282 {
283 GtkIMContext *delegate;
284
285 g_free (mem: priv->context_id);
286
287 priv->context_id = g_strdup (str: get_effective_context_id (multicontext));
288
289 delegate = _gtk_im_module_create (context_id: priv->context_id);
290 if (delegate)
291 {
292 gtk_im_multicontext_set_delegate (multicontext, delegate, FALSE);
293 g_object_unref (object: delegate);
294 }
295 }
296
297 return priv->delegate;
298}
299
300static void
301im_module_setting_changed (GtkSettings *settings,
302 GParamSpec *pspec,
303 GtkIMMulticontext *self)
304{
305 gtk_im_multicontext_set_delegate (multicontext: self, NULL, FALSE);
306}
307
308static void
309gtk_im_multicontext_set_client_widget (GtkIMContext *context,
310 GtkWidget *widget)
311{
312 GtkIMMulticontext *self = GTK_IM_MULTICONTEXT (context);
313 GtkIMMulticontextPrivate *priv = self->priv;
314 GtkIMContext *delegate;
315 GtkSettings *settings;
316
317 if (priv->client_widget == widget)
318 return;
319
320 gtk_im_multicontext_set_delegate (multicontext: self, NULL, TRUE);
321
322 if (priv->client_widget != NULL)
323 {
324 settings = gtk_widget_get_settings (widget: priv->client_widget);
325
326 g_signal_handlers_disconnect_by_func (settings,
327 im_module_setting_changed,
328 self);
329 }
330
331 priv->client_widget = widget;
332
333 if (widget)
334 {
335 settings = gtk_widget_get_settings (widget);
336
337 g_signal_connect (settings, "notify::gtk-im-module",
338 G_CALLBACK (im_module_setting_changed),
339 self);
340
341 delegate = gtk_im_multicontext_get_delegate (multicontext: self);
342 if (delegate)
343 gtk_im_context_set_client_widget (context: delegate, widget);
344 }
345}
346
347static void
348gtk_im_multicontext_get_preedit_string (GtkIMContext *context,
349 char **str,
350 PangoAttrList **attrs,
351 int *cursor_pos)
352{
353 GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT (context);
354 GtkIMContext *delegate = gtk_im_multicontext_get_delegate (multicontext);
355
356 if (delegate)
357 gtk_im_context_get_preedit_string (context: delegate, str, attrs, cursor_pos);
358 else
359 {
360 if (str)
361 *str = g_strdup (str: "");
362 if (attrs)
363 *attrs = pango_attr_list_new ();
364 }
365}
366
367static gboolean
368gtk_im_multicontext_filter_keypress (GtkIMContext *context,
369 GdkEvent *event)
370{
371 GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT (context);
372 GtkIMContext *delegate = gtk_im_multicontext_get_delegate (multicontext);
373 guint keyval, state;
374
375 if (delegate)
376 {
377 return gtk_im_context_filter_keypress (context: delegate, event);
378 }
379 else
380 {
381 GdkModifierType no_text_input_mask;
382
383 keyval = gdk_key_event_get_keyval (event);
384 state = gdk_event_get_modifier_state (event);
385
386 no_text_input_mask = GDK_ALT_MASK|GDK_CONTROL_MASK;
387
388 if (gdk_event_get_event_type (event) == GDK_KEY_PRESS &&
389 (state & no_text_input_mask) == 0)
390 {
391 gunichar ch;
392
393 ch = gdk_keyval_to_unicode (keyval);
394 if (ch != 0 && !g_unichar_iscntrl (c: ch))
395 {
396 int len;
397 char buf[10];
398
399 len = g_unichar_to_utf8 (c: ch, outbuf: buf);
400 buf[len] = '\0';
401
402 g_signal_emit_by_name (instance: multicontext, detailed_signal: "commit", buf);
403
404 return TRUE;
405 }
406 }
407 }
408
409 return FALSE;
410}
411
412static void
413gtk_im_multicontext_focus_in (GtkIMContext *context)
414{
415 GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT (context);
416 GtkIMMulticontextPrivate *priv = multicontext->priv;
417 GtkIMContext *delegate = gtk_im_multicontext_get_delegate (multicontext);
418
419 priv->focus_in = TRUE;
420
421 if (delegate)
422 gtk_im_context_focus_in (context: delegate);
423}
424
425static void
426gtk_im_multicontext_focus_out (GtkIMContext *context)
427{
428 GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT (context);
429 GtkIMMulticontextPrivate *priv = multicontext->priv;
430 GtkIMContext *delegate = gtk_im_multicontext_get_delegate (multicontext);
431
432 priv->focus_in = FALSE;
433
434 if (delegate)
435 gtk_im_context_focus_out (context: delegate);
436}
437
438static void
439gtk_im_multicontext_reset (GtkIMContext *context)
440{
441 GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT (context);
442 GtkIMContext *delegate = gtk_im_multicontext_get_delegate (multicontext);
443
444 if (delegate)
445 gtk_im_context_reset (context: delegate);
446}
447
448static void
449gtk_im_multicontext_set_cursor_location (GtkIMContext *context,
450 GdkRectangle *area)
451{
452 GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT (context);
453 GtkIMMulticontextPrivate *priv = multicontext->priv;
454 GtkIMContext *delegate = gtk_im_multicontext_get_delegate (multicontext);
455
456 priv->have_cursor_location = TRUE;
457 priv->cursor_location = *area;
458
459 if (delegate)
460 gtk_im_context_set_cursor_location (context: delegate, area);
461}
462
463static void
464gtk_im_multicontext_set_use_preedit (GtkIMContext *context,
465 gboolean use_preedit)
466{
467 GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT (context);
468 GtkIMMulticontextPrivate *priv = multicontext->priv;
469 GtkIMContext *delegate = gtk_im_multicontext_get_delegate (multicontext);
470
471 use_preedit = use_preedit != FALSE;
472
473 priv->use_preedit = use_preedit;
474
475 if (delegate)
476 gtk_im_context_set_use_preedit (context: delegate, use_preedit);
477}
478
479static gboolean
480gtk_im_multicontext_get_surrounding_with_selection (GtkIMContext *context,
481 char **text,
482 int *cursor_index,
483 int *anchor_index)
484{
485 GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT (context);
486 GtkIMContext *delegate = gtk_im_multicontext_get_delegate (multicontext);
487
488 if (delegate)
489 return gtk_im_context_get_surrounding_with_selection (context: delegate, text, cursor_index, anchor_index);
490 else
491 {
492 if (text)
493 *text = NULL;
494 if (cursor_index)
495 *cursor_index = 0;
496 if (anchor_index)
497 *anchor_index = 0;
498
499 return FALSE;
500 }
501}
502
503static void
504gtk_im_multicontext_set_surrounding_with_selection (GtkIMContext *context,
505 const char *text,
506 int len,
507 int cursor_index,
508 int anchor_index)
509{
510 GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT (context);
511 GtkIMContext *delegate = gtk_im_multicontext_get_delegate (multicontext);
512
513 if (delegate)
514 gtk_im_context_set_surrounding_with_selection (context: delegate, text, len, cursor_index, anchor_index);
515}
516
517static void
518gtk_im_multicontext_preedit_start_cb (GtkIMContext *delegate,
519 GtkIMMulticontext *multicontext)
520{
521 g_signal_emit_by_name (instance: multicontext, detailed_signal: "preedit-start");
522}
523
524static void
525gtk_im_multicontext_preedit_end_cb (GtkIMContext *delegate,
526 GtkIMMulticontext *multicontext)
527{
528 g_signal_emit_by_name (instance: multicontext, detailed_signal: "preedit-end");
529}
530
531static void
532gtk_im_multicontext_preedit_changed_cb (GtkIMContext *delegate,
533 GtkIMMulticontext *multicontext)
534{
535 g_signal_emit_by_name (instance: multicontext, detailed_signal: "preedit-changed");
536}
537
538static void
539gtk_im_multicontext_commit_cb (GtkIMContext *delegate,
540 const char *str,
541 GtkIMMulticontext *multicontext)
542{
543 g_signal_emit_by_name (instance: multicontext, detailed_signal: "commit", str);
544}
545
546static gboolean
547gtk_im_multicontext_retrieve_surrounding_cb (GtkIMContext *delegate,
548 GtkIMMulticontext *multicontext)
549{
550 gboolean result;
551
552 g_signal_emit_by_name (instance: multicontext, detailed_signal: "retrieve-surrounding", &result);
553
554 return result;
555}
556
557static gboolean
558gtk_im_multicontext_delete_surrounding_cb (GtkIMContext *delegate,
559 int offset,
560 int n_chars,
561 GtkIMMulticontext *multicontext)
562{
563 gboolean result;
564
565 g_signal_emit_by_name (instance: multicontext, detailed_signal: "delete-surrounding",
566 offset, n_chars, &result);
567
568 return result;
569}
570
571/**
572 * gtk_im_multicontext_get_context_id:
573 * @context: a `GtkIMMulticontext`
574 *
575 * Gets the id of the currently active delegate of the @context.
576 *
577 * Returns: the id of the currently active delegate
578 */
579const char *
580gtk_im_multicontext_get_context_id (GtkIMMulticontext *context)
581{
582 GtkIMMulticontextPrivate *priv = context->priv;
583
584 g_return_val_if_fail (GTK_IS_IM_MULTICONTEXT (context), NULL);
585
586 if (priv->context_id == NULL)
587 gtk_im_multicontext_get_delegate (multicontext: context);
588
589 return priv->context_id;
590}
591
592/**
593 * gtk_im_multicontext_set_context_id:
594 * @context: a `GtkIMMulticontext`
595 * @context_id: (nullable): the id to use
596 *
597 * Sets the context id for @context.
598 *
599 * This causes the currently active delegate of @context to be
600 * replaced by the delegate corresponding to the new context id.
601 *
602 * Setting this to a non-%NULL value overrides the system-wide
603 * IM module setting. See the [property@Gtk.Settings:gtk-im-module]
604 * property.
605 */
606void
607gtk_im_multicontext_set_context_id (GtkIMMulticontext *context,
608 const char *context_id)
609{
610 GtkIMMulticontextPrivate *priv;
611
612 g_return_if_fail (GTK_IS_IM_MULTICONTEXT (context));
613
614 priv = context->priv;
615
616 gtk_im_context_reset (GTK_IM_CONTEXT (context));
617 g_free (mem: priv->context_id_aux);
618 priv->context_id_aux = g_strdup (str: context_id);
619 gtk_im_multicontext_set_delegate (multicontext: context, NULL, FALSE);
620}
621
622static void
623propagate_purpose (GtkIMMulticontext *context)
624{
625 GtkInputPurpose purpose;
626 GtkInputHints hints;
627
628 if (context->priv->delegate == NULL)
629 return;
630
631 g_object_get (object: context, first_property_name: "input-purpose", &purpose, NULL);
632 g_object_set (object: context->priv->delegate, first_property_name: "input-purpose", purpose, NULL);
633
634 g_object_get (object: context, first_property_name: "input-hints", &hints, NULL);
635 g_object_set (object: context->priv->delegate, first_property_name: "input-hints", hints, NULL);
636}
637
638static void
639gtk_im_multicontext_notify (GObject *object,
640 GParamSpec *pspec)
641{
642 propagate_purpose (GTK_IM_MULTICONTEXT (object));
643}
644

source code of gtk/gtk/gtkimmulticontext.c