1 | /* |
2 | * Copyright © 2011 Canonical Limited |
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 licence, 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 | * Author: Ryan Lortie <desrt@desrt.ca> |
18 | */ |
19 | |
20 | #include "config.h" |
21 | |
22 | #include "gtkactionmuxerprivate.h" |
23 | |
24 | #include "gtkactionobservableprivate.h" |
25 | #include "gtkactionobserverprivate.h" |
26 | #include "gtkbitmaskprivate.h" |
27 | #include "gtkintl.h" |
28 | #include "gtkmarshalers.h" |
29 | #include "gtkwidgetprivate.h" |
30 | #include "gsettings-mapping.h" |
31 | |
32 | #include <string.h> |
33 | |
34 | typedef struct |
35 | { |
36 | char *action_and_target; |
37 | char *accel; |
38 | } GtkAccel; |
39 | |
40 | static void |
41 | gtk_accel_clear (GtkAccel *accel) |
42 | { |
43 | g_free (mem: accel->action_and_target); |
44 | g_free (mem: accel->accel); |
45 | } |
46 | |
47 | #define GDK_ARRAY_NAME gtk_accels |
48 | #define GDK_ARRAY_TYPE_NAME GtkAccels |
49 | #define GDK_ARRAY_ELEMENT_TYPE GtkAccel |
50 | #define GDK_ARRAY_FREE_FUNC gtk_accel_clear |
51 | #define GDK_ARRAY_BY_VALUE 1 |
52 | #define GDK_ARRAY_PREALLOC 2 |
53 | #include "gdk/gdkarrayimpl.c" |
54 | |
55 | static guint |
56 | gtk_accels_find (GtkAccels *accels, |
57 | const char *action_and_target) |
58 | { |
59 | guint i; |
60 | |
61 | for (i = 0; i < gtk_accels_get_size (self: accels); i++) |
62 | { |
63 | GtkAccel *accel = gtk_accels_index (self: accels, pos: i); |
64 | if (strcmp (s1: accel->action_and_target, s2: action_and_target) == 0) |
65 | return i; |
66 | } |
67 | |
68 | return G_MAXUINT; |
69 | } |
70 | |
71 | static void |
72 | gtk_accels_replace (GtkAccels *accels, |
73 | const char *action_and_target, |
74 | const char *primary_accel) |
75 | { |
76 | guint position; |
77 | |
78 | position = gtk_accels_find (accels, action_and_target); |
79 | if (position < gtk_accels_get_size (self: accels)) |
80 | { |
81 | GtkAccel *accel = gtk_accels_index (self: accels, pos: position); |
82 | g_free (mem: accel->accel); |
83 | accel->accel = g_strdup (str: primary_accel); |
84 | } |
85 | else |
86 | { |
87 | GtkAccel accel; |
88 | |
89 | accel.action_and_target = g_strdup (str: action_and_target); |
90 | accel.accel = g_strdup (str: primary_accel); |
91 | gtk_accels_append (self: accels, value: &accel); |
92 | } |
93 | } |
94 | |
95 | static void |
96 | gtk_accels_remove (GtkAccels *accels, |
97 | const char *action_and_target) |
98 | { |
99 | guint position; |
100 | |
101 | position = gtk_accels_find (accels, action_and_target); |
102 | if (position < gtk_accels_get_size (self: accels)) |
103 | gtk_accels_splice (self: accels, pos: position, removed: 1, FALSE, NULL, added: 0); |
104 | } |
105 | |
106 | /*< private > |
107 | * GtkActionMuxer: |
108 | * |
109 | * GtkActionMuxer aggregates and monitors actions from multiple sources. |
110 | * |
111 | * `GtkActionMuxer` is `GtkActionObservable` and `GtkActionObserver` that |
112 | * offers a `GActionGroup`-like api and is capable of containing other |
113 | * `GActionGroup` instances. `GtkActionMuxer` does not implement the |
114 | * `GActionGroup` interface because it requires excessive signal emissions |
115 | * and has poor scalability. We use the `GtkActionObserver` machinery |
116 | * instead to propagate changes between action muxer instances and |
117 | * to other users. |
118 | * |
119 | * Beyond action groups, `GtkActionMuxer` can incorporate actions that |
120 | * are associated with widget classes (*class actions*) and actions |
121 | * that are associated with the parent widget, allowing for recursive |
122 | * lookup. |
123 | * |
124 | * In addition to the action attributes provided by `GActionGroup`, |
125 | * `GtkActionMuxer` maintains a *primary accelerator* string for |
126 | * actions that can be shown in menuitems. |
127 | * |
128 | * The typical use is aggregating all of the actions applicable to a |
129 | * particular context into a single action group, with namespacing. |
130 | * |
131 | * Consider the case of two action groups -- one containing actions |
132 | * applicable to an entire application (such as “quit”) and one |
133 | * containing actions applicable to a particular window in the |
134 | * application (such as “fullscreen”). |
135 | * |
136 | * In this case, each of these action groups could be added to a |
137 | * `GtkActionMuxer` with the prefixes “app” and “win”, respectively. |
138 | * This would expose the actions as “app.quit” and “win.fullscreen” |
139 | * on the `GActionGroup`-like interface presented by the `GtkActionMuxer`. |
140 | * |
141 | * Activations and state change requests on the `GtkActionMuxer` are |
142 | * wired through to the underlying actions in the expected way. |
143 | * |
144 | * This class is typically only used at the site of “consumption” of |
145 | * actions (eg: when displaying a menu that contains many actions on |
146 | * different objects). |
147 | */ |
148 | |
149 | static void gtk_action_muxer_observable_iface_init (GtkActionObservableInterface *iface); |
150 | static void gtk_action_muxer_observer_iface_init (GtkActionObserverInterface *iface); |
151 | |
152 | typedef GObjectClass GtkActionMuxerClass; |
153 | |
154 | struct _GtkActionMuxer |
155 | { |
156 | GObject parent_instance; |
157 | GtkActionMuxer *parent; |
158 | GtkWidget *widget; |
159 | |
160 | GHashTable *observed_actions; |
161 | GHashTable *groups; |
162 | GtkAccels primary_accels; |
163 | |
164 | GtkBitmask *widget_actions_disabled; |
165 | }; |
166 | |
167 | G_DEFINE_TYPE_WITH_CODE (GtkActionMuxer, gtk_action_muxer, G_TYPE_OBJECT, |
168 | G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTION_OBSERVER, gtk_action_muxer_observer_iface_init) |
169 | G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTION_OBSERVABLE, gtk_action_muxer_observable_iface_init)) |
170 | |
171 | enum |
172 | { |
173 | PROP_0, |
174 | PROP_PARENT, |
175 | PROP_WIDGET, |
176 | NUM_PROPERTIES |
177 | }; |
178 | |
179 | static GParamSpec *properties[NUM_PROPERTIES]; |
180 | |
181 | typedef struct |
182 | { |
183 | GtkActionMuxer *muxer; |
184 | GSList *watchers; |
185 | char *fullname; |
186 | } Action; |
187 | |
188 | typedef struct |
189 | { |
190 | GtkActionMuxer *muxer; |
191 | GActionGroup *group; |
192 | char *prefix; |
193 | gulong handler_ids[4]; |
194 | } Group; |
195 | |
196 | static inline guint |
197 | get_action_position (GtkWidgetAction *action) |
198 | { |
199 | guint slot; |
200 | /* We use the length of @action to the end of the chain as the slot so that |
201 | * we have stable positions for any class or it's subclasses. Doing so helps |
202 | * us avoid having mutable arrays in the class data as we will not have |
203 | * access to the ClassPrivate data during instance _init() functions. |
204 | */ |
205 | for (slot = 0; action->next != NULL; slot++, action = action->next) {} |
206 | return slot; |
207 | } |
208 | |
209 | static void |
210 | gtk_action_muxer_append_group_actions (const char *prefix, |
211 | Group *group, |
212 | GHashTable *actions) |
213 | { |
214 | char **group_actions; |
215 | char **action; |
216 | |
217 | group_actions = g_action_group_list_actions (action_group: group->group); |
218 | for (action = group_actions; *action; action++) |
219 | { |
220 | char *name = g_strconcat (string1: prefix, "." , *action, NULL); |
221 | g_hash_table_add (hash_table: actions, key: name); |
222 | } |
223 | |
224 | g_strfreev (str_array: group_actions); |
225 | } |
226 | |
227 | char ** |
228 | gtk_action_muxer_list_actions (GtkActionMuxer *muxer, |
229 | gboolean local_only) |
230 | { |
231 | GHashTable *actions; |
232 | char **keys; |
233 | |
234 | actions = g_hash_table_new_full (hash_func: g_str_hash, key_equal_func: g_str_equal, key_destroy_func: g_free, NULL); |
235 | |
236 | for ( ; muxer != NULL; muxer = muxer->parent) |
237 | { |
238 | GHashTableIter iter; |
239 | const char *prefix; |
240 | Group *group; |
241 | |
242 | if (muxer->widget) |
243 | { |
244 | GtkWidgetClass *klass = GTK_WIDGET_GET_CLASS (muxer->widget); |
245 | GtkWidgetClassPrivate *priv = klass->priv; |
246 | GtkWidgetAction *action; |
247 | |
248 | for (action = priv->actions; action; action = action->next) |
249 | g_hash_table_add (hash_table: actions, key: g_strdup (str: action->name)); |
250 | } |
251 | |
252 | if (muxer->groups) |
253 | { |
254 | g_hash_table_iter_init (iter: &iter, hash_table: muxer->groups); |
255 | while (g_hash_table_iter_next (iter: &iter, key: (gpointer *)&prefix, value: (gpointer *)&group)) |
256 | gtk_action_muxer_append_group_actions (prefix, group, actions); |
257 | } |
258 | |
259 | if (local_only) |
260 | break; |
261 | } |
262 | |
263 | keys = (char **)g_hash_table_get_keys_as_array (hash_table: actions, NULL); |
264 | |
265 | g_hash_table_steal_all (hash_table: actions); |
266 | g_hash_table_unref (hash_table: actions); |
267 | |
268 | return (char **)keys; |
269 | } |
270 | |
271 | static Group * |
272 | gtk_action_muxer_find_group (GtkActionMuxer *muxer, |
273 | const char *full_name, |
274 | const char **action_name) |
275 | { |
276 | const char *dot; |
277 | char *prefix; |
278 | const char *name; |
279 | Group *group; |
280 | |
281 | if (!muxer->groups) |
282 | return NULL; |
283 | |
284 | dot = strchr (s: full_name, c: '.'); |
285 | |
286 | if (!dot) |
287 | return NULL; |
288 | |
289 | name = dot + 1; |
290 | |
291 | prefix = g_strndup (str: full_name, n: dot - full_name); |
292 | group = g_hash_table_lookup (hash_table: muxer->groups, key: prefix); |
293 | g_free (mem: prefix); |
294 | |
295 | if (action_name) |
296 | *action_name = name; |
297 | |
298 | if (group && |
299 | g_action_group_has_action (action_group: group->group, action_name: name)) |
300 | return group; |
301 | |
302 | return NULL; |
303 | } |
304 | |
305 | GActionGroup * |
306 | gtk_action_muxer_find (GtkActionMuxer *muxer, |
307 | const char *action_name, |
308 | const char **unprefixed_name) |
309 | { |
310 | Group *group; |
311 | |
312 | group = gtk_action_muxer_find_group (muxer, full_name: action_name, action_name: unprefixed_name); |
313 | if (group) |
314 | return group->group; |
315 | |
316 | return NULL; |
317 | } |
318 | |
319 | GActionGroup * |
320 | gtk_action_muxer_get_group (GtkActionMuxer *muxer, |
321 | const char *group_name) |
322 | { |
323 | Group *group; |
324 | |
325 | group = g_hash_table_lookup (hash_table: muxer->groups, key: group_name); |
326 | if (group) |
327 | return group->group; |
328 | |
329 | return NULL; |
330 | } |
331 | |
332 | static inline Action * |
333 | find_observers (GtkActionMuxer *muxer, |
334 | const char *action_name) |
335 | { |
336 | if (muxer->observed_actions) |
337 | return g_hash_table_lookup (hash_table: muxer->observed_actions, key: action_name); |
338 | |
339 | return NULL; |
340 | } |
341 | |
342 | void |
343 | gtk_action_muxer_action_enabled_changed (GtkActionMuxer *muxer, |
344 | const char *action_name, |
345 | gboolean enabled) |
346 | { |
347 | GtkWidgetAction *iter; |
348 | Action *action; |
349 | GSList *node; |
350 | |
351 | if (muxer->widget) |
352 | { |
353 | GtkWidgetClass *klass = GTK_WIDGET_GET_CLASS (muxer->widget); |
354 | GtkWidgetClassPrivate *priv = klass->priv; |
355 | |
356 | for (iter = priv->actions; iter; iter = iter->next) |
357 | { |
358 | if (strcmp (s1: action_name, s2: iter->name) == 0) |
359 | { |
360 | guint position = get_action_position (action: iter); |
361 | muxer->widget_actions_disabled = |
362 | _gtk_bitmask_set (mask: muxer->widget_actions_disabled, index_: position, value: !enabled); |
363 | break; |
364 | } |
365 | } |
366 | } |
367 | |
368 | action = find_observers (muxer, action_name); |
369 | |
370 | for (node = action ? action->watchers : NULL; node; node = node->next) |
371 | gtk_action_observer_action_enabled_changed (observer: node->data, GTK_ACTION_OBSERVABLE (muxer), action_name, enabled); |
372 | } |
373 | |
374 | static void |
375 | gtk_action_muxer_group_action_enabled_changed (GActionGroup *action_group, |
376 | const char *action_name, |
377 | gboolean enabled, |
378 | gpointer user_data) |
379 | { |
380 | Group *group = user_data; |
381 | char *fullname; |
382 | |
383 | fullname = g_strconcat (string1: group->prefix, "." , action_name, NULL); |
384 | gtk_action_muxer_action_enabled_changed (muxer: group->muxer, action_name: fullname, enabled); |
385 | g_free (mem: fullname); |
386 | } |
387 | |
388 | void |
389 | gtk_action_muxer_action_state_changed (GtkActionMuxer *muxer, |
390 | const char *action_name, |
391 | GVariant *state) |
392 | { |
393 | Action *action; |
394 | GSList *node; |
395 | |
396 | action = find_observers (muxer, action_name); |
397 | for (node = action ? action->watchers : NULL; node; node = node->next) |
398 | gtk_action_observer_action_state_changed (observer: node->data, GTK_ACTION_OBSERVABLE (muxer), action_name, state); |
399 | } |
400 | |
401 | static void |
402 | gtk_action_muxer_group_action_state_changed (GActionGroup *action_group, |
403 | const char *action_name, |
404 | GVariant *state, |
405 | gpointer user_data) |
406 | { |
407 | Group *group = user_data; |
408 | char *fullname; |
409 | |
410 | fullname = g_strconcat (string1: group->prefix, "." , action_name, NULL); |
411 | gtk_action_muxer_action_state_changed (muxer: group->muxer, action_name: fullname, state); |
412 | g_free (mem: fullname); |
413 | } |
414 | |
415 | static gboolean action_muxer_query_action (GtkActionMuxer *muxer, |
416 | const char *action_name, |
417 | gboolean *enabled, |
418 | const GVariantType **parameter_type, |
419 | const GVariantType **state_type, |
420 | GVariant **state_hint, |
421 | GVariant **state, |
422 | gboolean recurse); |
423 | |
424 | static void |
425 | notify_observers_added (GtkActionMuxer *muxer, |
426 | GtkActionMuxer *parent) |
427 | { |
428 | GHashTableIter iter; |
429 | const char *action_name; |
430 | Action *action; |
431 | |
432 | if (!muxer->observed_actions) |
433 | return; |
434 | |
435 | g_hash_table_iter_init (iter: &iter, hash_table: muxer->observed_actions); |
436 | while (g_hash_table_iter_next (iter: &iter, key: (gpointer *)&action_name, value: (gpointer *)&action)) |
437 | { |
438 | const GVariantType *parameter_type; |
439 | gboolean enabled; |
440 | GVariant *state; |
441 | GSList *node; |
442 | |
443 | if (!action->watchers) |
444 | continue; |
445 | |
446 | for (node = action ? action->watchers : NULL; node; node = node->next) |
447 | gtk_action_observer_primary_accel_changed (observer: node->data, GTK_ACTION_OBSERVABLE (muxer), |
448 | action_name, NULL); |
449 | |
450 | gtk_action_observable_register_observer (GTK_ACTION_OBSERVABLE (parent), action_name, GTK_ACTION_OBSERVER (muxer)); |
451 | |
452 | if (!action_muxer_query_action (muxer: parent, action_name, |
453 | enabled: &enabled, parameter_type: ¶meter_type, |
454 | NULL, NULL, state: &state, |
455 | TRUE)) |
456 | continue; |
457 | |
458 | for (node = action->watchers; node; node = node->next) |
459 | { |
460 | gtk_action_observer_action_added (observer: node->data, |
461 | GTK_ACTION_OBSERVABLE (muxer), |
462 | action_name, parameter_type, enabled, state); |
463 | } |
464 | |
465 | if (state) |
466 | g_variant_unref (value: state); |
467 | } |
468 | } |
469 | |
470 | static void |
471 | notify_observers_removed (GtkActionMuxer *muxer, |
472 | GtkActionMuxer *parent) |
473 | { |
474 | GHashTableIter iter; |
475 | const char *action_name; |
476 | Action *action; |
477 | |
478 | if (!muxer->observed_actions) |
479 | return; |
480 | |
481 | g_hash_table_iter_init (iter: &iter, hash_table: muxer->observed_actions); |
482 | while (g_hash_table_iter_next (iter: &iter, key: (gpointer *)&action_name, value: (gpointer *)&action)) |
483 | { |
484 | GSList *node; |
485 | |
486 | gtk_action_observable_unregister_observer (GTK_ACTION_OBSERVABLE (parent), action_name, GTK_ACTION_OBSERVER (muxer)); |
487 | |
488 | for (node = action->watchers; node; node = node->next) |
489 | { |
490 | gtk_action_observer_action_removed (observer: node->data, |
491 | GTK_ACTION_OBSERVABLE (muxer), |
492 | action_name); |
493 | } |
494 | } |
495 | } |
496 | |
497 | static void |
498 | gtk_action_muxer_action_added (GtkActionMuxer *muxer, |
499 | const char *action_name, |
500 | const GVariantType *parameter_type, |
501 | gboolean enabled, |
502 | GVariant *state) |
503 | { |
504 | Action *action; |
505 | GSList *node; |
506 | |
507 | action = find_observers (muxer, action_name); |
508 | for (node = action ? action->watchers : NULL; node; node = node->next) |
509 | gtk_action_observer_action_added (observer: node->data, |
510 | GTK_ACTION_OBSERVABLE (muxer), |
511 | action_name, parameter_type, enabled, state); |
512 | } |
513 | |
514 | static void |
515 | gtk_action_muxer_action_added_to_group (GActionGroup *action_group, |
516 | const char *action_name, |
517 | gpointer user_data) |
518 | { |
519 | Group *group = user_data; |
520 | GtkActionMuxer *muxer = group->muxer; |
521 | Action *action; |
522 | const GVariantType *parameter_type; |
523 | gboolean enabled; |
524 | GVariant *state; |
525 | char *fullname; |
526 | |
527 | fullname = g_strconcat (string1: group->prefix, "." , action_name, NULL); |
528 | |
529 | if (muxer->parent) |
530 | gtk_action_observable_unregister_observer (GTK_ACTION_OBSERVABLE (muxer->parent), |
531 | action_name: fullname, |
532 | GTK_ACTION_OBSERVER (muxer)); |
533 | |
534 | action = find_observers (muxer, action_name: fullname); |
535 | |
536 | if (action && action->watchers && |
537 | g_action_group_query_action (action_group, action_name, |
538 | enabled: &enabled, parameter_type: ¶meter_type, NULL, NULL, state: &state)) |
539 | { |
540 | gtk_action_muxer_action_added (muxer, action_name: fullname, parameter_type, enabled, state); |
541 | |
542 | if (state) |
543 | g_variant_unref (value: state); |
544 | } |
545 | |
546 | g_free (mem: fullname); |
547 | } |
548 | |
549 | static void |
550 | gtk_action_muxer_action_removed (GtkActionMuxer *muxer, |
551 | const char *action_name) |
552 | { |
553 | Action *action; |
554 | GSList *node; |
555 | |
556 | action = find_observers (muxer, action_name); |
557 | for (node = action ? action->watchers : NULL; node; node = node->next) |
558 | gtk_action_observer_action_removed (observer: node->data, GTK_ACTION_OBSERVABLE (muxer), action_name); |
559 | } |
560 | |
561 | static void |
562 | gtk_action_muxer_action_removed_from_group (GActionGroup *action_group, |
563 | const char *action_name, |
564 | gpointer user_data) |
565 | { |
566 | Group *group = user_data; |
567 | GtkActionMuxer *muxer = group->muxer; |
568 | char *fullname; |
569 | Action *action; |
570 | |
571 | fullname = g_strconcat (string1: group->prefix, "." , action_name, NULL); |
572 | gtk_action_muxer_action_removed (muxer, action_name: fullname); |
573 | g_free (mem: fullname); |
574 | |
575 | action = find_observers (muxer, action_name); |
576 | |
577 | if (action && action->watchers && |
578 | !action_muxer_query_action (muxer, action_name, |
579 | NULL, NULL, NULL, NULL, NULL, FALSE)) |
580 | { |
581 | if (muxer->parent) |
582 | gtk_action_observable_register_observer (GTK_ACTION_OBSERVABLE (muxer->parent), |
583 | action_name, |
584 | GTK_ACTION_OBSERVER (muxer)); |
585 | } |
586 | } |
587 | |
588 | static void |
589 | gtk_action_muxer_primary_accel_changed (GtkActionMuxer *muxer, |
590 | const char *action_name, |
591 | const char *action_and_target) |
592 | { |
593 | Action *action; |
594 | GSList *node; |
595 | |
596 | if (!action_name) |
597 | action_name = strrchr (s: action_and_target, c: '|') + 1; |
598 | |
599 | action = find_observers (muxer, action_name); |
600 | for (node = action ? action->watchers : NULL; node; node = node->next) |
601 | gtk_action_observer_primary_accel_changed (observer: node->data, GTK_ACTION_OBSERVABLE (muxer), |
602 | action_name, action_and_target); |
603 | } |
604 | |
605 | static GVariant * |
606 | prop_action_get_state (GtkWidget *widget, |
607 | GtkWidgetAction *action) |
608 | { |
609 | GValue value = G_VALUE_INIT; |
610 | GVariant *result; |
611 | |
612 | g_value_init (value: &value, g_type: action->pspec->value_type); |
613 | g_object_get_property (G_OBJECT (widget), property_name: action->pspec->name, value: &value); |
614 | |
615 | result = g_settings_set_mapping (value: &value, expected_type: action->state_type, NULL); |
616 | g_value_unset (value: &value); |
617 | |
618 | return g_variant_ref_sink (value: result); |
619 | } |
620 | |
621 | static GVariant * |
622 | prop_action_get_state_hint (GtkWidget *widget, |
623 | GtkWidgetAction *action) |
624 | { |
625 | if (action->pspec->value_type == G_TYPE_INT) |
626 | { |
627 | GParamSpecInt *pspec = (GParamSpecInt *)action->pspec; |
628 | return g_variant_new (format_string: "(ii)" , pspec->minimum, pspec->maximum); |
629 | } |
630 | else if (action->pspec->value_type == G_TYPE_UINT) |
631 | { |
632 | GParamSpecUInt *pspec = (GParamSpecUInt *)action->pspec; |
633 | return g_variant_new (format_string: "(uu)" , pspec->minimum, pspec->maximum); |
634 | } |
635 | else if (action->pspec->value_type == G_TYPE_FLOAT) |
636 | { |
637 | GParamSpecFloat *pspec = (GParamSpecFloat *)action->pspec; |
638 | return g_variant_new (format_string: "(dd)" , (double)pspec->minimum, (double)pspec->maximum); |
639 | } |
640 | else if (action->pspec->value_type == G_TYPE_DOUBLE) |
641 | { |
642 | GParamSpecDouble *pspec = (GParamSpecDouble *)action->pspec; |
643 | return g_variant_new (format_string: "(dd)" , pspec->minimum, pspec->maximum); |
644 | } |
645 | |
646 | return NULL; |
647 | } |
648 | |
649 | static void |
650 | prop_action_set_state (GtkWidget *widget, |
651 | GtkWidgetAction *action, |
652 | GVariant *state) |
653 | { |
654 | GValue value = G_VALUE_INIT; |
655 | |
656 | g_value_init (value: &value, g_type: action->pspec->value_type); |
657 | g_settings_get_mapping (value: &value, variant: state, NULL); |
658 | |
659 | g_object_set_property (G_OBJECT (widget), property_name: action->pspec->name, value: &value); |
660 | g_value_unset (value: &value); |
661 | } |
662 | |
663 | static void |
664 | prop_action_activate (GtkWidget *widget, |
665 | GtkWidgetAction *action, |
666 | GVariant *parameter) |
667 | { |
668 | if (action->pspec->value_type == G_TYPE_BOOLEAN) |
669 | { |
670 | gboolean value; |
671 | |
672 | g_return_if_fail (parameter == NULL); |
673 | |
674 | g_object_get (G_OBJECT (widget), first_property_name: action->pspec->name, &value, NULL); |
675 | value = !value; |
676 | g_object_set (G_OBJECT (widget), first_property_name: action->pspec->name, value, NULL); |
677 | } |
678 | else |
679 | { |
680 | g_return_if_fail (parameter != NULL && g_variant_is_of_type (parameter, action->state_type)); |
681 | |
682 | prop_action_set_state (widget, action, state: parameter); |
683 | } |
684 | } |
685 | |
686 | static void |
687 | prop_action_notify (GObject *object, |
688 | GParamSpec *pspec, |
689 | gpointer user_data) |
690 | { |
691 | GtkWidget *widget = GTK_WIDGET (object); |
692 | GtkWidgetAction *action = user_data; |
693 | GtkActionMuxer *muxer = _gtk_widget_get_action_muxer (widget, TRUE); |
694 | GVariant *state; |
695 | |
696 | g_assert (muxer->widget == widget); |
697 | g_assert (action->pspec == pspec); |
698 | |
699 | state = prop_action_get_state (widget, action); |
700 | gtk_action_muxer_action_state_changed (muxer, action_name: action->name, state); |
701 | g_variant_unref (value: state); |
702 | } |
703 | |
704 | static void |
705 | prop_actions_connect (GtkActionMuxer *muxer) |
706 | { |
707 | GtkWidgetClassPrivate *priv; |
708 | GtkWidgetAction *action; |
709 | GtkWidgetClass *klass; |
710 | guint signal_id; |
711 | |
712 | if (!muxer->widget) |
713 | return; |
714 | |
715 | klass = GTK_WIDGET_GET_CLASS (muxer->widget); |
716 | priv = klass->priv; |
717 | if (!priv->actions) |
718 | return; |
719 | |
720 | signal_id = g_signal_lookup (name: "notify" , G_TYPE_OBJECT); |
721 | |
722 | for (action = priv->actions; action; action = action->next) |
723 | { |
724 | if (!action->pspec) |
725 | continue; |
726 | |
727 | g_signal_connect_closure_by_id (instance: muxer->widget, |
728 | signal_id, |
729 | detail: g_param_spec_get_name_quark (pspec: action->pspec), |
730 | closure: g_cclosure_new (G_CALLBACK (prop_action_notify), |
731 | user_data: action, |
732 | NULL), |
733 | FALSE); |
734 | } |
735 | } |
736 | |
737 | static gboolean |
738 | action_muxer_query_action (GtkActionMuxer *muxer, |
739 | const char *action_name, |
740 | gboolean *enabled, |
741 | const GVariantType **parameter_type, |
742 | const GVariantType **state_type, |
743 | GVariant **state_hint, |
744 | GVariant **state, |
745 | gboolean recurse) |
746 | { |
747 | GtkWidgetAction *action; |
748 | Group *group; |
749 | const char *unprefixed_name; |
750 | |
751 | if (muxer->widget) |
752 | { |
753 | GtkWidgetClass *klass = GTK_WIDGET_GET_CLASS (muxer->widget); |
754 | GtkWidgetClassPrivate *priv = klass->priv; |
755 | |
756 | for (action = priv->actions; action; action = action->next) |
757 | { |
758 | guint position; |
759 | |
760 | if (strcmp (s1: action->name, s2: action_name) != 0) |
761 | continue; |
762 | |
763 | position = get_action_position (action); |
764 | |
765 | if (enabled) |
766 | *enabled = !_gtk_bitmask_get (mask: muxer->widget_actions_disabled, index_: position); |
767 | if (parameter_type) |
768 | *parameter_type = action->parameter_type; |
769 | if (state_type) |
770 | *state_type = action->state_type; |
771 | |
772 | if (state_hint) |
773 | *state_hint = NULL; |
774 | if (state) |
775 | *state = NULL; |
776 | |
777 | if (action->pspec) |
778 | { |
779 | if (state) |
780 | *state = prop_action_get_state (widget: muxer->widget, action); |
781 | if (state_hint) |
782 | *state_hint = prop_action_get_state_hint (widget: muxer->widget, action); |
783 | } |
784 | |
785 | return TRUE; |
786 | } |
787 | } |
788 | |
789 | group = gtk_action_muxer_find_group (muxer, full_name: action_name, action_name: &unprefixed_name); |
790 | |
791 | if (group) |
792 | return g_action_group_query_action (action_group: group->group, action_name: unprefixed_name, enabled, |
793 | parameter_type, state_type, state_hint, state); |
794 | |
795 | if (muxer->parent && recurse) |
796 | return gtk_action_muxer_query_action (muxer: muxer->parent, action_name, |
797 | enabled, parameter_type, |
798 | state_type, state_hint, state); |
799 | |
800 | return FALSE; |
801 | } |
802 | |
803 | gboolean |
804 | gtk_action_muxer_query_action (GtkActionMuxer *muxer, |
805 | const char *action_name, |
806 | gboolean *enabled, |
807 | const GVariantType **parameter_type, |
808 | const GVariantType **state_type, |
809 | GVariant **state_hint, |
810 | GVariant **state) |
811 | { |
812 | return action_muxer_query_action (muxer, action_name, |
813 | enabled, parameter_type, |
814 | state_type, state_hint, state, |
815 | TRUE); |
816 | } |
817 | |
818 | gboolean |
819 | gtk_action_muxer_has_action (GtkActionMuxer *muxer, |
820 | const char *action_name) |
821 | { |
822 | return action_muxer_query_action (muxer, action_name, |
823 | NULL, NULL, NULL, NULL, NULL, |
824 | TRUE); |
825 | } |
826 | |
827 | void |
828 | gtk_action_muxer_activate_action (GtkActionMuxer *muxer, |
829 | const char *action_name, |
830 | GVariant *parameter) |
831 | { |
832 | const char *unprefixed_name; |
833 | Group *group; |
834 | |
835 | if (muxer->widget) |
836 | { |
837 | GtkWidgetClass *klass = GTK_WIDGET_GET_CLASS (muxer->widget); |
838 | GtkWidgetClassPrivate *priv = klass->priv; |
839 | GtkWidgetAction *action; |
840 | |
841 | for (action = priv->actions; action; action = action->next) |
842 | { |
843 | if (strcmp (s1: action->name, s2: action_name) == 0) |
844 | { |
845 | guint position = get_action_position (action); |
846 | |
847 | if (!_gtk_bitmask_get (mask: muxer->widget_actions_disabled, index_: position)) |
848 | { |
849 | if (action->activate) |
850 | action->activate (muxer->widget, action->name, parameter); |
851 | else if (action->pspec) |
852 | prop_action_activate (widget: muxer->widget, action, parameter); |
853 | } |
854 | |
855 | return; |
856 | } |
857 | } |
858 | } |
859 | |
860 | group = gtk_action_muxer_find_group (muxer, full_name: action_name, action_name: &unprefixed_name); |
861 | |
862 | if (group) |
863 | g_action_group_activate_action (action_group: group->group, action_name: unprefixed_name, parameter); |
864 | else if (muxer->parent) |
865 | gtk_action_muxer_activate_action (muxer: muxer->parent, action_name, parameter); |
866 | } |
867 | |
868 | void |
869 | gtk_action_muxer_change_action_state (GtkActionMuxer *muxer, |
870 | const char *action_name, |
871 | GVariant *state) |
872 | { |
873 | GtkWidgetAction *action; |
874 | const char *unprefixed_name; |
875 | Group *group; |
876 | |
877 | if (muxer->widget) |
878 | { |
879 | GtkWidgetClass *klass = GTK_WIDGET_GET_CLASS (muxer->widget); |
880 | GtkWidgetClassPrivate *priv = klass->priv; |
881 | |
882 | for (action = priv->actions; action; action = action->next) |
883 | { |
884 | if (strcmp (s1: action->name, s2: action_name) == 0) |
885 | { |
886 | if (action->pspec) |
887 | prop_action_set_state (widget: muxer->widget, action, state); |
888 | |
889 | return; |
890 | } |
891 | } |
892 | } |
893 | |
894 | group = gtk_action_muxer_find_group (muxer, full_name: action_name, action_name: &unprefixed_name); |
895 | |
896 | if (group) |
897 | g_action_group_change_action_state (action_group: group->group, action_name: unprefixed_name, value: state); |
898 | else if (muxer->parent) |
899 | gtk_action_muxer_change_action_state (muxer: muxer->parent, action_name, state); |
900 | } |
901 | |
902 | static void |
903 | gtk_action_muxer_unregister_internal (Action *action, |
904 | gpointer observer) |
905 | { |
906 | GtkActionMuxer *muxer = action->muxer; |
907 | GSList **ptr; |
908 | |
909 | for (ptr = &action->watchers; *ptr; ptr = &(*ptr)->next) |
910 | if ((*ptr)->data == observer) |
911 | { |
912 | *ptr = g_slist_remove (list: *ptr, data: observer); |
913 | |
914 | if (action->watchers == NULL) |
915 | { |
916 | if (muxer->parent) |
917 | gtk_action_observable_unregister_observer (GTK_ACTION_OBSERVABLE (muxer->parent), |
918 | action_name: action->fullname, |
919 | GTK_ACTION_OBSERVER (muxer)); |
920 | g_hash_table_remove (hash_table: muxer->observed_actions, key: action->fullname); |
921 | } |
922 | |
923 | break; |
924 | } |
925 | } |
926 | |
927 | static void |
928 | gtk_action_muxer_weak_notify (gpointer data, |
929 | GObject *where_the_object_was) |
930 | { |
931 | Action *action = data; |
932 | |
933 | gtk_action_muxer_unregister_internal (action, observer: where_the_object_was); |
934 | } |
935 | |
936 | static void gtk_action_muxer_free_action (gpointer data); |
937 | |
938 | static void |
939 | gtk_action_muxer_register_observer (GtkActionObservable *observable, |
940 | const char *name, |
941 | GtkActionObserver *observer) |
942 | { |
943 | GtkActionMuxer *muxer = GTK_ACTION_MUXER (observable); |
944 | Action *action; |
945 | gboolean enabled; |
946 | const GVariantType *parameter_type; |
947 | GVariant *state; |
948 | gboolean is_duplicate; |
949 | |
950 | if (!muxer->observed_actions) |
951 | muxer->observed_actions = g_hash_table_new_full (hash_func: g_str_hash, key_equal_func: g_str_equal, NULL, value_destroy_func: gtk_action_muxer_free_action); |
952 | |
953 | action = g_hash_table_lookup (hash_table: muxer->observed_actions, key: name); |
954 | |
955 | if (action == NULL) |
956 | { |
957 | action = g_slice_new (Action); |
958 | action->muxer = muxer; |
959 | action->fullname = g_strdup (str: name); |
960 | action->watchers = NULL; |
961 | |
962 | g_hash_table_insert (hash_table: muxer->observed_actions, key: action->fullname, value: action); |
963 | } |
964 | |
965 | is_duplicate = g_slist_find (list: action->watchers, data: observer) != NULL; |
966 | action->watchers = g_slist_prepend (list: action->watchers, data: observer); |
967 | g_object_weak_ref (G_OBJECT (observer), notify: gtk_action_muxer_weak_notify, data: action); |
968 | |
969 | if (!is_duplicate) |
970 | { |
971 | if (action_muxer_query_action (muxer, action_name: name, |
972 | enabled: &enabled, parameter_type: ¶meter_type, |
973 | NULL, NULL, state: &state, TRUE)) |
974 | { |
975 | gtk_action_muxer_action_added (muxer, action_name: name, parameter_type, enabled, state); |
976 | g_clear_pointer (&state, g_variant_unref); |
977 | } |
978 | |
979 | if (muxer->parent) |
980 | { |
981 | gtk_action_observable_register_observer (GTK_ACTION_OBSERVABLE (muxer->parent), |
982 | action_name: name, |
983 | GTK_ACTION_OBSERVER (muxer)); |
984 | } |
985 | } |
986 | } |
987 | |
988 | static void |
989 | gtk_action_muxer_unregister_observer (GtkActionObservable *observable, |
990 | const char *name, |
991 | GtkActionObserver *observer) |
992 | { |
993 | GtkActionMuxer *muxer = GTK_ACTION_MUXER (observable); |
994 | Action *action = find_observers (muxer, action_name: name); |
995 | |
996 | if (action) |
997 | { |
998 | if (g_slist_find (list: action->watchers, data: observer) != NULL) |
999 | { |
1000 | g_object_weak_unref (G_OBJECT (observer), notify: gtk_action_muxer_weak_notify, data: action); |
1001 | gtk_action_muxer_unregister_internal (action, observer); |
1002 | } |
1003 | } |
1004 | } |
1005 | |
1006 | static void |
1007 | gtk_action_muxer_free_group (gpointer data) |
1008 | { |
1009 | Group *group = data; |
1010 | int i; |
1011 | |
1012 | /* 'for loop' or 'four loop'? */ |
1013 | for (i = 0; i < 4; i++) |
1014 | g_signal_handler_disconnect (instance: group->group, handler_id: group->handler_ids[i]); |
1015 | |
1016 | g_object_unref (object: group->group); |
1017 | g_free (mem: group->prefix); |
1018 | |
1019 | g_slice_free (Group, group); |
1020 | } |
1021 | |
1022 | static void |
1023 | gtk_action_muxer_free_action (gpointer data) |
1024 | { |
1025 | Action *action = data; |
1026 | GSList *it; |
1027 | |
1028 | for (it = action->watchers; it; it = it->next) |
1029 | g_object_weak_unref (G_OBJECT (it->data), notify: gtk_action_muxer_weak_notify, data: action); |
1030 | |
1031 | g_slist_free (list: action->watchers); |
1032 | g_free (mem: action->fullname); |
1033 | |
1034 | g_slice_free (Action, action); |
1035 | } |
1036 | |
1037 | static void |
1038 | gtk_action_muxer_finalize (GObject *object) |
1039 | { |
1040 | GtkActionMuxer *muxer = GTK_ACTION_MUXER (object); |
1041 | |
1042 | if (muxer->observed_actions) |
1043 | { |
1044 | g_assert_cmpint (g_hash_table_size (muxer->observed_actions), ==, 0); |
1045 | g_hash_table_unref (hash_table: muxer->observed_actions); |
1046 | } |
1047 | if (muxer->groups) |
1048 | g_hash_table_unref (hash_table: muxer->groups); |
1049 | |
1050 | gtk_accels_clear (self: &muxer->primary_accels); |
1051 | |
1052 | _gtk_bitmask_free (mask: muxer->widget_actions_disabled); |
1053 | |
1054 | G_OBJECT_CLASS (gtk_action_muxer_parent_class)->finalize (object); |
1055 | } |
1056 | |
1057 | static void |
1058 | gtk_action_muxer_dispose (GObject *object) |
1059 | { |
1060 | GtkActionMuxer *muxer = GTK_ACTION_MUXER (object); |
1061 | |
1062 | g_clear_object (&muxer->parent); |
1063 | if (muxer->observed_actions) |
1064 | g_hash_table_remove_all (hash_table: muxer->observed_actions); |
1065 | |
1066 | muxer->widget = NULL; |
1067 | |
1068 | G_OBJECT_CLASS (gtk_action_muxer_parent_class)->dispose (object); |
1069 | } |
1070 | |
1071 | void |
1072 | gtk_action_muxer_connect_class_actions (GtkActionMuxer *muxer) |
1073 | { |
1074 | prop_actions_connect (muxer); |
1075 | } |
1076 | |
1077 | static void |
1078 | gtk_action_muxer_get_property (GObject *object, |
1079 | guint property_id, |
1080 | GValue *value, |
1081 | GParamSpec *pspec) |
1082 | { |
1083 | GtkActionMuxer *muxer = GTK_ACTION_MUXER (object); |
1084 | |
1085 | switch (property_id) |
1086 | { |
1087 | case PROP_PARENT: |
1088 | g_value_set_object (value, v_object: gtk_action_muxer_get_parent (muxer)); |
1089 | break; |
1090 | |
1091 | case PROP_WIDGET: |
1092 | g_value_set_object (value, v_object: muxer->widget); |
1093 | break; |
1094 | |
1095 | default: |
1096 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); |
1097 | } |
1098 | } |
1099 | |
1100 | static void |
1101 | gtk_action_muxer_set_property (GObject *object, |
1102 | guint property_id, |
1103 | const GValue *value, |
1104 | GParamSpec *pspec) |
1105 | { |
1106 | GtkActionMuxer *muxer = GTK_ACTION_MUXER (object); |
1107 | |
1108 | switch (property_id) |
1109 | { |
1110 | case PROP_PARENT: |
1111 | gtk_action_muxer_set_parent (muxer, parent: g_value_get_object (value)); |
1112 | break; |
1113 | |
1114 | case PROP_WIDGET: |
1115 | muxer->widget = g_value_get_object (value); |
1116 | break; |
1117 | |
1118 | default: |
1119 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); |
1120 | } |
1121 | } |
1122 | |
1123 | static void |
1124 | gtk_action_muxer_init (GtkActionMuxer *muxer) |
1125 | { |
1126 | muxer->widget_actions_disabled = _gtk_bitmask_new (); |
1127 | } |
1128 | |
1129 | static void |
1130 | gtk_action_muxer_observable_iface_init (GtkActionObservableInterface *iface) |
1131 | { |
1132 | iface->register_observer = gtk_action_muxer_register_observer; |
1133 | iface->unregister_observer = gtk_action_muxer_unregister_observer; |
1134 | } |
1135 | |
1136 | |
1137 | static void |
1138 | gtk_action_muxer_observer_action_added (GtkActionObserver *observer, |
1139 | GtkActionObservable *observable, |
1140 | const char *action_name, |
1141 | const GVariantType *parameter_type, |
1142 | gboolean enabled, |
1143 | GVariant *state) |
1144 | { |
1145 | if (action_muxer_query_action (GTK_ACTION_MUXER (observer), action_name, |
1146 | NULL, NULL, NULL, NULL, NULL, FALSE)) |
1147 | return; |
1148 | |
1149 | gtk_action_muxer_action_added (GTK_ACTION_MUXER (observer), |
1150 | action_name, |
1151 | parameter_type, |
1152 | enabled, |
1153 | state); |
1154 | } |
1155 | |
1156 | static void |
1157 | gtk_action_muxer_observer_action_removed (GtkActionObserver *observer, |
1158 | GtkActionObservable *observable, |
1159 | const char *action_name) |
1160 | { |
1161 | if (action_muxer_query_action (GTK_ACTION_MUXER (observer), action_name, |
1162 | NULL, NULL, NULL, NULL, NULL, FALSE)) |
1163 | return; |
1164 | |
1165 | gtk_action_muxer_action_removed (GTK_ACTION_MUXER (observer), action_name); |
1166 | } |
1167 | |
1168 | static void |
1169 | gtk_action_muxer_observer_action_enabled_changed (GtkActionObserver *observer, |
1170 | GtkActionObservable *observable, |
1171 | const char *action_name, |
1172 | gboolean enabled) |
1173 | { |
1174 | if (action_muxer_query_action (GTK_ACTION_MUXER (observer), action_name, |
1175 | NULL, NULL, NULL, NULL, NULL, FALSE)) |
1176 | return; |
1177 | |
1178 | gtk_action_muxer_action_enabled_changed (GTK_ACTION_MUXER (observer), action_name, enabled); |
1179 | } |
1180 | |
1181 | static void |
1182 | gtk_action_muxer_observer_action_state_changed (GtkActionObserver *observer, |
1183 | GtkActionObservable *observable, |
1184 | const char *action_name, |
1185 | GVariant *state) |
1186 | { |
1187 | if (action_muxer_query_action (GTK_ACTION_MUXER (observer), action_name, |
1188 | NULL, NULL, NULL, NULL, NULL, FALSE)) |
1189 | return; |
1190 | |
1191 | gtk_action_muxer_action_state_changed (GTK_ACTION_MUXER (observer), action_name, state); |
1192 | } |
1193 | |
1194 | static void |
1195 | gtk_action_muxer_observer_primary_accel_changed (GtkActionObserver *observer, |
1196 | GtkActionObservable *observable, |
1197 | const char *action_name, |
1198 | const char *action_and_target) |
1199 | { |
1200 | gtk_action_muxer_primary_accel_changed (GTK_ACTION_MUXER (observer), |
1201 | action_name, |
1202 | action_and_target); |
1203 | } |
1204 | |
1205 | static void |
1206 | gtk_action_muxer_observer_iface_init (GtkActionObserverInterface *iface) |
1207 | { |
1208 | iface->action_added = gtk_action_muxer_observer_action_added; |
1209 | iface->action_removed = gtk_action_muxer_observer_action_removed; |
1210 | iface->action_enabled_changed = gtk_action_muxer_observer_action_enabled_changed; |
1211 | iface->action_state_changed = gtk_action_muxer_observer_action_state_changed; |
1212 | iface->primary_accel_changed = gtk_action_muxer_observer_primary_accel_changed; |
1213 | } |
1214 | |
1215 | static void |
1216 | gtk_action_muxer_class_init (GObjectClass *class) |
1217 | { |
1218 | class->get_property = gtk_action_muxer_get_property; |
1219 | class->set_property = gtk_action_muxer_set_property; |
1220 | class->finalize = gtk_action_muxer_finalize; |
1221 | class->dispose = gtk_action_muxer_dispose; |
1222 | |
1223 | properties[PROP_PARENT] = g_param_spec_object (name: "parent" , nick: "Parent" , |
1224 | blurb: "The parent muxer" , |
1225 | GTK_TYPE_ACTION_MUXER, |
1226 | flags: G_PARAM_READWRITE | |
1227 | G_PARAM_STATIC_STRINGS); |
1228 | |
1229 | properties[PROP_WIDGET] = g_param_spec_object (name: "widget" , nick: "Widget" , |
1230 | blurb: "The widget that owns the muxer" , |
1231 | GTK_TYPE_WIDGET, |
1232 | flags: G_PARAM_READWRITE | |
1233 | G_PARAM_CONSTRUCT_ONLY | |
1234 | G_PARAM_STATIC_STRINGS); |
1235 | |
1236 | g_object_class_install_properties (oclass: class, n_pspecs: NUM_PROPERTIES, pspecs: properties); |
1237 | } |
1238 | |
1239 | /*< private > |
1240 | * gtk_action_muxer_insert: |
1241 | * @muxer: a `GtkActionMuxer` |
1242 | * @prefix: the prefix string for the action group |
1243 | * @action_group: a `GActionGroup` |
1244 | * |
1245 | * Adds the actions in @action_group to the list of actions provided by |
1246 | * @muxer. @prefix is prefixed to each action name, such that for each |
1247 | * action `x` in @action_group, there is an equivalent |
1248 | * action @prefix`.x` in @muxer. |
1249 | * |
1250 | * For example, if @prefix is “`app`” and @action_group |
1251 | * contains an action called “`quit`”, then @muxer will |
1252 | * now contain an action called “`app.quit`”. |
1253 | * |
1254 | * If any `GtkActionObserver`s are registered for actions in the group, |
1255 | * “action_added” notifications will be emitted, as appropriate. |
1256 | * |
1257 | * @prefix must not contain a dot ('.'). |
1258 | */ |
1259 | void |
1260 | gtk_action_muxer_insert (GtkActionMuxer *muxer, |
1261 | const char *prefix, |
1262 | GActionGroup *action_group) |
1263 | { |
1264 | char **actions; |
1265 | Group *group; |
1266 | int i; |
1267 | |
1268 | /* TODO: diff instead of ripout and replace */ |
1269 | gtk_action_muxer_remove (muxer, prefix); |
1270 | |
1271 | if (!muxer->groups) |
1272 | muxer->groups = g_hash_table_new_full (hash_func: g_str_hash, key_equal_func: g_str_equal, NULL, value_destroy_func: gtk_action_muxer_free_group); |
1273 | |
1274 | group = g_slice_new0 (Group); |
1275 | group->muxer = muxer; |
1276 | group->group = g_object_ref (action_group); |
1277 | group->prefix = g_strdup (str: prefix); |
1278 | |
1279 | g_hash_table_insert (hash_table: muxer->groups, key: group->prefix, value: group); |
1280 | |
1281 | actions = g_action_group_list_actions (action_group: group->group); |
1282 | for (i = 0; actions[i]; i++) |
1283 | gtk_action_muxer_action_added_to_group (action_group: group->group, action_name: actions[i], user_data: group); |
1284 | g_strfreev (str_array: actions); |
1285 | |
1286 | group->handler_ids[0] = g_signal_connect (group->group, "action-added" , |
1287 | G_CALLBACK (gtk_action_muxer_action_added_to_group), group); |
1288 | group->handler_ids[1] = g_signal_connect (group->group, "action-removed" , |
1289 | G_CALLBACK (gtk_action_muxer_action_removed_from_group), group); |
1290 | group->handler_ids[2] = g_signal_connect (group->group, "action-enabled-changed" , |
1291 | G_CALLBACK (gtk_action_muxer_group_action_enabled_changed), group); |
1292 | group->handler_ids[3] = g_signal_connect (group->group, "action-state-changed" , |
1293 | G_CALLBACK (gtk_action_muxer_group_action_state_changed), group); |
1294 | } |
1295 | |
1296 | /*< private > |
1297 | * gtk_action_muxer_remove: |
1298 | * @muxer: a `GtkActionMuxer` |
1299 | * @prefix: the prefix of the action group to remove |
1300 | * |
1301 | * Removes a `GActionGroup` from the `GtkActionMuxer`. |
1302 | * |
1303 | * If any `GtkActionObserver`s are registered for actions in the group, |
1304 | * “action_removed” notifications will be emitted, as appropriate. |
1305 | */ |
1306 | void |
1307 | gtk_action_muxer_remove (GtkActionMuxer *muxer, |
1308 | const char *prefix) |
1309 | { |
1310 | Group *group; |
1311 | |
1312 | if (!muxer->groups) |
1313 | return; |
1314 | |
1315 | group = g_hash_table_lookup (hash_table: muxer->groups, key: prefix); |
1316 | |
1317 | if (group != NULL) |
1318 | { |
1319 | char **actions; |
1320 | int i; |
1321 | |
1322 | g_hash_table_steal (hash_table: muxer->groups, key: prefix); |
1323 | |
1324 | actions = g_action_group_list_actions (action_group: group->group); |
1325 | for (i = 0; actions[i]; i++) |
1326 | gtk_action_muxer_action_removed_from_group (action_group: group->group, action_name: actions[i], user_data: group); |
1327 | g_strfreev (str_array: actions); |
1328 | |
1329 | gtk_action_muxer_free_group (data: group); |
1330 | } |
1331 | } |
1332 | |
1333 | /*< private > |
1334 | * gtk_action_muxer_new: |
1335 | * @widget: the widget to which the muxer belongs |
1336 | * |
1337 | * Creates a new `GtkActionMuxer`. |
1338 | */ |
1339 | GtkActionMuxer * |
1340 | gtk_action_muxer_new (GtkWidget *widget) |
1341 | { |
1342 | return g_object_new (GTK_TYPE_ACTION_MUXER, |
1343 | first_property_name: "widget" , widget, |
1344 | NULL); |
1345 | } |
1346 | |
1347 | /*< private > |
1348 | * gtk_action_muxer_get_parent: |
1349 | * @muxer: a `GtkActionMuxer` |
1350 | * |
1351 | * Returns: (transfer none): the parent of @muxer, or NULL. |
1352 | */ |
1353 | GtkActionMuxer * |
1354 | gtk_action_muxer_get_parent (GtkActionMuxer *muxer) |
1355 | { |
1356 | g_return_val_if_fail (GTK_IS_ACTION_MUXER (muxer), NULL); |
1357 | |
1358 | return muxer->parent; |
1359 | } |
1360 | |
1361 | /*< private > |
1362 | * gtk_action_muxer_set_parent: |
1363 | * @muxer: a `GtkActionMuxer` |
1364 | * @parent: (nullable): the new parent `GtkActionMuxer` |
1365 | * |
1366 | * Sets the parent of @muxer to @parent. |
1367 | */ |
1368 | void |
1369 | gtk_action_muxer_set_parent (GtkActionMuxer *muxer, |
1370 | GtkActionMuxer *parent) |
1371 | { |
1372 | g_return_if_fail (GTK_IS_ACTION_MUXER (muxer)); |
1373 | g_return_if_fail (parent == NULL || GTK_IS_ACTION_MUXER (parent)); |
1374 | |
1375 | if (muxer->parent == parent) |
1376 | return; |
1377 | |
1378 | if (muxer->parent != NULL) |
1379 | { |
1380 | notify_observers_removed (muxer, parent: muxer->parent); |
1381 | g_object_unref (object: muxer->parent); |
1382 | } |
1383 | |
1384 | muxer->parent = parent; |
1385 | |
1386 | if (muxer->parent != NULL) |
1387 | { |
1388 | g_object_ref (muxer->parent); |
1389 | notify_observers_added (muxer, parent: muxer->parent); |
1390 | } |
1391 | |
1392 | g_object_notify_by_pspec (G_OBJECT (muxer), pspec: properties[PROP_PARENT]); |
1393 | } |
1394 | |
1395 | void |
1396 | gtk_action_muxer_set_primary_accel (GtkActionMuxer *muxer, |
1397 | const char *action_and_target, |
1398 | const char *primary_accel) |
1399 | { |
1400 | if (primary_accel) |
1401 | gtk_accels_replace (accels: &muxer->primary_accels, action_and_target, primary_accel); |
1402 | else |
1403 | gtk_accels_remove (accels: &muxer->primary_accels, action_and_target); |
1404 | |
1405 | gtk_action_muxer_primary_accel_changed (muxer, NULL, action_and_target); |
1406 | } |
1407 | |
1408 | const char * |
1409 | gtk_action_muxer_get_primary_accel (GtkActionMuxer *muxer, |
1410 | const char *action_and_target) |
1411 | { |
1412 | guint position; |
1413 | |
1414 | position = gtk_accels_find (accels: &muxer->primary_accels, action_and_target); |
1415 | if (position < G_MAXUINT) |
1416 | return gtk_accels_index (self: &muxer->primary_accels, pos: position)->accel; |
1417 | |
1418 | if (!muxer->parent) |
1419 | return NULL; |
1420 | |
1421 | return gtk_action_muxer_get_primary_accel (muxer: muxer->parent, action_and_target); |
1422 | } |
1423 | |
1424 | char * |
1425 | gtk_print_action_and_target (const char *action_namespace, |
1426 | const char *action_name, |
1427 | GVariant *target) |
1428 | { |
1429 | GString *result; |
1430 | |
1431 | g_return_val_if_fail (strchr (action_name, '|') == NULL, NULL); |
1432 | g_return_val_if_fail (action_namespace == NULL || strchr (action_namespace, '|') == NULL, NULL); |
1433 | |
1434 | result = g_string_new (NULL); |
1435 | |
1436 | if (target) |
1437 | g_variant_print_string (value: target, string: result, TRUE); |
1438 | g_string_append_c (result, '|'); |
1439 | |
1440 | if (action_namespace) |
1441 | { |
1442 | g_string_append (string: result, val: action_namespace); |
1443 | g_string_append_c (result, '.'); |
1444 | } |
1445 | |
1446 | g_string_append (string: result, val: action_name); |
1447 | |
1448 | return g_string_free (string: result, FALSE); |
1449 | } |
1450 | |
1451 | char * |
1452 | gtk_normalise_detailed_action_name (const char *detailed_action_name) |
1453 | { |
1454 | GError *error = NULL; |
1455 | char *action_and_target; |
1456 | char *action_name; |
1457 | GVariant *target; |
1458 | |
1459 | g_action_parse_detailed_name (detailed_name: detailed_action_name, action_name: &action_name, target_value: &target, error: &error); |
1460 | g_assert_no_error (error); |
1461 | |
1462 | action_and_target = gtk_print_action_and_target (NULL, action_name, target); |
1463 | |
1464 | if (target) |
1465 | g_variant_unref (value: target); |
1466 | |
1467 | g_free (mem: action_name); |
1468 | |
1469 | return action_and_target; |
1470 | } |
1471 | |
1472 | |