From ccf49466a64602051ddffca43488b77576abeab2 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Thu, 15 Mar 2007 19:33:57 +0000 Subject: [PATCH] Add GtkActionClass::get_submenu() vfunc: actions providing a menu item or 2007-03-15 Emmanuele Bassi * gtk/gtkaction.[ch]: Add GtkActionClass::get_submenu() vfunc: actions providing a menu item or a menu tool button with already a submenu should return the GtkMenu widget. * gtk/gtkuimanager.c (update_node): If an action provides its own submenu, use it instead of adding an empty one * gtk/gtkrecentaction.[ch]: Add GtkRecentAction, an action implementing the GtkRecentChooser interface for displaying the list of recently used files into menus and toolbars generated using GtkUIManager. (#338843) * gtk/Makefile.am: * gtk/gtk.h: * gtk/gtk.symbols: Add GtkRecentAction API to the build. * tests/testactions.c: Exercise the GtkRecentAction API. svn path=/trunk/; revision=17524 --- ChangeLog | 21 ++ gtk/Makefile.am | 2 + gtk/gtk.h | 1 + gtk/gtk.symbols | 10 + gtk/gtkaction.c | 29 +- gtk/gtkaction.h | 4 +- gtk/gtkactiongroup.c | 2 +- gtk/gtkrecentaction.c | 811 ++++++++++++++++++++++++++++++++++++++++++ gtk/gtkrecentaction.h | 70 ++++ gtk/gtkuimanager.c | 47 ++- tests/testactions.c | 52 ++- 11 files changed, 1018 insertions(+), 31 deletions(-) create mode 100644 gtk/gtkrecentaction.c create mode 100644 gtk/gtkrecentaction.h diff --git a/ChangeLog b/ChangeLog index a049c13f5..50c4cdf5c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,23 @@ +2007-03-15 Emmanuele Bassi + + * gtk/gtkaction.[ch]: Add GtkActionClass::get_submenu() vfunc: + actions providing a menu item or a menu tool button with already + a submenu should return the GtkMenu widget. + + * gtk/gtkuimanager.c (update_node): If an action provides its + own submenu, use it instead of adding an empty one + + * gtk/gtkrecentaction.[ch]: Add GtkRecentAction, an action + implementing the GtkRecentChooser interface for displaying the + list of recently used files into menus and toolbars generated + using GtkUIManager. (#338843) + + * gtk/Makefile.am: + * gtk/gtk.h: + * gtk/gtk.symbols: Add GtkRecentAction API to the build. + + * tests/testactions.c: Exercise the GtkRecentAction API. + 2007-03-15 Chris Wilson * gtk/gtkicontheme.c (ensure_valid_themes), (rescan_themes), @@ -25,6 +45,7 @@ 2007-03-15 Emmanuele Bassi +>>>>>>> .r17523 * gtk/gtkrecentchooserprivate.h: * gtk/gtkrecentchooserutils.c: Move filtering of the recent files list into the shared implementation; do the filtering diff --git a/gtk/Makefile.am b/gtk/Makefile.am index 82c4afd71..ce5d48f36 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -250,6 +250,7 @@ gtk_public_h_sources = \ gtkradiotoolbutton.h \ gtkrange.h \ gtkrc.h \ + gtkrecentaction.h \ gtkrecentchooser.h \ gtkrecentchooserdialog.h \ gtkrecentchoosermenu.h \ @@ -509,6 +510,7 @@ gtk_base_c_sources = \ gtkrange.c \ gtkrbtree.c \ gtkrc.c \ + gtkrecentaction.c \ gtkrecentchooserdefault.c \ gtkrecentchooserdialog.c \ gtkrecentchoosermenu.c \ diff --git a/gtk/gtk.h b/gtk/gtk.h index 487f5eae1..1fa2d0c8f 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -141,6 +141,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtk.symbols b/gtk/gtk.symbols index 1eedabd27..f66898460 100644 --- a/gtk/gtk.symbols +++ b/gtk/gtk.symbols @@ -3030,6 +3030,16 @@ gtk_rc_style_unref #endif #endif +#if IN_HEADER(__GTK_RECENT_ACTION_H__) +#if IN_FILE(__GTK_RECENT_ACTION_C__) +gtk_recent_action_get_type G_GNUC_CONST +gtk_recent_action_new +gtk_recent_action_new_for_manager +gtk_recent_action_set_show_numbers +gtk_recent_action_get_show_numbers +#endif +#endif + #if IN_HEADER(__GTK_RECENT_CHOOSER_H__) #if IN_FILE(__GTK_RECENT_CHOOSER_C__) gtk_recent_chooser_set_show_private diff --git a/gtk/gtkaction.c b/gtk/gtkaction.c index 93e1484a8..ea3fe8f44 100644 --- a/gtk/gtkaction.c +++ b/gtk/gtkaction.c @@ -147,10 +147,11 @@ static void gtk_action_sync_tooltip (GtkAction *action, static GtkWidget *create_menu_item (GtkAction *action); static GtkWidget *create_tool_item (GtkAction *action); -static void connect_proxy (GtkAction *action, - GtkWidget *proxy); +static void connect_proxy (GtkAction *action, + GtkWidget *proxy); static void disconnect_proxy (GtkAction *action, GtkWidget *proxy); + static void closure_accel_activate (GClosure *closure, GValue *return_value, guint n_param_values, @@ -184,6 +185,7 @@ gtk_action_class_init (GtkActionClass *klass) klass->menu_item_type = GTK_TYPE_IMAGE_MENU_ITEM; klass->toolbar_item_type = GTK_TYPE_TOOL_BUTTON; + klass->get_submenu = NULL; g_object_class_install_property (gobject_class, PROP_NAME, @@ -1774,5 +1776,28 @@ gtk_action_disconnect_accelerator (GtkAction *action) action->private_data->accel_closure); } +/** + * gtk_action_get_submenu: + * @action: a #GtkAction + * + * If @action provides a #GtkMenu widget as a submenu for the menu + * item or the toolbar item it creates, this function returns that + * widget. + * + * Return value: the menu item provided by the action, or %NULL. + * + * Since: 2.12 + */ +GtkWidget * +gtk_action_get_submenu (GtkAction *action) +{ + g_return_val_if_fail (GTK_IS_ACTION (action), FALSE); + + if (GTK_ACTION_GET_CLASS (action)->get_submenu) + return GTK_ACTION_GET_CLASS (action)->get_submenu (action); + + return NULL; +} + #define __GTK_ACTION_C__ #include "gtkaliasdef.c" diff --git a/gtk/gtkaction.h b/gtk/gtkaction.h index e1f3b1287..41a0e422a 100644 --- a/gtk/gtkaction.h +++ b/gtk/gtkaction.h @@ -73,8 +73,9 @@ struct _GtkActionClass void (* disconnect_proxy) (GtkAction *action, GtkWidget *proxy); + GtkWidget *(* get_submenu) (GtkAction *action); + /* Padding for future expansion */ - void (*_gtk_reserved1) (void); void (*_gtk_reserved2) (void); void (*_gtk_reserved3) (void); void (*_gtk_reserved4) (void); @@ -109,6 +110,7 @@ void gtk_action_connect_accelerator (GtkAction *action); void gtk_action_disconnect_accelerator (GtkAction *action); G_CONST_RETURN gchar *gtk_action_get_accel_path (GtkAction *action); GClosure *gtk_action_get_accel_closure (GtkAction *action); +GtkWidget * gtk_action_get_submenu (GtkAction *action); /* protected ... for use by child actions */ void gtk_action_block_activate_from (GtkAction *action, diff --git a/gtk/gtkactiongroup.c b/gtk/gtkactiongroup.c index 70a45ee84..56b658961 100644 --- a/gtk/gtkactiongroup.c +++ b/gtk/gtkactiongroup.c @@ -913,7 +913,7 @@ gtk_action_group_add_toggle_actions_full (GtkActionGroup *action_gro g_object_unref (action); } - shared_data_unref (shared_data); + shared_data_unref (shared_data); } /** diff --git a/gtk/gtkrecentaction.c b/gtk/gtkrecentaction.c new file mode 100644 index 000000000..eb70be2db --- /dev/null +++ b/gtk/gtkrecentaction.c @@ -0,0 +1,811 @@ +/* GTK - The GIMP Toolkit + * Recent chooser action for GtkUIManager + * + * Copyright (C) 2007, Emmanuele Bassi + * + * 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 License, 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include + +#include "gtkintl.h" +#include "gtkrecentaction.h" +#include "gtkimagemenuitem.h" +#include "gtkmenutoolbutton.h" +#include "gtkrecentchooser.h" +#include "gtkrecentchoosermenu.h" +#include "gtkrecentchooserutils.h" +#include "gtkrecentchooserprivate.h" +#include "gtkprivate.h" +#include "gtkalias.h" + +#define FALLBACK_ITEM_LIMIT 10 + +#define GTK_RECENT_ACTION_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \ + GTK_TYPE_RECENT_ACTION, \ + GtkRecentActionPrivate)) + +struct _GtkRecentActionPrivate +{ + GtkRecentManager *manager; + guint manager_changed_id; + + guint show_numbers : 1; + + /* RecentChooser properties */ + guint show_private : 1; + guint show_not_found : 1; + guint show_tips : 1; + guint show_icons : 1; + guint local_only : 1; + + gint limit; + + GtkRecentSortType sort_type; + GtkRecentSortFunc sort_func; + gpointer sort_data; + GDestroyNotify data_destroy; + + GtkRecentFilter *current_filter; + + GSList *choosers; + GtkRecentChooser *current_chooser; +}; + +enum +{ + PROP_0, + + PROP_SHOW_NUMBERS +}; + +static void gtk_recent_chooser_iface_init (GtkRecentChooserIface *iface); + +G_DEFINE_TYPE_WITH_CODE (GtkRecentAction, + gtk_recent_action, + GTK_TYPE_ACTION, + G_IMPLEMENT_INTERFACE (GTK_TYPE_RECENT_CHOOSER, + gtk_recent_chooser_iface_init)); + +static inline void +recent_chooser_set_property (GtkRecentAction *action, + const gchar *property_name, + const GValue *value) +{ + GSList *proxies, *l; + + proxies = gtk_action_get_proxies (GTK_ACTION (action)); + for (l = proxies; l != NULL; l = l->next) + { + GObject *proxy = l->data; + + g_object_set_property (proxy, property_name, value); + } + g_slist_free (proxies); +} + +static gboolean +gtk_recent_action_set_current_uri (GtkRecentChooser *chooser, + const gchar *uri, + GError **error) +{ + GtkRecentAction *action = GTK_RECENT_ACTION (chooser); + GtkRecentActionPrivate *priv = action->priv; + GSList *l; + + for (l = priv->choosers; l; l = l->next) + { + GtkRecentChooser *recent_chooser = l->data; + + if (!gtk_recent_chooser_set_current_uri (recent_chooser, uri, error)) + return FALSE; + } + + return TRUE; +} + +static gchar * +gtk_recent_action_get_current_uri (GtkRecentChooser *chooser) +{ + GtkRecentAction *recent_action = GTK_RECENT_ACTION (chooser); + GtkRecentActionPrivate *priv = recent_action->priv; + + if (priv->current_chooser) + return gtk_recent_chooser_get_current_uri (priv->current_chooser); + + return NULL; +} + +static gboolean +gtk_recent_action_select_uri (GtkRecentChooser *chooser, + const gchar *uri, + GError **error) +{ + GtkRecentAction *action = GTK_RECENT_ACTION (chooser); + GtkRecentActionPrivate *priv = action->priv; + GSList *l; + + for (l = priv->choosers; l; l = l->next) + { + GtkRecentChooser *recent_chooser = l->data; + + if (!gtk_recent_chooser_select_uri (recent_chooser, uri, error)) + return FALSE; + } + + return TRUE; +} + +static void +gtk_recent_action_unselect_uri (GtkRecentChooser *chooser, + const gchar *uri) +{ + GtkRecentAction *action = GTK_RECENT_ACTION (chooser); + GtkRecentActionPrivate *priv = action->priv; + GSList *l; + + for (l = priv->choosers; l; l = l->next) + { + GtkRecentChooser *chooser = l->data; + + gtk_recent_chooser_unselect_uri (chooser, uri); + } +} + +static void +gtk_recent_action_select_all (GtkRecentChooser *chooser) +{ + g_warning (_("This function is not implemented for " + "widgets of class '%s'"), + g_type_name (G_OBJECT_TYPE (chooser))); +} + +static void +gtk_recent_action_unselect_all (GtkRecentChooser *chooser) +{ + g_warning (_("This function is not implemented for " + "widgets of class '%s'"), + g_type_name (G_OBJECT_TYPE (chooser))); +} + + +static GList * +gtk_recent_action_get_items (GtkRecentChooser *chooser) +{ + GtkRecentAction *action = GTK_RECENT_ACTION (chooser); + GtkRecentActionPrivate *priv = action->priv; + + return _gtk_recent_chooser_get_items (chooser, + priv->current_filter, + priv->sort_func, + priv->sort_data); +} + +static GtkRecentManager * +gtk_recent_action_get_recent_manager (GtkRecentChooser *chooser) +{ + return GTK_RECENT_ACTION_GET_PRIVATE (chooser)->manager; +} + +static void +gtk_recent_action_set_sort_func (GtkRecentChooser *chooser, + GtkRecentSortFunc sort_func, + gpointer sort_data, + GDestroyNotify data_destroy) +{ + GtkRecentAction *action = GTK_RECENT_ACTION (chooser); + GtkRecentActionPrivate *priv = action->priv; + + if (priv->data_destroy) + { + priv->data_destroy (priv->sort_data); + priv->data_destroy = NULL; + } + + priv->sort_func = NULL; + priv->sort_data = NULL; + + if (sort_func) + { + priv->sort_func = sort_func; + priv->sort_data = sort_data; + priv->data_destroy = data_destroy; + } +} + +static inline void +set_current_filter (GtkRecentAction *action, + GtkRecentFilter *filter) +{ + GtkRecentActionPrivate *priv = action->priv; + + g_object_ref (action); + + if (priv->current_filter) + g_object_unref (priv->current_filter); + + priv->current_filter = filter; + + if (priv->current_filter) + g_object_ref_sink (priv->current_filter); + + g_object_notify (G_OBJECT (action), "filter"); + + g_object_unref (action); +} + +static void +gtk_recent_action_add_filter (GtkRecentChooser *chooser, + GtkRecentFilter *filter) +{ + GtkRecentActionPrivate *priv = GTK_RECENT_ACTION_GET_PRIVATE (chooser); + + if (priv->current_filter != filter) + set_current_filter (GTK_RECENT_ACTION (chooser), filter); +} + +static void +gtk_recent_action_remove_filter (GtkRecentChooser *chooser, + GtkRecentFilter *filter) +{ + GtkRecentActionPrivate *priv = GTK_RECENT_ACTION_GET_PRIVATE (chooser); + + if (priv->current_filter == filter) + set_current_filter (GTK_RECENT_ACTION (chooser), NULL); +} + +static GSList * +gtk_recent_action_list_filters (GtkRecentChooser *chooser) +{ + GSList *retval = NULL; + GtkRecentFilter *current_filter; + + current_filter = GTK_RECENT_ACTION_GET_PRIVATE (chooser)->current_filter; + retval = g_slist_prepend (retval, current_filter); + + return retval; +} + + +static void +gtk_recent_chooser_iface_init (GtkRecentChooserIface *iface) +{ + iface->set_current_uri = gtk_recent_action_set_current_uri; + iface->get_current_uri = gtk_recent_action_get_current_uri; + iface->select_uri = gtk_recent_action_select_uri; + iface->unselect_uri = gtk_recent_action_unselect_uri; + iface->select_all = gtk_recent_action_select_all; + iface->unselect_all = gtk_recent_action_unselect_all; + iface->get_items = gtk_recent_action_get_items; + iface->get_recent_manager = gtk_recent_action_get_recent_manager; + iface->set_sort_func = gtk_recent_action_set_sort_func; + iface->add_filter = gtk_recent_action_add_filter; + iface->remove_filter = gtk_recent_action_remove_filter; + iface->list_filters = gtk_recent_action_list_filters; +} + +static void +gtk_recent_action_activate (GtkAction *action) +{ + /* we have probably been invoked by a menu tool button or by a + * direct call of gtk_action_activate(); since no item has been + * selected, we must unset the current recent chooser pointer + */ + GTK_RECENT_ACTION_GET_PRIVATE (action)->current_chooser = NULL; +} + +static void +delegate_selection_changed (GtkRecentAction *action, + GtkRecentChooser *chooser) +{ + GtkRecentActionPrivate *priv = action->priv; + + priv->current_chooser = chooser; + + g_signal_emit_by_name (action, "selection-changed"); +} + +static void +delegate_item_activated (GtkRecentAction *action, + GtkRecentChooser *chooser) +{ + GtkRecentActionPrivate *priv = action->priv; + + priv->current_chooser = chooser; + + g_signal_emit_by_name (action, "item-activated"); +} + +static void +gtk_recent_action_connect_proxy (GtkAction *action, + GtkWidget *widget) +{ + GtkRecentAction *recent_action = GTK_RECENT_ACTION (action); + GtkRecentActionPrivate *priv = recent_action->priv; + + if (GTK_IS_RECENT_CHOOSER (widget) && + !g_slist_find (priv->choosers, widget)) + { + g_object_set (G_OBJECT (widget), + "show-private", priv->show_private, + "show-not-found", priv->show_not_found, + "show-tips", priv->show_tips, + "show-icons", priv->show_icons, + "show-numbers", priv->show_numbers, + "limit", priv->limit, + "sort-type", priv->sort_type, + NULL); + + if (priv->sort_func) + { + gtk_recent_chooser_set_sort_func (GTK_RECENT_CHOOSER (widget), + priv->sort_func, + priv->sort_data, + priv->data_destroy); + } + + g_signal_connect_swapped (widget, "selection_changed", + G_CALLBACK (delegate_selection_changed), + action); + g_signal_connect_swapped (widget, "item-activated", + G_CALLBACK (delegate_item_activated), + action); + } + + GTK_ACTION_CLASS (gtk_recent_action_parent_class)->connect_proxy (action, widget); +} + +static void +gtk_recent_action_disconnect_proxy (GtkAction *action, + GtkWidget *widget) +{ + GtkRecentAction *recent_action = GTK_RECENT_ACTION (action); + GtkRecentActionPrivate *priv = recent_action->priv; + + /* if it was one of the recent choosers we created, remove it + * from the list + */ + if (g_slist_find (priv->choosers, widget)) + priv->choosers = g_slist_remove (priv->choosers, widget); + + GTK_ACTION_CLASS (gtk_recent_action_parent_class)->disconnect_proxy (action, widget); +} + +static GtkWidget * +gtk_recent_action_get_submenu (GtkAction *action) +{ + GtkRecentAction *recent_action = GTK_RECENT_ACTION (action); + GtkRecentActionPrivate *priv = recent_action->priv; + GtkWidget *widget; + + widget = g_object_new (GTK_TYPE_RECENT_CHOOSER_MENU, + "show-private", priv->show_private, + "show-not-found", priv->show_not_found, + "show-tips", priv->show_tips, + "show-icons", priv->show_icons, + "show-numbers", priv->show_numbers, + "limit", priv->limit, + "sort-type", priv->sort_type, + "recent-manager", priv->manager, + NULL); + + if (priv->sort_func) + { + gtk_recent_chooser_set_sort_func (GTK_RECENT_CHOOSER (widget), + priv->sort_func, + priv->sort_data, + priv->data_destroy); + } + + g_signal_connect_swapped (widget, "selection_changed", + G_CALLBACK (delegate_selection_changed), + recent_action); + g_signal_connect_swapped (widget, "item-activated", + G_CALLBACK (delegate_item_activated), + recent_action); + + /* keep track of the choosers we create */ + priv->choosers = g_slist_prepend (priv->choosers, widget); + + return widget; +} + +static GtkWidget * +gtk_recent_action_create_menu_item (GtkAction *action) +{ + GtkWidget *menu; + GtkWidget *menuitem; + + menu = gtk_recent_action_get_submenu (action); + menuitem = g_object_new (GTK_TYPE_IMAGE_MENU_ITEM, NULL); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem), menu); + gtk_widget_show (menu); + + return menuitem; +} + +static GtkWidget * +gtk_recent_action_create_tool_item (GtkAction *action) +{ + GtkWidget *menu; + GtkWidget *toolitem; + + menu = gtk_recent_action_get_submenu (action); + toolitem = g_object_new (GTK_TYPE_MENU_TOOL_BUTTON, NULL); + gtk_menu_tool_button_set_menu (GTK_MENU_TOOL_BUTTON (toolitem), menu); + gtk_widget_show (menu); + + return toolitem; +} + +static void +manager_changed_cb (GtkRecentManager *manager, + gpointer user_data) +{ + /* do we need to propagate the signal to all the proxies? guess not */ +} + +static void +set_recent_manager (GtkRecentAction *action, + GtkRecentManager *manager) +{ + GtkRecentActionPrivate *priv = action->priv; + + if (priv->manager) + { + if (priv->manager_changed_id) + { + g_signal_handler_disconnect (priv->manager, priv->manager_changed_id); + priv->manager_changed_id = 0; + } + + priv->manager = NULL; + } + + if (manager) + priv->manager = NULL; + else + priv->manager = gtk_recent_manager_get_default (); + + if (priv->manager) + priv->manager_changed_id = g_signal_connect (priv->manager, "changed", + G_CALLBACK (manager_changed_cb), + action); +} + +static void +gtk_recent_action_finalize (GObject *gobject) +{ + GtkRecentAction *action = GTK_RECENT_ACTION (gobject); + GtkRecentActionPrivate *priv = action->priv; + + priv->manager = NULL; + + if (priv->data_destroy) + { + priv->data_destroy (priv->sort_data); + priv->data_destroy = NULL; + } + + priv->sort_data = NULL; + priv->sort_func = NULL; + + g_slist_free (priv->choosers); + + G_OBJECT_CLASS (gtk_recent_action_parent_class)->finalize (gobject); +} + +static void +gtk_recent_action_dispose (GObject *gobject) +{ + GtkRecentAction *action = GTK_RECENT_ACTION (gobject); + GtkRecentActionPrivate *priv = action->priv; + + if (priv->manager_changed_id) + { + if (priv->manager) + g_signal_handler_disconnect (priv->manager, priv->manager_changed_id); + + priv->manager_changed_id = 0; + } + + if (priv->current_filter) + { + g_object_unref (priv->current_filter); + priv->current_filter = NULL; + } + + G_OBJECT_CLASS (gtk_recent_action_parent_class)->dispose (gobject); +} + +static void +gtk_recent_action_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkRecentAction *action = GTK_RECENT_ACTION (gobject); + GtkRecentActionPrivate *priv = action->priv; + + switch (prop_id) + { + case PROP_SHOW_NUMBERS: + priv->show_numbers = g_value_get_boolean (value); + break; + case GTK_RECENT_CHOOSER_PROP_SHOW_PRIVATE: + priv->show_private = g_value_get_boolean (value); + break; + case GTK_RECENT_CHOOSER_PROP_SHOW_NOT_FOUND: + priv->show_not_found = g_value_get_boolean (value); + break; + case GTK_RECENT_CHOOSER_PROP_SHOW_TIPS: + priv->show_tips = g_value_get_boolean (value); + break; + case GTK_RECENT_CHOOSER_PROP_SHOW_ICONS: + priv->show_icons = g_value_get_boolean (value); + break; + case GTK_RECENT_CHOOSER_PROP_LIMIT: + priv->limit = g_value_get_int (value); + break; + case GTK_RECENT_CHOOSER_PROP_LOCAL_ONLY: + priv->local_only = g_value_get_boolean (value); + break; + case GTK_RECENT_CHOOSER_PROP_SORT_TYPE: + priv->sort_type = g_value_get_enum (value); + break; + case GTK_RECENT_CHOOSER_PROP_FILTER: + set_current_filter (action, g_value_get_object (value)); + break; + case GTK_RECENT_CHOOSER_PROP_SELECT_MULTIPLE: + g_warning ("%s: Choosers of type `%s' do not support selecting multiple items.", + G_STRFUNC, + G_OBJECT_TYPE_NAME (gobject)); + break; + case GTK_RECENT_CHOOSER_PROP_RECENT_MANAGER: + set_recent_manager (action, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + return; + } + + recent_chooser_set_property (action, pspec->name, value); +} + +static void +gtk_recent_action_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkRecentActionPrivate *priv = GTK_RECENT_ACTION_GET_PRIVATE (gobject); + + switch (prop_id) + { + case PROP_SHOW_NUMBERS: + g_value_set_boolean (value, priv->show_numbers); + break; + case GTK_RECENT_CHOOSER_PROP_SHOW_PRIVATE: + g_value_set_boolean (value, priv->show_private); + break; + case GTK_RECENT_CHOOSER_PROP_SHOW_NOT_FOUND: + g_value_set_boolean (value, priv->show_not_found); + break; + case GTK_RECENT_CHOOSER_PROP_SHOW_TIPS: + g_value_set_boolean (value, priv->show_tips); + break; + case GTK_RECENT_CHOOSER_PROP_SHOW_ICONS: + g_value_set_boolean (value, priv->show_icons); + break; + case GTK_RECENT_CHOOSER_PROP_LIMIT: + g_value_set_int (value, priv->limit); + break; + case GTK_RECENT_CHOOSER_PROP_LOCAL_ONLY: + g_value_set_boolean (value, priv->local_only); + break; + case GTK_RECENT_CHOOSER_PROP_SORT_TYPE: + g_value_set_enum (value, priv->sort_type); + break; + case GTK_RECENT_CHOOSER_PROP_FILTER: + g_value_set_object (value, priv->current_filter); + break; + case GTK_RECENT_CHOOSER_PROP_SELECT_MULTIPLE: + g_warning ("%s: Choosers of type `%s' do not support selecting multiple items.", + G_STRFUNC, + G_OBJECT_TYPE_NAME (gobject)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +gtk_recent_action_class_init (GtkRecentActionClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GtkActionClass *action_class = GTK_ACTION_CLASS (klass); + + g_type_class_add_private (klass, sizeof (GtkRecentActionPrivate)); + + gobject_class->finalize = gtk_recent_action_finalize; + gobject_class->dispose = gtk_recent_action_dispose; + gobject_class->set_property = gtk_recent_action_set_property; + gobject_class->get_property = gtk_recent_action_get_property; + + action_class->activate = gtk_recent_action_activate; + action_class->connect_proxy = gtk_recent_action_connect_proxy; + action_class->disconnect_proxy = gtk_recent_action_disconnect_proxy; + action_class->create_menu_item = gtk_recent_action_create_menu_item; + action_class->create_tool_item = gtk_recent_action_create_tool_item; + action_class->get_submenu = gtk_recent_action_get_submenu; + action_class->menu_item_type = GTK_TYPE_IMAGE_MENU_ITEM; + action_class->toolbar_item_type = GTK_TYPE_MENU_TOOL_BUTTON; + + _gtk_recent_chooser_install_properties (gobject_class); + + g_object_class_install_property (gobject_class, + PROP_SHOW_NUMBERS, + g_param_spec_boolean ("show-numbers", + P_("Show Numbers"), + P_("Whether the items should be displayed with a number"), + FALSE, + G_PARAM_READWRITE)); + +} + +static void +gtk_recent_action_init (GtkRecentAction *action) +{ + GtkRecentActionPrivate *priv; + + action->priv = priv = GTK_RECENT_ACTION_GET_PRIVATE (action); + + priv->show_numbers = FALSE; + priv->show_icons = TRUE; + priv->show_tips = FALSE; + priv->show_not_found = TRUE; + priv->show_private = FALSE; + priv->local_only = TRUE; + + priv->limit = FALLBACK_ITEM_LIMIT; + + priv->sort_type = GTK_RECENT_SORT_NONE; + priv->sort_func = NULL; + priv->sort_data = NULL; + priv->data_destroy = NULL; + + priv->current_filter = NULL; + + priv->manager = NULL; + priv->manager_changed_id = 0; +} + +/** + * gtk_recent_action_new: + * @name: a unique name for the action + * @label: the label displayed in menu items and on buttons + * @tooltip: a tooltip for the action + * @stock_id: the stock icon to display in widgets representing the action + * + * Creates a new #GtkRecentAction object. To add the action to + * a #GtkActionGroup and set the accelerator for the action, + * call gtk_action_group_add_action_with_accel(). + * + * Return value: the newly created #GtkRecentAction. + * + * Since: 2.12 + */ +GtkAction * +gtk_recent_action_new (const gchar *name, + const gchar *label, + const gchar *tooltip, + const gchar *stock_id) +{ + return g_object_new (GTK_TYPE_RECENT_ACTION, + "name", name, + "label", label, + "tooltip", tooltip, + "stock-id", stock_id, + NULL); +} + +/** + * gtk_recent_action_new_for_manager: + * @name: a unique name for the action + * @label: the label displayed in menu items and on buttons + * @tooltip: a tooltip for the action + * @stock_id: the stock icon to display in widgets representing the action + * @manager: a #GtkRecentManager or %NULL + * + * Creates a new #GtkRecentAction object. To add the action to + * a #GtkActionGroup and set the accelerator for the action, + * call gtk_action_group_add_action_with_accel(). + * + * Return value: the newly created #GtkRecentAction + * + * Since: 2.12 + */ +GtkAction * +gtk_recent_action_new_for_manager (const gchar *name, + const gchar *label, + const gchar *tooltip, + const gchar *stock_id, + GtkRecentManager *manager) +{ + return g_object_new (GTK_TYPE_RECENT_ACTION, + "name", name, + "label", label, + "tooltip", tooltip, + "stock-id", stock_id, + "recent-manager", manager, + NULL); +} + +/** + * gtk_recent_action_get_show_numbers: + * @action: a #GtkRecentAction + * + * Returns the value set by gtk_recent_chooser_menu_set_show_numbers(). + * + * Return value: %TRUE if numbers should be shown. + * + * Since: 2.12 + */ +gboolean +gtk_recent_action_get_show_numbers (GtkRecentAction *action) +{ + g_return_val_if_fail (GTK_IS_RECENT_ACTION (action), FALSE); + + return action->priv->show_numbers; +} + +/** + * gtk_recent_action_set_show_numbers: + * @action: a #GtkRecentAction + * @show_numbers + * + * Sets whether a number should be added to the items shown by the + * widgets representing @action. The numbers are shown to provide + * a unique character for a mnemonic to be used inside the menu item's + * label. Only the first ten items get a number to avoid clashes. + * + * Since: 2.12 + */ +void +gtk_recent_action_set_show_numbers (GtkRecentAction *action, + gboolean show_numbers) +{ + GtkRecentActionPrivate *priv; + + g_return_if_fail (GTK_IS_RECENT_ACTION (action)); + + priv = action->priv; + + if (priv->show_numbers != show_numbers) + { + g_object_ref (action); + + priv->show_numbers = show_numbers; + + g_object_notify (G_OBJECT (action), "show-numbers"); + g_object_unref (action); + } +} + +#define __GTK_RECENT_ACTION_C__ +#include "gtkaliasdef.c" diff --git a/gtk/gtkrecentaction.h b/gtk/gtkrecentaction.h new file mode 100644 index 000000000..776f94265 --- /dev/null +++ b/gtk/gtkrecentaction.h @@ -0,0 +1,70 @@ +/* GTK - The GIMP Toolkit + * Recent chooser action for GtkUIManager + * + * Copyright (C) 2007, Emmanuele Bassi + * + * 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 License, 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GTK_RECENT_ACTION_H__ +#define __GTK_RECENT_ACTION_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_RECENT_ACTION (gtk_recent_action_get_type ()) +#define GTK_RECENT_ACTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_RECENT_ACTION, GtkRecentAction)) +#define GTK_IS_RECENT_ACTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_RECENT_ACTION)) +#define GTK_RECENT_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_RECENT_ACTION, GtkRecentActionClass)) +#define GTK_IS_RECENT_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_RECENT_ACTION)) +#define GTK_RECENT_ACTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_RECENT_ACTION, GtkRecentActionClass)) + +typedef struct _GtkRecentAction GtkRecentAction; +typedef struct _GtkRecentActionPrivate GtkRecentActionPrivate; +typedef struct _GtkRecentActionClass GtkRecentActionClass; + +struct _GtkRecentAction +{ + GtkAction parent_instance; + + /*< private >*/ + GtkRecentActionPrivate *priv; +}; + +struct _GtkRecentActionClass +{ + GtkActionClass parent_class; +}; + +GType gtk_recent_action_get_type (void) G_GNUC_CONST; +GtkAction *gtk_recent_action_new (const gchar *name, + const gchar *label, + const gchar *tooltip, + const gchar *stock_id); +GtkAction *gtk_recent_action_new_for_manager (const gchar *name, + const gchar *label, + const gchar *tooltip, + const gchar *stock_id, + GtkRecentManager *manager); +gboolean gtk_recent_action_get_show_numbers (GtkRecentAction *action); +void gtk_recent_action_set_show_numbers (GtkRecentAction *action, + gboolean show_numbers); + +G_END_DECLS + +#endif /* __GTK_RECENT_ACTION_H__ */ diff --git a/gtk/gtkuimanager.c b/gtk/gtkuimanager.c index bcf06b1c6..76e7c284b 100644 --- a/gtk/gtkuimanager.c +++ b/gtk/gtkuimanager.c @@ -2204,8 +2204,9 @@ update_node (GtkUIManager *self, case NODE_TYPE_MENU: { GtkWidget *prev_submenu = NULL; - GtkWidget *menu; + GtkWidget *menu = NULL; GList *siblings; + /* remove the proxy if it is of the wrong type ... */ if (info->proxy && G_OBJECT_TYPE (info->proxy) != GTK_ACTION_GET_CLASS (action)->menu_item_type) @@ -2226,26 +2227,39 @@ update_node (GtkUIManager *self, g_object_unref (info->proxy); info->proxy = NULL; } + /* create proxy if needed ... */ if (info->proxy == NULL) { - GtkWidget *tearoff; - GtkWidget *filler; + /* ... if the action already provides a menu, then use + * that menu instead of creating an empty one + */ + if (NODE_INFO (node->parent)->type == NODE_TYPE_TOOLITEM && + GTK_ACTION_GET_CLASS (action)->get_submenu) + { + menu = gtk_action_get_submenu (action); + } + + if (!menu) + { + GtkWidget *tearoff; + GtkWidget *filler; - menu = gtk_menu_new (); - gtk_widget_set_name (menu, info->name); - tearoff = gtk_tearoff_menu_item_new (); - gtk_widget_set_no_show_all (tearoff, TRUE); - gtk_menu_shell_append (GTK_MENU_SHELL (menu), tearoff); - filler = gtk_menu_item_new_with_label (_("Empty")); - g_object_set_data (G_OBJECT (filler), - I_("gtk-empty-menu-item"), - GINT_TO_POINTER (TRUE)); - gtk_widget_set_sensitive (filler, FALSE); - gtk_widget_set_no_show_all (filler, TRUE); - gtk_menu_shell_append (GTK_MENU_SHELL (menu), filler); + menu = gtk_menu_new (); + gtk_widget_set_name (menu, info->name); + tearoff = gtk_tearoff_menu_item_new (); + gtk_widget_set_no_show_all (tearoff, TRUE); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), tearoff); + filler = gtk_menu_item_new_with_label (_("Empty")); + g_object_set_data (G_OBJECT (filler), + I_("gtk-empty-menu-item"), + GINT_TO_POINTER (TRUE)); + gtk_widget_set_sensitive (filler, FALSE); + gtk_widget_set_no_show_all (filler, TRUE); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), filler); + } - if (NODE_INFO (node->parent)->type == NODE_TYPE_TOOLITEM) + if (NODE_INFO (node->parent)->type == NODE_TYPE_TOOLITEM) { info->proxy = menu; g_object_ref_sink (info->proxy); @@ -2284,6 +2298,7 @@ update_node (GtkUIManager *self, menu = info->proxy; else menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy)); + siblings = gtk_container_get_children (GTK_CONTAINER (menu)); if (siblings != NULL && GTK_IS_TEAROFF_MENU_ITEM (siblings->data)) { diff --git a/tests/testactions.c b/tests/testactions.c index 4f971c2fd..01b665517 100644 --- a/tests/testactions.c +++ b/tests/testactions.c @@ -55,6 +55,19 @@ radio_action (GtkAction *action) gtk_radio_action_get_current_value (GTK_RADIO_ACTION (action))); } +static void +recent_action (GtkAction *action) +{ + const gchar *name = gtk_action_get_name (action); + const gchar *typename = G_OBJECT_TYPE_NAME (action); + gchar *uri = gtk_recent_chooser_get_current_uri (GTK_RECENT_CHOOSER (action)); + + g_message ("Action %s (type=%s) activated (uri=%s)", + name, typename, + uri ? uri : "no item selected"); + g_free (uri); +} + static void toggle_cnp_actions (GtkAction *action) { @@ -181,46 +194,49 @@ static const gchar *ui_info = " \n" " \n" " \n" -" \n" +" \n" " \n" +" \n" +" \n" " \n" " \n" " \n" " \n" " \n" " \n" -" \n" -" \n" " \n" +" \n" +" \n" " \n" " \n" " \n" " \n" -" \n" -" \n" " \n" +" \n" +" \n" " \n" " \n" " \n" " \n" -" \n" +" \n" " \n" " \n" " \n" - " \n" +" \n" " \n" " \n" " \n" " \n" " \n" -" \n" -" \n" +" \n" " \n" +" \n" +" \n" " \n" " \n" " \n" " \n" -" \n" +" \n" " \n" " \n" " \n" @@ -383,11 +399,23 @@ create_window (GtkActionGroup *action_group) int main (int argc, char **argv) { + GtkAction *action; + gtk_init (&argc, &argv); if (g_file_test ("accels", G_FILE_TEST_IS_REGULAR)) gtk_accel_map_load ("accels"); + action = gtk_recent_action_new ("recent", + "Open Recent", "Open recent files", + NULL); + g_signal_connect (action, "item-activated", + G_CALLBACK (recent_action), + NULL); + g_signal_connect (action, "activate", + G_CALLBACK (recent_action), + NULL); + action_group = gtk_action_group_new ("TestActions"); gtk_action_group_add_actions (action_group, entries, n_entries, @@ -403,6 +431,7 @@ main (int argc, char **argv) toolbar_entries, n_toolbar_entries, GTK_TOOLBAR_BOTH, G_CALLBACK (radio_action), NULL); + gtk_action_group_add_action_with_accel (action_group, action, NULL); create_window (action_group); @@ -422,7 +451,8 @@ main (int argc, char **argv) } } #endif - + + g_object_unref (action); g_object_unref (action_group); gtk_accel_map_save ("accels"); -- 2.43.2