2 * Copyright © 2012 Canonical Limited
4 * This library is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * licence or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful, but
10 * 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.
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/>.
17 * Authors: Ryan Lortie <desrt@desrt.ca>
20 #include "gtkactionhelper.h"
21 #include "gactionobservable.h"
23 #include "gtkwidget.h"
24 #include "gtkwidgetprivate.h"
33 } GtkActionHelperGroup;
35 static void gtk_action_helper_action_added (GtkActionHelper *helper,
37 const GVariantType *parameter_type,
39 gboolean should_emit_signals);
41 static void gtk_action_helper_action_removed (GtkActionHelper *helper);
43 static void gtk_action_helper_action_enabled_changed (GtkActionHelper *helper,
46 static void gtk_action_helper_action_state_changed (GtkActionHelper *helper,
49 typedef GObjectClass GtkActionHelperClass;
51 struct _GtkActionHelper
53 GObject parent_instance;
55 GtkApplication *application;
58 GtkActionHelperGroup *group;
60 GActionMuxer *action_context;
65 GtkActionHelperRole role;
66 gboolean can_activate;
82 static GParamSpec *gtk_action_helper_pspecs[N_PROPS];
84 static void gtk_action_helper_observer_iface_init (GActionObserverInterface *iface);
86 G_DEFINE_TYPE_WITH_CODE (GtkActionHelper, gtk_action_helper, G_TYPE_OBJECT,
87 G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_OBSERVER, gtk_action_helper_observer_iface_init))
90 gtk_action_helper_report_change (GtkActionHelper *helper,
95 if (!helper->application)
100 gtk_widget_set_sensitive (GTK_WIDGET (helper->widget), helper->enabled);
107 pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (helper->widget), "active");
109 if (pspec && G_PARAM_SPEC_VALUE_TYPE (pspec) == G_TYPE_BOOLEAN)
110 g_object_set (G_OBJECT (helper->widget), "active", helper->active, NULL);
118 pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (helper->widget), "action-role");
120 if (pspec && G_PARAM_SPEC_VALUE_TYPE (pspec) == G_TYPE_UINT)
121 g_object_set (G_OBJECT (helper->widget), "action-role", helper->role, NULL);
126 g_assert_not_reached ();
130 g_object_notify_by_pspec (G_OBJECT (helper), gtk_action_helper_pspecs[prop_id]);
135 gtk_action_helper_action_added (GtkActionHelper *helper,
137 const GVariantType *parameter_type,
139 gboolean should_emit_signals)
141 /* we can only activate if we have the correct type of parameter */
142 helper->can_activate = (helper->target == NULL && parameter_type == NULL) ||
143 (helper->target != NULL && parameter_type != NULL &&
144 g_variant_is_of_type (helper->target, parameter_type));
146 if (!helper->can_activate)
149 helper->enabled = enabled;
151 if (helper->target != NULL && state != NULL)
153 helper->active = g_variant_equal (state, helper->target);
154 helper->role = GTK_ACTION_HELPER_ROLE_RADIO;
157 else if (state != NULL && g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN))
159 helper->active = g_variant_get_boolean (state);
160 helper->role = GTK_ACTION_HELPER_ROLE_TOGGLE;
163 if (should_emit_signals)
166 gtk_action_helper_report_change (helper, PROP_ENABLED);
169 gtk_action_helper_report_change (helper, PROP_ACTIVE);
172 gtk_action_helper_report_change (helper, PROP_ROLE);
177 gtk_action_helper_action_removed (GtkActionHelper *helper)
179 if (!helper->can_activate)
182 helper->can_activate = FALSE;
186 helper->enabled = FALSE;
187 gtk_action_helper_report_change (helper, PROP_ENABLED);
192 helper->active = FALSE;
193 gtk_action_helper_report_change (helper, PROP_ACTIVE);
198 helper->role = GTK_ACTION_HELPER_ROLE_NORMAL;
199 gtk_action_helper_report_change (helper, PROP_ROLE);
204 gtk_action_helper_action_enabled_changed (GtkActionHelper *helper,
207 if (!helper->can_activate)
210 if (helper->enabled == enabled)
213 helper->enabled = enabled;
214 gtk_action_helper_report_change (helper, PROP_ENABLED);
218 gtk_action_helper_action_state_changed (GtkActionHelper *helper,
223 if (!helper->can_activate)
226 was_active = helper->active;
229 helper->active = g_variant_equal (new_state, helper->target);
231 else if (g_variant_is_of_type (new_state, G_VARIANT_TYPE_BOOLEAN))
232 helper->active = g_variant_get_boolean (new_state);
235 helper->active = FALSE;
237 if (helper->active != was_active)
238 gtk_action_helper_report_change (helper, PROP_ACTIVE);
242 gtk_action_helper_get_property (GObject *object, guint prop_id,
243 GValue *value, GParamSpec *pspec)
245 GtkActionHelper *helper = GTK_ACTION_HELPER (object);
250 g_value_set_boolean (value, helper->enabled);
254 g_value_set_boolean (value, helper->active);
258 g_value_set_uint (value, helper->role);
262 g_assert_not_reached ();
267 gtk_action_helper_finalize (GObject *object)
269 GtkActionHelper *helper = GTK_ACTION_HELPER (object);
271 if (helper->application)
273 g_signal_handlers_disconnect_by_data (helper->application, helper);
274 g_object_unref (helper->action_context);
275 g_object_unref (helper->application);
278 g_object_unref (helper->widget);
281 g_free (helper->action_name);
284 g_variant_unref (helper->target);
286 G_OBJECT_CLASS (gtk_action_helper_parent_class)
291 gtk_action_helper_observer_action_added (GActionObserver *observer,
292 GActionObservable *observable,
293 const gchar *action_name,
294 const GVariantType *parameter_type,
298 gtk_action_helper_action_added (GTK_ACTION_HELPER (observer), enabled, parameter_type, state, TRUE);
302 gtk_action_helper_observer_action_enabled_changed (GActionObserver *observer,
303 GActionObservable *observable,
304 const gchar *action_name,
307 gtk_action_helper_action_enabled_changed (GTK_ACTION_HELPER (observer), enabled);
311 gtk_action_helper_observer_action_state_changed (GActionObserver *observer,
312 GActionObservable *observable,
313 const gchar *action_name,
316 gtk_action_helper_action_state_changed (GTK_ACTION_HELPER (observer), state);
320 gtk_action_helper_observer_action_removed (GActionObserver *observer,
321 GActionObservable *observable,
322 const gchar *action_name)
324 gtk_action_helper_action_removed (GTK_ACTION_HELPER (observer));
328 gtk_action_helper_init (GtkActionHelper *helper)
333 gtk_action_helper_class_init (GtkActionHelperClass *class)
335 class->get_property = gtk_action_helper_get_property;
336 class->finalize = gtk_action_helper_finalize;
338 gtk_action_helper_pspecs[PROP_ENABLED] = g_param_spec_boolean ("enabled", "enabled", "enabled", FALSE,
339 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
340 gtk_action_helper_pspecs[PROP_ACTIVE] = g_param_spec_boolean ("active", "active", "active", FALSE,
341 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
342 gtk_action_helper_pspecs[PROP_ROLE] = g_param_spec_uint ("role", "role", "role", 0, 2, 0,
343 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
344 g_object_class_install_properties (class, N_PROPS, gtk_action_helper_pspecs);
348 gtk_action_helper_observer_iface_init (GActionObserverInterface *iface)
350 iface->action_added = gtk_action_helper_observer_action_added;
351 iface->action_enabled_changed = gtk_action_helper_observer_action_enabled_changed;
352 iface->action_state_changed = gtk_action_helper_observer_action_state_changed;
353 iface->action_removed = gtk_action_helper_observer_action_removed;
357 * gtk_action_helper_new:
358 * @widget: a #GtkWidget implementing #GtkActionable
360 * Creates a helper to track the state of a named action. This will
361 * usually be used by widgets implementing #GtkActionable.
363 * This helper class is usually used by @widget itself. In order to
364 * avoid reference cycles, the helper does not hold a reference on
365 * @widget, but will assume that it continues to exist for the duration
366 * of the life of the helper. If you are using the helper from outside
367 * of the widget, you should take a ref on @widget for each ref you hold
370 * Returns: a new #GtkActionHelper
373 gtk_action_helper_new (GtkActionable *widget)
375 GtkActionHelper *helper;
377 g_return_val_if_fail (GTK_IS_ACTIONABLE (widget), NULL);
378 helper = g_object_new (GTK_TYPE_ACTION_HELPER, NULL);
380 helper->widget = GTK_WIDGET (widget);
386 helper->enabled = gtk_widget_get_sensitive (GTK_WIDGET (helper->widget));
388 pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (helper->widget), "active");
389 if (pspec && G_PARAM_SPEC_VALUE_TYPE (pspec) == G_TYPE_BOOLEAN)
390 g_object_get (G_OBJECT (helper->widget), "active", &helper->active, NULL);
393 helper->action_context = _gtk_widget_get_action_muxer (GTK_WIDGET (widget));
399 gtk_action_helper_active_window_changed (GObject *object,
403 GtkActionHelper *helper = user_data;
404 GActionMuxer *parent;
407 g_object_unref (helper->widget);
409 helper->widget = GTK_WIDGET (gtk_application_get_active_window (helper->application));
413 parent = g_object_ref (_gtk_widget_get_action_muxer (GTK_WIDGET (helper->widget)));
414 g_object_ref (helper->widget);
418 parent = g_action_muxer_new ();
419 g_action_muxer_insert (parent, "app", G_ACTION_GROUP (helper->application));
422 g_action_muxer_set_parent (helper->action_context, parent);
423 g_object_unref (parent);
427 gtk_action_helper_new_with_application (GtkApplication *application)
429 GtkActionHelper *helper;
431 g_return_val_if_fail (GTK_IS_APPLICATION (application), NULL);
433 helper = g_object_new (GTK_TYPE_ACTION_HELPER, NULL);
434 helper->application = g_object_ref (application);
436 helper->action_context = g_action_muxer_new ();
437 g_signal_connect (application, "notify::active-window", G_CALLBACK (gtk_action_helper_active_window_changed), helper);
438 gtk_action_helper_active_window_changed (NULL, NULL, helper);
444 gtk_action_helper_set_action_name (GtkActionHelper *helper,
445 const gchar *action_name)
447 gboolean was_enabled, was_active;
448 GtkActionHelperRole old_role;
449 const GVariantType *parameter_type;
453 if (g_strcmp0 (action_name, helper->action_name) == 0)
456 if (helper->action_name)
458 g_action_observable_unregister_observer (G_ACTION_OBSERVABLE (helper->action_context),
460 G_ACTION_OBSERVER (helper));
461 g_free (helper->action_name);
464 helper->action_name = g_strdup (action_name);
466 g_action_observable_register_observer (G_ACTION_OBSERVABLE (helper->action_context),
468 G_ACTION_OBSERVER (helper));
470 /* Start by recording the current state of our properties so we know
471 * what notify signals we will need to send.
473 was_enabled = helper->enabled;
474 was_active = helper->active;
475 old_role = helper->role;
477 if (g_action_group_query_action (G_ACTION_GROUP (helper->action_context), helper->action_name,
478 &enabled, ¶meter_type, NULL, NULL, &state))
480 gtk_action_helper_action_added (helper, enabled, parameter_type, state, FALSE);
483 g_variant_unref (state);
487 helper->enabled = FALSE;
490 /* Send the notifies for the properties that changed.
492 * When called during construction, widget is NULL. We don't need to
493 * report in that case.
495 if (helper->enabled != was_enabled)
496 gtk_action_helper_report_change (helper, PROP_ENABLED);
498 if (helper->active != was_active)
499 gtk_action_helper_report_change (helper, PROP_ACTIVE);
501 if (helper->role != old_role)
502 gtk_action_helper_report_change (helper, PROP_ROLE);
504 if (!helper->application)
505 g_object_notify (G_OBJECT (helper->widget), "action-name");
509 * gtk_action_helper_set_action_target_value:
510 * @helper: a #GtkActionHelper
511 * @target_value: an action target, as per #GtkActionable
513 * This function consumes @action_target if it is floating.
516 gtk_action_helper_set_action_target_value (GtkActionHelper *helper,
517 GVariant *target_value)
519 gboolean was_enabled;
522 if (target_value == helper->target)
525 if (target_value && helper->target && g_variant_equal (target_value, helper->target))
527 g_variant_unref (g_variant_ref_sink (target_value));
533 g_variant_unref (helper->target);
534 helper->target = NULL;
538 helper->target = g_variant_ref_sink (target_value);
540 /* The action_name has not yet been set. Don't do anything yet. */
541 if (helper->action_name == NULL)
544 was_enabled = helper->enabled;
545 was_active = helper->active;
547 /* If we are attached to an action group then it is possible that this
548 * change of the target value could impact our properties (including
549 * changes to 'can_activate' and therefore 'enabled', due to resolving
550 * a parameter type mismatch).
552 * Start over again by pretending the action gets re-added.
554 helper->can_activate = FALSE;
555 helper->enabled = FALSE;
556 helper->active = FALSE;
558 if (helper->action_context)
560 const GVariantType *parameter_type;
564 if (g_action_group_query_action (G_ACTION_GROUP (helper->action_context),
565 helper->action_name, &enabled, ¶meter_type,
568 gtk_action_helper_action_added (helper, enabled, parameter_type, state, FALSE);
571 g_variant_unref (state);
575 if (helper->enabled != was_enabled)
576 gtk_action_helper_report_change (helper, PROP_ENABLED);
578 if (helper->active != was_active)
579 gtk_action_helper_report_change (helper, PROP_ACTIVE);
581 if (!helper->application)
582 g_object_notify (G_OBJECT (helper->widget), "action-target");
586 gtk_action_helper_get_action_name (GtkActionHelper *helper)
591 return helper->action_name;
595 gtk_action_helper_get_action_target_value (GtkActionHelper *helper)
600 return helper->target;
604 gtk_action_helper_get_role (GtkActionHelper *helper)
607 return GTK_ACTION_HELPER_ROLE_NORMAL;
613 gtk_action_helper_get_enabled (GtkActionHelper *helper)
615 g_return_val_if_fail (GTK_IS_ACTION_HELPER (helper), FALSE);
617 return helper->enabled;
621 gtk_action_helper_get_active (GtkActionHelper *helper)
623 g_return_val_if_fail (GTK_IS_ACTION_HELPER (helper), FALSE);
625 return helper->active;
629 gtk_action_helper_activate (GtkActionHelper *helper)
631 g_return_if_fail (GTK_IS_ACTION_HELPER (helper));
633 if (!helper->can_activate || helper->reporting)
636 g_action_group_activate_action (G_ACTION_GROUP (helper->action_context),
637 helper->action_name, helper->target);