gnome-shell-3.6.3.1/src/gactionmuxer.c

No issues found

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