/* * Copyright 2013 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . * * Authors: * Lars Uebernickel */ #include "idoactionhelper.h" typedef GObjectClass IdoActionHelperClass; struct _IdoActionHelper { GObject parent; GtkWidget *widget; GActionGroup *actions; gchar *action_name; GVariant *action_target; guint idle_source_id; }; G_DEFINE_TYPE (IdoActionHelper, ido_action_helper, G_TYPE_OBJECT) enum { PROP_0, PROP_WIDGET, PROP_ACTION_GROUP, PROP_ACTION_NAME, PROP_ACTION_TARGET, NUM_PROPERTIES }; enum { ACTION_STATE_CHANGED, NUM_SIGNALS }; static GParamSpec *properties[NUM_PROPERTIES]; static guint signals[NUM_SIGNALS]; static void ido_action_helper_action_added (GActionGroup *actions, const gchar *action_name, gpointer user_data) { IdoActionHelper *helper = user_data; gboolean enabled; GVariant *state; if (!g_str_equal (action_name, helper->action_name)) return; if (g_action_group_query_action (actions, action_name, &enabled, NULL, NULL, NULL, &state)) { gtk_widget_set_sensitive (helper->widget, enabled); if (state) { g_signal_emit (helper, signals[ACTION_STATE_CHANGED], 0, state); g_variant_unref (state); } } else { gtk_widget_set_sensitive (helper->widget, FALSE); } } static void ido_action_helper_action_removed (GActionGroup *action_group, gchar *action_name, gpointer user_data) { IdoActionHelper *helper = user_data; if (g_str_equal (action_name, helper->action_name)) gtk_widget_set_sensitive (helper->widget, FALSE); } static void ido_action_helper_action_enabled_changed (GActionGroup *action_group, gchar *action_name, gboolean enabled, gpointer user_data) { IdoActionHelper *helper = user_data; if (g_str_equal (action_name, helper->action_name)) gtk_widget_set_sensitive (helper->widget, enabled); } static void ido_action_helper_action_state_changed (GActionGroup *action_group, gchar *action_name, GVariant *value, gpointer user_data) { IdoActionHelper *helper = user_data; if (g_str_equal (action_name, helper->action_name)) g_signal_emit (helper, signals[ACTION_STATE_CHANGED], 0, value); } static gboolean call_action_added (gpointer user_data) { IdoActionHelper *helper = user_data; ido_action_helper_action_added (helper->actions, helper->action_name, helper); helper->idle_source_id = 0; return G_SOURCE_REMOVE; } static void ido_action_helper_constructed (GObject *object) { IdoActionHelper *helper = IDO_ACTION_HELPER (object); g_signal_connect (helper->actions, "action-added", G_CALLBACK (ido_action_helper_action_added), helper); g_signal_connect (helper->actions, "action-removed", G_CALLBACK (ido_action_helper_action_removed), helper); g_signal_connect (helper->actions, "action-enabled-changed", G_CALLBACK (ido_action_helper_action_enabled_changed), helper); g_signal_connect (helper->actions, "action-state-changed", G_CALLBACK (ido_action_helper_action_state_changed), helper); if (g_action_group_has_action (helper->actions, helper->action_name)) { /* call action_added in an idle, so that we don't fire the * state-changed signal during construction (nobody could have * connected by then). */ helper->idle_source_id = g_idle_add (call_action_added, helper); } G_OBJECT_CLASS (ido_action_helper_parent_class)->constructed (object); } static void ido_action_helper_get_property (GObject *object, guint id, GValue *value, GParamSpec *pspec) { IdoActionHelper *helper = IDO_ACTION_HELPER (object); switch (id) { case PROP_WIDGET: g_value_set_object (value, helper->widget); break; case PROP_ACTION_GROUP: g_value_set_object (value, helper->actions); break; case PROP_ACTION_NAME: g_value_set_string (value, helper->action_name); break; case PROP_ACTION_TARGET: g_value_set_variant (value, helper->action_target); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, id, pspec); } } static void ido_action_helper_set_property (GObject *object, guint id, const GValue *value, GParamSpec *pspec) { IdoActionHelper *helper = IDO_ACTION_HELPER (object); switch (id) { case PROP_WIDGET: /* construct-only */ helper->widget = g_value_dup_object (value); break; case PROP_ACTION_GROUP: /* construct-only */ helper->actions = g_value_dup_object (value); break; case PROP_ACTION_NAME: /* construct-only */ helper->action_name = g_value_dup_string (value); break; case PROP_ACTION_TARGET: /* construct-only */ helper->action_target = g_value_dup_variant (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, id, pspec); } } static void ido_action_helper_finalize (GObject *object) { IdoActionHelper *helper = IDO_ACTION_HELPER (object); if (helper->idle_source_id) g_source_remove (helper->idle_source_id); g_object_unref (helper->widget); g_signal_handlers_disconnect_by_data (helper->actions, helper); g_object_unref (helper->actions); g_free (helper->action_name); if (helper->action_target) g_variant_unref (helper->action_target); G_OBJECT_CLASS (ido_action_helper_parent_class)->finalize (object); } static void ido_action_helper_class_init (IdoActionHelperClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); object_class->constructed = ido_action_helper_constructed; object_class->get_property = ido_action_helper_get_property; object_class->set_property = ido_action_helper_set_property; object_class->finalize = ido_action_helper_finalize; /** * IdoActionHelper::action-state-changed: * @helper: the #IdoActionHelper watching the action * @state: the new state of the action * * Emitted when the widget must be updated from the action's state, * which happens every time the action appears in the group and when * the action changes its state. */ signals[ACTION_STATE_CHANGED] = g_signal_new ("action-state-changed", IDO_TYPE_ACTION_HELPER, G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_VOID__VARIANT, G_TYPE_NONE, 1, G_TYPE_VARIANT); /** * IdoActionHelper:widget: * * The widget that is associated with this action helper. The action * helper updates the widget's "sensitive" property to reflect whether * the action #IdoActionHelper:action-name exists in * #IdoActionHelper:action-group. */ properties[PROP_WIDGET] = g_param_spec_object ("widget", "", "", GTK_TYPE_WIDGET, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * IdoActionHelper:action-group: * * The action group that eventually contains the action that * #IdoActionHelper:widget should be bound to. */ properties[PROP_ACTION_GROUP] = g_param_spec_object ("action-group", "", "", G_TYPE_ACTION_GROUP, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * IdoActionHelper:action-name: * * The name of the action in #IdoActionHelper:action-group that * should be bound to #IdoActionHelper:widget */ properties[PROP_ACTION_NAME] = g_param_spec_string ("action-name", "", "", NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * IdoActionHelper:action-target: * * The target of #IdoActionHelper:widget. ido_action_helper_activate() * passes the target as parameter when activating the action. * * The handler of #IdoActionHelper:action-state-changed is responsible * for comparing this target with the action's state and updating the * #IdoActionHelper:widget appropriately. */ properties[PROP_ACTION_TARGET] = g_param_spec_variant ("action-target", "", "", G_VARIANT_TYPE_ANY, NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, NUM_PROPERTIES, properties); } static void ido_action_helper_init (IdoActionHelper *helper) { } /** * ido_action_helper_new: * @widget: a #GtkWidget * @action_group: a #GActionGroup * @action_name: the name of an action in @action_group * @target: the target of the action * * Creates a new #IdoActionHelper. This helper ties @widget to an action * (and a target), and performs some common tasks: * * @widget will be set to insensitive whenever @action_group does not * contain an action with the name @action_name, or the action with that * name is disabled. * * Also, the helper emits the "action-state-changed" signal whenever the * widget must be updated from the action's state. This includes once * when the action was added, and every time the action changes its * state. * * Returns: (transfer full): a new #IdoActionHelper */ IdoActionHelper * ido_action_helper_new (GtkWidget *widget, GActionGroup *action_group, const gchar *action_name, GVariant *target) { g_return_val_if_fail (widget != NULL, NULL); g_return_val_if_fail (action_group != NULL, NULL); g_return_val_if_fail (action_name != NULL, NULL); return g_object_new (IDO_TYPE_ACTION_HELPER, "widget", widget, "action-group", action_group, "action-name", action_name, "action-target", target, NULL); } /** * ido_action_helper_get_widget: * @helper: an #IdoActionHelper * * Returns: (transfer none): the #GtkWidget associated with @helper */ GtkWidget * ido_action_helper_get_widget (IdoActionHelper *helper) { g_return_val_if_fail (IDO_IS_ACTION_HELPER (helper), NULL); return helper->widget; } /** * ido_action_helper_get_action_target: * @helper: an #IdoActionHelper * * Returns: (transfer none): the action target that was set in * ido_action_helper_new() as a #GVariant */ GVariant * ido_action_helper_get_action_target (IdoActionHelper *helper) { g_return_val_if_fail (IDO_IS_ACTION_HELPER (helper), NULL); return helper->action_target; } /** * ido_action_helper_activate: * @helper: an #IdoActionHelper * * Activates the action that is associated with this helper. */ void ido_action_helper_activate (IdoActionHelper *helper) { g_return_if_fail (IDO_IS_ACTION_HELPER (helper)); if (helper->actions && helper->action_name) g_action_group_activate_action (helper->actions, helper->action_name, helper->action_target); } /** * ido_action_helper_activate_with_parameter: * @helper: an #IdoActionHelper * @parameter: a #GVariant containing the parameter * * Activates the action that is associated with this helper passing * @parameter instead the "target" associated with the menu item this * helper is bound to. */ void ido_action_helper_activate_with_parameter (IdoActionHelper *helper, GVariant *parameter) { g_return_if_fail (IDO_IS_ACTION_HELPER (helper)); g_return_if_fail (parameter != NULL); g_variant_ref_sink (parameter); if (helper->actions && helper->action_name) g_action_group_activate_action (helper->actions, helper->action_name, parameter); g_variant_unref (parameter); } /** * ido_action_helper_change_action_state: * @helper: an #IdoActionHelper * @state: the proposed new state of the action * * Requests changing the state of the action that is associated with * @helper to @state. * * If @state is floating, it is consumed. */ void ido_action_helper_change_action_state (IdoActionHelper *helper, GVariant *state) { g_return_if_fail (IDO_IS_ACTION_HELPER (helper)); g_return_if_fail (state != NULL); g_variant_ref_sink (state); if (helper->actions && helper->action_name) g_action_group_change_action_state (helper->actions, helper->action_name, state); g_variant_unref (state); }