X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkactiongroup.c;h=ac177e1ab3d2b6bfdf119af56b6321e03e12c735;hb=HEAD;hp=235ee71493ca76a403262d07dbb817f06f715e84;hpb=9542740711c9661b47f044600dcc274711d03e82;p=~andy%2Fgtk diff --git a/gtk/gtkactiongroup.c b/gtk/gtkactiongroup.c index 235ee7149..ac177e1ab 100644 --- a/gtk/gtkactiongroup.c +++ b/gtk/gtkactiongroup.c @@ -14,9 +14,7 @@ * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public - * License along with the Gnome Library; see the file COPYING.LIB. If not, - * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * License along with this library. If not, see . */ /* @@ -28,28 +26,148 @@ * GTK+ at ftp://ftp.gtk.org/pub/gtk/. */ -#include +/** + * SECTION:gtkactiongroup + * @Short_description: A group of actions + * @Title: GtkActionGroup + * + * Actions are organised into groups. An action group is essentially a + * map from names to #GtkAction objects. + * + * All actions that would make sense to use in a particular context + * should be in a single group. Multiple action groups may be used for a + * particular user interface. In fact, it is expected that most nontrivial + * applications will make use of multiple groups. For example, in an + * application that can edit multiple documents, one group holding global + * actions (e.g. quit, about, new), and one group per document holding + * actions that act on that document (eg. save, cut/copy/paste, etc). Each + * window's menus would be constructed from a combination of two action + * groups. + * + * + * Accelerators are handled by the GTK+ accelerator map. All actions are + * assigned an accelerator path (which normally has the form + * <Actions>/group-name/action-name) + * and a shortcut is associated with this accelerator path. All menuitems + * and toolitems take on this accelerator path. The GTK+ accelerator map + * code makes sure that the correct shortcut is displayed next to the menu + * item. + * + * + * GtkActionGroup as GtkBuildable + * + * The #GtkActionGroup implementation of the #GtkBuildable interface accepts + * #GtkAction objects as <child> elements in UI definitions. + * + * Note that it is probably more common to define actions and action groups + * in the code, since they are directly related to what the code can do. + * + * The GtkActionGroup implementation of the GtkBuildable interface supports + * a custom <accelerator> element, which has attributes named key and + * modifiers and allows to specify accelerators. This is similar to the + * <accelerator> element of #GtkWidget, the main difference is that + * it doesn't allow you to specify a signal. + * + * + * A #GtkDialog UI definition fragment. + * + * + * + * About + * gtk-about + * + * + * + * + * + * ]]> + * + * + */ + +#include "config.h" +#include #include "gtkactiongroup.h" +#include "gtkbuildable.h" +#include "gtkiconfactory.h" +#include "gtkicontheme.h" +#include "gtkstock.h" #include "gtktoggleaction.h" #include "gtkradioaction.h" #include "gtkaccelmap.h" +#include "gtkmarshalers.h" +#include "gtkbuilderprivate.h" +#include "gtkprivate.h" #include "gtkintl.h" -#define GTK_ACTION_GROUP_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_ACTION_GROUP, GtkActionGroupPrivate)) struct _GtkActionGroupPrivate { gchar *name; + gboolean sensitive; + gboolean visible; GHashTable *actions; + GtkAccelGroup *accel_group; GtkTranslateFunc translate_func; gpointer translate_data; - GtkDestroyNotify translate_notify; + GDestroyNotify translate_notify; +}; + +enum +{ + CONNECT_PROXY, + DISCONNECT_PROXY, + PRE_ACTIVATE, + POST_ACTIVATE, + LAST_SIGNAL }; -static void gtk_action_group_init (GtkActionGroup *self); -static void gtk_action_group_class_init (GtkActionGroupClass *class); +enum +{ + PROP_0, + PROP_NAME, + PROP_SENSITIVE, + PROP_VISIBLE, + PROP_ACCEL_GROUP +}; + +static void gtk_action_group_init (GtkActionGroup *self); +static void gtk_action_group_class_init (GtkActionGroupClass *class); +static void gtk_action_group_finalize (GObject *object); +static void gtk_action_group_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gtk_action_group_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static GtkAction *gtk_action_group_real_get_action (GtkActionGroup *self, + const gchar *name); + +/* GtkBuildable */ +static void gtk_action_group_buildable_init (GtkBuildableIface *iface); +static void gtk_action_group_buildable_add_child (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *type); +static void gtk_action_group_buildable_set_name (GtkBuildable *buildable, + const gchar *name); +static const gchar* gtk_action_group_buildable_get_name (GtkBuildable *buildable); +static gboolean gtk_action_group_buildable_custom_tag_start (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *tagname, + GMarkupParser *parser, + gpointer *data); +static void gtk_action_group_buildable_custom_tag_end (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *tagname, + gpointer *user_data); GType gtk_action_group_get_type (void) @@ -58,7 +176,7 @@ gtk_action_group_get_type (void) if (!type) { - static const GTypeInfo type_info = + const GTypeInfo type_info = { sizeof (GtkActionGroupClass), NULL, /* base_init */ @@ -71,17 +189,25 @@ gtk_action_group_get_type (void) (GInstanceInitFunc) gtk_action_group_init, }; - type = g_type_register_static (G_TYPE_OBJECT, "GtkActionGroup", + const GInterfaceInfo buildable_info = + { + (GInterfaceInitFunc) gtk_action_group_buildable_init, + NULL, + NULL + }; + + type = g_type_register_static (G_TYPE_OBJECT, I_("GtkActionGroup"), &type_info, 0); - } + g_type_add_interface_static (type, + GTK_TYPE_BUILDABLE, + &buildable_info); + } return type; } static GObjectClass *parent_class = NULL; -static void gtk_action_group_finalize (GObject *object); -static GtkAction *gtk_action_group_real_get_action (GtkActionGroup *self, - const gchar *name); +static guint action_group_signals[LAST_SIGNAL] = { 0 }; static void gtk_action_group_class_init (GtkActionGroupClass *klass) @@ -92,29 +218,319 @@ gtk_action_group_class_init (GtkActionGroupClass *klass) parent_class = g_type_class_peek_parent (klass); gobject_class->finalize = gtk_action_group_finalize; + gobject_class->set_property = gtk_action_group_set_property; + gobject_class->get_property = gtk_action_group_get_property; klass->get_action = gtk_action_group_real_get_action; + g_object_class_install_property (gobject_class, + PROP_NAME, + g_param_spec_string ("name", + P_("Name"), + P_("A name for the action group."), + NULL, + GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (gobject_class, + PROP_SENSITIVE, + g_param_spec_boolean ("sensitive", + P_("Sensitive"), + P_("Whether the action group is enabled."), + TRUE, + GTK_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, + PROP_VISIBLE, + g_param_spec_boolean ("visible", + P_("Visible"), + P_("Whether the action group is visible."), + TRUE, + GTK_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, + PROP_ACCEL_GROUP, + g_param_spec_object ("accel-group", + P_("Accelerator Group"), + P_("The accelerator group the actions of this group should use."), + GTK_TYPE_ACCEL_GROUP, + GTK_PARAM_READWRITE)); + + /** + * GtkActionGroup::connect-proxy: + * @action_group: the group + * @action: the action + * @proxy: the proxy + * + * The ::connect-proxy signal is emitted after connecting a proxy to + * an action in the group. Note that the proxy may have been connected + * to a different action before. + * + * This is intended for simple customizations for which a custom action + * class would be too clumsy, e.g. showing tooltips for menuitems in the + * statusbar. + * + * #GtkUIManager proxies the signal and provides global notification + * just before any action is connected to a proxy, which is probably more + * convenient to use. + * + * Since: 2.4 + */ + action_group_signals[CONNECT_PROXY] = + g_signal_new (I_("connect-proxy"), + G_OBJECT_CLASS_TYPE (klass), + 0, 0, NULL, NULL, + _gtk_marshal_VOID__OBJECT_OBJECT, + G_TYPE_NONE, 2, + GTK_TYPE_ACTION, GTK_TYPE_WIDGET); + + /** + * GtkActionGroup::disconnect-proxy: + * @action_group: the group + * @action: the action + * @proxy: the proxy + * + * The ::disconnect-proxy signal is emitted after disconnecting a proxy + * from an action in the group. + * + * #GtkUIManager proxies the signal and provides global notification + * just before any action is connected to a proxy, which is probably more + * convenient to use. + * + * Since: 2.4 + */ + action_group_signals[DISCONNECT_PROXY] = + g_signal_new (I_("disconnect-proxy"), + G_OBJECT_CLASS_TYPE (klass), + 0, 0, NULL, NULL, + _gtk_marshal_VOID__OBJECT_OBJECT, + G_TYPE_NONE, 2, + GTK_TYPE_ACTION, GTK_TYPE_WIDGET); + + /** + * GtkActionGroup::pre-activate: + * @action_group: the group + * @action: the action + * + * The ::pre-activate signal is emitted just before the @action in the + * @action_group is activated + * + * This is intended for #GtkUIManager to proxy the signal and provide global + * notification just before any action is activated. + * + * Since: 2.4 + */ + action_group_signals[PRE_ACTIVATE] = + g_signal_new (I_("pre-activate"), + G_OBJECT_CLASS_TYPE (klass), + 0, 0, NULL, NULL, + _gtk_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + GTK_TYPE_ACTION); + + /** + * GtkActionGroup::post-activate: + * @action_group: the group + * @action: the action + * + * The ::post-activate signal is emitted just after the @action in the + * @action_group is activated + * + * This is intended for #GtkUIManager to proxy the signal and provide global + * notification just after any action is activated. + * + * Since: 2.4 + */ + action_group_signals[POST_ACTIVATE] = + g_signal_new (I_("post-activate"), + G_OBJECT_CLASS_TYPE (klass), + 0, 0, NULL, NULL, + _gtk_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + GTK_TYPE_ACTION); + g_type_class_add_private (gobject_class, sizeof (GtkActionGroupPrivate)); } + +static void +remove_action (GtkAction *action) +{ + g_object_set (action, I_("action-group"), NULL, NULL); + g_object_unref (action); +} + +static void +gtk_action_group_init (GtkActionGroup *action_group) +{ + GtkActionGroupPrivate *private; + + action_group->priv = G_TYPE_INSTANCE_GET_PRIVATE (action_group, + GTK_TYPE_ACTION_GROUP, + GtkActionGroupPrivate); + private = action_group->priv; + + private->name = NULL; + private->sensitive = TRUE; + private->visible = TRUE; + private->actions = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, + (GDestroyNotify) remove_action); + private->translate_func = NULL; + private->translate_data = NULL; + private->translate_notify = NULL; +} + +static void +gtk_action_group_buildable_init (GtkBuildableIface *iface) +{ + iface->add_child = gtk_action_group_buildable_add_child; + iface->set_name = gtk_action_group_buildable_set_name; + iface->get_name = gtk_action_group_buildable_get_name; + iface->custom_tag_start = gtk_action_group_buildable_custom_tag_start; + iface->custom_tag_end = gtk_action_group_buildable_custom_tag_end; +} + +static void +gtk_action_group_buildable_add_child (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *type) +{ + gtk_action_group_add_action_with_accel (GTK_ACTION_GROUP (buildable), + GTK_ACTION (child), NULL); +} + +static void +gtk_action_group_buildable_set_name (GtkBuildable *buildable, + const gchar *name) +{ + GtkActionGroup *self = GTK_ACTION_GROUP (buildable); + GtkActionGroupPrivate *private = self->priv; + + private->name = g_strdup (name); +} + +static const gchar * +gtk_action_group_buildable_get_name (GtkBuildable *buildable) +{ + GtkActionGroup *self = GTK_ACTION_GROUP (buildable); + GtkActionGroupPrivate *private = self->priv; + + return private->name; +} + +typedef struct { + GObject *child; + guint key; + GdkModifierType modifiers; +} AcceleratorParserData; + +static void +accelerator_start_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **names, + const gchar **values, + gpointer user_data, + GError **error) +{ + gint i; + guint key = 0; + GdkModifierType modifiers = 0; + AcceleratorParserData *parser_data = (AcceleratorParserData*)user_data; + + if (strcmp (element_name, "accelerator") != 0) + g_warning ("Unknown tag: %s", element_name); + + for (i = 0; names[i]; i++) + { + if (strcmp (names[i], "key") == 0) + key = gdk_keyval_from_name (values[i]); + else if (strcmp (names[i], "modifiers") == 0) + { + if (!_gtk_builder_flags_from_string (GDK_TYPE_MODIFIER_TYPE, + values[i], + &modifiers, + error)) + return; + } + } + + if (key == 0) + { + g_warning (" requires a key attribute"); + return; + } + parser_data->key = key; + parser_data->modifiers = modifiers; +} + +static const GMarkupParser accelerator_parser = + { + accelerator_start_element + }; + +static gboolean +gtk_action_group_buildable_custom_tag_start (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *tagname, + GMarkupParser *parser, + gpointer *user_data) +{ + AcceleratorParserData *parser_data; + + if (child && strcmp (tagname, "accelerator") == 0) + { + parser_data = g_slice_new0 (AcceleratorParserData); + parser_data->child = child; + *user_data = parser_data; + *parser = accelerator_parser; + + return TRUE; + } + return FALSE; +} + static void -gtk_action_group_init (GtkActionGroup *self) +gtk_action_group_buildable_custom_tag_end (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *tagname, + gpointer *user_data) { - self->private_data = GTK_ACTION_GROUP_GET_PRIVATE (self); - self->private_data->name = NULL; - self->private_data->actions = g_hash_table_new_full (g_str_hash, g_str_equal, - (GDestroyNotify) g_free, - (GDestroyNotify) g_object_unref); - self->private_data->translate_func = NULL; - self->private_data->translate_data = NULL; - self->private_data->translate_notify = NULL; + AcceleratorParserData *data; + + if (strcmp (tagname, "accelerator") == 0) + { + GtkActionGroup *action_group; + GtkActionGroupPrivate *private; + GtkAction *action; + gchar *accel_path; + + data = (AcceleratorParserData*)user_data; + action_group = GTK_ACTION_GROUP (buildable); + private = action_group->priv; + action = GTK_ACTION (child); + + accel_path = g_strconcat ("/", + private->name, "/", + gtk_action_get_name (action), NULL); + + if (gtk_accel_map_lookup_entry (accel_path, NULL)) + gtk_accel_map_change_entry (accel_path, data->key, data->modifiers, TRUE); + else + gtk_accel_map_add_entry (accel_path, data->key, data->modifiers); + + gtk_action_set_accel_path (action, accel_path); + + g_free (accel_path); + g_slice_free (AcceleratorParserData, data); + } } /** * gtk_action_group_new: - * @name: the name of the action group + * @name: the name of the action group. * - * Creates a new #GtkActionGroup object. + * Creates a new #GtkActionGroup object. The name of the action group + * is used when associating keybindings + * with the actions. * * Returns: the new #GtkActionGroup * @@ -124,9 +540,11 @@ GtkActionGroup * gtk_action_group_new (const gchar *name) { GtkActionGroup *self; + GtkActionGroupPrivate *private; self = g_object_new (GTK_TYPE_ACTION_GROUP, NULL); - self->private_data->name = g_strdup (name); + private = self->priv; + private->name = g_strdup (name); return self; } @@ -135,27 +553,101 @@ static void gtk_action_group_finalize (GObject *object) { GtkActionGroup *self; + GtkActionGroupPrivate *private; self = GTK_ACTION_GROUP (object); + private = self->priv; + + g_free (private->name); + private->name = NULL; - g_free (self->private_data->name); - self->private_data->name = NULL; + g_hash_table_destroy (private->actions); + private->actions = NULL; - g_hash_table_destroy (self->private_data->actions); - self->private_data->actions = NULL; + g_clear_object (&private->accel_group); - if (self->private_data->translate_notify) - self->private_data->translate_notify (self->private_data->translate_data); + if (private->translate_notify) + private->translate_notify (private->translate_data); - if (parent_class->finalize) - (* parent_class->finalize) (object); + parent_class->finalize (object); +} + +static void +gtk_action_group_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkActionGroup *self; + GtkActionGroupPrivate *private; + gchar *tmp; + + self = GTK_ACTION_GROUP (object); + private = self->priv; + + switch (prop_id) + { + case PROP_NAME: + tmp = private->name; + private->name = g_value_dup_string (value); + g_free (tmp); + break; + case PROP_SENSITIVE: + gtk_action_group_set_sensitive (self, g_value_get_boolean (value)); + break; + case PROP_VISIBLE: + gtk_action_group_set_visible (self, g_value_get_boolean (value)); + break; + case PROP_ACCEL_GROUP: + gtk_action_group_set_accel_group (self, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_action_group_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkActionGroup *self; + GtkActionGroupPrivate *private; + + self = GTK_ACTION_GROUP (object); + private = self->priv; + + switch (prop_id) + { + case PROP_NAME: + g_value_set_string (value, private->name); + break; + case PROP_SENSITIVE: + g_value_set_boolean (value, private->sensitive); + break; + case PROP_VISIBLE: + g_value_set_boolean (value, private->visible); + break; + case PROP_ACCEL_GROUP: + g_value_set_object (value, private->accel_group); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } } static GtkAction * gtk_action_group_real_get_action (GtkActionGroup *self, const gchar *action_name) { - return g_hash_table_lookup (self->private_data->actions, action_name); + GtkActionGroupPrivate *private; + + private = self->priv; + + return g_hash_table_lookup (private->actions, action_name); } /** @@ -171,9 +663,202 @@ gtk_action_group_real_get_action (GtkActionGroup *self, const gchar * gtk_action_group_get_name (GtkActionGroup *action_group) { + GtkActionGroupPrivate *private; + g_return_val_if_fail (GTK_IS_ACTION_GROUP (action_group), NULL); - return action_group->private_data->name; + private = action_group->priv; + + return private->name; +} + +/** + * gtk_action_group_get_sensitive: + * @action_group: the action group + * + * Returns %TRUE if the group is sensitive. The constituent actions + * can only be logically sensitive (see gtk_action_is_sensitive()) if + * they are sensitive (see gtk_action_get_sensitive()) and their group + * is sensitive. + * + * Return value: %TRUE if the group is sensitive. + * + * Since: 2.4 + */ +gboolean +gtk_action_group_get_sensitive (GtkActionGroup *action_group) +{ + GtkActionGroupPrivate *private; + + g_return_val_if_fail (GTK_IS_ACTION_GROUP (action_group), FALSE); + + private = action_group->priv; + + return private->sensitive; +} + +static void +cb_set_action_sensitivity (const gchar *name, + GtkAction *action) +{ + /* Minor optimization, the action_groups state only affects actions + * that are themselves sensitive */ + g_object_notify (G_OBJECT (action), "sensitive"); + +} + +/** + * gtk_action_group_set_sensitive: + * @action_group: the action group + * @sensitive: new sensitivity + * + * Changes the sensitivity of @action_group + * + * Since: 2.4 + */ +void +gtk_action_group_set_sensitive (GtkActionGroup *action_group, + gboolean sensitive) +{ + GtkActionGroupPrivate *private; + + g_return_if_fail (GTK_IS_ACTION_GROUP (action_group)); + + private = action_group->priv; + sensitive = sensitive != FALSE; + + if (private->sensitive != sensitive) + { + private->sensitive = sensitive; + g_hash_table_foreach (private->actions, + (GHFunc) cb_set_action_sensitivity, NULL); + + g_object_notify (G_OBJECT (action_group), "sensitive"); + } +} + +/** + * gtk_action_group_get_visible: + * @action_group: the action group + * + * Returns %TRUE if the group is visible. The constituent actions + * can only be logically visible (see gtk_action_is_visible()) if + * they are visible (see gtk_action_get_visible()) and their group + * is visible. + * + * Return value: %TRUE if the group is visible. + * + * Since: 2.4 + */ +gboolean +gtk_action_group_get_visible (GtkActionGroup *action_group) +{ + GtkActionGroupPrivate *private; + + g_return_val_if_fail (GTK_IS_ACTION_GROUP (action_group), FALSE); + + private = action_group->priv; + + return private->visible; +} + +/** + * gtk_action_group_get_accel_group: + * @action_group: a #GtkActionGroup + * + * Gets the accelerator group. + * + * Returns: (transfer none): the accelerator group associated with this action + * group or %NULL if there is none. + * + * Since: 3.6 + */ +GtkAccelGroup * +gtk_action_group_get_accel_group (GtkActionGroup *action_group) +{ + g_return_val_if_fail (GTK_IS_ACTION_GROUP (action_group), FALSE); + + return action_group->priv->accel_group; +} + +static void +cb_set_action_visiblity (const gchar *name, + GtkAction *action) +{ + /* Minor optimization, the action_groups state only affects actions + * that are themselves visible */ + g_object_notify (G_OBJECT (action), "visible"); +} + +/** + * gtk_action_group_set_visible: + * @action_group: the action group + * @visible: new visiblity + * + * Changes the visible of @action_group. + * + * Since: 2.4 + */ +void +gtk_action_group_set_visible (GtkActionGroup *action_group, + gboolean visible) +{ + GtkActionGroupPrivate *private; + + g_return_if_fail (GTK_IS_ACTION_GROUP (action_group)); + + private = action_group->priv; + visible = visible != FALSE; + + if (private->visible != visible) + { + private->visible = visible; + g_hash_table_foreach (private->actions, + (GHFunc) cb_set_action_visiblity, NULL); + + g_object_notify (G_OBJECT (action_group), "visible"); + } +} + +static void +gtk_action_group_accel_group_foreach (gpointer key, gpointer val, gpointer data) +{ + gtk_action_set_accel_group (val, data); +} + +/** + * gtk_action_group_set_accel_group: + * @action_group: a #GtkActionGroup + * @accel_group: (allow-none): a #GtkAccelGroup to set or %NULL + * + * Sets the accelerator group to be used by every action in this group. + * + * Since: 3.6 + */ +void +gtk_action_group_set_accel_group (GtkActionGroup *action_group, + GtkAccelGroup *accel_group) +{ + GtkActionGroupPrivate *private; + + g_return_if_fail (GTK_IS_ACTION_GROUP (action_group)); + + private = action_group->priv; + + if (private->accel_group == accel_group) + return; + + g_clear_object (&private->accel_group); + + if (accel_group) + private->accel_group = g_object_ref (accel_group); + + /* Set the new accel group on every action */ + g_hash_table_foreach (private->actions, + gtk_action_group_accel_group_foreach, + accel_group); + + g_object_notify (G_OBJECT (action_group), "accel-group"); } /** @@ -183,7 +868,7 @@ gtk_action_group_get_name (GtkActionGroup *action_group) * * Looks up an action in the action group by name. * - * Returns: the action, or %NULL if no action by that name exists + * Returns: (transfer none): the action, or %NULL if no action by that name exists * * Since: 2.4 */ @@ -194,8 +879,27 @@ gtk_action_group_get_action (GtkActionGroup *action_group, g_return_val_if_fail (GTK_IS_ACTION_GROUP (action_group), NULL); g_return_val_if_fail (GTK_ACTION_GROUP_GET_CLASS (action_group)->get_action != NULL, NULL); - return (* GTK_ACTION_GROUP_GET_CLASS (action_group)->get_action) - (action_group, action_name); + return GTK_ACTION_GROUP_GET_CLASS (action_group)->get_action (action_group, + action_name); +} + +static gboolean +check_unique_action (GtkActionGroup *action_group, + const gchar *action_name) +{ + if (gtk_action_group_get_action (action_group, action_name) != NULL) + { + GtkActionGroupPrivate *private; + + private = action_group->priv; + + g_warning ("Refusing to add non-unique action '%s' to action group '%s'", + action_name, + private->name); + return FALSE; + } + + return TRUE; } /** @@ -203,7 +907,12 @@ gtk_action_group_get_action (GtkActionGroup *action_group, * @action_group: the action group * @action: an action * - * Adds an action object to the action group. + * Adds an action object to the action group. Note that this function + * does not set up the accel path of the action, which can lead to problems + * if a user tries to modify the accelerator of a menuitem associated with + * the action. Therefore you must either set the accel path yourself with + * gtk_action_set_accel_path(), or use + * gtk_action_group_add_action_with_accel (..., NULL). * * Since: 2.4 */ @@ -211,17 +920,105 @@ void gtk_action_group_add_action (GtkActionGroup *action_group, GtkAction *action) { + GtkActionGroupPrivate *private; + const gchar *name; + g_return_if_fail (GTK_IS_ACTION_GROUP (action_group)); g_return_if_fail (GTK_IS_ACTION (action)); - g_return_if_fail (gtk_action_get_name (action) != NULL); - g_hash_table_insert (action_group->private_data->actions, - g_strdup (gtk_action_get_name (action)), + name = gtk_action_get_name (action); + g_return_if_fail (name != NULL); + + if (!check_unique_action (action_group, name)) + return; + + private = action_group->priv; + + g_hash_table_insert (private->actions, + (gpointer) name, g_object_ref (action)); + g_object_set (action, I_("action-group"), action_group, NULL); + + if (private->accel_group) + gtk_action_set_accel_group (action, private->accel_group); +} + +/** + * gtk_action_group_add_action_with_accel: + * @action_group: the action group + * @action: the action to add + * @accelerator: (allow-none): the accelerator for the action, in + * the format understood by gtk_accelerator_parse(), or "" for no accelerator, or + * %NULL to use the stock accelerator + * + * Adds an action object to the action group and sets up the accelerator. + * + * If @accelerator is %NULL, attempts to use the accelerator associated + * with the stock_id of the action. + * + * Accel paths are set to + * <Actions>/group-name/action-name. + * + * Since: 2.4 + */ +void +gtk_action_group_add_action_with_accel (GtkActionGroup *action_group, + GtkAction *action, + const gchar *accelerator) +{ + GtkActionGroupPrivate *private; + gchar *accel_path; + guint accel_key = 0; + GdkModifierType accel_mods; + const gchar *name; + + name = gtk_action_get_name (action); + if (!check_unique_action (action_group, name)) + return; + + private = action_group->priv; + accel_path = g_strconcat ("/", + private->name, "/", name, NULL); + + if (accelerator) + { + if (accelerator[0] == 0) + accel_key = 0; + else + { + gtk_accelerator_parse (accelerator, &accel_key, &accel_mods); + if (accel_key == 0) + g_warning ("Unable to parse accelerator '%s' for action '%s'", + accelerator, name); + } + } + else + { + gchar *stock_id; + GtkStockItem stock_item; + + g_object_get (action, "stock-id", &stock_id, NULL); + + if (stock_id && gtk_stock_lookup (stock_id, &stock_item)) + { + accel_key = stock_item.keyval; + accel_mods = stock_item.modifier; + } + + g_free (stock_id); + } + + if (accel_key) + gtk_accel_map_add_entry (accel_path, accel_key, accel_mods); + + gtk_action_set_accel_path (action, accel_path); + gtk_action_group_add_action (action_group, action); + + g_free (accel_path); } /** - * gtk_action_group_removes_action: + * gtk_action_group_remove_action: * @action_group: the action group * @action: an action * @@ -233,14 +1030,18 @@ void gtk_action_group_remove_action (GtkActionGroup *action_group, GtkAction *action) { + GtkActionGroupPrivate *private; + const gchar *name; + g_return_if_fail (GTK_IS_ACTION_GROUP (action_group)); g_return_if_fail (GTK_IS_ACTION (action)); - g_return_if_fail (gtk_action_get_name (action) != NULL); - /* extra protection to make sure action->name is valid */ - g_object_ref (action); - g_hash_table_remove (action_group->private_data->actions, gtk_action_get_name (action)); - g_object_unref (action); + name = gtk_action_get_name (action); + g_return_if_fail (name != NULL); + + private = action_group->priv; + + g_hash_table_remove (private->actions, name); } static void @@ -259,51 +1060,81 @@ add_single_action (gpointer key, * * Lists the actions in the action group. * - * Returns: an allocated list of the action objects in the action group - * + * Returns: (element-type GtkAction) (transfer container): an allocated list of the action objects in the action group + * * Since: 2.4 */ GList * gtk_action_group_list_actions (GtkActionGroup *action_group) { + GtkActionGroupPrivate *private; GList *actions = NULL; + g_return_val_if_fail (GTK_IS_ACTION_GROUP (action_group), NULL); + + private = action_group->priv; - g_hash_table_foreach (action_group->private_data->actions, add_single_action, &actions); + g_hash_table_foreach (private->actions, add_single_action, &actions); return g_list_reverse (actions); } /** - * gtk_action_group_add_actions: + * gtk_action_group_add_actions: (skip) * @action_group: the action group - * @entries: an array of action descriptions + * @entries: (array length=n_entries): an array of action descriptions * @n_entries: the number of entries * @user_data: data to pass to the action callbacks * - * This is a convenience routine to create a number of actions and add - * them to the action group. Each member of the array describes an - * action to create. + * This is a convenience function to create a number of actions and add them + * to the action group. + * + * The "activate" signals of the actions are connected to the callbacks and + * their accel paths are set to + * <Actions>/group-name/action-name. * * Since: 2.4 */ void -gtk_action_group_add_actions (GtkActionGroup *action_group, - GtkActionEntry *entries, - guint n_entries, - gpointer user_data) +gtk_action_group_add_actions (GtkActionGroup *action_group, + const GtkActionEntry *entries, + guint n_entries, + gpointer user_data) { gtk_action_group_add_actions_full (action_group, entries, n_entries, user_data, NULL); } +typedef struct _SharedData SharedData; + +struct _SharedData { + guint ref_count; + gpointer data; + GDestroyNotify destroy; +}; + +static void +shared_data_unref (gpointer data) +{ + SharedData *shared_data = (SharedData *)data; + + shared_data->ref_count--; + if (shared_data->ref_count == 0) + { + if (shared_data->destroy) + shared_data->destroy (shared_data->data); + + g_slice_free (SharedData, shared_data); + } +} + /** - * gtk_action_group_add_actions_full: + * gtk_action_group_add_actions_full: (skip) * @action_group: the action group - * @entries: an array of action descriptions + * @entries: (array length=n_entries): an array of action descriptions * @n_entries: the number of entries * @user_data: data to pass to the action callbacks * @destroy: destroy notification callback for @user_data @@ -314,182 +1145,293 @@ gtk_action_group_add_actions (GtkActionGroup *action_group, * Since: 2.4 */ void -gtk_action_group_add_actions_full (GtkActionGroup *action_group, - GtkActionEntry *entries, - guint n_entries, - gpointer user_data, - GDestroyNotify destroy) +gtk_action_group_add_actions_full (GtkActionGroup *action_group, + const GtkActionEntry *entries, + guint n_entries, + gpointer user_data, + GDestroyNotify destroy) { + + /* Keep this in sync with the other + * gtk_action_group_add_..._actions_full() functions. + */ guint i; - GtkTranslateFunc translate_func; - gpointer translate_data; + SharedData *shared_data; g_return_if_fail (GTK_IS_ACTION_GROUP (action_group)); - translate_func = action_group->private_data->translate_func; - translate_data = action_group->private_data->translate_data; + shared_data = g_slice_new0 (SharedData); + shared_data->ref_count = 1; + shared_data->data = user_data; + shared_data->destroy = destroy; for (i = 0; i < n_entries; i++) { GtkAction *action; - GType action_type; - gchar *accel_path; - gchar *label; - gchar *tooltip; + const gchar *label; + const gchar *tooltip; - if (entries[i].is_toggle) - action_type = GTK_TYPE_TOGGLE_ACTION; - else - action_type = GTK_TYPE_ACTION; + if (!check_unique_action (action_group, entries[i].name)) + continue; + + label = gtk_action_group_translate_string (action_group, entries[i].label); + tooltip = gtk_action_group_translate_string (action_group, entries[i].tooltip); + + action = gtk_action_new (entries[i].name, + label, + tooltip, + NULL); - if (translate_func) + if (entries[i].stock_id) { - label = translate_func (entries[i].label, translate_data); - tooltip = translate_func (entries[i].tooltip, translate_data); + g_object_set (action, "stock-id", entries[i].stock_id, NULL); + if (gtk_icon_theme_has_icon (gtk_icon_theme_get_default (), + entries[i].stock_id)) + g_object_set (action, "icon-name", entries[i].stock_id, NULL); } - else + + if (entries[i].callback) { - label = entries[i].label; - tooltip = entries[i].tooltip; + GClosure *closure; + + closure = g_cclosure_new (entries[i].callback, user_data, NULL); + g_closure_add_finalize_notifier (closure, shared_data, + (GClosureNotify)shared_data_unref); + shared_data->ref_count++; + + g_signal_connect_closure (action, "activate", closure, FALSE); } + + gtk_action_group_add_action_with_accel (action_group, + action, + entries[i].accelerator); + g_object_unref (action); + } - action = g_object_new (action_type, - "name", entries[i].name, - "label", label, - "tooltip", tooltip, - "stock_id", entries[i].stock_id, - NULL); + shared_data_unref (shared_data); +} - if (entries[i].callback) - g_signal_connect_data (action, "activate", - entries[i].callback, - user_data, (GClosureNotify)destroy, 0); - - /* set the accel path for the menu item */ - accel_path = g_strconcat ("/", action_group->private_data->name, "/", - entries[i].name, NULL); - if (entries[i].accelerator) - { - guint accel_key = 0; - GdkModifierType accel_mods; +/** + * gtk_action_group_add_toggle_actions: (skip) + * @action_group: the action group + * @entries: (array length=n_entries): an array of toggle action descriptions + * @n_entries: the number of entries + * @user_data: data to pass to the action callbacks + * + * This is a convenience function to create a number of toggle actions and add them + * to the action group. + * + * The "activate" signals of the actions are connected to the callbacks and + * their accel paths are set to + * <Actions>/group-name/action-name. + * + * Since: 2.4 + */ +void +gtk_action_group_add_toggle_actions (GtkActionGroup *action_group, + const GtkToggleActionEntry *entries, + guint n_entries, + gpointer user_data) +{ + gtk_action_group_add_toggle_actions_full (action_group, + entries, n_entries, + user_data, NULL); +} + + +/** + * gtk_action_group_add_toggle_actions_full: (skip) + * @action_group: the action group + * @entries: (array length=n_entries): an array of toggle action descriptions + * @n_entries: the number of entries + * @user_data: data to pass to the action callbacks + * @destroy: destroy notification callback for @user_data + * + * This variant of gtk_action_group_add_toggle_actions() adds a + * #GDestroyNotify callback for @user_data. + * + * Since: 2.4 + */ +void +gtk_action_group_add_toggle_actions_full (GtkActionGroup *action_group, + const GtkToggleActionEntry *entries, + guint n_entries, + gpointer user_data, + GDestroyNotify destroy) +{ + /* Keep this in sync with the other + * gtk_action_group_add_..._actions_full() functions. + */ + guint i; + SharedData *shared_data; + + g_return_if_fail (GTK_IS_ACTION_GROUP (action_group)); - gtk_accelerator_parse (entries[i].accelerator, &accel_key, - &accel_mods); - if (accel_key) - gtk_accel_map_add_entry (accel_path, accel_key, accel_mods); + shared_data = g_slice_new0 (SharedData); + shared_data->ref_count = 1; + shared_data->data = user_data; + shared_data->destroy = destroy; + + for (i = 0; i < n_entries; i++) + { + GtkToggleAction *action; + const gchar *label; + const gchar *tooltip; + + if (!check_unique_action (action_group, entries[i].name)) + continue; + + label = gtk_action_group_translate_string (action_group, entries[i].label); + tooltip = gtk_action_group_translate_string (action_group, entries[i].tooltip); + + action = gtk_toggle_action_new (entries[i].name, + label, + tooltip, + NULL); + + if (entries[i].stock_id) + { + if (gtk_icon_factory_lookup_default (entries[i].stock_id)) + g_object_set (action, "stock-id", entries[i].stock_id, NULL); + else + g_object_set (action, "icon-name", entries[i].stock_id, NULL); } - gtk_action_set_accel_path (action, accel_path); - g_free (accel_path); + gtk_toggle_action_set_active (action, entries[i].is_active); + + if (entries[i].callback) + { + GClosure *closure; + + closure = g_cclosure_new (entries[i].callback, user_data, NULL); + g_closure_add_finalize_notifier (closure, shared_data, + (GClosureNotify)shared_data_unref); + shared_data->ref_count++; - gtk_action_group_add_action (action_group, action); + g_signal_connect_closure (action, "activate", closure, FALSE); + } + + gtk_action_group_add_action_with_accel (action_group, + GTK_ACTION (action), + entries[i].accelerator); g_object_unref (action); } + + shared_data_unref (shared_data); } /** - * gtk_action_group_add_radio_actions: + * gtk_action_group_add_radio_actions: (skip) * @action_group: the action group - * @entries: an array of radio action descriptions + * @entries: (array length=n_entries): an array of radio action descriptions * @n_entries: the number of entries + * @value: the value of the action to activate initially, or -1 if + * no action should be activated * @on_change: the callback to connect to the changed signal * @user_data: data to pass to the action callbacks * - * This is a convenience routine to create a group of radio actions and - * add them to the action group. Each member of the array describes a - * radio action to create. + * This is a convenience routine to create a group of radio actions and + * add them to the action group. + * + * The "changed" signal of the first radio action is connected to the + * @on_change callback and the accel paths of the actions are set to + * <Actions>/group-name/action-name. * * Since: 2.4 - * **/ void -gtk_action_group_add_radio_actions (GtkActionGroup *action_group, - GtkRadioActionEntry *entries, - guint n_entries, - GCallback on_change, - gpointer user_data) +gtk_action_group_add_radio_actions (GtkActionGroup *action_group, + const GtkRadioActionEntry *entries, + guint n_entries, + gint value, + GCallback on_change, + gpointer user_data) { gtk_action_group_add_radio_actions_full (action_group, entries, n_entries, + value, on_change, user_data, NULL); } +/** + * gtk_action_group_add_radio_actions_full: (skip) + * @action_group: the action group + * @entries: (array length=n_entries): an array of radio action descriptions + * @n_entries: the number of entries + * @value: the value of the action to activate initially, or -1 if + * no action should be activated + * @on_change: the callback to connect to the changed signal + * @user_data: data to pass to the action callbacks + * @destroy: destroy notification callback for @user_data + * + * This variant of gtk_action_group_add_radio_actions() adds a + * #GDestroyNotify callback for @user_data. + * + * Since: 2.4 + **/ void -gtk_action_group_add_radio_actions_full (GtkActionGroup *action_group, - GtkRadioActionEntry *entries, - guint n_entries, - GCallback on_change, - gpointer user_data, - GDestroyNotify destroy) +gtk_action_group_add_radio_actions_full (GtkActionGroup *action_group, + const GtkRadioActionEntry *entries, + guint n_entries, + gint value, + GCallback on_change, + gpointer user_data, + GDestroyNotify destroy) { + /* Keep this in sync with the other + * gtk_action_group_add_..._actions_full() functions. + */ guint i; - GtkTranslateFunc translate_func; - gpointer translate_data; + GSList *group = NULL; + GtkRadioAction *first_action = NULL; g_return_if_fail (GTK_IS_ACTION_GROUP (action_group)); - translate_func = action_group->private_data->translate_func; - translate_data = action_group->private_data->translate_data; - for (i = 0; i < n_entries; i++) { - GtkAction *action; - gchar *accel_path; - gchar *label; - gchar *tooltip; + GtkRadioAction *action; + const gchar *label; + const gchar *tooltip; - if (translate_func) - { - label = translate_func (entries[i].label, translate_data); - tooltip = translate_func (entries[i].tooltip, translate_data); - } - else + if (!check_unique_action (action_group, entries[i].name)) + continue; + + label = gtk_action_group_translate_string (action_group, entries[i].label); + tooltip = gtk_action_group_translate_string (action_group, entries[i].tooltip); + + action = gtk_radio_action_new (entries[i].name, + label, + tooltip, + NULL, + entries[i].value); + + if (entries[i].stock_id) { - label = entries[i].label; - tooltip = entries[i].tooltip; + if (gtk_icon_factory_lookup_default (entries[i].stock_id)) + g_object_set (action, "stock-id", entries[i].stock_id, NULL); + else + g_object_set (action, "icon-name", entries[i].stock_id, NULL); } - action = g_object_new (GTK_TYPE_RADIO_ACTION, - "name", entries[i].name, - "label", label, - "tooltip", tooltip, - "stock_id", entries[i].stock_id, - "value", entries[i].value, - NULL); - if (i == 0) - { - if (on_change) - g_signal_connect_data (action, "changed", - on_change, user_data, - (GClosureNotify)destroy, 0); - } - else - { - GSList *group = gtk_radio_action_get_group (GTK_RADIO_ACTION (action)); - gtk_radio_action_set_group (GTK_RADIO_ACTION (action), group); - } + first_action = action; - /* set the accel path for the menu item */ - accel_path = g_strconcat ("/", action_group->private_data->name, "/", - entries[i].name, NULL); - if (entries[i].accelerator) - { - guint accel_key = 0; - GdkModifierType accel_mods; + gtk_radio_action_set_group (action, group); + group = gtk_radio_action_get_group (action); - gtk_accelerator_parse (entries[i].accelerator, &accel_key, - &accel_mods); - if (accel_key) - gtk_accel_map_add_entry (accel_path, accel_key, accel_mods); - } + if (value == entries[i].value) + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE); - gtk_action_set_accel_path (action, accel_path); - g_free (accel_path); - - gtk_action_group_add_action (action_group, action); + gtk_action_group_add_action_with_accel (action_group, + GTK_ACTION (action), + entries[i].accelerator); g_object_unref (action); } + + if (on_change && first_action) + g_signal_connect_data (first_action, "changed", + on_change, user_data, + (GClosureNotify)destroy, 0); } /** @@ -497,11 +1439,11 @@ gtk_action_group_add_radio_actions_full (GtkActionGroup *action_group, * @action_group: a #GtkActionGroup * @func: a #GtkTranslateFunc * @data: data to be passed to @func and @notify - * @notify: a #GtkDestroyNotify function to be called when @action_group is + * @notify: a #GDestroyNotify function to be called when @action_group is * destroyed and when the translation function is changed again - * + * * Sets a function to be used for translating the @label and @tooltip of - * #GtkActionGroupEntrys added by gtk_action_group_add_actions(). + * #GtkActionEntrys added by gtk_action_group_add_actions(). * * If you're using gettext(), it is enough to set the translation domain * with gtk_action_group_set_translation_domain(). @@ -509,34 +1451,43 @@ gtk_action_group_add_radio_actions_full (GtkActionGroup *action_group, * Since: 2.4 **/ void -gtk_action_group_set_translate_func (GtkActionGroup *action_group, - GtkTranslateFunc func, - gpointer data, - GtkDestroyNotify notify) +gtk_action_group_set_translate_func (GtkActionGroup *action_group, + GtkTranslateFunc func, + gpointer data, + GDestroyNotify notify) { + GtkActionGroupPrivate *private; + g_return_if_fail (GTK_IS_ACTION_GROUP (action_group)); - if (action_group->private_data->translate_notify) - action_group->private_data->translate_notify (action_group->private_data->translate_data); + private = action_group->priv; + + if (private->translate_notify) + private->translate_notify (private->translate_data); - action_group->private_data->translate_func = func; - action_group->private_data->translate_data = data; - action_group->private_data->translate_notify = notify; + private->translate_func = func; + private->translate_data = data; + private->translate_notify = notify; } static gchar * dgettext_swapped (const gchar *msgid, const gchar *domainname) { - return dgettext (domainname, msgid); + /* Pass through g_dgettext if and only if msgid is nonempty. */ + if (msgid && *msgid) + return (gchar*) g_dgettext (domainname, msgid); + else + return (gchar*) msgid; } /** * gtk_action_group_set_translation_domain: * @action_group: a #GtkActionGroup - * @domain: the translation domain to use for dgettext() calls + * @domain: (allow-none): the translation domain to use for g_dgettext() + * calls, or %NULL to use the domain set with textdomain() * - * Sets the translation domain and uses dgettext() for translating the + * Sets the translation domain and uses g_dgettext() for translating the * @label and @tooltip of #GtkActionEntrys added by * gtk_action_group_add_actions(). * @@ -556,3 +1507,74 @@ gtk_action_group_set_translation_domain (GtkActionGroup *action_group, g_strdup (domain), g_free); } + + +/** + * gtk_action_group_translate_string: + * @action_group: a #GtkActionGroup + * @string: a string + * + * Translates a string using the function set with + * gtk_action_group_set_translate_func(). This + * is mainly intended for language bindings. + * + * Returns: the translation of @string + * + * Since: 2.6 + **/ +const gchar * +gtk_action_group_translate_string (GtkActionGroup *action_group, + const gchar *string) +{ + GtkActionGroupPrivate *private; + GtkTranslateFunc translate_func; + gpointer translate_data; + + g_return_val_if_fail (GTK_IS_ACTION_GROUP (action_group), string); + + if (string == NULL) + return NULL; + + private = action_group->priv; + + translate_func = private->translate_func; + translate_data = private->translate_data; + + if (translate_func) + return translate_func (string, translate_data); + else + return string; +} + +/* Protected for use by GtkAction */ +void +_gtk_action_group_emit_connect_proxy (GtkActionGroup *action_group, + GtkAction *action, + GtkWidget *proxy) +{ + g_signal_emit (action_group, action_group_signals[CONNECT_PROXY], 0, + action, proxy); +} + +void +_gtk_action_group_emit_disconnect_proxy (GtkActionGroup *action_group, + GtkAction *action, + GtkWidget *proxy) +{ + g_signal_emit (action_group, action_group_signals[DISCONNECT_PROXY], 0, + action, proxy); +} + +void +_gtk_action_group_emit_pre_activate (GtkActionGroup *action_group, + GtkAction *action) +{ + g_signal_emit (action_group, action_group_signals[PRE_ACTIVATE], 0, action); +} + +void +_gtk_action_group_emit_post_activate (GtkActionGroup *action_group, + GtkAction *action) +{ + g_signal_emit (action_group, action_group_signals[POST_ACTIVATE], 0, action); +}