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, ¶meter_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 }