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, &parameter_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