]> Pileus Git - ~andy/gtk/blob - gtk/gactionmuxer.c
Change FSF Address
[~andy/gtk] / gtk / gactionmuxer.c
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 "gactionmuxer.h"
23
24 #include "gactionobservable.h"
25 #include "gactionobserver.h"
26
27 #include <string.h>
28
29 /*
30  * SECTION:gactionmuxer
31  * @short_description: Aggregate and monitor several action groups
32  *
33  * #GActionMuxer is a #GActionGroup and #GActionObservable that is
34  * capable of containing other #GActionGroup instances.
35  *
36  * The typical use is aggregating all of the actions applicable to a
37  * particular context into a single action group, with namespacing.
38  *
39  * Consider the case of two action groups -- one containing actions
40  * applicable to an entire application (such as 'quit') and one
41  * containing actions applicable to a particular window in the
42  * application (such as 'fullscreen').
43  *
44  * In this case, each of these action groups could be added to a
45  * #GActionMuxer with the prefixes "app" and "win", respectively.  This
46  * would expose the actions as "app.quit" and "win.fullscreen" on the
47  * #GActionGroup interface presented by the #GActionMuxer.
48  *
49  * Activations and state change requests on the #GActionMuxer are wired
50  * through to the underlying action group in the expected way.
51  *
52  * This class is typically only used at the site of "consumption" of
53  * actions (eg: when displaying a menu that contains many actions on
54  * different objects).
55  */
56
57 static void     g_action_muxer_group_iface_init         (GActionGroupInterface      *iface);
58 static void     g_action_muxer_observable_iface_init    (GActionObservableInterface *iface);
59
60 typedef GObjectClass GActionMuxerClass;
61
62 struct _GActionMuxer
63 {
64   GObject parent_instance;
65
66   GHashTable *actions;
67   GHashTable *groups;
68 };
69
70 G_DEFINE_TYPE_WITH_CODE (GActionMuxer, g_action_muxer, G_TYPE_OBJECT,
71                          G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, g_action_muxer_group_iface_init)
72                          G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_OBSERVABLE, g_action_muxer_observable_iface_init))
73
74 typedef struct
75 {
76   GActionMuxer *muxer;
77   GSList       *watchers;
78   gchar        *fullname;
79 } Action;
80
81 typedef struct
82 {
83   GActionMuxer *muxer;
84   GActionGroup *group;
85   gchar        *prefix;
86   gulong        handler_ids[4];
87 } Group;
88
89 static gchar **
90 g_action_muxer_list_actions (GActionGroup *action_group)
91 {
92   GActionMuxer *muxer = G_ACTION_MUXER (action_group);
93
94   return (gchar **) muxer->groups;
95 }
96
97 static Group *
98 g_action_muxer_find_group (GActionMuxer  *muxer,
99                               const gchar     **name)
100 {
101   const gchar *dot;
102   gchar *prefix;
103   Group *group;
104
105   dot = strchr (*name, '.');
106
107   if (!dot)
108     return NULL;
109
110   prefix = g_strndup (*name, dot - *name);
111   group = g_hash_table_lookup (muxer->groups, prefix);
112   g_free (prefix);
113
114   *name = dot + 1;
115
116   return group;
117 }
118
119 static Action *
120 g_action_muxer_lookup_action (GActionMuxer  *muxer,
121                               const gchar   *prefix,
122                               const gchar   *action_name,
123                               gchar        **fullname)
124 {
125   Action *action;
126
127   *fullname = g_strconcat (prefix, ".", action_name, NULL);
128   action = g_hash_table_lookup (muxer->actions, *fullname);
129
130   return action;
131 }
132
133 static void
134 g_action_muxer_action_enabled_changed (GActionGroup *action_group,
135                                        const gchar  *action_name,
136                                        gboolean      enabled,
137                                        gpointer      user_data)
138 {
139   Group *group = user_data;
140   gchar *fullname;
141   Action *action;
142   GSList *node;
143
144   action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname);
145   for (node = action ? action->watchers : NULL; node; node = node->next)
146     g_action_observer_action_enabled_changed (node->data, G_ACTION_OBSERVABLE (group->muxer), fullname, enabled);
147   g_action_group_action_enabled_changed (G_ACTION_GROUP (group->muxer), fullname, enabled);
148   g_free (fullname);
149 }
150
151 static void
152 g_action_muxer_action_state_changed (GActionGroup *action_group,
153                                      const gchar  *action_name,
154                                      GVariant     *state,
155                                      gpointer      user_data)
156 {
157   Group *group = user_data;
158   gchar *fullname;
159   Action *action;
160   GSList *node;
161
162   action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname);
163   for (node = action ? action->watchers : NULL; node; node = node->next)
164     g_action_observer_action_state_changed (node->data, G_ACTION_OBSERVABLE (group->muxer), fullname, state);
165   g_action_group_action_state_changed (G_ACTION_GROUP (group->muxer), fullname, state);
166   g_free (fullname);
167 }
168
169 static void
170 g_action_muxer_action_added (GActionGroup *action_group,
171                              const gchar  *action_name,
172                              gpointer      user_data)
173 {
174   const GVariantType *parameter_type;
175   Group *group = user_data;
176   gboolean enabled;
177   GVariant *state;
178
179   if (g_action_group_query_action (group->group, action_name, &enabled, &parameter_type, NULL, NULL, &state))
180     {
181       gchar *fullname;
182       Action *action;
183       GSList *node;
184
185       action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname);
186
187       for (node = action ? action->watchers : NULL; node; node = node->next)
188         g_action_observer_action_added (node->data,
189                                         G_ACTION_OBSERVABLE (group->muxer),
190                                         fullname, parameter_type, enabled, state);
191
192       g_action_group_action_added (G_ACTION_GROUP (group->muxer), fullname);
193
194       if (state)
195         g_variant_unref (state);
196
197       g_free (fullname);
198     }
199 }
200
201 static void
202 g_action_muxer_action_removed (GActionGroup *action_group,
203                                const gchar  *action_name,
204                                gpointer      user_data)
205 {
206   Group *group = user_data;
207   gchar *fullname;
208   Action *action;
209   GSList *node;
210
211   action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname);
212   for (node = action ? action->watchers : NULL; node; node = node->next)
213     g_action_observer_action_removed (node->data, G_ACTION_OBSERVABLE (group->muxer), fullname);
214   g_action_group_action_removed (G_ACTION_GROUP (group->muxer), fullname);
215   g_free (fullname);
216 }
217
218 static gboolean
219 g_action_muxer_query_action (GActionGroup        *action_group,
220                              const gchar         *action_name,
221                              gboolean            *enabled,
222                              const GVariantType **parameter_type,
223                              const GVariantType **state_type,
224                              GVariant           **state_hint,
225                              GVariant           **state)
226 {
227   GActionMuxer *muxer = G_ACTION_MUXER (action_group);
228   Group *group;
229
230   group = g_action_muxer_find_group (muxer, &action_name);
231
232   if (!group)
233     return FALSE;
234
235   return g_action_group_query_action (group->group, action_name, enabled,
236                                       parameter_type, state_type, state_hint, state);
237 }
238
239 static void
240 g_action_muxer_activate_action (GActionGroup *action_group,
241                                 const gchar  *action_name,
242                                 GVariant     *parameter)
243 {
244   GActionMuxer *muxer = G_ACTION_MUXER (action_group);
245   Group *group;
246
247   group = g_action_muxer_find_group (muxer, &action_name);
248
249   if (group)
250     g_action_group_activate_action (group->group, action_name, parameter);
251 }
252
253 static void
254 g_action_muxer_change_action_state (GActionGroup *action_group,
255                                     const gchar  *action_name,
256                                     GVariant     *state)
257 {
258   GActionMuxer *muxer = G_ACTION_MUXER (action_group);
259   Group *group;
260
261   group = g_action_muxer_find_group (muxer, &action_name);
262
263   if (group)
264     g_action_group_change_action_state (group->group, action_name, state);
265 }
266
267 static void
268 g_action_muxer_unregister_internal (Action   *action,
269                                     gpointer  observer)
270 {
271   GActionMuxer *muxer = action->muxer;
272   GSList **ptr;
273
274   for (ptr = &action->watchers; *ptr; ptr = &(*ptr)->next)
275     if ((*ptr)->data == observer)
276       {
277         *ptr = g_slist_remove (*ptr, observer);
278
279         if (action->watchers == NULL)
280           {
281             g_hash_table_remove (muxer->actions, action->fullname);
282             g_free (action->fullname);
283
284             g_slice_free (Action, action);
285
286             g_object_unref (muxer);
287           }
288
289         break;
290       }
291 }
292
293 static void
294 g_action_muxer_weak_notify (gpointer  data,
295                             GObject  *where_the_object_was)
296 {
297   Action *action = data;
298
299   g_action_muxer_unregister_internal (action, where_the_object_was);
300 }
301
302 static void
303 g_action_muxer_register_observer (GActionObservable *observable,
304                                   const gchar       *name,
305                                   GActionObserver   *observer)
306 {
307   GActionMuxer *muxer = G_ACTION_MUXER (observable);
308   Action *action;
309
310   action = g_hash_table_lookup (muxer->actions, name);
311
312   if (action == NULL)
313     {
314       action = g_slice_new (Action);
315       action->muxer = g_object_ref (muxer);
316       action->fullname = g_strdup (name);
317       action->watchers = NULL;
318
319       g_hash_table_insert (muxer->actions, action->fullname, action);
320     }
321
322   action->watchers = g_slist_prepend (action->watchers, observer);
323   g_object_weak_ref (G_OBJECT (observer), g_action_muxer_weak_notify, action);
324 }
325
326 static void
327 g_action_muxer_unregister_observer (GActionObservable *observable,
328                                     const gchar       *name,
329                                     GActionObserver   *observer)
330 {
331   GActionMuxer *muxer = G_ACTION_MUXER (observable);
332   Action *action;
333
334   action = g_hash_table_lookup (muxer->actions, name);
335   g_object_weak_unref (G_OBJECT (observer), g_action_muxer_weak_notify, action);
336   g_action_muxer_unregister_internal (action, observer);
337 }
338
339 static void
340 g_action_muxer_free_group (gpointer data)
341 {
342   Group *group = data;
343
344   g_object_unref (group->group);
345   g_free (group->prefix);
346
347   g_slice_free (Group, group);
348 }
349
350 static void
351 g_action_muxer_finalize (GObject *object)
352 {
353   GActionMuxer *muxer = G_ACTION_MUXER (object);
354
355   g_assert_cmpint (g_hash_table_size (muxer->actions), ==, 0);
356   g_hash_table_unref (muxer->actions);
357   g_hash_table_unref (muxer->groups);
358
359   G_OBJECT_CLASS (g_action_muxer_parent_class)
360     ->finalize (object);
361 }
362
363 static void
364 g_action_muxer_init (GActionMuxer *muxer)
365 {
366   muxer->actions = g_hash_table_new (g_str_hash, g_str_equal);
367   muxer->groups = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_action_muxer_free_group);
368 }
369
370 static void
371 g_action_muxer_observable_iface_init (GActionObservableInterface *iface)
372 {
373   iface->register_observer = g_action_muxer_register_observer;
374   iface->unregister_observer = g_action_muxer_unregister_observer;
375 }
376
377 static void
378 g_action_muxer_group_iface_init (GActionGroupInterface *iface)
379 {
380   iface->list_actions = g_action_muxer_list_actions;
381   iface->query_action = g_action_muxer_query_action;
382   iface->activate_action = g_action_muxer_activate_action;
383   iface->change_action_state = g_action_muxer_change_action_state;
384 }
385
386 static void
387 g_action_muxer_class_init (GObjectClass *class)
388 {
389   class->finalize = g_action_muxer_finalize;
390 }
391
392 /*
393  * g_action_muxer_insert:
394  * @muxer: a #GActionMuxer
395  * @prefix: the prefix string for the action group
396  * @action_group: a #GActionGroup
397  *
398  * Adds the actions in @action_group to the list of actions provided by
399  * @muxer.  @prefix is prefixed to each action name, such that for each
400  * action <varname>x</varname> in @action_group, there is an equivalent
401  * action @prefix<literal>.</literal><varname>x</varname> in @muxer.
402  *
403  * For example, if @prefix is "<literal>app</literal>" and @action_group
404  * contains an action called "<literal>quit</literal>", then @muxer will
405  * now contain an action called "<literal>app.quit</literal>".
406  *
407  * If any #GActionObservers are registered for actions in the group,
408  * "action_added" notifications will be emitted, as appropriate.
409  *
410  * @prefix must not contain a dot ('.').
411  */
412 void
413 g_action_muxer_insert (GActionMuxer *muxer,
414                        const gchar  *prefix,
415                        GActionGroup *action_group)
416 {
417   gchar **actions;
418   Group *group;
419   gint i;
420
421   /* TODO: diff instead of ripout and replace */
422   g_action_muxer_remove (muxer, prefix);
423
424   group = g_slice_new (Group);
425   group->muxer = muxer;
426   group->group = g_object_ref (action_group);
427   group->prefix = g_strdup (prefix);
428
429   g_hash_table_insert (muxer->groups, group->prefix, group);
430
431   actions = g_action_group_list_actions (group->group);
432   for (i = 0; actions[i]; i++)
433     g_action_muxer_action_added (group->group, actions[i], group);
434   g_strfreev (actions);
435
436   group->handler_ids[0] = g_signal_connect (group->group, "action-added",
437                                             G_CALLBACK (g_action_muxer_action_added), group);
438   group->handler_ids[1] = g_signal_connect (group->group, "action-removed",
439                                             G_CALLBACK (g_action_muxer_action_removed), group);
440   group->handler_ids[2] = g_signal_connect (group->group, "action-enabled-changed",
441                                             G_CALLBACK (g_action_muxer_action_enabled_changed), group);
442   group->handler_ids[3] = g_signal_connect (group->group, "action-state-changed",
443                                             G_CALLBACK (g_action_muxer_action_state_changed), group);
444 }
445
446 /*
447  * g_action_muxer_remove:
448  * @muxer: a #GActionMuxer
449  * @prefix: the prefix of the action group to remove
450  *
451  * Removes a #GActionGroup from the #GActionMuxer.
452  *
453  * If any #GActionObservers are registered for actions in the group,
454  * "action_removed" notifications will be emitted, as appropriate.
455  */
456 void
457 g_action_muxer_remove (GActionMuxer *muxer,
458                        const gchar  *prefix)
459 {
460   Group *group;
461
462   group = g_hash_table_lookup (muxer->groups, prefix);
463
464   if (group != NULL)
465     {
466       gchar **actions;
467       gint i;
468
469       g_hash_table_steal (muxer->groups, prefix);
470
471       actions = g_action_group_list_actions (group->group);
472       for (i = 0; actions[i]; i++)
473         g_action_muxer_action_removed (group->group, actions[i], group);
474       g_strfreev (actions);
475
476       /* 'for loop' or 'four loop'? */
477       for (i = 0; i < 4; i++)
478         g_signal_handler_disconnect (group->group, group->handler_ids[i]);
479
480       g_action_muxer_free_group (group);
481     }
482 }
483
484 /*
485  * g_action_muxer_new:
486  *
487  * Creates a new #GActionMuxer.
488  */
489 GActionMuxer *
490 g_action_muxer_new (void)
491 {
492   return g_object_new (G_TYPE_ACTION_MUXER, NULL);
493 }