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 "gtkactionmuxer.h"
23
24 #include "gtkactionobservable.h"
25 #include "gtkactionobserver.h"
26 #include "gtkintl.h"
27 #include "gtkmarshalers.h"
28
29 #include <string.h>
30
31 /*< private >
32 * SECTION:gtkactionmuxer
33 * @short_description: Aggregate and monitor several action groups
34 *
35 * #GtkActionMuxer is a #GActionGroup and #GtkActionObservable that is
36 * capable of containing other #GActionGroup instances.
37 *
38 * The typical use is aggregating all of the actions applicable to a
39 * particular context into a single action group, with namespacing.
40 *
41 * Consider the case of two action groups -- one containing actions
42 * applicable to an entire application (such as “quit”) and one
43 * containing actions applicable to a particular window in the
44 * application (such as “fullscreen”).
45 *
46 * In this case, each of these action groups could be added to a
47 * #GtkActionMuxer with the prefixes “app” and “win”, respectively. This
48 * would expose the actions as “app.quit” and “win.fullscreen” on the
49 * #GActionGroup interface presented by the #GtkActionMuxer.
50 *
51 * Activations and state change requests on the #GtkActionMuxer are wired
52 * through to the underlying action group in the expected way.
53 *
54 * This class is typically only used at the site of “consumption” of
55 * actions (eg: when displaying a menu that contains many actions on
56 * different objects).
57 */
58
59 static void gtk_action_muxer_group_iface_init (GActionGroupInterface *iface);
60 static void gtk_action_muxer_observable_iface_init (GtkActionObservableInterface *iface);
61
62 typedef GObjectClass GtkActionMuxerClass;
63
64 struct _GtkActionMuxer
65 {
66 GObject parent_instance;
67
68 GHashTable *observed_actions;
69 GHashTable *groups;
70 GHashTable *primary_accels;
71 GtkActionMuxer *parent;
72 };
73
74 G_DEFINE_TYPE_WITH_CODE (GtkActionMuxer, gtk_action_muxer, G_TYPE_OBJECT,
75 G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, gtk_action_muxer_group_iface_init)
76 G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTION_OBSERVABLE, gtk_action_muxer_observable_iface_init))
77
78 enum
79 {
80 PROP_0,
81 PROP_PARENT,
82 NUM_PROPERTIES
83 };
84
85 static GParamSpec *properties[NUM_PROPERTIES];
86
87 guint accel_signal;
88
89 typedef struct
90 {
91 GtkActionMuxer *muxer;
92 GSList *watchers;
93 gchar *fullname;
94 } Action;
95
96 typedef struct
97 {
98 GtkActionMuxer *muxer;
99 GActionGroup *group;
100 gchar *prefix;
101 gulong handler_ids[4];
102 } Group;
103
104 static void
gtk_action_muxer_append_group_actions(gpointer key,gpointer value,gpointer user_data)105 gtk_action_muxer_append_group_actions (gpointer key,
106 gpointer value,
107 gpointer user_data)
108 {
109 const gchar *prefix = key;
110 Group *group = value;
111 GArray *actions = user_data;
112 gchar **group_actions;
113 gchar **action;
114
115 group_actions = g_action_group_list_actions (group->group);
116 for (action = group_actions; *action; action++)
117 {
118 gchar *fullname;
119
120 fullname = g_strconcat (prefix, ".", *action, NULL);
121 g_array_append_val (actions, fullname);
122 }
123
124 g_strfreev (group_actions);
125 }
126
127 static gchar **
gtk_action_muxer_list_actions(GActionGroup * action_group)128 gtk_action_muxer_list_actions (GActionGroup *action_group)
129 {
130 GtkActionMuxer *muxer = GTK_ACTION_MUXER (action_group);
131 GArray *actions;
132
133 actions = g_array_new (TRUE, FALSE, sizeof (gchar *));
134
135 for ( ; muxer != NULL; muxer = muxer->parent)
136 {
137 g_hash_table_foreach (muxer->groups,
138 gtk_action_muxer_append_group_actions,
139 actions);
140 }
141
142 return (gchar **)(void *) g_array_free (actions, FALSE);
143 }
144
145 static Group *
gtk_action_muxer_find_group(GtkActionMuxer * muxer,const gchar * full_name,const gchar ** action_name)146 gtk_action_muxer_find_group (GtkActionMuxer *muxer,
147 const gchar *full_name,
148 const gchar **action_name)
149 {
150 const gchar *dot;
151 gchar *prefix;
152 Group *group;
153
154 dot = strchr (full_name, '.');
155
156 if (!dot)
157 return NULL;
158
159 prefix = g_strndup (full_name, dot - full_name);
160 group = g_hash_table_lookup (muxer->groups, prefix);
161 g_free (prefix);
162
163 if (action_name)
164 *action_name = dot + 1;
165
166 return group;
167 }
168
169 static void
gtk_action_muxer_action_enabled_changed(GtkActionMuxer * muxer,const gchar * action_name,gboolean enabled)170 gtk_action_muxer_action_enabled_changed (GtkActionMuxer *muxer,
171 const gchar *action_name,
172 gboolean enabled)
173 {
174 Action *action;
175 GSList *node;
176
177 action = g_hash_table_lookup (muxer->observed_actions, action_name);
178 for (node = action ? action->watchers : NULL; node; node = node->next)
179 gtk_action_observer_action_enabled_changed (node->data, GTK_ACTION_OBSERVABLE (muxer), action_name, enabled);
180 g_action_group_action_enabled_changed (G_ACTION_GROUP (muxer), action_name, enabled);
181 }
182
183 static void
gtk_action_muxer_group_action_enabled_changed(GActionGroup * action_group,const gchar * action_name,gboolean enabled,gpointer user_data)184 gtk_action_muxer_group_action_enabled_changed (GActionGroup *action_group,
185 const gchar *action_name,
186 gboolean enabled,
187 gpointer user_data)
188 {
189 Group *group = user_data;
190 gchar *fullname;
191
192 fullname = g_strconcat (group->prefix, ".", action_name, NULL);
193 gtk_action_muxer_action_enabled_changed (group->muxer, fullname, enabled);
194
195 g_free (fullname);
196 }
197
198 static void
gtk_action_muxer_parent_action_enabled_changed(GActionGroup * action_group,const gchar * action_name,gboolean enabled,gpointer user_data)199 gtk_action_muxer_parent_action_enabled_changed (GActionGroup *action_group,
200 const gchar *action_name,
201 gboolean enabled,
202 gpointer user_data)
203 {
204 GtkActionMuxer *muxer = user_data;
205
206 gtk_action_muxer_action_enabled_changed (muxer, action_name, enabled);
207 }
208
209 static void
gtk_action_muxer_action_state_changed(GtkActionMuxer * muxer,const gchar * action_name,GVariant * state)210 gtk_action_muxer_action_state_changed (GtkActionMuxer *muxer,
211 const gchar *action_name,
212 GVariant *state)
213 {
214 Action *action;
215 GSList *node;
216
217 action = g_hash_table_lookup (muxer->observed_actions, action_name);
218 for (node = action ? action->watchers : NULL; node; node = node->next)
219 gtk_action_observer_action_state_changed (node->data, GTK_ACTION_OBSERVABLE (muxer), action_name, state);
220 g_action_group_action_state_changed (G_ACTION_GROUP (muxer), action_name, state);
221 }
222
223 static void
gtk_action_muxer_group_action_state_changed(GActionGroup * action_group,const gchar * action_name,GVariant * state,gpointer user_data)224 gtk_action_muxer_group_action_state_changed (GActionGroup *action_group,
225 const gchar *action_name,
226 GVariant *state,
227 gpointer user_data)
228 {
229 Group *group = user_data;
230 gchar *fullname;
231
232 fullname = g_strconcat (group->prefix, ".", action_name, NULL);
233 gtk_action_muxer_action_state_changed (group->muxer, fullname, state);
234
235 g_free (fullname);
236 }
237
238 static void
gtk_action_muxer_parent_action_state_changed(GActionGroup * action_group,const gchar * action_name,GVariant * state,gpointer user_data)239 gtk_action_muxer_parent_action_state_changed (GActionGroup *action_group,
240 const gchar *action_name,
241 GVariant *state,
242 gpointer user_data)
243 {
244 GtkActionMuxer *muxer = user_data;
245
246 gtk_action_muxer_action_state_changed (muxer, action_name, state);
247 }
248
249 static void
gtk_action_muxer_action_added(GtkActionMuxer * muxer,const gchar * action_name,GActionGroup * original_group,const gchar * orignal_action_name)250 gtk_action_muxer_action_added (GtkActionMuxer *muxer,
251 const gchar *action_name,
252 GActionGroup *original_group,
253 const gchar *orignal_action_name)
254 {
255 const GVariantType *parameter_type;
256 gboolean enabled;
257 GVariant *state;
258 Action *action;
259
260 action = g_hash_table_lookup (muxer->observed_actions, action_name);
261
262 if (action && action->watchers &&
263 g_action_group_query_action (original_group, orignal_action_name,
264 &enabled, ¶meter_type, NULL, NULL, &state))
265 {
266 GSList *node;
267
268 for (node = action->watchers; node; node = node->next)
269 gtk_action_observer_action_added (node->data,
270 GTK_ACTION_OBSERVABLE (muxer),
271 action_name, parameter_type, enabled, state);
272
273 if (state)
274 g_variant_unref (state);
275 }
276
277 g_action_group_action_added (G_ACTION_GROUP (muxer), action_name);
278 }
279
280 static void
gtk_action_muxer_action_added_to_group(GActionGroup * action_group,const gchar * action_name,gpointer user_data)281 gtk_action_muxer_action_added_to_group (GActionGroup *action_group,
282 const gchar *action_name,
283 gpointer user_data)
284 {
285 Group *group = user_data;
286 gchar *fullname;
287
288 fullname = g_strconcat (group->prefix, ".", action_name, NULL);
289 gtk_action_muxer_action_added (group->muxer, fullname, action_group, action_name);
290
291 g_free (fullname);
292 }
293
294 static void
gtk_action_muxer_action_added_to_parent(GActionGroup * action_group,const gchar * action_name,gpointer user_data)295 gtk_action_muxer_action_added_to_parent (GActionGroup *action_group,
296 const gchar *action_name,
297 gpointer user_data)
298 {
299 GtkActionMuxer *muxer = user_data;
300
301 gtk_action_muxer_action_added (muxer, action_name, action_group, action_name);
302 }
303
304 static void
gtk_action_muxer_action_removed(GtkActionMuxer * muxer,const gchar * action_name)305 gtk_action_muxer_action_removed (GtkActionMuxer *muxer,
306 const gchar *action_name)
307 {
308 Action *action;
309 GSList *node;
310
311 action = g_hash_table_lookup (muxer->observed_actions, action_name);
312 for (node = action ? action->watchers : NULL; node; node = node->next)
313 gtk_action_observer_action_removed (node->data, GTK_ACTION_OBSERVABLE (muxer), action_name);
314 g_action_group_action_removed (G_ACTION_GROUP (muxer), action_name);
315 }
316
317 static void
gtk_action_muxer_action_removed_from_group(GActionGroup * action_group,const gchar * action_name,gpointer user_data)318 gtk_action_muxer_action_removed_from_group (GActionGroup *action_group,
319 const gchar *action_name,
320 gpointer user_data)
321 {
322 Group *group = user_data;
323 gchar *fullname;
324
325 fullname = g_strconcat (group->prefix, ".", action_name, NULL);
326 gtk_action_muxer_action_removed (group->muxer, fullname);
327
328 g_free (fullname);
329 }
330
331 static void
gtk_action_muxer_action_removed_from_parent(GActionGroup * action_group,const gchar * action_name,gpointer user_data)332 gtk_action_muxer_action_removed_from_parent (GActionGroup *action_group,
333 const gchar *action_name,
334 gpointer user_data)
335 {
336 GtkActionMuxer *muxer = user_data;
337
338 gtk_action_muxer_action_removed (muxer, action_name);
339 }
340
341 static void
gtk_action_muxer_primary_accel_changed(GtkActionMuxer * muxer,const gchar * action_name,const gchar * action_and_target)342 gtk_action_muxer_primary_accel_changed (GtkActionMuxer *muxer,
343 const gchar *action_name,
344 const gchar *action_and_target)
345 {
346 Action *action;
347 GSList *node;
348
349 if (!action_name)
350 action_name = strrchr (action_and_target, '|') + 1;
351
352 action = g_hash_table_lookup (muxer->observed_actions, action_name);
353 for (node = action ? action->watchers : NULL; node; node = node->next)
354 gtk_action_observer_primary_accel_changed (node->data, GTK_ACTION_OBSERVABLE (muxer),
355 action_name, action_and_target);
356 g_signal_emit (muxer, accel_signal, 0, action_name, action_and_target);
357 }
358
359 static void
gtk_action_muxer_parent_primary_accel_changed(GtkActionMuxer * parent,const gchar * action_name,const gchar * action_and_target,gpointer user_data)360 gtk_action_muxer_parent_primary_accel_changed (GtkActionMuxer *parent,
361 const gchar *action_name,
362 const gchar *action_and_target,
363 gpointer user_data)
364 {
365 GtkActionMuxer *muxer = user_data;
366
367 /* If it's in our table then don't let the parent one filter through */
368 if (muxer->primary_accels && g_hash_table_lookup (muxer->primary_accels, action_and_target))
369 return;
370
371 gtk_action_muxer_primary_accel_changed (muxer, action_name, action_and_target);
372 }
373
374 static gboolean
gtk_action_muxer_query_action(GActionGroup * action_group,const gchar * action_name,gboolean * enabled,const GVariantType ** parameter_type,const GVariantType ** state_type,GVariant ** state_hint,GVariant ** state)375 gtk_action_muxer_query_action (GActionGroup *action_group,
376 const gchar *action_name,
377 gboolean *enabled,
378 const GVariantType **parameter_type,
379 const GVariantType **state_type,
380 GVariant **state_hint,
381 GVariant **state)
382 {
383 GtkActionMuxer *muxer = GTK_ACTION_MUXER (action_group);
384 Group *group;
385 const gchar *unprefixed_name;
386
387 group = gtk_action_muxer_find_group (muxer, action_name, &unprefixed_name);
388
389 if (group)
390 return g_action_group_query_action (group->group, unprefixed_name, enabled,
391 parameter_type, state_type, state_hint, state);
392
393 if (muxer->parent)
394 return g_action_group_query_action (G_ACTION_GROUP (muxer->parent), action_name,
395 enabled, parameter_type,
396 state_type, state_hint, state);
397
398 return FALSE;
399 }
400
401 static void
gtk_action_muxer_activate_action(GActionGroup * action_group,const gchar * action_name,GVariant * parameter)402 gtk_action_muxer_activate_action (GActionGroup *action_group,
403 const gchar *action_name,
404 GVariant *parameter)
405 {
406 GtkActionMuxer *muxer = GTK_ACTION_MUXER (action_group);
407 Group *group;
408 const gchar *unprefixed_name;
409
410 group = gtk_action_muxer_find_group (muxer, action_name, &unprefixed_name);
411
412 if (group)
413 g_action_group_activate_action (group->group, unprefixed_name, parameter);
414 else if (muxer->parent)
415 g_action_group_activate_action (G_ACTION_GROUP (muxer->parent), action_name, parameter);
416 }
417
418 static void
gtk_action_muxer_change_action_state(GActionGroup * action_group,const gchar * action_name,GVariant * state)419 gtk_action_muxer_change_action_state (GActionGroup *action_group,
420 const gchar *action_name,
421 GVariant *state)
422 {
423 GtkActionMuxer *muxer = GTK_ACTION_MUXER (action_group);
424 Group *group;
425 const gchar *unprefixed_name;
426
427 group = gtk_action_muxer_find_group (muxer, action_name, &unprefixed_name);
428
429 if (group)
430 g_action_group_change_action_state (group->group, unprefixed_name, state);
431 else if (muxer->parent)
432 g_action_group_change_action_state (G_ACTION_GROUP (muxer->parent), action_name, state);
433 }
434
435 static void
gtk_action_muxer_unregister_internal(Action * action,gpointer observer)436 gtk_action_muxer_unregister_internal (Action *action,
437 gpointer observer)
438 {
439 GtkActionMuxer *muxer = action->muxer;
440 GSList **ptr;
441
442 for (ptr = &action->watchers; *ptr; ptr = &(*ptr)->next)
443 if ((*ptr)->data == observer)
444 {
445 *ptr = g_slist_remove (*ptr, observer);
446
447 if (action->watchers == NULL)
448 g_hash_table_remove (muxer->observed_actions, action->fullname);
449
450 break;
451 }
452 }
453
454 static void
gtk_action_muxer_weak_notify(gpointer data,GObject * where_the_object_was)455 gtk_action_muxer_weak_notify (gpointer data,
456 GObject *where_the_object_was)
457 {
458 Action *action = data;
459
460 gtk_action_muxer_unregister_internal (action, where_the_object_was);
461 }
462
463 static void
gtk_action_muxer_register_observer(GtkActionObservable * observable,const gchar * name,GtkActionObserver * observer)464 gtk_action_muxer_register_observer (GtkActionObservable *observable,
465 const gchar *name,
466 GtkActionObserver *observer)
467 {
468 GtkActionMuxer *muxer = GTK_ACTION_MUXER (observable);
469 Action *action;
470
471 action = g_hash_table_lookup (muxer->observed_actions, name);
472
473 if (action == NULL)
474 {
475 action = g_slice_new (Action);
476 action->muxer = muxer;
477 action->fullname = g_strdup (name);
478 action->watchers = NULL;
479
480 g_hash_table_insert (muxer->observed_actions, action->fullname, action);
481 }
482
483 action->watchers = g_slist_prepend (action->watchers, observer);
484 g_object_weak_ref (G_OBJECT (observer), gtk_action_muxer_weak_notify, action);
485 }
486
487 static void
gtk_action_muxer_unregister_observer(GtkActionObservable * observable,const gchar * name,GtkActionObserver * observer)488 gtk_action_muxer_unregister_observer (GtkActionObservable *observable,
489 const gchar *name,
490 GtkActionObserver *observer)
491 {
492 GtkActionMuxer *muxer = GTK_ACTION_MUXER (observable);
493 Action *action;
494
495 action = g_hash_table_lookup (muxer->observed_actions, name);
496 g_object_weak_unref (G_OBJECT (observer), gtk_action_muxer_weak_notify, action);
497 gtk_action_muxer_unregister_internal (action, observer);
498 }
499
500 static void
gtk_action_muxer_free_group(gpointer data)501 gtk_action_muxer_free_group (gpointer data)
502 {
503 Group *group = data;
504 gint i;
505
506 /* 'for loop' or 'four loop'? */
507 for (i = 0; i < 4; i++)
508 g_signal_handler_disconnect (group->group, group->handler_ids[i]);
509
510 g_object_unref (group->group);
511 g_free (group->prefix);
512
513 g_slice_free (Group, group);
514 }
515
516 static void
gtk_action_muxer_free_action(gpointer data)517 gtk_action_muxer_free_action (gpointer data)
518 {
519 Action *action = data;
520 GSList *it;
521
522 for (it = action->watchers; it; it = it->next)
523 g_object_weak_unref (G_OBJECT (it->data), gtk_action_muxer_weak_notify, action);
524
525 g_slist_free (action->watchers);
526 g_free (action->fullname);
527
528 g_slice_free (Action, action);
529 }
530
531 static void
gtk_action_muxer_finalize(GObject * object)532 gtk_action_muxer_finalize (GObject *object)
533 {
534 GtkActionMuxer *muxer = GTK_ACTION_MUXER (object);
535
536 g_assert_cmpint (g_hash_table_size (muxer->observed_actions), ==, 0);
537 g_hash_table_unref (muxer->observed_actions);
538 g_hash_table_unref (muxer->groups);
539 if (muxer->primary_accels)
540 g_hash_table_unref (muxer->primary_accels);
541
542 G_OBJECT_CLASS (gtk_action_muxer_parent_class)
543 ->finalize (object);
544 }
545
546 static void
gtk_action_muxer_dispose(GObject * object)547 gtk_action_muxer_dispose (GObject *object)
548 {
549 GtkActionMuxer *muxer = GTK_ACTION_MUXER (object);
550
551 if (muxer->parent)
552 {
553 g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_action_added_to_parent, muxer);
554 g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_action_removed_from_parent, muxer);
555 g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_action_enabled_changed, muxer);
556 g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_action_state_changed, muxer);
557 g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_primary_accel_changed, muxer);
558
559 g_clear_object (&muxer->parent);
560 }
561
562 g_hash_table_remove_all (muxer->observed_actions);
563
564 G_OBJECT_CLASS (gtk_action_muxer_parent_class)
565 ->dispose (object);
566 }
567
568 static void
gtk_action_muxer_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)569 gtk_action_muxer_get_property (GObject *object,
570 guint property_id,
571 GValue *value,
572 GParamSpec *pspec)
573 {
574 GtkActionMuxer *muxer = GTK_ACTION_MUXER (object);
575
576 switch (property_id)
577 {
578 case PROP_PARENT:
579 g_value_set_object (value, gtk_action_muxer_get_parent (muxer));
580 break;
581
582 default:
583 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
584 }
585 }
586
587 static void
gtk_action_muxer_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)588 gtk_action_muxer_set_property (GObject *object,
589 guint property_id,
590 const GValue *value,
591 GParamSpec *pspec)
592 {
593 GtkActionMuxer *muxer = GTK_ACTION_MUXER (object);
594
595 switch (property_id)
596 {
597 case PROP_PARENT:
598 gtk_action_muxer_set_parent (muxer, g_value_get_object (value));
599 break;
600
601 default:
602 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
603 }
604 }
605
606 static void
gtk_action_muxer_init(GtkActionMuxer * muxer)607 gtk_action_muxer_init (GtkActionMuxer *muxer)
608 {
609 muxer->observed_actions = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, gtk_action_muxer_free_action);
610 muxer->groups = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, gtk_action_muxer_free_group);
611 }
612
613 static void
gtk_action_muxer_observable_iface_init(GtkActionObservableInterface * iface)614 gtk_action_muxer_observable_iface_init (GtkActionObservableInterface *iface)
615 {
616 iface->register_observer = gtk_action_muxer_register_observer;
617 iface->unregister_observer = gtk_action_muxer_unregister_observer;
618 }
619
620 static void
gtk_action_muxer_group_iface_init(GActionGroupInterface * iface)621 gtk_action_muxer_group_iface_init (GActionGroupInterface *iface)
622 {
623 iface->list_actions = gtk_action_muxer_list_actions;
624 iface->query_action = gtk_action_muxer_query_action;
625 iface->activate_action = gtk_action_muxer_activate_action;
626 iface->change_action_state = gtk_action_muxer_change_action_state;
627 }
628
629 static void
gtk_action_muxer_class_init(GObjectClass * class)630 gtk_action_muxer_class_init (GObjectClass *class)
631 {
632 class->get_property = gtk_action_muxer_get_property;
633 class->set_property = gtk_action_muxer_set_property;
634 class->finalize = gtk_action_muxer_finalize;
635 class->dispose = gtk_action_muxer_dispose;
636
637 accel_signal = g_signal_new (I_("primary-accel-changed"),
638 GTK_TYPE_ACTION_MUXER,
639 G_SIGNAL_RUN_LAST,
640 0,
641 NULL, NULL,
642 _gtk_marshal_VOID__STRING_STRING,
643 G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
644 g_signal_set_va_marshaller (accel_signal,
645 G_TYPE_FROM_CLASS (class),
646 _gtk_marshal_VOID__STRING_STRINGv);
647
648 properties[PROP_PARENT] = g_param_spec_object ("parent", "Parent",
649 "The parent muxer",
650 GTK_TYPE_ACTION_MUXER,
651 G_PARAM_READWRITE |
652 G_PARAM_STATIC_STRINGS);
653
654 g_object_class_install_properties (class, NUM_PROPERTIES, properties);
655 }
656
657 /*< private >
658 * gtk_action_muxer_insert:
659 * @muxer: a #GtkActionMuxer
660 * @prefix: the prefix string for the action group
661 * @action_group: a #GActionGroup
662 *
663 * Adds the actions in @action_group to the list of actions provided by
664 * @muxer. @prefix is prefixed to each action name, such that for each
665 * action `x` in @action_group, there is an equivalent
666 * action @prefix`.x` in @muxer.
667 *
668 * For example, if @prefix is “`app`” and @action_group
669 * contains an action called “`quit`”, then @muxer will
670 * now contain an action called “`app.quit`”.
671 *
672 * If any #GtkActionObservers are registered for actions in the group,
673 * “action_added” notifications will be emitted, as appropriate.
674 *
675 * @prefix must not contain a dot ('.').
676 */
677 void
gtk_action_muxer_insert(GtkActionMuxer * muxer,const gchar * prefix,GActionGroup * action_group)678 gtk_action_muxer_insert (GtkActionMuxer *muxer,
679 const gchar *prefix,
680 GActionGroup *action_group)
681 {
682 gchar **actions;
683 Group *group;
684 gint i;
685
686 g_object_ref (action_group);
687
688 /* TODO: diff instead of ripout and replace */
689 gtk_action_muxer_remove (muxer, prefix);
690
691 group = g_slice_new (Group);
692 group->muxer = muxer;
693 group->group = action_group;
694 group->prefix = g_strdup (prefix);
695
696 g_hash_table_insert (muxer->groups, group->prefix, group);
697
698 actions = g_action_group_list_actions (group->group);
699 for (i = 0; actions[i]; i++)
700 gtk_action_muxer_action_added_to_group (group->group, actions[i], group);
701 g_strfreev (actions);
702
703 group->handler_ids[0] = g_signal_connect (group->group, "action-added",
704 G_CALLBACK (gtk_action_muxer_action_added_to_group), group);
705 group->handler_ids[1] = g_signal_connect (group->group, "action-removed",
706 G_CALLBACK (gtk_action_muxer_action_removed_from_group), group);
707 group->handler_ids[2] = g_signal_connect (group->group, "action-enabled-changed",
708 G_CALLBACK (gtk_action_muxer_group_action_enabled_changed), group);
709 group->handler_ids[3] = g_signal_connect (group->group, "action-state-changed",
710 G_CALLBACK (gtk_action_muxer_group_action_state_changed), group);
711 }
712
713 /*< private >
714 * gtk_action_muxer_remove:
715 * @muxer: a #GtkActionMuxer
716 * @prefix: the prefix of the action group to remove
717 *
718 * Removes a #GActionGroup from the #GtkActionMuxer.
719 *
720 * If any #GtkActionObservers are registered for actions in the group,
721 * “action_removed” notifications will be emitted, as appropriate.
722 */
723 void
gtk_action_muxer_remove(GtkActionMuxer * muxer,const gchar * prefix)724 gtk_action_muxer_remove (GtkActionMuxer *muxer,
725 const gchar *prefix)
726 {
727 Group *group;
728
729 group = g_hash_table_lookup (muxer->groups, prefix);
730
731 if (group != NULL)
732 {
733 gchar **actions;
734 gint i;
735
736 g_hash_table_steal (muxer->groups, prefix);
737
738 actions = g_action_group_list_actions (group->group);
739 for (i = 0; actions[i]; i++)
740 gtk_action_muxer_action_removed_from_group (group->group, actions[i], group);
741 g_strfreev (actions);
742
743 gtk_action_muxer_free_group (group);
744 }
745 }
746
747 static void
gtk_action_muxer_append_prefixes(gpointer key,gpointer value,gpointer user_data)748 gtk_action_muxer_append_prefixes (gpointer key,
749 gpointer value,
750 gpointer user_data)
751 {
752 const gchar *prefix = key;
753 GArray *prefixes= user_data;
754
755 g_array_append_val (prefixes, prefix);
756 }
757
758 const gchar **
gtk_action_muxer_list_prefixes(GtkActionMuxer * muxer)759 gtk_action_muxer_list_prefixes (GtkActionMuxer *muxer)
760 {
761 GArray *prefixes;
762
763 prefixes = g_array_new (TRUE, FALSE, sizeof (gchar *));
764
765 for ( ; muxer != NULL; muxer = muxer->parent)
766 {
767 g_hash_table_foreach (muxer->groups,
768 gtk_action_muxer_append_prefixes,
769 prefixes);
770 }
771
772 return (const gchar **)(void *) g_array_free (prefixes, FALSE);
773 }
774
775 GActionGroup *
gtk_action_muxer_lookup(GtkActionMuxer * muxer,const gchar * prefix)776 gtk_action_muxer_lookup (GtkActionMuxer *muxer,
777 const gchar *prefix)
778 {
779 Group *group;
780
781 for ( ; muxer != NULL; muxer = muxer->parent)
782 {
783 group = g_hash_table_lookup (muxer->groups, prefix);
784
785 if (group != NULL)
786 return group->group;
787 }
788
789 return NULL;
790 }
791
792 /*< private >
793 * gtk_action_muxer_new:
794 *
795 * Creates a new #GtkActionMuxer.
796 */
797 GtkActionMuxer *
gtk_action_muxer_new(void)798 gtk_action_muxer_new (void)
799 {
800 return g_object_new (GTK_TYPE_ACTION_MUXER, NULL);
801 }
802
803 /*< private >
804 * gtk_action_muxer_get_parent:
805 * @muxer: a #GtkActionMuxer
806 *
807 * Returns: (transfer none): the parent of @muxer, or NULL.
808 */
809 GtkActionMuxer *
gtk_action_muxer_get_parent(GtkActionMuxer * muxer)810 gtk_action_muxer_get_parent (GtkActionMuxer *muxer)
811 {
812 g_return_val_if_fail (GTK_IS_ACTION_MUXER (muxer), NULL);
813
814 return muxer->parent;
815 }
816
817 static void
emit_changed_accels(GtkActionMuxer * muxer,GtkActionMuxer * parent)818 emit_changed_accels (GtkActionMuxer *muxer,
819 GtkActionMuxer *parent)
820 {
821 while (parent)
822 {
823 if (parent->primary_accels)
824 {
825 GHashTableIter iter;
826 gpointer key;
827
828 g_hash_table_iter_init (&iter, parent->primary_accels);
829 while (g_hash_table_iter_next (&iter, &key, NULL))
830 gtk_action_muxer_primary_accel_changed (muxer, NULL, key);
831 }
832
833 parent = parent->parent;
834 }
835 }
836
837 /*< private >
838 * gtk_action_muxer_set_parent:
839 * @muxer: a #GtkActionMuxer
840 * @parent: (allow-none): the new parent #GtkActionMuxer
841 *
842 * Sets the parent of @muxer to @parent.
843 */
844 void
gtk_action_muxer_set_parent(GtkActionMuxer * muxer,GtkActionMuxer * parent)845 gtk_action_muxer_set_parent (GtkActionMuxer *muxer,
846 GtkActionMuxer *parent)
847 {
848 g_return_if_fail (GTK_IS_ACTION_MUXER (muxer));
849 g_return_if_fail (parent == NULL || GTK_IS_ACTION_MUXER (parent));
850
851 if (muxer->parent == parent)
852 return;
853
854 if (muxer->parent != NULL)
855 {
856 gchar **actions;
857 gchar **it;
858
859 actions = g_action_group_list_actions (G_ACTION_GROUP (muxer->parent));
860 for (it = actions; *it; it++)
861 gtk_action_muxer_action_removed (muxer, *it);
862 g_strfreev (actions);
863
864 emit_changed_accels (muxer, muxer->parent);
865
866 g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_action_added_to_parent, muxer);
867 g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_action_removed_from_parent, muxer);
868 g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_action_enabled_changed, muxer);
869 g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_action_state_changed, muxer);
870 g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_primary_accel_changed, muxer);
871
872 g_object_unref (muxer->parent);
873 }
874
875 muxer->parent = parent;
876
877 if (muxer->parent != NULL)
878 {
879 gchar **actions;
880 gchar **it;
881
882 g_object_ref (muxer->parent);
883
884 actions = g_action_group_list_actions (G_ACTION_GROUP (muxer->parent));
885 for (it = actions; *it; it++)
886 gtk_action_muxer_action_added (muxer, *it, G_ACTION_GROUP (muxer->parent), *it);
887 g_strfreev (actions);
888
889 emit_changed_accels (muxer, muxer->parent);
890
891 g_signal_connect (muxer->parent, "action-added",
892 G_CALLBACK (gtk_action_muxer_action_added_to_parent), muxer);
893 g_signal_connect (muxer->parent, "action-removed",
894 G_CALLBACK (gtk_action_muxer_action_removed_from_parent), muxer);
895 g_signal_connect (muxer->parent, "action-enabled-changed",
896 G_CALLBACK (gtk_action_muxer_parent_action_enabled_changed), muxer);
897 g_signal_connect (muxer->parent, "action-state-changed",
898 G_CALLBACK (gtk_action_muxer_parent_action_state_changed), muxer);
899 g_signal_connect (muxer->parent, "primary-accel-changed",
900 G_CALLBACK (gtk_action_muxer_parent_primary_accel_changed), muxer);
901 }
902
903 g_object_notify_by_pspec (G_OBJECT (muxer), properties[PROP_PARENT]);
904 }
905
906 void
gtk_action_muxer_set_primary_accel(GtkActionMuxer * muxer,const gchar * action_and_target,const gchar * primary_accel)907 gtk_action_muxer_set_primary_accel (GtkActionMuxer *muxer,
908 const gchar *action_and_target,
909 const gchar *primary_accel)
910 {
911 if (!muxer->primary_accels)
912 muxer->primary_accels = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
913
914 if (primary_accel)
915 g_hash_table_insert (muxer->primary_accels, g_strdup (action_and_target), g_strdup (primary_accel));
916 else
917 g_hash_table_remove (muxer->primary_accels, action_and_target);
918
919 gtk_action_muxer_primary_accel_changed (muxer, NULL, action_and_target);
920 }
921
922 const gchar *
gtk_action_muxer_get_primary_accel(GtkActionMuxer * muxer,const gchar * action_and_target)923 gtk_action_muxer_get_primary_accel (GtkActionMuxer *muxer,
924 const gchar *action_and_target)
925 {
926 if (muxer->primary_accels)
927 {
928 const gchar *primary_accel;
929
930 primary_accel = g_hash_table_lookup (muxer->primary_accels, action_and_target);
931
932 if (primary_accel)
933 return primary_accel;
934 }
935
936 if (!muxer->parent)
937 return NULL;
938
939 return gtk_action_muxer_get_primary_accel (muxer->parent, action_and_target);
940 }
941
942 gchar *
gtk_print_action_and_target(const gchar * action_namespace,const gchar * action_name,GVariant * target)943 gtk_print_action_and_target (const gchar *action_namespace,
944 const gchar *action_name,
945 GVariant *target)
946 {
947 GString *result;
948
949 g_return_val_if_fail (strchr (action_name, '|') == NULL, NULL);
950 g_return_val_if_fail (action_namespace == NULL || strchr (action_namespace, '|') == NULL, NULL);
951
952 result = g_string_new (NULL);
953
954 if (target)
955 g_variant_print_string (target, result, TRUE);
956 g_string_append_c (result, '|');
957
958 if (action_namespace)
959 {
960 g_string_append (result, action_namespace);
961 g_string_append_c (result, '.');
962 }
963
964 g_string_append (result, action_name);
965
966 return g_string_free (result, FALSE);
967 }
968
969 gchar *
gtk_normalise_detailed_action_name(const gchar * detailed_action_name)970 gtk_normalise_detailed_action_name (const gchar *detailed_action_name)
971 {
972 GError *error = NULL;
973 gchar *action_and_target;
974 gchar *action_name;
975 GVariant *target;
976
977 g_action_parse_detailed_name (detailed_action_name, &action_name, &target, &error);
978 g_assert_no_error (error);
979
980 action_and_target = gtk_print_action_and_target (NULL, action_name, target);
981
982 if (target)
983 g_variant_unref (target);
984
985 g_free (action_name);
986
987 return action_and_target;
988 }
989