]> Pileus Git - ~andy/gtk/blob - gtk/gactionmuxer.c
22990a40e31f096684bd8183f0e1c555e1a5ccac
[~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   GHashTableIter iter;
94   gchar *key;
95   gchar **keys;
96   gsize i;
97
98   keys = g_new (gchar *, g_hash_table_size (muxer->actions) + 1);
99
100   i = 0;
101   g_hash_table_iter_init (&iter, muxer->actions);
102   while (g_hash_table_iter_next (&iter, (gpointer *) &key, NULL))
103     keys[i++] = g_strdup (key);
104   keys[i] = NULL;
105
106   return keys;
107 }
108
109 static Group *
110 g_action_muxer_find_group (GActionMuxer  *muxer,
111                               const gchar     **name)
112 {
113   const gchar *dot;
114   gchar *prefix;
115   Group *group;
116
117   dot = strchr (*name, '.');
118
119   if (!dot)
120     return NULL;
121
122   prefix = g_strndup (*name, dot - *name);
123   group = g_hash_table_lookup (muxer->groups, prefix);
124   g_free (prefix);
125
126   *name = dot + 1;
127
128   return group;
129 }
130
131 static Action *
132 g_action_muxer_lookup_action (GActionMuxer  *muxer,
133                               const gchar   *prefix,
134                               const gchar   *action_name,
135                               gchar        **fullname)
136 {
137   Action *action;
138
139   *fullname = g_strconcat (prefix, ".", action_name, NULL);
140   action = g_hash_table_lookup (muxer->actions, *fullname);
141
142   return action;
143 }
144
145 static void
146 g_action_muxer_action_enabled_changed (GActionGroup *action_group,
147                                        const gchar  *action_name,
148                                        gboolean      enabled,
149                                        gpointer      user_data)
150 {
151   Group *group = user_data;
152   gchar *fullname;
153   Action *action;
154   GSList *node;
155
156   action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname);
157   for (node = action ? action->watchers : NULL; node; node = node->next)
158     g_action_observer_action_enabled_changed (node->data, G_ACTION_OBSERVABLE (group->muxer), fullname, enabled);
159   g_action_group_action_enabled_changed (G_ACTION_GROUP (group->muxer), fullname, enabled);
160   g_free (fullname);
161 }
162
163 static void
164 g_action_muxer_action_state_changed (GActionGroup *action_group,
165                                      const gchar  *action_name,
166                                      GVariant     *state,
167                                      gpointer      user_data)
168 {
169   Group *group = user_data;
170   gchar *fullname;
171   Action *action;
172   GSList *node;
173
174   action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname);
175   for (node = action ? action->watchers : NULL; node; node = node->next)
176     g_action_observer_action_state_changed (node->data, G_ACTION_OBSERVABLE (group->muxer), fullname, state);
177   g_action_group_action_state_changed (G_ACTION_GROUP (group->muxer), fullname, state);
178   g_free (fullname);
179 }
180
181 static void
182 g_action_muxer_action_added (GActionGroup *action_group,
183                              const gchar  *action_name,
184                              gpointer      user_data)
185 {
186   const GVariantType *parameter_type;
187   Group *group = user_data;
188   gboolean enabled;
189   GVariant *state;
190
191   if (g_action_group_query_action (group->group, action_name, &enabled, &parameter_type, NULL, NULL, &state))
192     {
193       gchar *fullname;
194       Action *action;
195       GSList *node;
196
197       action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname);
198
199       for (node = action ? action->watchers : NULL; node; node = node->next)
200         g_action_observer_action_added (node->data,
201                                         G_ACTION_OBSERVABLE (group->muxer),
202                                         fullname, parameter_type, enabled, state);
203
204       g_action_group_action_added (G_ACTION_GROUP (group->muxer), fullname);
205
206       if (state)
207         g_variant_unref (state);
208
209       g_free (fullname);
210     }
211 }
212
213 static void
214 g_action_muxer_action_removed (GActionGroup *action_group,
215                                const gchar  *action_name,
216                                gpointer      user_data)
217 {
218   Group *group = user_data;
219   gchar *fullname;
220   Action *action;
221   GSList *node;
222
223   action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname);
224   for (node = action ? action->watchers : NULL; node; node = node->next)
225     g_action_observer_action_removed (node->data, G_ACTION_OBSERVABLE (group->muxer), fullname);
226   g_action_group_action_removed (G_ACTION_GROUP (group->muxer), fullname);
227   g_free (fullname);
228 }
229
230 static gboolean
231 g_action_muxer_query_action (GActionGroup        *action_group,
232                              const gchar         *action_name,
233                              gboolean            *enabled,
234                              const GVariantType **parameter_type,
235                              const GVariantType **state_type,
236                              GVariant           **state_hint,
237                              GVariant           **state)
238 {
239   GActionMuxer *muxer = G_ACTION_MUXER (action_group);
240   Group *group;
241
242   group = g_action_muxer_find_group (muxer, &action_name);
243
244   if (!group)
245     return FALSE;
246
247   return g_action_group_query_action (group->group, action_name, enabled,
248                                       parameter_type, state_type, state_hint, state);
249 }
250
251 static void
252 g_action_muxer_activate_action (GActionGroup *action_group,
253                                 const gchar  *action_name,
254                                 GVariant     *parameter)
255 {
256   GActionMuxer *muxer = G_ACTION_MUXER (action_group);
257   Group *group;
258
259   group = g_action_muxer_find_group (muxer, &action_name);
260
261   if (group)
262     g_action_group_activate_action (group->group, action_name, parameter);
263 }
264
265 static void
266 g_action_muxer_change_action_state (GActionGroup *action_group,
267                                     const gchar  *action_name,
268                                     GVariant     *state)
269 {
270   GActionMuxer *muxer = G_ACTION_MUXER (action_group);
271   Group *group;
272
273   group = g_action_muxer_find_group (muxer, &action_name);
274
275   if (group)
276     g_action_group_change_action_state (group->group, action_name, state);
277 }
278
279 static void
280 g_action_muxer_unregister_internal (Action   *action,
281                                     gpointer  observer)
282 {
283   GActionMuxer *muxer = action->muxer;
284   GSList **ptr;
285
286   for (ptr = &action->watchers; *ptr; ptr = &(*ptr)->next)
287     if ((*ptr)->data == observer)
288       {
289         *ptr = g_slist_remove (*ptr, observer);
290
291         if (action->watchers == NULL)
292           {
293             g_hash_table_remove (muxer->actions, action->fullname);
294             g_free (action->fullname);
295
296             g_slice_free (Action, action);
297
298             g_object_unref (muxer);
299           }
300
301         break;
302       }
303 }
304
305 static void
306 g_action_muxer_weak_notify (gpointer  data,
307                             GObject  *where_the_object_was)
308 {
309   Action *action = data;
310
311   g_action_muxer_unregister_internal (action, where_the_object_was);
312 }
313
314 static void
315 g_action_muxer_register_observer (GActionObservable *observable,
316                                   const gchar       *name,
317                                   GActionObserver   *observer)
318 {
319   GActionMuxer *muxer = G_ACTION_MUXER (observable);
320   Action *action;
321
322   action = g_hash_table_lookup (muxer->actions, name);
323
324   if (action == NULL)
325     {
326       action = g_slice_new (Action);
327       action->muxer = g_object_ref (muxer);
328       action->fullname = g_strdup (name);
329       action->watchers = NULL;
330
331       g_hash_table_insert (muxer->actions, action->fullname, action);
332     }
333
334   action->watchers = g_slist_prepend (action->watchers, observer);
335   g_object_weak_ref (G_OBJECT (observer), g_action_muxer_weak_notify, action);
336 }
337
338 static void
339 g_action_muxer_unregister_observer (GActionObservable *observable,
340                                     const gchar       *name,
341                                     GActionObserver   *observer)
342 {
343   GActionMuxer *muxer = G_ACTION_MUXER (observable);
344   Action *action;
345
346   action = g_hash_table_lookup (muxer->actions, name);
347   g_object_weak_unref (G_OBJECT (observer), g_action_muxer_weak_notify, action);
348   g_action_muxer_unregister_internal (action, observer);
349 }
350
351 static void
352 g_action_muxer_free_group (gpointer data)
353 {
354   Group *group = data;
355
356   g_object_unref (group->group);
357   g_free (group->prefix);
358
359   g_slice_free (Group, group);
360 }
361
362 static void
363 g_action_muxer_finalize (GObject *object)
364 {
365   GActionMuxer *muxer = G_ACTION_MUXER (object);
366
367   g_assert_cmpint (g_hash_table_size (muxer->actions), ==, 0);
368   g_hash_table_unref (muxer->actions);
369   g_hash_table_unref (muxer->groups);
370
371   G_OBJECT_CLASS (g_action_muxer_parent_class)
372     ->finalize (object);
373 }
374
375 static void
376 g_action_muxer_init (GActionMuxer *muxer)
377 {
378   muxer->actions = g_hash_table_new (g_str_hash, g_str_equal);
379   muxer->groups = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_action_muxer_free_group);
380 }
381
382 static void
383 g_action_muxer_observable_iface_init (GActionObservableInterface *iface)
384 {
385   iface->register_observer = g_action_muxer_register_observer;
386   iface->unregister_observer = g_action_muxer_unregister_observer;
387 }
388
389 static void
390 g_action_muxer_group_iface_init (GActionGroupInterface *iface)
391 {
392   iface->list_actions = g_action_muxer_list_actions;
393   iface->query_action = g_action_muxer_query_action;
394   iface->activate_action = g_action_muxer_activate_action;
395   iface->change_action_state = g_action_muxer_change_action_state;
396 }
397
398 static void
399 g_action_muxer_class_init (GObjectClass *class)
400 {
401   class->finalize = g_action_muxer_finalize;
402 }
403
404 /*
405  * g_action_muxer_insert:
406  * @muxer: a #GActionMuxer
407  * @prefix: the prefix string for the action group
408  * @action_group: a #GActionGroup
409  *
410  * Adds the actions in @action_group to the list of actions provided by
411  * @muxer.  @prefix is prefixed to each action name, such that for each
412  * action <varname>x</varname> in @action_group, there is an equivalent
413  * action @prefix<literal>.</literal><varname>x</varname> in @muxer.
414  *
415  * For example, if @prefix is "<literal>app</literal>" and @action_group
416  * contains an action called "<literal>quit</literal>", then @muxer will
417  * now contain an action called "<literal>app.quit</literal>".
418  *
419  * If any #GActionObservers are registered for actions in the group,
420  * "action_added" notifications will be emitted, as appropriate.
421  *
422  * @prefix must not contain a dot ('.').
423  */
424 void
425 g_action_muxer_insert (GActionMuxer *muxer,
426                        const gchar  *prefix,
427                        GActionGroup *action_group)
428 {
429   gchar **actions;
430   Group *group;
431   gint i;
432
433   /* TODO: diff instead of ripout and replace */
434   g_action_muxer_remove (muxer, prefix);
435
436   group = g_slice_new (Group);
437   group->muxer = muxer;
438   group->group = g_object_ref (action_group);
439   group->prefix = g_strdup (prefix);
440
441   g_hash_table_insert (muxer->groups, group->prefix, group);
442
443   actions = g_action_group_list_actions (group->group);
444   for (i = 0; actions[i]; i++)
445     g_action_muxer_action_added (group->group, actions[i], group);
446   g_strfreev (actions);
447
448   group->handler_ids[0] = g_signal_connect (group->group, "action-added",
449                                             G_CALLBACK (g_action_muxer_action_added), group);
450   group->handler_ids[1] = g_signal_connect (group->group, "action-removed",
451                                             G_CALLBACK (g_action_muxer_action_removed), group);
452   group->handler_ids[2] = g_signal_connect (group->group, "action-enabled-changed",
453                                             G_CALLBACK (g_action_muxer_action_enabled_changed), group);
454   group->handler_ids[3] = g_signal_connect (group->group, "action-state-changed",
455                                             G_CALLBACK (g_action_muxer_action_state_changed), group);
456 }
457
458 /*
459  * g_action_muxer_remove:
460  * @muxer: a #GActionMuxer
461  * @prefix: the prefix of the action group to remove
462  *
463  * Removes a #GActionGroup from the #GActionMuxer.
464  *
465  * If any #GActionObservers are registered for actions in the group,
466  * "action_removed" notifications will be emitted, as appropriate.
467  */
468 void
469 g_action_muxer_remove (GActionMuxer *muxer,
470                        const gchar  *prefix)
471 {
472   Group *group;
473
474   group = g_hash_table_lookup (muxer->groups, prefix);
475
476   if (group != NULL)
477     {
478       gchar **actions;
479       gint i;
480
481       g_hash_table_steal (muxer->groups, prefix);
482
483       actions = g_action_group_list_actions (group->group);
484       for (i = 0; actions[i]; i++)
485         g_action_muxer_action_removed (group->group, actions[i], group);
486       g_strfreev (actions);
487
488       /* 'for loop' or 'four loop'? */
489       for (i = 0; i < 4; i++)
490         g_signal_handler_disconnect (group->group, group->handler_ids[i]);
491
492       g_action_muxer_free_group (group);
493     }
494 }
495
496 /*
497  * g_action_muxer_new:
498  *
499  * Creates a new #GActionMuxer.
500  */
501 GActionMuxer *
502 g_action_muxer_new (void)
503 {
504   return g_object_new (G_TYPE_ACTION_MUXER, NULL);
505 }