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 | #include <string.h> |
20 | #include "gtkimcontext.h" |
21 | #include "gtkprivate.h" |
22 | #include "gtktypebuiltins.h" |
23 | #include "gtkmarshalers.h" |
24 | #include "gtkintl.h" |
25 | |
26 | #include "gdk/gdkeventsprivate.h" |
27 | |
28 | /** |
29 | * GtkIMContext: |
30 | * |
31 | * `GtkIMContext` defines the interface for GTK input methods. |
32 | * |
33 | * `GtkIMContext` is used by GTK text input widgets like `GtkText` |
34 | * to map from key events to Unicode character strings. |
35 | * |
36 | * An input method may consume multiple key events in sequence before finally |
37 | * outputting the composed result. This is called *preediting*, and an input |
38 | * method may provide feedback about this process by displaying the intermediate |
39 | * composition states as preedit text. To do so, the `GtkIMContext` will emit |
40 | * [signal@Gtk.IMContext::preedit-start], [signal@Gtk.IMContext::preedit-changed] |
41 | * and [signal@Gtk.IMContext::preedit-end] signals. |
42 | * |
43 | * For instance, the built-in GTK input method [class@Gtk.IMContextSimple] |
44 | * implements the input of arbitrary Unicode code points by holding down the |
45 | * <kbd>Control</kbd> and <kbd>Shift</kbd> keys and then typing <kbd>u</kbd> |
46 | * followed by the hexadecimal digits of the code point. When releasing the |
47 | * <kbd>Control</kbd> and <kbd>Shift</kbd> keys, preediting ends and the |
48 | * character is inserted as text. For example, |
49 | * |
50 | * Ctrl+Shift+u 2 0 A C |
51 | * |
52 | * results in the € sign. |
53 | * |
54 | * Additional input methods can be made available for use by GTK widgets as |
55 | * loadable modules. An input method module is a small shared library which |
56 | * provides a `GIOExtension` for the extension point named "gtk-im-module". |
57 | * |
58 | * To connect a widget to the users preferred input method, you should use |
59 | * [class@Gtk.IMMulticontext]. |
60 | */ |
61 | |
62 | enum { |
63 | PREEDIT_START, |
64 | PREEDIT_END, |
65 | PREEDIT_CHANGED, |
66 | COMMIT, |
67 | RETRIEVE_SURROUNDING, |
68 | DELETE_SURROUNDING, |
69 | LAST_SIGNAL |
70 | }; |
71 | |
72 | enum { |
73 | PROP_INPUT_PURPOSE = 1, |
74 | PROP_INPUT_HINTS, |
75 | LAST_PROPERTY |
76 | }; |
77 | |
78 | static guint im_context_signals[LAST_SIGNAL] = { 0, }; |
79 | static GParamSpec *properties[LAST_PROPERTY] = { NULL, }; |
80 | |
81 | typedef struct _GtkIMContextPrivate GtkIMContextPrivate; |
82 | struct _GtkIMContextPrivate { |
83 | GtkInputPurpose purpose; |
84 | GtkInputHints hints; |
85 | }; |
86 | |
87 | static void gtk_im_context_real_get_preedit_string (GtkIMContext *context, |
88 | char **str, |
89 | PangoAttrList **attrs, |
90 | int *cursor_pos); |
91 | static gboolean gtk_im_context_real_filter_keypress (GtkIMContext *context, |
92 | GdkEvent *event); |
93 | |
94 | static gboolean gtk_im_context_real_get_surrounding_with_selection |
95 | (GtkIMContext *context, |
96 | char **text, |
97 | int *cursor_index, |
98 | int *selection_bound); |
99 | static void gtk_im_context_real_set_surrounding_with_selection |
100 | (GtkIMContext *context, |
101 | const char *text, |
102 | int len, |
103 | int cursor_index, |
104 | int selection_bound); |
105 | |
106 | static void gtk_im_context_get_property (GObject *obj, |
107 | guint property_id, |
108 | GValue *value, |
109 | GParamSpec *pspec); |
110 | static void gtk_im_context_set_property (GObject *obj, |
111 | guint property_id, |
112 | const GValue *value, |
113 | GParamSpec *pspec); |
114 | |
115 | |
116 | G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GtkIMContext, gtk_im_context, G_TYPE_OBJECT) |
117 | |
118 | /** |
119 | * GtkIMContextClass: |
120 | * @preedit_start: Default handler of the [signal@Gtk.IMContext::preedit-start] signal. |
121 | * @preedit_end: Default handler of the [signal@Gtk.IMContext::preedit-end] signal. |
122 | * @preedit_changed: Default handler of the [signal@Gtk.IMContext::preedit-changed] |
123 | * signal. |
124 | * @commit: Default handler of the [signal@Gtk.IMContext::commit] signal. |
125 | * @retrieve_surrounding: Default handler of the |
126 | * [signal@Gtk.IMContext::retrieve-surrounding] signal. |
127 | * @delete_surrounding: Default handler of the |
128 | * [signal@Gtk.IMContext::delete-surrounding] signal. |
129 | * @set_client_widget: Called via [method@Gtk.IMContext.set_client_widget] when |
130 | * the input window where the entered text will appear changes. Override this |
131 | * to keep track of the current input window, for instance for the purpose of |
132 | * positioning a status display of your input method. |
133 | * @get_preedit_string: Called via [method@Gtk.IMContext.get_preedit_string] |
134 | * to retrieve the text currently being preedited for display at the cursor |
135 | * position. Any input method which composes complex characters or any |
136 | * other compositions from multiple sequential key presses should override |
137 | * this method to provide feedback. |
138 | * @filter_keypress: Called via [method@Gtk.IMContext.filter_keypress] on every |
139 | * key press or release event. Every non-trivial input method needs to |
140 | * override this in order to implement the mapping from key events to text. |
141 | * A return value of %TRUE indicates to the caller that the event was |
142 | * consumed by the input method. In that case, the [signal@Gtk.IMContext::commit] |
143 | * signal should be emitted upon completion of a key sequence to pass the |
144 | * resulting text back to the input widget. Alternatively, %FALSE may be |
145 | * returned to indicate that the event wasn’t handled by the input method. |
146 | * If a builtin mapping exists for the key, it is used to produce a |
147 | * character. |
148 | * @focus_in: Called via [method@Gtk.IMContext.focus_in] when the input widget |
149 | * has gained focus. May be overridden to keep track of the current focus. |
150 | * @focus_out: Called via [method@Gtk.IMContext.focus_out] when the input widget |
151 | * has lost focus. May be overridden to keep track of the current focus. |
152 | * @reset: Called via [method@Gtk.IMContext.reset] to signal a change such as a |
153 | * change in cursor position. An input method that implements preediting |
154 | * should override this method to clear the preedit state on reset. |
155 | * @set_cursor_location: Called via [method@Gtk.IMContext.set_cursor_location] |
156 | * to inform the input method of the current cursor location relative to |
157 | * the client window. May be overridden to implement the display of popup |
158 | * windows at the cursor position. |
159 | * @set_use_preedit: Called via [method@Gtk.IMContext.set_use_preedit] to control |
160 | * the use of the preedit string. Override this to display feedback by some |
161 | * other means if turned off. |
162 | * @set_surrounding: Called via [method@Gtk.IMContext.set_surrounding] in |
163 | * response to [signal@Gtk.IMContext::retrieve-surrounding] signal to update |
164 | * the input method’s idea of the context around the cursor. It is not necessary |
165 | * to override this method even with input methods which implement |
166 | * context-dependent behavior. The base implementation is sufficient for |
167 | * [method@Gtk.IMContext.get_surrounding] to work. |
168 | * @get_surrounding: Called via [method@Gtk.IMContext.get_surrounding] to update |
169 | * the context around the cursor location. It is not necessary to override this |
170 | * method even with input methods which implement context-dependent behavior. |
171 | * The base implementation emits [signal@Gtk.IMContext::retrieve-surrounding] |
172 | * and records the context received by the subsequent invocation of |
173 | * [vfunc@Gtk.IMContext.get_surrounding]. |
174 | * @set_surrounding_with_selection: Called via |
175 | * [method@Gtk.IMContext.set_surrounding_with_selection] in response to the |
176 | * [signal@Gtk.IMContext::retrieve-surrounding] signal to update the input |
177 | * method’s idea of the context around the cursor. It is not necessary to |
178 | * override this method even with input methods which implement |
179 | * context-dependent behavior. The base implementation is sufficient for |
180 | * [method@Gtk.IMContext.get_surrounding] to work. |
181 | * @get_surrounding_with_selection: Called via |
182 | * [method@Gtk.IMContext.get_surrounding_with_selection] to update the |
183 | * context around the cursor location. It is not necessary to override |
184 | * this method even with input methods which implement context-dependent |
185 | * behavior. The base implementation emits |
186 | * [signal@Gtk.IMContext::retrieve-surrounding] and records the context |
187 | * received by the subsequent invocation of [vfunc@Gtk.IMContext.get_surrounding]. |
188 | */ |
189 | static void |
190 | gtk_im_context_class_init (GtkIMContextClass *klass) |
191 | { |
192 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
193 | |
194 | object_class->get_property = gtk_im_context_get_property; |
195 | object_class->set_property = gtk_im_context_set_property; |
196 | |
197 | klass->get_preedit_string = gtk_im_context_real_get_preedit_string; |
198 | klass->filter_keypress = gtk_im_context_real_filter_keypress; |
199 | klass->get_surrounding_with_selection = gtk_im_context_real_get_surrounding_with_selection; |
200 | klass->set_surrounding_with_selection = gtk_im_context_real_set_surrounding_with_selection; |
201 | |
202 | /** |
203 | * GtkIMContext::preedit-start: |
204 | * @context: the object on which the signal is emitted |
205 | * |
206 | * The ::preedit-start signal is emitted when a new preediting sequence |
207 | * starts. |
208 | */ |
209 | im_context_signals[PREEDIT_START] = |
210 | g_signal_new (I_("preedit-start" ), |
211 | G_TYPE_FROM_CLASS (klass), |
212 | signal_flags: G_SIGNAL_RUN_LAST, |
213 | G_STRUCT_OFFSET (GtkIMContextClass, preedit_start), |
214 | NULL, NULL, |
215 | NULL, |
216 | G_TYPE_NONE, n_params: 0); |
217 | |
218 | /** |
219 | * GtkIMContext::preedit-end: |
220 | * @context: the object on which the signal is emitted |
221 | * |
222 | * The ::preedit-end signal is emitted when a preediting sequence |
223 | * has been completed or canceled. |
224 | */ |
225 | im_context_signals[PREEDIT_END] = |
226 | g_signal_new (I_("preedit-end" ), |
227 | G_TYPE_FROM_CLASS (klass), |
228 | signal_flags: G_SIGNAL_RUN_LAST, |
229 | G_STRUCT_OFFSET (GtkIMContextClass, preedit_end), |
230 | NULL, NULL, |
231 | NULL, |
232 | G_TYPE_NONE, n_params: 0); |
233 | |
234 | /** |
235 | * GtkIMContext::preedit-changed: |
236 | * @context: the object on which the signal is emitted |
237 | * |
238 | * The ::preedit-changed signal is emitted whenever the preedit sequence |
239 | * currently being entered has changed. |
240 | * |
241 | * It is also emitted at the end of a preedit sequence, in which case |
242 | * [method@Gtk.IMContext.get_preedit_string] returns the empty string. |
243 | */ |
244 | im_context_signals[PREEDIT_CHANGED] = |
245 | g_signal_new (I_("preedit-changed" ), |
246 | G_TYPE_FROM_CLASS (klass), |
247 | signal_flags: G_SIGNAL_RUN_LAST, |
248 | G_STRUCT_OFFSET (GtkIMContextClass, preedit_changed), |
249 | NULL, NULL, |
250 | NULL, |
251 | G_TYPE_NONE, n_params: 0); |
252 | |
253 | /** |
254 | * GtkIMContext::commit: |
255 | * @context: the object on which the signal is emitted |
256 | * @str: the completed character(s) entered by the user |
257 | * |
258 | * The ::commit signal is emitted when a complete input sequence |
259 | * has been entered by the user. |
260 | * |
261 | * If the commit comes after a preediting sequence, the |
262 | * ::commit signal is emitted after ::preedit-end. |
263 | * |
264 | * This can be a single character immediately after a key press or |
265 | * the final result of preediting. |
266 | */ |
267 | im_context_signals[COMMIT] = |
268 | g_signal_new (I_("commit" ), |
269 | G_TYPE_FROM_CLASS (klass), |
270 | signal_flags: G_SIGNAL_RUN_LAST, |
271 | G_STRUCT_OFFSET (GtkIMContextClass, commit), |
272 | NULL, NULL, |
273 | NULL, |
274 | G_TYPE_NONE, n_params: 1, |
275 | G_TYPE_STRING); |
276 | |
277 | /** |
278 | * GtkIMContext::retrieve-surrounding: |
279 | * @context: the object on which the signal is emitted |
280 | * |
281 | * The ::retrieve-surrounding signal is emitted when the input method |
282 | * requires the context surrounding the cursor. |
283 | * |
284 | * The callback should set the input method surrounding context by |
285 | * calling the [method@Gtk.IMContext.set_surrounding] method. |
286 | * |
287 | * Returns: %TRUE if the signal was handled. |
288 | */ |
289 | im_context_signals[RETRIEVE_SURROUNDING] = |
290 | g_signal_new (I_("retrieve-surrounding" ), |
291 | G_TYPE_FROM_CLASS (klass), |
292 | signal_flags: G_SIGNAL_RUN_LAST, |
293 | G_STRUCT_OFFSET (GtkIMContextClass, retrieve_surrounding), |
294 | accumulator: _gtk_boolean_handled_accumulator, NULL, |
295 | c_marshaller: _gtk_marshal_BOOLEAN__VOID, |
296 | G_TYPE_BOOLEAN, n_params: 0); |
297 | g_signal_set_va_marshaller (signal_id: im_context_signals[RETRIEVE_SURROUNDING], |
298 | G_TYPE_FROM_CLASS (klass), |
299 | va_marshaller: _gtk_marshal_BOOLEAN__VOIDv); |
300 | |
301 | /** |
302 | * GtkIMContext::delete-surrounding: |
303 | * @context: the object on which the signal is emitted |
304 | * @offset: the character offset from the cursor position of the text |
305 | * to be deleted. A negative value indicates a position before |
306 | * the cursor. |
307 | * @n_chars: the number of characters to be deleted |
308 | * |
309 | * The ::delete-surrounding signal is emitted when the input method |
310 | * needs to delete all or part of the context surrounding the cursor. |
311 | * |
312 | * Returns: %TRUE if the signal was handled. |
313 | */ |
314 | im_context_signals[DELETE_SURROUNDING] = |
315 | g_signal_new (I_("delete-surrounding" ), |
316 | G_TYPE_FROM_CLASS (klass), |
317 | signal_flags: G_SIGNAL_RUN_LAST, |
318 | G_STRUCT_OFFSET (GtkIMContextClass, delete_surrounding), |
319 | accumulator: _gtk_boolean_handled_accumulator, NULL, |
320 | c_marshaller: _gtk_marshal_BOOLEAN__INT_INT, |
321 | G_TYPE_BOOLEAN, n_params: 2, |
322 | G_TYPE_INT, |
323 | G_TYPE_INT); |
324 | g_signal_set_va_marshaller (signal_id: im_context_signals[DELETE_SURROUNDING], |
325 | G_TYPE_FROM_CLASS (klass), |
326 | va_marshaller: _gtk_marshal_BOOLEAN__INT_INTv); |
327 | |
328 | /** |
329 | * GtkIMContext:input-purpose: |
330 | * |
331 | * The purpose of the text field that the `GtkIMContext is connected to. |
332 | * |
333 | * This property can be used by on-screen keyboards and other input |
334 | * methods to adjust their behaviour. |
335 | */ |
336 | properties[PROP_INPUT_PURPOSE] = |
337 | g_param_spec_enum (name: "input-purpose" , |
338 | P_("Purpose" ), |
339 | P_("Purpose of the text field" ), |
340 | enum_type: GTK_TYPE_INPUT_PURPOSE, |
341 | default_value: GTK_INPUT_PURPOSE_FREE_FORM, |
342 | flags: G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS|G_PARAM_EXPLICIT_NOTIFY); |
343 | |
344 | /** |
345 | * GtkIMContext:input-hints: |
346 | * |
347 | * Additional hints that allow input methods to fine-tune |
348 | * their behaviour. |
349 | */ |
350 | properties[PROP_INPUT_HINTS] = |
351 | g_param_spec_flags (name: "input-hints" , |
352 | P_("hints" ), |
353 | P_("Hints for the text field behaviour" ), |
354 | flags_type: GTK_TYPE_INPUT_HINTS, |
355 | default_value: GTK_INPUT_HINT_NONE, |
356 | flags: G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS|G_PARAM_EXPLICIT_NOTIFY); |
357 | |
358 | g_object_class_install_properties (oclass: object_class, n_pspecs: LAST_PROPERTY, pspecs: properties); |
359 | } |
360 | |
361 | static void |
362 | gtk_im_context_init (GtkIMContext *im_context) |
363 | { |
364 | } |
365 | |
366 | static void |
367 | gtk_im_context_real_get_preedit_string (GtkIMContext *context, |
368 | char **str, |
369 | PangoAttrList **attrs, |
370 | int *cursor_pos) |
371 | { |
372 | if (str) |
373 | *str = g_strdup (str: "" ); |
374 | if (attrs) |
375 | *attrs = pango_attr_list_new (); |
376 | if (cursor_pos) |
377 | *cursor_pos = 0; |
378 | } |
379 | |
380 | static gboolean |
381 | gtk_im_context_real_filter_keypress (GtkIMContext *context, |
382 | GdkEvent *event) |
383 | { |
384 | return FALSE; |
385 | } |
386 | |
387 | typedef struct |
388 | { |
389 | char *text; |
390 | int cursor_index; |
391 | int selection_bound; |
392 | } SurroundingInfo; |
393 | |
394 | static void |
395 | gtk_im_context_real_set_surrounding_with_selection (GtkIMContext *context, |
396 | const char *text, |
397 | int len, |
398 | int cursor_index, |
399 | int selection_bound) |
400 | { |
401 | SurroundingInfo *info = g_object_get_data (G_OBJECT (context), |
402 | key: "gtk-im-surrounding-info" ); |
403 | |
404 | if (info) |
405 | { |
406 | g_free (mem: info->text); |
407 | info->text = g_strndup (str: text, n: len); |
408 | info->cursor_index = cursor_index; |
409 | info->selection_bound = selection_bound; |
410 | } |
411 | } |
412 | |
413 | static gboolean |
414 | gtk_im_context_real_get_surrounding_with_selection (GtkIMContext *context, |
415 | char **text, |
416 | int *cursor_index, |
417 | int *selection_bound) |
418 | { |
419 | gboolean result; |
420 | gboolean info_is_local = FALSE; |
421 | SurroundingInfo local_info = { NULL, 0 }; |
422 | SurroundingInfo *info; |
423 | |
424 | info = g_object_get_data (G_OBJECT (context), key: "gtk-im-surrounding-info" ); |
425 | if (!info) |
426 | { |
427 | info = &local_info; |
428 | g_object_set_data (G_OBJECT (context), I_("gtk-im-surrounding-info" ), data: info); |
429 | info_is_local = TRUE; |
430 | } |
431 | |
432 | g_signal_emit (instance: context, |
433 | signal_id: im_context_signals[RETRIEVE_SURROUNDING], detail: 0, |
434 | &result); |
435 | |
436 | if (result) |
437 | { |
438 | *text = g_strdup (str: info->text ? info->text : "" ); |
439 | *cursor_index = info->cursor_index; |
440 | *selection_bound = info->selection_bound; |
441 | } |
442 | else |
443 | { |
444 | *text = NULL; |
445 | *cursor_index = 0; |
446 | *selection_bound = 0; |
447 | } |
448 | |
449 | if (info_is_local) |
450 | { |
451 | g_free (mem: info->text); |
452 | g_object_set_data (G_OBJECT (context), I_("gtk-im-surrounding-info" ), NULL); |
453 | } |
454 | |
455 | return result; |
456 | } |
457 | |
458 | /** |
459 | * gtk_im_context_set_client_widget: |
460 | * @context: a `GtkIMContext` |
461 | * @widget: (nullable): the client widget. This may be %NULL to indicate |
462 | * that the previous client widget no longer exists. |
463 | * |
464 | * Set the client widget for the input context. |
465 | * |
466 | * This is the `GtkWidget` holding the input focus. This widget is |
467 | * used in order to correctly position status windows, and may |
468 | * also be used for purposes internal to the input method. |
469 | */ |
470 | void |
471 | gtk_im_context_set_client_widget (GtkIMContext *context, |
472 | GtkWidget *widget) |
473 | { |
474 | GtkIMContextClass *klass; |
475 | |
476 | g_return_if_fail (GTK_IS_IM_CONTEXT (context)); |
477 | |
478 | klass = GTK_IM_CONTEXT_GET_CLASS (context); |
479 | if (klass->set_client_widget) |
480 | klass->set_client_widget (context, widget); |
481 | } |
482 | |
483 | /** |
484 | * gtk_im_context_get_preedit_string: |
485 | * @context: a `GtkIMContext` |
486 | * @str: (out) (transfer full): location to store the retrieved |
487 | * string. The string retrieved must be freed with g_free(). |
488 | * @attrs: (out) (transfer full): location to store the retrieved |
489 | * attribute list. When you are done with this list, you |
490 | * must unreference it with [method@Pango.AttrList.unref]. |
491 | * @cursor_pos: (out): location to store position of cursor |
492 | * (in characters) within the preedit string. |
493 | * |
494 | * Retrieve the current preedit string for the input context, |
495 | * and a list of attributes to apply to the string. |
496 | * |
497 | * This string should be displayed inserted at the insertion point. |
498 | */ |
499 | void |
500 | gtk_im_context_get_preedit_string (GtkIMContext *context, |
501 | char **str, |
502 | PangoAttrList **attrs, |
503 | int *cursor_pos) |
504 | { |
505 | GtkIMContextClass *klass; |
506 | |
507 | g_return_if_fail (GTK_IS_IM_CONTEXT (context)); |
508 | |
509 | klass = GTK_IM_CONTEXT_GET_CLASS (context); |
510 | klass->get_preedit_string (context, str, attrs, cursor_pos); |
511 | g_return_if_fail (str == NULL || g_utf8_validate (*str, -1, NULL)); |
512 | } |
513 | |
514 | /** |
515 | * gtk_im_context_filter_keypress: |
516 | * @context: a `GtkIMContext` |
517 | * @event: the key event |
518 | * |
519 | * Allow an input method to internally handle key press and release |
520 | * events. |
521 | * |
522 | * If this function returns %TRUE, then no further processing |
523 | * should be done for this key event. |
524 | * |
525 | * Returns: %TRUE if the input method handled the key event. |
526 | */ |
527 | gboolean |
528 | gtk_im_context_filter_keypress (GtkIMContext *context, |
529 | GdkEvent *key) |
530 | { |
531 | GtkIMContextClass *klass; |
532 | |
533 | g_return_val_if_fail (GTK_IS_IM_CONTEXT (context), FALSE); |
534 | g_return_val_if_fail (key != NULL, FALSE); |
535 | |
536 | klass = GTK_IM_CONTEXT_GET_CLASS (context); |
537 | return klass->filter_keypress (context, key); |
538 | } |
539 | |
540 | /** |
541 | * gtk_im_context_filter_key: |
542 | * @context: a `GtkIMContext` |
543 | * @press: whether to forward a key press or release event |
544 | * @surface: the surface the event is for |
545 | * @device: the device that the event is for |
546 | * @time: the timestamp for the event |
547 | * @keycode: the keycode for the event |
548 | * @state: modifier state for the event |
549 | * @group: the active keyboard group for the event |
550 | * |
551 | * Allow an input method to forward key press and release events |
552 | * to another input method without necessarily having a `GdkEvent` |
553 | * available. |
554 | * |
555 | * Returns: %TRUE if the input method handled the key event. |
556 | */ |
557 | gboolean |
558 | gtk_im_context_filter_key (GtkIMContext *context, |
559 | gboolean press, |
560 | GdkSurface *surface, |
561 | GdkDevice *device, |
562 | guint32 time, |
563 | guint keycode, |
564 | GdkModifierType state, |
565 | int group) |
566 | { |
567 | GdkTranslatedKey translated, no_lock; |
568 | GdkEvent *key; |
569 | gboolean ret; |
570 | guint keyval; |
571 | int layout; |
572 | int level; |
573 | GdkModifierType consumed; |
574 | |
575 | g_return_val_if_fail (GTK_IS_IM_CONTEXT (context), FALSE); |
576 | |
577 | if (!gdk_display_translate_key (display: gdk_surface_get_display (surface), |
578 | keycode, |
579 | state, |
580 | group, |
581 | keyval: &keyval, |
582 | effective_group: &layout, |
583 | level: &level, |
584 | consumed: &consumed)) |
585 | return FALSE; |
586 | |
587 | translated.keyval = keyval; |
588 | translated.layout = layout; |
589 | translated.level = level; |
590 | translated.consumed = consumed; |
591 | |
592 | if (!gdk_display_translate_key (display: gdk_surface_get_display (surface), |
593 | keycode, |
594 | state: state & ~GDK_LOCK_MASK, |
595 | group, |
596 | keyval: &keyval, |
597 | effective_group: &layout, |
598 | level: &level, |
599 | consumed: &consumed)) |
600 | return FALSE; |
601 | |
602 | no_lock.keyval = keyval; |
603 | no_lock.layout = layout; |
604 | no_lock.level = level; |
605 | no_lock.consumed = consumed; |
606 | |
607 | key = gdk_key_event_new (type: press ? GDK_KEY_PRESS : GDK_KEY_RELEASE, |
608 | surface, |
609 | device, |
610 | time, |
611 | keycode, |
612 | modifiers: state, |
613 | FALSE, /* FIXME */ |
614 | translated: &translated, |
615 | no_lock: &no_lock); |
616 | |
617 | ret = GTK_IM_CONTEXT_GET_CLASS (context)->filter_keypress (context, key); |
618 | |
619 | gdk_event_unref (event: key); |
620 | |
621 | return ret; |
622 | } |
623 | |
624 | /** |
625 | * gtk_im_context_focus_in: |
626 | * @context: a `GtkIMContext` |
627 | * |
628 | * Notify the input method that the widget to which this |
629 | * input context corresponds has gained focus. |
630 | * |
631 | * The input method may, for example, change the displayed |
632 | * feedback to reflect this change. |
633 | */ |
634 | void |
635 | gtk_im_context_focus_in (GtkIMContext *context) |
636 | { |
637 | GtkIMContextClass *klass; |
638 | |
639 | g_return_if_fail (GTK_IS_IM_CONTEXT (context)); |
640 | |
641 | klass = GTK_IM_CONTEXT_GET_CLASS (context); |
642 | if (klass->focus_in) |
643 | klass->focus_in (context); |
644 | } |
645 | |
646 | /** |
647 | * gtk_im_context_focus_out: |
648 | * @context: a `GtkIMContext` |
649 | * |
650 | * Notify the input method that the widget to which this |
651 | * input context corresponds has lost focus. |
652 | * |
653 | * The input method may, for example, change the displayed |
654 | * feedback or reset the contexts state to reflect this change. |
655 | */ |
656 | void |
657 | gtk_im_context_focus_out (GtkIMContext *context) |
658 | { |
659 | GtkIMContextClass *klass; |
660 | |
661 | g_return_if_fail (GTK_IS_IM_CONTEXT (context)); |
662 | |
663 | klass = GTK_IM_CONTEXT_GET_CLASS (context); |
664 | if (klass->focus_out) |
665 | klass->focus_out (context); |
666 | } |
667 | |
668 | /** |
669 | * gtk_im_context_reset: |
670 | * @context: a `GtkIMContext` |
671 | * |
672 | * Notify the input method that a change such as a change in cursor |
673 | * position has been made. |
674 | * |
675 | * This will typically cause the input method to clear the preedit state. |
676 | */ |
677 | void |
678 | gtk_im_context_reset (GtkIMContext *context) |
679 | { |
680 | GtkIMContextClass *klass; |
681 | |
682 | g_return_if_fail (GTK_IS_IM_CONTEXT (context)); |
683 | |
684 | klass = GTK_IM_CONTEXT_GET_CLASS (context); |
685 | if (klass->reset) |
686 | klass->reset (context); |
687 | } |
688 | |
689 | |
690 | /** |
691 | * gtk_im_context_set_cursor_location: |
692 | * @context: a `GtkIMContext` |
693 | * @area: new location |
694 | * |
695 | * Notify the input method that a change in cursor |
696 | * position has been made. |
697 | * |
698 | * The location is relative to the client widget. |
699 | */ |
700 | void |
701 | gtk_im_context_set_cursor_location (GtkIMContext *context, |
702 | const GdkRectangle *area) |
703 | { |
704 | GtkIMContextClass *klass; |
705 | |
706 | g_return_if_fail (GTK_IS_IM_CONTEXT (context)); |
707 | |
708 | klass = GTK_IM_CONTEXT_GET_CLASS (context); |
709 | if (klass->set_cursor_location) |
710 | klass->set_cursor_location (context, (GdkRectangle *) area); |
711 | } |
712 | |
713 | /** |
714 | * gtk_im_context_set_use_preedit: |
715 | * @context: a `GtkIMContext` |
716 | * @use_preedit: whether the IM context should use the preedit string. |
717 | * |
718 | * Sets whether the IM context should use the preedit string |
719 | * to display feedback. |
720 | * |
721 | * If @use_preedit is %FALSE (default is %TRUE), then the IM context |
722 | * may use some other method to display feedback, such as displaying |
723 | * it in a child of the root window. |
724 | */ |
725 | void |
726 | gtk_im_context_set_use_preedit (GtkIMContext *context, |
727 | gboolean use_preedit) |
728 | { |
729 | GtkIMContextClass *klass; |
730 | |
731 | g_return_if_fail (GTK_IS_IM_CONTEXT (context)); |
732 | |
733 | klass = GTK_IM_CONTEXT_GET_CLASS (context); |
734 | if (klass->set_use_preedit) |
735 | klass->set_use_preedit (context, use_preedit); |
736 | } |
737 | |
738 | /** |
739 | * gtk_im_context_set_surrounding: |
740 | * @context: a `GtkIMContext` |
741 | * @text: text surrounding the insertion point, as UTF-8. |
742 | * the preedit string should not be included within @text |
743 | * @len: the length of @text, or -1 if @text is nul-terminated |
744 | * @cursor_index: the byte index of the insertion cursor within @text. |
745 | * |
746 | * Sets surrounding context around the insertion point and preedit |
747 | * string. |
748 | * |
749 | * This function is expected to be called in response to the |
750 | * [signal@Gtk.IMContext::retrieve-surrounding] signal, and will |
751 | * likely have no effect if called at other times. |
752 | * |
753 | * Deprecated: 4.2: Use [method@Gtk.IMContext.set_surrounding_with_selection] instead |
754 | */ |
755 | void |
756 | gtk_im_context_set_surrounding (GtkIMContext *context, |
757 | const char *text, |
758 | int len, |
759 | int cursor_index) |
760 | { |
761 | gtk_im_context_set_surrounding_with_selection (context, text, len, cursor_index, anchor_index: cursor_index); |
762 | } |
763 | |
764 | /** |
765 | * gtk_im_context_set_surrounding_with_selection: |
766 | * @context: a `GtkIMContext` |
767 | * @text: text surrounding the insertion point, as UTF-8. |
768 | * the preedit string should not be included within @text |
769 | * @len: the length of @text, or -1 if @text is nul-terminated |
770 | * @cursor_index: the byte index of the insertion cursor within @text |
771 | * @anchor_index: the byte index of the selection bound within @text |
772 | * |
773 | * Sets surrounding context around the insertion point and preedit |
774 | * string. This function is expected to be called in response to the |
775 | * [signal@Gtk.IMContext::retrieve_surrounding] signal, and will likely |
776 | * have no effect if called at other times. |
777 | * |
778 | * Since: 4.2 |
779 | */ |
780 | void |
781 | gtk_im_context_set_surrounding_with_selection (GtkIMContext *context, |
782 | const char *text, |
783 | int len, |
784 | int cursor_index, |
785 | int anchor_index) |
786 | { |
787 | GtkIMContextClass *klass; |
788 | |
789 | g_return_if_fail (GTK_IS_IM_CONTEXT (context)); |
790 | g_return_if_fail (text != NULL || len == 0); |
791 | |
792 | if (text == NULL && len == 0) |
793 | text = "" ; |
794 | if (len < 0) |
795 | len = strlen (s: text); |
796 | |
797 | g_return_if_fail (cursor_index >= 0 && cursor_index <= len); |
798 | |
799 | klass = GTK_IM_CONTEXT_GET_CLASS (context); |
800 | if (klass->set_surrounding_with_selection) |
801 | klass->set_surrounding_with_selection (context, text, len, cursor_index, anchor_index); |
802 | else if (klass->set_surrounding) |
803 | klass->set_surrounding (context, text, len, cursor_index); |
804 | } |
805 | |
806 | /** |
807 | * gtk_im_context_get_surrounding: |
808 | * @context: a `GtkIMContext` |
809 | * @text: (out) (transfer full): location to store a UTF-8 encoded |
810 | * string of text holding context around the insertion point. |
811 | * If the function returns %TRUE, then you must free the result |
812 | * stored in this location with g_free(). |
813 | * @cursor_index: (out): location to store byte index of the insertion |
814 | * cursor within @text. |
815 | * |
816 | * Retrieves context around the insertion point. |
817 | * |
818 | * Input methods typically want context in order to constrain input text |
819 | * based on existing text; this is important for languages such as Thai |
820 | * where only some sequences of characters are allowed. |
821 | * |
822 | * This function is implemented by emitting the |
823 | * [signal@Gtk.IMContext::retrieve-surrounding] signal on the input method; |
824 | * in response to this signal, a widget should provide as much context as |
825 | * is available, up to an entire paragraph, by calling |
826 | * [method@Gtk.IMContext.set_surrounding]. |
827 | * |
828 | * Note that there is no obligation for a widget to respond to the |
829 | * `::retrieve-surrounding` signal, so input methods must be prepared to |
830 | * function without context. |
831 | * |
832 | * Returns: `TRUE` if surrounding text was provided; in this case |
833 | * you must free the result stored in `text`. |
834 | * |
835 | * Deprecated: 4.2: Use [method@Gtk.IMContext.get_surrounding_with_selection] instead. |
836 | */ |
837 | gboolean |
838 | gtk_im_context_get_surrounding (GtkIMContext *context, |
839 | char **text, |
840 | int *cursor_index) |
841 | { |
842 | return gtk_im_context_get_surrounding_with_selection (context, |
843 | text, |
844 | cursor_index, |
845 | NULL); |
846 | } |
847 | |
848 | /** |
849 | * gtk_im_context_get_surrounding_with_selection: |
850 | * @context: a `GtkIMContext` |
851 | * @text: (out) (transfer full): location to store a UTF-8 encoded |
852 | * string of text holding context around the insertion point. |
853 | * If the function returns %TRUE, then you must free the result |
854 | * stored in this location with g_free(). |
855 | * @cursor_index: (out): location to store byte index of the insertion |
856 | * cursor within @text. |
857 | * @anchor_index: (out): location to store byte index of the selection |
858 | * bound within @text |
859 | * |
860 | * Retrieves context around the insertion point. |
861 | * |
862 | * Input methods typically want context in order to constrain input |
863 | * text based on existing text; this is important for languages such |
864 | * as Thai where only some sequences of characters are allowed. |
865 | * |
866 | * This function is implemented by emitting the |
867 | * [signal@Gtk.IMContext::retrieve-surrounding] signal on the input method; |
868 | * in response to this signal, a widget should provide as much context as |
869 | * is available, up to an entire paragraph, by calling |
870 | * [method@Gtk.IMContext.set_surrounding_with_selection]. |
871 | * |
872 | * Note that there is no obligation for a widget to respond to the |
873 | * `::retrieve-surrounding` signal, so input methods must be prepared to |
874 | * function without context. |
875 | * |
876 | * Returns: `TRUE` if surrounding text was provided; in this case |
877 | * you must free the result stored in `text`. |
878 | * |
879 | * Since: 4.2 |
880 | */ |
881 | gboolean |
882 | gtk_im_context_get_surrounding_with_selection (GtkIMContext *context, |
883 | char **text, |
884 | int *cursor_index, |
885 | int *anchor_index) |
886 | { |
887 | GtkIMContextClass *klass; |
888 | char *local_text = NULL; |
889 | int local_index; |
890 | gboolean result = FALSE; |
891 | |
892 | g_return_val_if_fail (GTK_IS_IM_CONTEXT (context), FALSE); |
893 | |
894 | klass = GTK_IM_CONTEXT_GET_CLASS (context); |
895 | if (klass->get_surrounding_with_selection) |
896 | result = klass->get_surrounding_with_selection |
897 | (context, |
898 | text ? text : &local_text, |
899 | cursor_index ? cursor_index : &local_index, |
900 | anchor_index ? anchor_index : &local_index); |
901 | else if (klass->get_surrounding) |
902 | { |
903 | result = klass->get_surrounding (context, |
904 | text ? text : &local_text, |
905 | &local_index); |
906 | if (cursor_index) |
907 | *cursor_index = local_index; |
908 | if (anchor_index) |
909 | *anchor_index = local_index; |
910 | } |
911 | |
912 | if (result) |
913 | g_free (mem: local_text); |
914 | |
915 | return result; |
916 | } |
917 | |
918 | /** |
919 | * gtk_im_context_delete_surrounding: |
920 | * @context: a `GtkIMContext` |
921 | * @offset: offset from cursor position in chars; |
922 | * a negative value means start before the cursor. |
923 | * @n_chars: number of characters to delete. |
924 | * |
925 | * Asks the widget that the input context is attached to delete |
926 | * characters around the cursor position by emitting the |
927 | * `::delete_surrounding` signal. |
928 | * |
929 | * Note that @offset and @n_chars are in characters not in bytes |
930 | * which differs from the usage other places in `GtkIMContext`. |
931 | * |
932 | * In order to use this function, you should first call |
933 | * [method@Gtk.IMContext.get_surrounding] to get the current context, |
934 | * and call this function immediately afterwards to make sure that you |
935 | * know what you are deleting. You should also account for the fact |
936 | * that even if the signal was handled, the input context might not |
937 | * have deleted all the characters that were requested to be deleted. |
938 | * |
939 | * This function is used by an input method that wants to make |
940 | * subsitutions in the existing text in response to new input. |
941 | * It is not useful for applications. |
942 | * |
943 | * Returns: %TRUE if the signal was handled. |
944 | */ |
945 | gboolean |
946 | gtk_im_context_delete_surrounding (GtkIMContext *context, |
947 | int offset, |
948 | int n_chars) |
949 | { |
950 | gboolean result; |
951 | |
952 | g_return_val_if_fail (GTK_IS_IM_CONTEXT (context), FALSE); |
953 | |
954 | g_signal_emit (instance: context, |
955 | signal_id: im_context_signals[DELETE_SURROUNDING], detail: 0, |
956 | offset, n_chars, &result); |
957 | |
958 | return result; |
959 | } |
960 | |
961 | static void |
962 | gtk_im_context_get_property (GObject *obj, |
963 | guint property_id, |
964 | GValue *value, |
965 | GParamSpec *pspec) |
966 | { |
967 | GtkIMContextPrivate *priv = gtk_im_context_get_instance_private (GTK_IM_CONTEXT (obj)); |
968 | |
969 | switch (property_id) |
970 | { |
971 | case PROP_INPUT_PURPOSE: |
972 | g_value_set_enum (value, v_enum: priv->purpose); |
973 | break; |
974 | case PROP_INPUT_HINTS: |
975 | g_value_set_flags (value, v_flags: priv->hints); |
976 | break; |
977 | default: |
978 | G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec); |
979 | break; |
980 | } |
981 | } |
982 | |
983 | static void |
984 | gtk_im_context_set_property (GObject *obj, |
985 | guint property_id, |
986 | const GValue *value, |
987 | GParamSpec *pspec) |
988 | { |
989 | GtkIMContextPrivate *priv = gtk_im_context_get_instance_private (GTK_IM_CONTEXT (obj)); |
990 | |
991 | switch (property_id) |
992 | { |
993 | case PROP_INPUT_PURPOSE: |
994 | if (priv->purpose != g_value_get_enum (value)) |
995 | { |
996 | priv->purpose = g_value_get_enum (value); |
997 | g_object_notify_by_pspec (object: obj, pspec); |
998 | } |
999 | break; |
1000 | case PROP_INPUT_HINTS: |
1001 | if (priv->hints != g_value_get_flags (value)) |
1002 | { |
1003 | priv->hints = g_value_get_flags (value); |
1004 | g_object_notify_by_pspec (object: obj, pspec); |
1005 | } |
1006 | break; |
1007 | default: |
1008 | G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec); |
1009 | break; |
1010 | } |
1011 | } |
1012 | |