1 | /* |
2 | * Copyright © 2018 Benjamin Otte |
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.1 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 | * Authors: Benjamin Otte <otte@gnome.org> |
18 | */ |
19 | |
20 | /** |
21 | * GtkShortcutAction: |
22 | * |
23 | * `GtkShortcutAction` encodes an action that can be triggered by a |
24 | * keyboard shortcut. |
25 | * |
26 | * `GtkShortcutActions` contain functions that allow easy presentation |
27 | * to end users as well as being printed for debugging. |
28 | * |
29 | * All `GtkShortcutActions` are immutable, you can only specify their |
30 | * properties during construction. If you want to change a action, you |
31 | * have to replace it with a new one. If you need to pass arguments to |
32 | * an action, these are specified by the higher-level `GtkShortcut` object. |
33 | * |
34 | * To activate a `GtkShortcutAction` manually, [method@Gtk.ShortcutAction.activate] |
35 | * can be called. |
36 | * |
37 | * GTK provides various actions: |
38 | * |
39 | * - [class@Gtk.MnemonicAction]: a shortcut action that calls |
40 | * gtk_widget_mnemonic_activate() |
41 | * - [class@Gtk.CallbackAction]: a shortcut action that invokes |
42 | * a given callback |
43 | * - [class@Gtk.SignalAction]: a shortcut action that emits a |
44 | * given signal |
45 | * - [class@Gtk.ActivateAction]: a shortcut action that calls |
46 | * gtk_widget_activate() |
47 | * - [class@Gtk.NamedAction]: a shortcut action that calls |
48 | * gtk_widget_activate_action() |
49 | * - [class@Gtk.NothingAction]: a shortcut action that does nothing |
50 | */ |
51 | |
52 | #include "config.h" |
53 | |
54 | #include "gtkshortcutactionprivate.h" |
55 | |
56 | #include "gtkbuilder.h" |
57 | #include "gtkintl.h" |
58 | #include "gtkwidgetprivate.h" |
59 | #include "gtkdebug.h" |
60 | |
61 | /* {{{ GtkShortcutAction */ |
62 | |
63 | struct _GtkShortcutAction |
64 | { |
65 | GObject parent_instance; |
66 | }; |
67 | |
68 | struct _GtkShortcutActionClass |
69 | { |
70 | GObjectClass parent_class; |
71 | |
72 | gboolean (* activate) (GtkShortcutAction *action, |
73 | GtkShortcutActionFlags flags, |
74 | GtkWidget *widget, |
75 | GVariant *args); |
76 | void (* print) (GtkShortcutAction *action, |
77 | GString *string); |
78 | }; |
79 | |
80 | G_DEFINE_ABSTRACT_TYPE (GtkShortcutAction, gtk_shortcut_action, G_TYPE_OBJECT) |
81 | |
82 | static void |
83 | gtk_shortcut_action_class_init (GtkShortcutActionClass *klass) |
84 | { |
85 | } |
86 | |
87 | static void |
88 | gtk_shortcut_action_init (GtkShortcutAction *self) |
89 | { |
90 | } |
91 | |
92 | /** |
93 | * gtk_shortcut_action_to_string: |
94 | * @self: a `GtkShortcutAction` |
95 | * |
96 | * Prints the given action into a human-readable string. |
97 | * |
98 | * This is a small wrapper around [method@Gtk.ShortcutAction.print] |
99 | * to help when debugging. |
100 | * |
101 | * Returns: (transfer full): a new string |
102 | */ |
103 | char * |
104 | gtk_shortcut_action_to_string (GtkShortcutAction *self) |
105 | { |
106 | GString *string; |
107 | |
108 | g_return_val_if_fail (GTK_IS_SHORTCUT_ACTION (self), NULL); |
109 | |
110 | string = g_string_new (NULL); |
111 | gtk_shortcut_action_print (self, string); |
112 | |
113 | return g_string_free (string, FALSE); |
114 | } |
115 | |
116 | /** |
117 | * gtk_shortcut_action_print: |
118 | * @self: a `GtkShortcutAction` |
119 | * @string: a `GString` to print into |
120 | * |
121 | * Prints the given action into a string for the developer. |
122 | * |
123 | * This is meant for debugging and logging. |
124 | * |
125 | * The form of the representation may change at any time and is |
126 | * not guaranteed to stay identical. |
127 | */ |
128 | void |
129 | gtk_shortcut_action_print (GtkShortcutAction *self, |
130 | GString *string) |
131 | { |
132 | g_return_if_fail (GTK_IS_SHORTCUT_ACTION (self)); |
133 | g_return_if_fail (string != NULL); |
134 | |
135 | GTK_SHORTCUT_ACTION_GET_CLASS (ptr: self)->print (self, string); |
136 | } |
137 | |
138 | /** |
139 | * gtk_shortcut_action_activate: |
140 | * @self: a `GtkShortcutAction` |
141 | * @flags: flags to activate with |
142 | * @widget: Target of the activation |
143 | * @args: (nullable): arguments to pass |
144 | * |
145 | * Activates the action on the @widget with the given @args. |
146 | * |
147 | * Note that some actions ignore the passed in @flags, @widget or @args. |
148 | * |
149 | * Activation of an action can fail for various reasons. If the action |
150 | * is not supported by the @widget, if the @args don't match the action |
151 | * or if the activation otherwise had no effect, %FALSE will be returned. |
152 | * |
153 | * Returns: %TRUE if this action was activated successfully |
154 | */ |
155 | gboolean |
156 | gtk_shortcut_action_activate (GtkShortcutAction *self, |
157 | GtkShortcutActionFlags flags, |
158 | GtkWidget *widget, |
159 | GVariant *args) |
160 | { |
161 | g_return_val_if_fail (GTK_IS_SHORTCUT_ACTION (self), FALSE); |
162 | g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); |
163 | |
164 | GTK_NOTE (KEYBINDINGS, { |
165 | char *act = gtk_shortcut_action_to_string (self); |
166 | g_print ("Shortcut action activate on %s: %s\n" , G_OBJECT_TYPE_NAME (widget), act); |
167 | g_free (act); |
168 | }); |
169 | |
170 | return GTK_SHORTCUT_ACTION_GET_CLASS (ptr: self)->activate (self, flags, widget, args); |
171 | } |
172 | |
173 | static char * |
174 | string_is_function (const char *string, |
175 | const char *function_name) |
176 | { |
177 | gsize len; |
178 | |
179 | if (!g_str_has_prefix (str: string, prefix: function_name)) |
180 | return NULL; |
181 | string += strlen (s: function_name); |
182 | |
183 | if (string[0] != '(') |
184 | return NULL; |
185 | string ++; |
186 | |
187 | len = strlen (s: string); |
188 | if (len == 0 || string[len - 1] != ')') |
189 | return NULL; |
190 | |
191 | return g_strndup (str: string, n: len - 1); |
192 | } |
193 | |
194 | /** |
195 | * gtk_shortcut_action_parse_string: (constructor) |
196 | * @string: the string to parse |
197 | * |
198 | * Tries to parse the given string into an action. |
199 | * |
200 | * On success, the parsed action is returned. When parsing |
201 | * failed, %NULL is returned. |
202 | * |
203 | * The accepted strings are: |
204 | * |
205 | * - `nothing`, for `GtkNothingAction` |
206 | * - `activate`, for `GtkActivateAction` |
207 | * - `mnemonic-activate`, for `GtkMnemonicAction` |
208 | * - `action(NAME)`, for a `GtkNamedAction` for the action named `NAME` |
209 | * - `signal(NAME)`, for a `GtkSignalAction` for the signal `NAME` |
210 | * |
211 | * Returns: (nullable) (transfer full): a new `GtkShortcutAction` |
212 | */ |
213 | GtkShortcutAction * |
214 | gtk_shortcut_action_parse_string (const char *string) |
215 | { |
216 | GtkShortcutAction *result; |
217 | char *arg; |
218 | |
219 | if (g_str_equal (v1: string, v2: "nothing" )) |
220 | return g_object_ref (gtk_nothing_action_get ()); |
221 | if (g_str_equal (v1: string, v2: "activate" )) |
222 | return g_object_ref (gtk_activate_action_get ()); |
223 | if (g_str_equal (v1: string, v2: "mnemonic-activate" )) |
224 | return g_object_ref (gtk_mnemonic_action_get ()); |
225 | |
226 | if ((arg = string_is_function (string, function_name: "action" ))) |
227 | { |
228 | result = gtk_named_action_new (name: arg); |
229 | g_free (mem: arg); |
230 | } |
231 | else if ((arg = string_is_function (string, function_name: "signal" ))) |
232 | { |
233 | result = gtk_signal_action_new (signal_name: arg); |
234 | g_free (mem: arg); |
235 | } |
236 | else |
237 | return NULL; |
238 | |
239 | return result; |
240 | } |
241 | |
242 | GtkShortcutAction * |
243 | gtk_shortcut_action_parse_builder (GtkBuilder *builder, |
244 | const char *string, |
245 | GError **error) |
246 | { |
247 | GtkShortcutAction *result; |
248 | |
249 | result = gtk_shortcut_action_parse_string (string); |
250 | |
251 | if (!result) |
252 | { |
253 | g_set_error (err: error, |
254 | GTK_BUILDER_ERROR, code: GTK_BUILDER_ERROR_INVALID_VALUE, |
255 | format: "String \"%s\" does not specify a GtkShortcutAction" , string); |
256 | return NULL; |
257 | } |
258 | |
259 | return result; |
260 | } |
261 | |
262 | /* }}} */ |
263 | |
264 | /* {{{ GtkNothingAction */ |
265 | |
266 | struct _GtkNothingAction |
267 | { |
268 | GtkShortcutAction parent_instance; |
269 | }; |
270 | |
271 | struct _GtkNothingActionClass |
272 | { |
273 | GtkShortcutActionClass parent_class; |
274 | }; |
275 | |
276 | G_DEFINE_TYPE (GtkNothingAction, gtk_nothing_action, GTK_TYPE_SHORTCUT_ACTION) |
277 | |
278 | static void G_GNUC_NORETURN |
279 | gtk_nothing_action_finalize (GObject *gobject) |
280 | { |
281 | g_assert_not_reached (); |
282 | |
283 | G_OBJECT_CLASS (gtk_nothing_action_parent_class)->finalize (gobject); |
284 | } |
285 | |
286 | static gboolean |
287 | gtk_nothing_action_activate (GtkShortcutAction *action, |
288 | GtkShortcutActionFlags flags, |
289 | GtkWidget *widget, |
290 | GVariant *args) |
291 | { |
292 | return FALSE; |
293 | } |
294 | |
295 | static void |
296 | gtk_nothing_action_print (GtkShortcutAction *action, |
297 | GString *string) |
298 | { |
299 | g_string_append (string, val: "nothing" ); |
300 | } |
301 | |
302 | static void |
303 | gtk_nothing_action_class_init (GtkNothingActionClass *klass) |
304 | { |
305 | GtkShortcutActionClass *action_class = GTK_SHORTCUT_ACTION_CLASS (ptr: klass); |
306 | |
307 | G_OBJECT_CLASS (klass)->finalize = gtk_nothing_action_finalize; |
308 | |
309 | action_class->activate = gtk_nothing_action_activate; |
310 | action_class->print = gtk_nothing_action_print; |
311 | } |
312 | |
313 | static void |
314 | gtk_nothing_action_init (GtkNothingAction *self) |
315 | { |
316 | } |
317 | |
318 | /** |
319 | * gtk_nothing_action_get: |
320 | * |
321 | * Gets the nothing action. |
322 | * |
323 | * This is an action that does nothing and where |
324 | * activating it always fails. |
325 | * |
326 | * Returns: (transfer none) (type GtkNothingAction): The nothing action |
327 | */ |
328 | GtkShortcutAction * |
329 | gtk_nothing_action_get (void) |
330 | { |
331 | static GtkShortcutAction *nothing; |
332 | |
333 | if (nothing == NULL) |
334 | nothing = g_object_new (GTK_TYPE_NOTHING_ACTION, NULL); |
335 | |
336 | return nothing; |
337 | } |
338 | |
339 | /* }}} */ |
340 | |
341 | /* {{{ GtkCallbackAction */ |
342 | |
343 | struct _GtkCallbackAction |
344 | { |
345 | GtkShortcutAction parent_instance; |
346 | |
347 | GtkShortcutFunc callback; |
348 | gpointer user_data; |
349 | GDestroyNotify destroy_notify; |
350 | }; |
351 | |
352 | struct _GtkCallbackActionClass |
353 | { |
354 | GtkShortcutActionClass parent_class; |
355 | }; |
356 | |
357 | G_DEFINE_TYPE (GtkCallbackAction, gtk_callback_action, GTK_TYPE_SHORTCUT_ACTION) |
358 | |
359 | static void |
360 | gtk_callback_action_finalize (GObject *gobject) |
361 | { |
362 | GtkCallbackAction *self = GTK_CALLBACK_ACTION (ptr: gobject); |
363 | |
364 | if (self->destroy_notify != NULL) |
365 | self->destroy_notify (self->user_data); |
366 | |
367 | G_OBJECT_CLASS (gtk_callback_action_parent_class)->finalize (gobject); |
368 | } |
369 | |
370 | static gboolean |
371 | gtk_callback_action_activate (GtkShortcutAction *action, |
372 | GtkShortcutActionFlags flags, |
373 | GtkWidget *widget, |
374 | GVariant *args) |
375 | { |
376 | GtkCallbackAction *self = GTK_CALLBACK_ACTION (ptr: action); |
377 | |
378 | return self->callback (widget, args, self->user_data); |
379 | } |
380 | |
381 | static void |
382 | gtk_callback_action_print (GtkShortcutAction *action, |
383 | GString *string) |
384 | { |
385 | GtkCallbackAction *self = GTK_CALLBACK_ACTION (ptr: action); |
386 | |
387 | g_string_append_printf (string, format: "callback<%p>" , self->callback); |
388 | } |
389 | |
390 | static void |
391 | gtk_callback_action_class_init (GtkCallbackActionClass *klass) |
392 | { |
393 | GtkShortcutActionClass *action_class = GTK_SHORTCUT_ACTION_CLASS (ptr: klass); |
394 | |
395 | G_OBJECT_CLASS (klass)->finalize = gtk_callback_action_finalize; |
396 | |
397 | action_class->activate = gtk_callback_action_activate; |
398 | action_class->print = gtk_callback_action_print; |
399 | } |
400 | |
401 | static void |
402 | gtk_callback_action_init (GtkCallbackAction *self) |
403 | { |
404 | } |
405 | |
406 | /** |
407 | * gtk_callback_action_new: |
408 | * @callback: (scope notified): the callback to call |
409 | * @data: (closure callback): the data to be passed to @callback |
410 | * @destroy: (destroy data): the function to be called when the |
411 | * callback action is finalized |
412 | * |
413 | * Create a custom action that calls the given @callback when |
414 | * activated. |
415 | * |
416 | * Returns: (transfer full) (type GtkCallbackAction): A new shortcut action |
417 | */ |
418 | GtkShortcutAction * |
419 | gtk_callback_action_new (GtkShortcutFunc callback, |
420 | gpointer data, |
421 | GDestroyNotify destroy) |
422 | { |
423 | GtkCallbackAction *self; |
424 | |
425 | g_return_val_if_fail (callback != NULL, NULL); |
426 | |
427 | self = g_object_new (GTK_TYPE_CALLBACK_ACTION, NULL); |
428 | |
429 | self->callback = callback; |
430 | self->user_data = data; |
431 | self->destroy_notify = destroy; |
432 | |
433 | return GTK_SHORTCUT_ACTION (ptr: self); |
434 | } |
435 | |
436 | /* }}} */ |
437 | |
438 | /* {{{ GtkActivateAction */ |
439 | |
440 | struct _GtkActivateAction |
441 | { |
442 | GtkShortcutAction parent_instance; |
443 | }; |
444 | |
445 | struct _GtkActivateActionClass |
446 | { |
447 | GtkShortcutActionClass parent_class; |
448 | }; |
449 | |
450 | G_DEFINE_TYPE (GtkActivateAction, gtk_activate_action, GTK_TYPE_SHORTCUT_ACTION) |
451 | |
452 | static void G_GNUC_NORETURN |
453 | gtk_activate_action_finalize (GObject *gobject) |
454 | { |
455 | g_assert_not_reached (); |
456 | |
457 | G_OBJECT_CLASS (gtk_activate_action_parent_class)->finalize (gobject); |
458 | } |
459 | |
460 | static gboolean |
461 | gtk_activate_action_activate (GtkShortcutAction *action, |
462 | GtkShortcutActionFlags flags, |
463 | GtkWidget *widget, |
464 | GVariant *args) |
465 | { |
466 | return gtk_widget_activate (widget); |
467 | } |
468 | |
469 | static void |
470 | gtk_activate_action_print (GtkShortcutAction *action, |
471 | GString *string) |
472 | { |
473 | g_string_append (string, val: "activate" ); |
474 | } |
475 | |
476 | static void |
477 | gtk_activate_action_class_init (GtkActivateActionClass *klass) |
478 | { |
479 | GtkShortcutActionClass *action_class = GTK_SHORTCUT_ACTION_CLASS (ptr: klass); |
480 | |
481 | G_OBJECT_CLASS (klass)->finalize = gtk_activate_action_finalize; |
482 | |
483 | action_class->activate = gtk_activate_action_activate; |
484 | action_class->print = gtk_activate_action_print; |
485 | } |
486 | |
487 | static void |
488 | gtk_activate_action_init (GtkActivateAction *self) |
489 | { |
490 | } |
491 | |
492 | /** |
493 | * gtk_activate_action_get: |
494 | * |
495 | * Gets the activate action. |
496 | * |
497 | * This is an action that calls gtk_widget_activate() |
498 | * on the given widget upon activation. |
499 | * |
500 | * Returns: (transfer none) (type GtkActivateAction): The activate action |
501 | */ |
502 | GtkShortcutAction * |
503 | gtk_activate_action_get (void) |
504 | { |
505 | static GtkShortcutAction *action; |
506 | |
507 | if (action == NULL) |
508 | action = g_object_new (GTK_TYPE_ACTIVATE_ACTION, NULL); |
509 | |
510 | return action; |
511 | } |
512 | |
513 | /* }}} */ |
514 | |
515 | /* {{{ GtkMnemonicAction */ |
516 | |
517 | struct _GtkMnemonicAction |
518 | { |
519 | GtkShortcutAction parent_instance; |
520 | }; |
521 | |
522 | struct _GtkMnemonicActionClass |
523 | { |
524 | GtkShortcutActionClass parent_class; |
525 | }; |
526 | |
527 | G_DEFINE_TYPE (GtkMnemonicAction, gtk_mnemonic_action, GTK_TYPE_SHORTCUT_ACTION) |
528 | |
529 | static void G_GNUC_NORETURN |
530 | gtk_mnemonic_action_finalize (GObject *gobject) |
531 | { |
532 | g_assert_not_reached (); |
533 | |
534 | G_OBJECT_CLASS (gtk_mnemonic_action_parent_class)->finalize (gobject); |
535 | } |
536 | |
537 | static gboolean |
538 | gtk_mnemonic_action_activate (GtkShortcutAction *action, |
539 | GtkShortcutActionFlags flags, |
540 | GtkWidget *widget, |
541 | GVariant *args) |
542 | { |
543 | return gtk_widget_mnemonic_activate (widget, group_cycling: flags & GTK_SHORTCUT_ACTION_EXCLUSIVE ? FALSE : TRUE); |
544 | } |
545 | |
546 | static void |
547 | gtk_mnemonic_action_print (GtkShortcutAction *action, |
548 | GString *string) |
549 | { |
550 | g_string_append (string, val: "mnemonic-activate" ); |
551 | } |
552 | |
553 | static void |
554 | gtk_mnemonic_action_class_init (GtkMnemonicActionClass *klass) |
555 | { |
556 | GtkShortcutActionClass *action_class = GTK_SHORTCUT_ACTION_CLASS (ptr: klass); |
557 | |
558 | G_OBJECT_CLASS (klass)->finalize = gtk_mnemonic_action_finalize; |
559 | |
560 | action_class->activate = gtk_mnemonic_action_activate; |
561 | action_class->print = gtk_mnemonic_action_print; |
562 | } |
563 | |
564 | static void |
565 | gtk_mnemonic_action_init (GtkMnemonicAction *self) |
566 | { |
567 | } |
568 | |
569 | /** |
570 | * gtk_mnemonic_action_get: |
571 | * |
572 | * Gets the mnemonic action. |
573 | * |
574 | * This is an action that calls gtk_widget_mnemonic_activate() |
575 | * on the given widget upon activation. |
576 | * |
577 | * Returns: (transfer none) (type GtkMnemonicAction): The mnemonic action |
578 | */ |
579 | GtkShortcutAction * |
580 | gtk_mnemonic_action_get (void) |
581 | { |
582 | static GtkShortcutAction *mnemonic; |
583 | |
584 | if (G_UNLIKELY (mnemonic == NULL)) |
585 | mnemonic = g_object_new (GTK_TYPE_MNEMONIC_ACTION, NULL); |
586 | |
587 | return mnemonic; |
588 | } |
589 | |
590 | /* }}} */ |
591 | |
592 | /* {{{ GtkSignalAction */ |
593 | |
594 | struct _GtkSignalAction |
595 | { |
596 | GtkShortcutAction parent_instance; |
597 | |
598 | char *name; |
599 | }; |
600 | |
601 | struct _GtkSignalActionClass |
602 | { |
603 | GtkShortcutActionClass parent_class; |
604 | }; |
605 | |
606 | enum |
607 | { |
608 | SIGNAL_PROP_SIGNAL_NAME = 1, |
609 | SIGNAL_N_PROPS |
610 | }; |
611 | |
612 | static GParamSpec *signal_props[SIGNAL_N_PROPS]; |
613 | |
614 | G_DEFINE_TYPE (GtkSignalAction, gtk_signal_action, GTK_TYPE_SHORTCUT_ACTION) |
615 | |
616 | static void |
617 | gtk_signal_action_finalize (GObject *gobject) |
618 | { |
619 | GtkSignalAction *self = GTK_SIGNAL_ACTION (ptr: gobject); |
620 | |
621 | g_free (mem: self->name); |
622 | |
623 | G_OBJECT_CLASS (gtk_signal_action_parent_class)->finalize (gobject); |
624 | } |
625 | |
626 | static gboolean |
627 | binding_compose_params (GtkWidget *widget, |
628 | GVariantIter *args, |
629 | GSignalQuery *query, |
630 | GValue **params_p) |
631 | { |
632 | GValue *params; |
633 | const GType *types; |
634 | guint i; |
635 | gboolean valid; |
636 | |
637 | params = g_new0 (GValue, query->n_params + 1); |
638 | *params_p = params; |
639 | |
640 | /* The instance we emit on is the first object in the array |
641 | */ |
642 | g_value_init (value: params, G_TYPE_OBJECT); |
643 | g_value_set_object (value: params, G_OBJECT (widget)); |
644 | params++; |
645 | |
646 | types = query->param_types; |
647 | valid = TRUE; |
648 | for (i = 1; i < query->n_params + 1 && valid; i++) |
649 | { |
650 | GValue tmp_value = G_VALUE_INIT; |
651 | GVariant *tmp_variant; |
652 | |
653 | g_value_init (value: params, g_type: *types); |
654 | tmp_variant = g_variant_iter_next_value (iter: args); |
655 | |
656 | switch ((guint) g_variant_classify (value: tmp_variant)) |
657 | { |
658 | case G_VARIANT_CLASS_BOOLEAN: |
659 | g_value_init (value: &tmp_value, G_TYPE_BOOLEAN); |
660 | g_value_set_boolean (value: &tmp_value, v_boolean: g_variant_get_boolean (value: tmp_variant)); |
661 | break; |
662 | case G_VARIANT_CLASS_DOUBLE: |
663 | g_value_init (value: &tmp_value, G_TYPE_DOUBLE); |
664 | g_value_set_double (value: &tmp_value, v_double: g_variant_get_double (value: tmp_variant)); |
665 | break; |
666 | case G_VARIANT_CLASS_INT32: |
667 | g_value_init (value: &tmp_value, G_TYPE_LONG); |
668 | g_value_set_long (value: &tmp_value, v_long: g_variant_get_int32 (value: tmp_variant)); |
669 | break; |
670 | case G_VARIANT_CLASS_UINT32: |
671 | g_value_init (value: &tmp_value, G_TYPE_LONG); |
672 | g_value_set_long (value: &tmp_value, v_long: g_variant_get_uint32 (value: tmp_variant)); |
673 | break; |
674 | case G_VARIANT_CLASS_INT64: |
675 | g_value_init (value: &tmp_value, G_TYPE_LONG); |
676 | g_value_set_long (value: &tmp_value, v_long: g_variant_get_int64 (value: tmp_variant)); |
677 | break; |
678 | case G_VARIANT_CLASS_STRING: |
679 | /* gtk_rc_parse_flags/enum() has fancier parsing for this; we can't call |
680 | * that since we don't have a GParamSpec, so just do something simple |
681 | */ |
682 | if (G_TYPE_FUNDAMENTAL (*types) == G_TYPE_ENUM) |
683 | { |
684 | GEnumClass *class = G_ENUM_CLASS (g_type_class_ref (*types)); |
685 | GEnumValue *enum_value; |
686 | const char *s = g_variant_get_string (value: tmp_variant, NULL); |
687 | |
688 | valid = FALSE; |
689 | |
690 | enum_value = g_enum_get_value_by_name (enum_class: class, name: s); |
691 | if (!enum_value) |
692 | enum_value = g_enum_get_value_by_nick (enum_class: class, nick: s); |
693 | |
694 | if (enum_value) |
695 | { |
696 | g_value_init (value: &tmp_value, g_type: *types); |
697 | g_value_set_enum (value: &tmp_value, v_enum: enum_value->value); |
698 | valid = TRUE; |
699 | } |
700 | |
701 | g_type_class_unref (g_class: class); |
702 | } |
703 | /* This is just a hack for compatibility with GTK 1.2 where a string |
704 | * could be used for a single flag value / without the support for multiple |
705 | * values in gtk_rc_parse_flags(), this isn't very useful. |
706 | */ |
707 | else if (G_TYPE_FUNDAMENTAL (*types) == G_TYPE_FLAGS) |
708 | { |
709 | GFlagsClass *class = G_FLAGS_CLASS (g_type_class_ref (*types)); |
710 | GFlagsValue *flags_value; |
711 | const char *s = g_variant_get_string (value: tmp_variant, NULL); |
712 | |
713 | valid = FALSE; |
714 | |
715 | flags_value = g_flags_get_value_by_name (flags_class: class, name: s); |
716 | if (!flags_value) |
717 | flags_value = g_flags_get_value_by_nick (flags_class: class, nick: s); |
718 | if (flags_value) |
719 | { |
720 | g_value_init (value: &tmp_value, g_type: *types); |
721 | g_value_set_flags (value: &tmp_value, v_flags: flags_value->value); |
722 | valid = TRUE; |
723 | } |
724 | |
725 | g_type_class_unref (g_class: class); |
726 | } |
727 | else |
728 | { |
729 | g_value_init (value: &tmp_value, G_TYPE_STRING); |
730 | g_value_set_static_string (value: &tmp_value, v_string: g_variant_get_string (value: tmp_variant, NULL)); |
731 | } |
732 | break; |
733 | default: |
734 | valid = FALSE; |
735 | break; |
736 | } |
737 | |
738 | if (valid) |
739 | { |
740 | if (!g_value_transform (src_value: &tmp_value, dest_value: params)) |
741 | valid = FALSE; |
742 | |
743 | g_value_unset (value: &tmp_value); |
744 | } |
745 | |
746 | g_variant_unref (value: tmp_variant); |
747 | types++; |
748 | params++; |
749 | } |
750 | |
751 | if (!valid) |
752 | { |
753 | guint j; |
754 | |
755 | for (j = 0; j < i; j++) |
756 | g_value_unset (value: &(*params_p)[j]); |
757 | |
758 | g_free (mem: *params_p); |
759 | *params_p = NULL; |
760 | } |
761 | |
762 | return valid; |
763 | } |
764 | |
765 | static gboolean |
766 | gtk_signal_action_emit_signal (GtkWidget *widget, |
767 | const char *signal, |
768 | GVariant *args, |
769 | gboolean *handled, |
770 | GError **error) |
771 | { |
772 | GSignalQuery query; |
773 | guint signal_id; |
774 | GValue *params = NULL; |
775 | GValue return_val = G_VALUE_INIT; |
776 | GVariantIter args_iter; |
777 | gsize n_args; |
778 | guint i; |
779 | |
780 | *handled = FALSE; |
781 | |
782 | signal_id = g_signal_lookup (name: signal, G_OBJECT_TYPE (widget)); |
783 | if (!signal_id) |
784 | { |
785 | g_set_error (err: error, G_IO_ERROR, code: G_IO_ERROR_FAILED, |
786 | format: "Could not find signal \"%s\" in the '%s' class ancestry" , |
787 | signal, |
788 | g_type_name (G_OBJECT_TYPE (widget))); |
789 | return FALSE; |
790 | } |
791 | |
792 | g_signal_query (signal_id, query: &query); |
793 | if (args == NULL) |
794 | n_args = 0; |
795 | else if (g_variant_is_of_type (value: args, G_VARIANT_TYPE_TUPLE)) |
796 | n_args = g_variant_iter_init (iter: &args_iter, value: args); |
797 | else |
798 | { |
799 | g_set_error (err: error, G_IO_ERROR, code: G_IO_ERROR_FAILED, |
800 | format: "argument GVariant is not a tuple" ); |
801 | return FALSE; |
802 | } |
803 | if (query.n_params != n_args || |
804 | (query.return_type != G_TYPE_NONE && query.return_type != G_TYPE_BOOLEAN) || |
805 | !binding_compose_params (widget, args: &args_iter, query: &query, params_p: ¶ms)) |
806 | { |
807 | g_set_error (err: error, G_IO_ERROR, code: G_IO_ERROR_FAILED, |
808 | format: "signature mismatch for signal \"%s\" in the '%s' class ancestry" , |
809 | signal, |
810 | g_type_name (G_OBJECT_TYPE (widget))); |
811 | return FALSE; |
812 | } |
813 | else if (!(query.signal_flags & G_SIGNAL_ACTION)) |
814 | { |
815 | g_set_error (err: error, G_IO_ERROR, code: G_IO_ERROR_FAILED, |
816 | format: "signal \"%s\" in the '%s' class ancestry cannot be used for action emissions" , |
817 | signal, |
818 | g_type_name (G_OBJECT_TYPE (widget))); |
819 | return FALSE; |
820 | } |
821 | |
822 | if (query.return_type == G_TYPE_BOOLEAN) |
823 | g_value_init (value: &return_val, G_TYPE_BOOLEAN); |
824 | |
825 | g_signal_emitv (instance_and_params: params, signal_id, detail: 0, return_value: &return_val); |
826 | |
827 | if (query.return_type == G_TYPE_BOOLEAN) |
828 | { |
829 | if (g_value_get_boolean (value: &return_val)) |
830 | *handled = TRUE; |
831 | g_value_unset (value: &return_val); |
832 | } |
833 | else |
834 | *handled = TRUE; |
835 | |
836 | if (params != NULL) |
837 | { |
838 | for (i = 0; i < query.n_params + 1; i++) |
839 | g_value_unset (value: ¶ms[i]); |
840 | |
841 | g_free (mem: params); |
842 | } |
843 | |
844 | return TRUE; |
845 | } |
846 | |
847 | static gboolean |
848 | gtk_signal_action_activate (GtkShortcutAction *action, |
849 | GtkShortcutActionFlags flags, |
850 | GtkWidget *widget, |
851 | GVariant *args) |
852 | { |
853 | GtkSignalAction *self = GTK_SIGNAL_ACTION (ptr: action); |
854 | GError *error = NULL; |
855 | gboolean handled; |
856 | |
857 | if (!gtk_signal_action_emit_signal (widget, |
858 | signal: self->name, |
859 | args, |
860 | handled: &handled, |
861 | error: &error)) |
862 | { |
863 | g_warning ("gtk_signal_action_activate(): %s" , |
864 | error->message); |
865 | g_clear_error (err: &error); |
866 | return FALSE; |
867 | } |
868 | |
869 | return handled; |
870 | } |
871 | |
872 | static void |
873 | gtk_signal_action_print (GtkShortcutAction *action, |
874 | GString *string) |
875 | { |
876 | GtkSignalAction *self = GTK_SIGNAL_ACTION (ptr: action); |
877 | |
878 | g_string_append_printf (string, format: "signal(%s)" , self->name); |
879 | } |
880 | |
881 | static void |
882 | gtk_signal_action_constructed (GObject *gobject) |
883 | { |
884 | GtkSignalAction *self G_GNUC_UNUSED = GTK_SIGNAL_ACTION (ptr: gobject); |
885 | |
886 | g_assert (self->name != NULL && self->name[0] != '\0'); |
887 | |
888 | G_OBJECT_CLASS (gtk_signal_action_parent_class)->constructed (gobject); |
889 | } |
890 | |
891 | static void |
892 | gtk_signal_action_set_property (GObject *gobject, |
893 | guint prop_id, |
894 | const GValue *value, |
895 | GParamSpec *pspec) |
896 | { |
897 | GtkSignalAction *self = GTK_SIGNAL_ACTION (ptr: gobject); |
898 | |
899 | switch (prop_id) |
900 | { |
901 | case SIGNAL_PROP_SIGNAL_NAME: |
902 | self->name = g_value_dup_string (value); |
903 | break; |
904 | |
905 | default: |
906 | G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); |
907 | } |
908 | } |
909 | |
910 | static void |
911 | gtk_signal_action_get_property (GObject *gobject, |
912 | guint prop_id, |
913 | GValue *value, |
914 | GParamSpec *pspec) |
915 | { |
916 | GtkSignalAction *self = GTK_SIGNAL_ACTION (ptr: gobject); |
917 | |
918 | switch (prop_id) |
919 | { |
920 | case SIGNAL_PROP_SIGNAL_NAME: |
921 | g_value_set_string (value, v_string: self->name); |
922 | break; |
923 | |
924 | default: |
925 | G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); |
926 | } |
927 | } |
928 | |
929 | static void |
930 | gtk_signal_action_class_init (GtkSignalActionClass *klass) |
931 | { |
932 | GtkShortcutActionClass *action_class = GTK_SHORTCUT_ACTION_CLASS (ptr: klass); |
933 | GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
934 | |
935 | gobject_class->constructed = gtk_signal_action_constructed; |
936 | gobject_class->set_property = gtk_signal_action_set_property; |
937 | gobject_class->get_property = gtk_signal_action_get_property; |
938 | gobject_class->finalize = gtk_signal_action_finalize; |
939 | |
940 | action_class->activate = gtk_signal_action_activate; |
941 | action_class->print = gtk_signal_action_print; |
942 | |
943 | /** |
944 | * GtkSignalAction:signal-name: (attributes org.gtk.Property.get=gtk_signal_action_get_signal_name) |
945 | * |
946 | * The name of the signal to emit. |
947 | */ |
948 | signal_props[SIGNAL_PROP_SIGNAL_NAME] = |
949 | g_param_spec_string (I_("signal-name" ), |
950 | P_("Signal Name" ), |
951 | P_("The name of the signal to emit" ), |
952 | NULL, |
953 | G_PARAM_STATIC_STRINGS | |
954 | G_PARAM_READWRITE | |
955 | G_PARAM_CONSTRUCT_ONLY); |
956 | |
957 | g_object_class_install_properties (oclass: gobject_class, n_pspecs: SIGNAL_N_PROPS, pspecs: signal_props); |
958 | } |
959 | |
960 | static void |
961 | gtk_signal_action_init (GtkSignalAction *self) |
962 | { |
963 | } |
964 | |
965 | /** |
966 | * gtk_signal_action_new: |
967 | * @signal_name: name of the signal to emit |
968 | * |
969 | * Creates an action that when activated, emits the given action signal |
970 | * on the provided widget. |
971 | * |
972 | * It will also unpack the args into arguments passed to the signal. |
973 | * |
974 | * Returns: (transfer full) (type GtkSignalAction): a new `GtkShortcutAction` |
975 | */ |
976 | GtkShortcutAction * |
977 | gtk_signal_action_new (const char *signal_name) |
978 | { |
979 | g_return_val_if_fail (signal_name != NULL, NULL); |
980 | |
981 | return g_object_new (GTK_TYPE_SIGNAL_ACTION, |
982 | first_property_name: "signal-name" , signal_name, |
983 | NULL); |
984 | } |
985 | |
986 | /** |
987 | * gtk_signal_action_get_signal_name: (attributes org.gtk.Method.get_property=signal-name) |
988 | * @self: a signal action |
989 | * |
990 | * Returns the name of the signal that will be emitted. |
991 | * |
992 | * Returns: (transfer none): the name of the signal to emit |
993 | */ |
994 | const char * |
995 | gtk_signal_action_get_signal_name (GtkSignalAction *self) |
996 | { |
997 | g_return_val_if_fail (GTK_IS_SIGNAL_ACTION (self), NULL); |
998 | |
999 | return self->name; |
1000 | } |
1001 | |
1002 | /* }}} */ |
1003 | |
1004 | /* {{{ GtkNamedAction */ |
1005 | |
1006 | struct _GtkNamedAction |
1007 | { |
1008 | GtkShortcutAction parent_instance; |
1009 | |
1010 | char *name; |
1011 | }; |
1012 | |
1013 | struct _GtkNamedActionClass |
1014 | { |
1015 | GtkShortcutActionClass parent_class; |
1016 | }; |
1017 | |
1018 | enum |
1019 | { |
1020 | NAMED_PROP_ACTION_NAME = 1, |
1021 | NAMED_N_PROPS |
1022 | }; |
1023 | |
1024 | static GParamSpec *named_props[NAMED_N_PROPS]; |
1025 | |
1026 | G_DEFINE_TYPE (GtkNamedAction, gtk_named_action, GTK_TYPE_SHORTCUT_ACTION) |
1027 | |
1028 | static void |
1029 | gtk_named_action_finalize (GObject *gobject) |
1030 | { |
1031 | GtkNamedAction *self = GTK_NAMED_ACTION (ptr: gobject); |
1032 | |
1033 | g_free (mem: self->name); |
1034 | |
1035 | G_OBJECT_CLASS (gtk_named_action_parent_class)->finalize (gobject); |
1036 | } |
1037 | |
1038 | static gboolean |
1039 | check_parameter_type (GVariant *args, |
1040 | const GVariantType *parameter_type) |
1041 | { |
1042 | if (args) |
1043 | { |
1044 | if (parameter_type == NULL) |
1045 | { |
1046 | g_warning ("Trying to invoke action with arguments, but action has no parameter" ); |
1047 | return FALSE; |
1048 | } |
1049 | |
1050 | if (!g_variant_is_of_type (value: args, type: parameter_type)) |
1051 | { |
1052 | char *typestr = g_variant_type_dup_string (type: parameter_type); |
1053 | char *targetstr = g_variant_print (value: args, TRUE); |
1054 | g_warning ("Trying to invoke action with target '%s'," |
1055 | " but action expects parameter with type '%s'" , targetstr, typestr); |
1056 | g_free (mem: targetstr); |
1057 | g_free (mem: typestr); |
1058 | return FALSE; |
1059 | } |
1060 | } |
1061 | else |
1062 | { |
1063 | if (parameter_type != NULL) |
1064 | { |
1065 | char *typestr = g_variant_type_dup_string (type: parameter_type); |
1066 | g_warning ("Trying to invoke action without arguments," |
1067 | " but action expects parameter with type '%s'" , typestr); |
1068 | g_free (mem: typestr); |
1069 | return FALSE; |
1070 | } |
1071 | } |
1072 | |
1073 | return TRUE; |
1074 | } |
1075 | |
1076 | static gboolean |
1077 | gtk_named_action_activate (GtkShortcutAction *action, |
1078 | GtkShortcutActionFlags flags, |
1079 | GtkWidget *widget, |
1080 | GVariant *args) |
1081 | { |
1082 | GtkNamedAction *self = GTK_NAMED_ACTION (ptr: action); |
1083 | const GVariantType *parameter_type; |
1084 | GtkActionMuxer *muxer; |
1085 | gboolean enabled; |
1086 | |
1087 | muxer = _gtk_widget_get_action_muxer (widget, FALSE); |
1088 | if (muxer == NULL) |
1089 | return FALSE; |
1090 | |
1091 | if (!gtk_action_muxer_query_action (muxer, action_name: self->name, |
1092 | enabled: &enabled, parameter_type: ¶meter_type, |
1093 | NULL, NULL, NULL)) |
1094 | return FALSE; |
1095 | |
1096 | if (!enabled) |
1097 | return FALSE; |
1098 | |
1099 | /* We found an action with the correct name and it's enabled. |
1100 | * This is the action that we are going to try to invoke. |
1101 | * |
1102 | * There is still the possibility that the args don't |
1103 | * match the expected parameter type. In that case, we will print |
1104 | * a warning. |
1105 | */ |
1106 | if (!check_parameter_type (args, parameter_type)) |
1107 | return FALSE; |
1108 | |
1109 | gtk_action_muxer_activate_action (muxer, action_name: self->name, parameter: args); |
1110 | |
1111 | return TRUE; |
1112 | } |
1113 | |
1114 | static void |
1115 | gtk_named_action_print (GtkShortcutAction *action, |
1116 | GString *string) |
1117 | { |
1118 | GtkNamedAction *self = GTK_NAMED_ACTION (ptr: action); |
1119 | |
1120 | g_string_append_printf (string, format: "action(%s)" , self->name); |
1121 | } |
1122 | |
1123 | static void |
1124 | gtk_named_action_set_property (GObject *gobject, |
1125 | guint prop_id, |
1126 | const GValue *value, |
1127 | GParamSpec *pspec) |
1128 | { |
1129 | GtkNamedAction *self = GTK_NAMED_ACTION (ptr: gobject); |
1130 | |
1131 | switch (prop_id) |
1132 | { |
1133 | case NAMED_PROP_ACTION_NAME: |
1134 | self->name = g_value_dup_string (value); |
1135 | break; |
1136 | |
1137 | default: |
1138 | G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); |
1139 | } |
1140 | } |
1141 | |
1142 | static void |
1143 | gtk_named_action_get_property (GObject *gobject, |
1144 | guint prop_id, |
1145 | GValue *value, |
1146 | GParamSpec *pspec) |
1147 | { |
1148 | GtkNamedAction *self = GTK_NAMED_ACTION (ptr: gobject); |
1149 | |
1150 | switch (prop_id) |
1151 | { |
1152 | case NAMED_PROP_ACTION_NAME: |
1153 | g_value_set_string (value, v_string: self->name); |
1154 | break; |
1155 | |
1156 | default: |
1157 | G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); |
1158 | } |
1159 | } |
1160 | |
1161 | static void |
1162 | gtk_named_action_constructed (GObject *gobject) |
1163 | { |
1164 | GtkNamedAction *self G_GNUC_UNUSED = GTK_NAMED_ACTION (ptr: gobject); |
1165 | |
1166 | g_assert (self->name != NULL && self->name[0] != '\0'); |
1167 | |
1168 | G_OBJECT_CLASS (gtk_named_action_parent_class)->constructed (gobject); |
1169 | } |
1170 | |
1171 | static void |
1172 | gtk_named_action_class_init (GtkNamedActionClass *klass) |
1173 | { |
1174 | GtkShortcutActionClass *action_class = GTK_SHORTCUT_ACTION_CLASS (ptr: klass); |
1175 | GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
1176 | |
1177 | gobject_class->constructed = gtk_named_action_constructed; |
1178 | gobject_class->set_property = gtk_named_action_set_property; |
1179 | gobject_class->get_property = gtk_named_action_get_property; |
1180 | gobject_class->finalize = gtk_named_action_finalize; |
1181 | |
1182 | action_class->activate = gtk_named_action_activate; |
1183 | action_class->print = gtk_named_action_print; |
1184 | |
1185 | /** |
1186 | * GtkNamedAction:action-name: (attributes org.gtk.Property.get=gtk_named_action_get_action_name) |
1187 | * |
1188 | * The name of the action to activate. |
1189 | */ |
1190 | named_props[NAMED_PROP_ACTION_NAME] = |
1191 | g_param_spec_string (I_("action-name" ), |
1192 | P_("Action Name" ), |
1193 | P_("The name of the action to activate" ), |
1194 | NULL, |
1195 | G_PARAM_STATIC_STRINGS | |
1196 | G_PARAM_READWRITE | |
1197 | G_PARAM_CONSTRUCT_ONLY); |
1198 | |
1199 | g_object_class_install_properties (oclass: gobject_class, n_pspecs: NAMED_N_PROPS, pspecs: named_props); |
1200 | } |
1201 | |
1202 | static void |
1203 | gtk_named_action_init (GtkNamedAction *self) |
1204 | { |
1205 | } |
1206 | |
1207 | /** |
1208 | * gtk_named_action_new: |
1209 | * @name: the detailed name of the action |
1210 | * |
1211 | * Creates an action that when activated, activates |
1212 | * the named action on the widget. |
1213 | * |
1214 | * It also passes the given arguments to it. |
1215 | * |
1216 | * See [method@Gtk.Widget.insert_action_group] for |
1217 | * how to add actions to widgets. |
1218 | * |
1219 | * Returns: (transfer full) (type GtkNamedAction): a new `GtkShortcutAction` |
1220 | */ |
1221 | GtkShortcutAction * |
1222 | gtk_named_action_new (const char *name) |
1223 | { |
1224 | g_return_val_if_fail (name != NULL, NULL); |
1225 | |
1226 | return g_object_new (GTK_TYPE_NAMED_ACTION, |
1227 | first_property_name: "action-name" , name, |
1228 | NULL); |
1229 | } |
1230 | |
1231 | /** |
1232 | * gtk_named_action_get_action_name: (attributes org.gtk.Method.get_property=action-name) |
1233 | * @self: a named action |
1234 | * |
1235 | * Returns the name of the action that will be activated. |
1236 | * |
1237 | * Returns: the name of the action to activate |
1238 | */ |
1239 | const char * |
1240 | gtk_named_action_get_action_name (GtkNamedAction *self) |
1241 | { |
1242 | g_return_val_if_fail (GTK_IS_NAMED_ACTION (self), NULL); |
1243 | |
1244 | return self->name; |
1245 | } |
1246 | |