forked from AuroraMiddleware/gtk
3519226c5a
The code clearly meant to reset action->active here. This was found by Coverity.
639 lines
19 KiB
C
639 lines
19 KiB
C
/*
|
|
* Copyright © 2012 Canonical Limited
|
|
*
|
|
* This library is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as
|
|
* published by the Free Software Foundation; either version 2 of the
|
|
* licence or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Authors: Ryan Lortie <desrt@desrt.ca>
|
|
*/
|
|
|
|
#include "gtkactionhelper.h"
|
|
#include "gactionobservable.h"
|
|
|
|
#include "gtkwidget.h"
|
|
#include "gtkwidgetprivate.h"
|
|
|
|
#include <string.h>
|
|
|
|
typedef struct
|
|
{
|
|
GActionGroup *group;
|
|
|
|
GHashTable *watchers;
|
|
} GtkActionHelperGroup;
|
|
|
|
static void gtk_action_helper_action_added (GtkActionHelper *helper,
|
|
gboolean enabled,
|
|
const GVariantType *parameter_type,
|
|
GVariant *state,
|
|
gboolean should_emit_signals);
|
|
|
|
static void gtk_action_helper_action_removed (GtkActionHelper *helper);
|
|
|
|
static void gtk_action_helper_action_enabled_changed (GtkActionHelper *helper,
|
|
gboolean enabled);
|
|
|
|
static void gtk_action_helper_action_state_changed (GtkActionHelper *helper,
|
|
GVariant *new_state);
|
|
|
|
typedef GObjectClass GtkActionHelperClass;
|
|
|
|
struct _GtkActionHelper
|
|
{
|
|
GObject parent_instance;
|
|
|
|
GtkApplication *application;
|
|
GtkWidget *widget;
|
|
|
|
GtkActionHelperGroup *group;
|
|
|
|
GActionMuxer *action_context;
|
|
gchar *action_name;
|
|
|
|
GVariant *target;
|
|
|
|
GtkActionHelperRole role;
|
|
gboolean can_activate;
|
|
gboolean enabled;
|
|
gboolean active;
|
|
|
|
gint reporting;
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_ENABLED,
|
|
PROP_ACTIVE,
|
|
PROP_ROLE,
|
|
N_PROPS
|
|
};
|
|
|
|
static GParamSpec *gtk_action_helper_pspecs[N_PROPS];
|
|
|
|
static void gtk_action_helper_observer_iface_init (GActionObserverInterface *iface);
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (GtkActionHelper, gtk_action_helper, G_TYPE_OBJECT,
|
|
G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_OBSERVER, gtk_action_helper_observer_iface_init))
|
|
|
|
static void
|
|
gtk_action_helper_report_change (GtkActionHelper *helper,
|
|
guint prop_id)
|
|
{
|
|
helper->reporting++;
|
|
|
|
if (!helper->application)
|
|
{
|
|
switch (prop_id)
|
|
{
|
|
case PROP_ENABLED:
|
|
gtk_widget_set_sensitive (GTK_WIDGET (helper->widget), helper->enabled);
|
|
break;
|
|
|
|
case PROP_ACTIVE:
|
|
{
|
|
GParamSpec *pspec;
|
|
|
|
pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (helper->widget), "active");
|
|
|
|
if (pspec && G_PARAM_SPEC_VALUE_TYPE (pspec) == G_TYPE_BOOLEAN)
|
|
g_object_set (G_OBJECT (helper->widget), "active", helper->active, NULL);
|
|
}
|
|
break;
|
|
|
|
case PROP_ROLE:
|
|
{
|
|
GParamSpec *pspec;
|
|
|
|
pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (helper->widget), "action-role");
|
|
|
|
if (pspec && G_PARAM_SPEC_VALUE_TYPE (pspec) == G_TYPE_UINT)
|
|
g_object_set (G_OBJECT (helper->widget), "action-role", helper->role, NULL);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (helper), gtk_action_helper_pspecs[prop_id]);
|
|
helper->reporting--;
|
|
}
|
|
|
|
static void
|
|
gtk_action_helper_action_added (GtkActionHelper *helper,
|
|
gboolean enabled,
|
|
const GVariantType *parameter_type,
|
|
GVariant *state,
|
|
gboolean should_emit_signals)
|
|
{
|
|
/* we can only activate if we have the correct type of parameter */
|
|
helper->can_activate = (helper->target == NULL && parameter_type == NULL) ||
|
|
(helper->target != NULL && parameter_type != NULL &&
|
|
g_variant_is_of_type (helper->target, parameter_type));
|
|
|
|
if (!helper->can_activate)
|
|
return;
|
|
|
|
helper->enabled = enabled;
|
|
|
|
if (helper->target != NULL && state != NULL)
|
|
{
|
|
helper->active = g_variant_equal (state, helper->target);
|
|
helper->role = GTK_ACTION_HELPER_ROLE_RADIO;
|
|
}
|
|
|
|
else if (state != NULL && g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN))
|
|
{
|
|
helper->active = g_variant_get_boolean (state);
|
|
helper->role = GTK_ACTION_HELPER_ROLE_TOGGLE;
|
|
}
|
|
|
|
if (should_emit_signals)
|
|
{
|
|
if (helper->enabled)
|
|
gtk_action_helper_report_change (helper, PROP_ENABLED);
|
|
|
|
if (helper->active)
|
|
gtk_action_helper_report_change (helper, PROP_ACTIVE);
|
|
|
|
if (helper->role)
|
|
gtk_action_helper_report_change (helper, PROP_ROLE);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_action_helper_action_removed (GtkActionHelper *helper)
|
|
{
|
|
if (!helper->can_activate)
|
|
return;
|
|
|
|
helper->can_activate = FALSE;
|
|
|
|
if (helper->enabled)
|
|
{
|
|
helper->enabled = FALSE;
|
|
gtk_action_helper_report_change (helper, PROP_ENABLED);
|
|
}
|
|
|
|
if (helper->active)
|
|
{
|
|
helper->active = FALSE;
|
|
gtk_action_helper_report_change (helper, PROP_ACTIVE);
|
|
}
|
|
|
|
if (helper->role)
|
|
{
|
|
helper->role = GTK_ACTION_HELPER_ROLE_NORMAL;
|
|
gtk_action_helper_report_change (helper, PROP_ROLE);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_action_helper_action_enabled_changed (GtkActionHelper *helper,
|
|
gboolean enabled)
|
|
{
|
|
if (!helper->can_activate)
|
|
return;
|
|
|
|
if (helper->enabled == enabled)
|
|
return;
|
|
|
|
helper->enabled = enabled;
|
|
gtk_action_helper_report_change (helper, PROP_ENABLED);
|
|
}
|
|
|
|
static void
|
|
gtk_action_helper_action_state_changed (GtkActionHelper *helper,
|
|
GVariant *new_state)
|
|
{
|
|
gboolean was_active;
|
|
|
|
if (!helper->can_activate)
|
|
return;
|
|
|
|
was_active = helper->active;
|
|
|
|
if (helper->target)
|
|
helper->active = g_variant_equal (new_state, helper->target);
|
|
|
|
else if (g_variant_is_of_type (new_state, G_VARIANT_TYPE_BOOLEAN))
|
|
helper->active = g_variant_get_boolean (new_state);
|
|
|
|
else
|
|
helper->active = FALSE;
|
|
|
|
if (helper->active != was_active)
|
|
gtk_action_helper_report_change (helper, PROP_ACTIVE);
|
|
}
|
|
|
|
static void
|
|
gtk_action_helper_get_property (GObject *object, guint prop_id,
|
|
GValue *value, GParamSpec *pspec)
|
|
{
|
|
GtkActionHelper *helper = GTK_ACTION_HELPER (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_ENABLED:
|
|
g_value_set_boolean (value, helper->enabled);
|
|
break;
|
|
|
|
case PROP_ACTIVE:
|
|
g_value_set_boolean (value, helper->active);
|
|
break;
|
|
|
|
case PROP_ROLE:
|
|
g_value_set_uint (value, helper->role);
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_action_helper_finalize (GObject *object)
|
|
{
|
|
GtkActionHelper *helper = GTK_ACTION_HELPER (object);
|
|
|
|
if (helper->application)
|
|
{
|
|
g_signal_handlers_disconnect_by_data (helper->application, helper);
|
|
g_object_unref (helper->action_context);
|
|
g_object_unref (helper->application);
|
|
|
|
if (helper->widget)
|
|
g_object_unref (helper->widget);
|
|
}
|
|
|
|
g_free (helper->action_name);
|
|
|
|
if (helper->target)
|
|
g_variant_unref (helper->target);
|
|
|
|
G_OBJECT_CLASS (gtk_action_helper_parent_class)
|
|
->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gtk_action_helper_observer_action_added (GActionObserver *observer,
|
|
GActionObservable *observable,
|
|
const gchar *action_name,
|
|
const GVariantType *parameter_type,
|
|
gboolean enabled,
|
|
GVariant *state)
|
|
{
|
|
gtk_action_helper_action_added (GTK_ACTION_HELPER (observer), enabled, parameter_type, state, TRUE);
|
|
}
|
|
|
|
static void
|
|
gtk_action_helper_observer_action_enabled_changed (GActionObserver *observer,
|
|
GActionObservable *observable,
|
|
const gchar *action_name,
|
|
gboolean enabled)
|
|
{
|
|
gtk_action_helper_action_enabled_changed (GTK_ACTION_HELPER (observer), enabled);
|
|
}
|
|
|
|
static void
|
|
gtk_action_helper_observer_action_state_changed (GActionObserver *observer,
|
|
GActionObservable *observable,
|
|
const gchar *action_name,
|
|
GVariant *state)
|
|
{
|
|
gtk_action_helper_action_state_changed (GTK_ACTION_HELPER (observer), state);
|
|
}
|
|
|
|
static void
|
|
gtk_action_helper_observer_action_removed (GActionObserver *observer,
|
|
GActionObservable *observable,
|
|
const gchar *action_name)
|
|
{
|
|
gtk_action_helper_action_removed (GTK_ACTION_HELPER (observer));
|
|
}
|
|
|
|
static void
|
|
gtk_action_helper_init (GtkActionHelper *helper)
|
|
{
|
|
}
|
|
|
|
static void
|
|
gtk_action_helper_class_init (GtkActionHelperClass *class)
|
|
{
|
|
class->get_property = gtk_action_helper_get_property;
|
|
class->finalize = gtk_action_helper_finalize;
|
|
|
|
gtk_action_helper_pspecs[PROP_ENABLED] = g_param_spec_boolean ("enabled", "enabled", "enabled", FALSE,
|
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
|
|
gtk_action_helper_pspecs[PROP_ACTIVE] = g_param_spec_boolean ("active", "active", "active", FALSE,
|
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
|
|
gtk_action_helper_pspecs[PROP_ROLE] = g_param_spec_uint ("role", "role", "role", 0, 2, 0,
|
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
|
|
g_object_class_install_properties (class, N_PROPS, gtk_action_helper_pspecs);
|
|
}
|
|
|
|
static void
|
|
gtk_action_helper_observer_iface_init (GActionObserverInterface *iface)
|
|
{
|
|
iface->action_added = gtk_action_helper_observer_action_added;
|
|
iface->action_enabled_changed = gtk_action_helper_observer_action_enabled_changed;
|
|
iface->action_state_changed = gtk_action_helper_observer_action_state_changed;
|
|
iface->action_removed = gtk_action_helper_observer_action_removed;
|
|
}
|
|
|
|
/*< private >
|
|
* gtk_action_helper_new:
|
|
* @widget: a #GtkWidget implementing #GtkActionable
|
|
*
|
|
* Creates a helper to track the state of a named action. This will
|
|
* usually be used by widgets implementing #GtkActionable.
|
|
*
|
|
* This helper class is usually used by @widget itself. In order to
|
|
* avoid reference cycles, the helper does not hold a reference on
|
|
* @widget, but will assume that it continues to exist for the duration
|
|
* of the life of the helper. If you are using the helper from outside
|
|
* of the widget, you should take a ref on @widget for each ref you hold
|
|
* on the helper.
|
|
*
|
|
* Returns: a new #GtkActionHelper
|
|
*/
|
|
GtkActionHelper *
|
|
gtk_action_helper_new (GtkActionable *widget)
|
|
{
|
|
GtkActionHelper *helper;
|
|
|
|
g_return_val_if_fail (GTK_IS_ACTIONABLE (widget), NULL);
|
|
helper = g_object_new (GTK_TYPE_ACTION_HELPER, NULL);
|
|
|
|
helper->widget = GTK_WIDGET (widget);
|
|
|
|
if (helper->widget)
|
|
{
|
|
GParamSpec *pspec;
|
|
|
|
helper->enabled = gtk_widget_get_sensitive (GTK_WIDGET (helper->widget));
|
|
|
|
pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (helper->widget), "active");
|
|
if (pspec && G_PARAM_SPEC_VALUE_TYPE (pspec) == G_TYPE_BOOLEAN)
|
|
g_object_get (G_OBJECT (helper->widget), "active", &helper->active, NULL);
|
|
}
|
|
|
|
helper->action_context = _gtk_widget_get_action_muxer (GTK_WIDGET (widget));
|
|
|
|
return helper;
|
|
}
|
|
|
|
static void
|
|
gtk_action_helper_active_window_changed (GObject *object,
|
|
GParamSpec *pspec,
|
|
gpointer user_data)
|
|
{
|
|
GtkActionHelper *helper = user_data;
|
|
GActionMuxer *parent;
|
|
|
|
if (helper->widget)
|
|
g_object_unref (helper->widget);
|
|
|
|
helper->widget = GTK_WIDGET (gtk_application_get_active_window (helper->application));
|
|
|
|
if (helper->widget)
|
|
{
|
|
parent = g_object_ref (_gtk_widget_get_action_muxer (GTK_WIDGET (helper->widget)));
|
|
g_object_ref (helper->widget);
|
|
}
|
|
else
|
|
{
|
|
parent = g_action_muxer_new ();
|
|
g_action_muxer_insert (parent, "app", G_ACTION_GROUP (helper->application));
|
|
}
|
|
|
|
g_action_muxer_set_parent (helper->action_context, parent);
|
|
g_object_unref (parent);
|
|
}
|
|
|
|
GtkActionHelper *
|
|
gtk_action_helper_new_with_application (GtkApplication *application)
|
|
{
|
|
GtkActionHelper *helper;
|
|
|
|
g_return_val_if_fail (GTK_IS_APPLICATION (application), NULL);
|
|
|
|
helper = g_object_new (GTK_TYPE_ACTION_HELPER, NULL);
|
|
helper->application = g_object_ref (application);
|
|
|
|
helper->action_context = g_action_muxer_new ();
|
|
g_signal_connect (application, "notify::active-window", G_CALLBACK (gtk_action_helper_active_window_changed), helper);
|
|
gtk_action_helper_active_window_changed (NULL, NULL, helper);
|
|
|
|
return helper;
|
|
}
|
|
|
|
void
|
|
gtk_action_helper_set_action_name (GtkActionHelper *helper,
|
|
const gchar *action_name)
|
|
{
|
|
gboolean was_enabled, was_active;
|
|
GtkActionHelperRole old_role;
|
|
const GVariantType *parameter_type;
|
|
gboolean enabled;
|
|
GVariant *state;
|
|
|
|
if (g_strcmp0 (action_name, helper->action_name) == 0)
|
|
return;
|
|
|
|
if (helper->action_name)
|
|
{
|
|
g_action_observable_unregister_observer (G_ACTION_OBSERVABLE (helper->action_context),
|
|
helper->action_name,
|
|
G_ACTION_OBSERVER (helper));
|
|
g_free (helper->action_name);
|
|
}
|
|
|
|
helper->action_name = g_strdup (action_name);
|
|
|
|
g_action_observable_register_observer (G_ACTION_OBSERVABLE (helper->action_context),
|
|
helper->action_name,
|
|
G_ACTION_OBSERVER (helper));
|
|
|
|
/* Start by recording the current state of our properties so we know
|
|
* what notify signals we will need to send.
|
|
*/
|
|
was_enabled = helper->enabled;
|
|
was_active = helper->active;
|
|
old_role = helper->role;
|
|
|
|
if (g_action_group_query_action (G_ACTION_GROUP (helper->action_context), helper->action_name,
|
|
&enabled, ¶meter_type, NULL, NULL, &state))
|
|
{
|
|
gtk_action_helper_action_added (helper, enabled, parameter_type, state, FALSE);
|
|
|
|
if (state)
|
|
g_variant_unref (state);
|
|
}
|
|
else
|
|
{
|
|
helper->enabled = FALSE;
|
|
}
|
|
|
|
/* Send the notifies for the properties that changed.
|
|
*
|
|
* When called during construction, widget is NULL. We don't need to
|
|
* report in that case.
|
|
*/
|
|
if (helper->enabled != was_enabled)
|
|
gtk_action_helper_report_change (helper, PROP_ENABLED);
|
|
|
|
if (helper->active != was_active)
|
|
gtk_action_helper_report_change (helper, PROP_ACTIVE);
|
|
|
|
if (helper->role != old_role)
|
|
gtk_action_helper_report_change (helper, PROP_ROLE);
|
|
|
|
if (!helper->application)
|
|
g_object_notify (G_OBJECT (helper->widget), "action-name");
|
|
}
|
|
|
|
/*< private >
|
|
* gtk_action_helper_set_action_target_value:
|
|
* @helper: a #GtkActionHelper
|
|
* @target_value: an action target, as per #GtkActionable
|
|
*
|
|
* This function consumes @action_target if it is floating.
|
|
*/
|
|
void
|
|
gtk_action_helper_set_action_target_value (GtkActionHelper *helper,
|
|
GVariant *target_value)
|
|
{
|
|
gboolean was_enabled;
|
|
gboolean was_active;
|
|
|
|
if (target_value == helper->target)
|
|
return;
|
|
|
|
if (target_value && helper->target && g_variant_equal (target_value, helper->target))
|
|
{
|
|
g_variant_unref (g_variant_ref_sink (target_value));
|
|
return;
|
|
}
|
|
|
|
if (helper->target)
|
|
{
|
|
g_variant_unref (helper->target);
|
|
helper->target = NULL;
|
|
}
|
|
|
|
if (target_value)
|
|
helper->target = g_variant_ref_sink (target_value);
|
|
|
|
/* The action_name has not yet been set. Don't do anything yet. */
|
|
if (helper->action_name == NULL)
|
|
return;
|
|
|
|
was_enabled = helper->enabled;
|
|
was_active = helper->active;
|
|
|
|
/* If we are attached to an action group then it is possible that this
|
|
* change of the target value could impact our properties (including
|
|
* changes to 'can_activate' and therefore 'enabled', due to resolving
|
|
* a parameter type mismatch).
|
|
*
|
|
* Start over again by pretending the action gets re-added.
|
|
*/
|
|
helper->can_activate = FALSE;
|
|
helper->enabled = FALSE;
|
|
helper->active = FALSE;
|
|
|
|
if (helper->action_context)
|
|
{
|
|
const GVariantType *parameter_type;
|
|
gboolean enabled;
|
|
GVariant *state;
|
|
|
|
if (g_action_group_query_action (G_ACTION_GROUP (helper->action_context),
|
|
helper->action_name, &enabled, ¶meter_type,
|
|
NULL, NULL, &state))
|
|
{
|
|
gtk_action_helper_action_added (helper, enabled, parameter_type, state, FALSE);
|
|
|
|
if (state)
|
|
g_variant_unref (state);
|
|
}
|
|
}
|
|
|
|
if (helper->enabled != was_enabled)
|
|
gtk_action_helper_report_change (helper, PROP_ENABLED);
|
|
|
|
if (helper->active != was_active)
|
|
gtk_action_helper_report_change (helper, PROP_ACTIVE);
|
|
|
|
if (!helper->application)
|
|
g_object_notify (G_OBJECT (helper->widget), "action-target");
|
|
}
|
|
|
|
const gchar *
|
|
gtk_action_helper_get_action_name (GtkActionHelper *helper)
|
|
{
|
|
if (helper == NULL)
|
|
return NULL;
|
|
|
|
return helper->action_name;
|
|
}
|
|
|
|
GVariant *
|
|
gtk_action_helper_get_action_target_value (GtkActionHelper *helper)
|
|
{
|
|
if (helper == NULL)
|
|
return NULL;
|
|
|
|
return helper->target;
|
|
}
|
|
|
|
GtkActionHelperRole
|
|
gtk_action_helper_get_role (GtkActionHelper *helper)
|
|
{
|
|
if (helper == NULL)
|
|
return GTK_ACTION_HELPER_ROLE_NORMAL;
|
|
|
|
return helper->role;
|
|
}
|
|
|
|
gboolean
|
|
gtk_action_helper_get_enabled (GtkActionHelper *helper)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_ACTION_HELPER (helper), FALSE);
|
|
|
|
return helper->enabled;
|
|
}
|
|
|
|
gboolean
|
|
gtk_action_helper_get_active (GtkActionHelper *helper)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_ACTION_HELPER (helper), FALSE);
|
|
|
|
return helper->active;
|
|
}
|
|
|
|
void
|
|
gtk_action_helper_activate (GtkActionHelper *helper)
|
|
{
|
|
g_return_if_fail (GTK_IS_ACTION_HELPER (helper));
|
|
|
|
if (!helper->can_activate || helper->reporting)
|
|
return;
|
|
|
|
g_action_group_activate_action (G_ACTION_GROUP (helper->action_context),
|
|
helper->action_name, helper->target);
|
|
}
|