gtk2/gtk/gtkrecentchoosermenu.c
2016-10-16 18:17:21 +02:00

1322 lines
38 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* GTK - The GIMP Toolkit
* gtkrecentchoosermenu.c - Recently used items menu widget
* Copyright (C) 2005, 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, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <string.h>
#include "gtkrecentmanager.h"
#include "gtkrecentfilter.h"
#include "gtkrecentchooser.h"
#include "gtkrecentchooserutils.h"
#include "gtkrecentchooserprivate.h"
#include "gtkrecentchoosermenu.h"
#include "gtkicontheme.h"
#include "gtkintl.h"
#include "gtksettings.h"
#include "gtkmenushell.h"
#include "gtkmenuitem.h"
#include "gtkseparatormenuitem.h"
#include "gtkmenu.h"
#include "gtkimage.h"
#include "gtklabel.h"
#include "gtkbox.h"
#include "gtktooltip.h"
#include "deprecated/gtkactivatable.h"
#include "gtktypebuiltins.h"
#include "gtkprivate.h"
/**
* SECTION:gtkrecentchoosermenu
* @Short_description: Displays recently used files in a menu
* @Title: GtkRecentChooserMenu
* @See_also:#GtkRecentChooser
*
* #GtkRecentChooserMenu is a widget suitable for displaying recently used files
* inside a menu. It can be used to set a sub-menu of a #GtkMenuItem using
* gtk_menu_item_set_submenu(), or as the menu of a #GtkMenuToolButton.
*
* Note that #GtkRecentChooserMenu does not have any methods of its own. Instead,
* you should use the functions that work on a #GtkRecentChooser.
*
* Note also that #GtkRecentChooserMenu does not support multiple filters, as it
* has no way to let the user choose between them as the #GtkRecentChooserWidget
* and #GtkRecentChooserDialog widgets do. Thus using gtk_recent_chooser_add_filter()
* on a #GtkRecentChooserMenu widget will yield the same effects as using
* gtk_recent_chooser_set_filter(), replacing any currently set filter
* with the supplied filter; gtk_recent_chooser_remove_filter() will remove
* any currently set #GtkRecentFilter object and will unset the current filter;
* gtk_recent_chooser_list_filters() will return a list containing a single
* #GtkRecentFilter object.
*
* Recently used files are supported since GTK+ 2.10.
*/
struct _GtkRecentChooserMenuPrivate
{
/* the recent manager object */
GtkRecentManager *manager;
/* max size of the menu item label */
gint label_width;
gint first_recent_item_pos;
GtkWidget *placeholder;
/* RecentChooser properties */
gint limit;
guint show_private : 1;
guint show_not_found : 1;
guint show_tips : 1;
guint show_icons : 1;
guint local_only : 1;
guint show_numbers : 1;
GtkRecentSortType sort_type;
GtkRecentSortFunc sort_func;
gpointer sort_data;
GDestroyNotify sort_data_destroy;
GSList *filters;
GtkRecentFilter *current_filter;
guint local_manager : 1;
gulong manager_changed_id;
gulong populate_id;
};
enum {
PROP_0,
PROP_SHOW_NUMBERS,
/* activatable properties */
PROP_ACTIVATABLE_RELATED_ACTION,
PROP_ACTIVATABLE_USE_ACTION_APPEARANCE
};
#define FALLBACK_ITEM_LIMIT 10
#define DEFAULT_LABEL_WIDTH 30
static void gtk_recent_chooser_menu_finalize (GObject *object);
static void gtk_recent_chooser_menu_dispose (GObject *object);
static void gtk_recent_chooser_menu_constructed (GObject *object);
static void gtk_recent_chooser_iface_init (GtkRecentChooserIface *iface);
static void gtk_recent_chooser_menu_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec);
static void gtk_recent_chooser_menu_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec);
static gboolean gtk_recent_chooser_menu_set_current_uri (GtkRecentChooser *chooser,
const gchar *uri,
GError **error);
static gchar * gtk_recent_chooser_menu_get_current_uri (GtkRecentChooser *chooser);
static gboolean gtk_recent_chooser_menu_select_uri (GtkRecentChooser *chooser,
const gchar *uri,
GError **error);
static void gtk_recent_chooser_menu_unselect_uri (GtkRecentChooser *chooser,
const gchar *uri);
static void gtk_recent_chooser_menu_select_all (GtkRecentChooser *chooser);
static void gtk_recent_chooser_menu_unselect_all (GtkRecentChooser *chooser);
static GList * gtk_recent_chooser_menu_get_items (GtkRecentChooser *chooser);
static GtkRecentManager *gtk_recent_chooser_menu_get_recent_manager (GtkRecentChooser *chooser);
static void gtk_recent_chooser_menu_set_sort_func (GtkRecentChooser *chooser,
GtkRecentSortFunc sort_func,
gpointer sort_data,
GDestroyNotify data_destroy);
static void gtk_recent_chooser_menu_add_filter (GtkRecentChooser *chooser,
GtkRecentFilter *filter);
static void gtk_recent_chooser_menu_remove_filter (GtkRecentChooser *chooser,
GtkRecentFilter *filter);
static GSList * gtk_recent_chooser_menu_list_filters (GtkRecentChooser *chooser);
static void gtk_recent_chooser_menu_set_current_filter (GtkRecentChooserMenu *menu,
GtkRecentFilter *filter);
static void gtk_recent_chooser_menu_populate (GtkRecentChooserMenu *menu);
static void gtk_recent_chooser_menu_set_show_tips (GtkRecentChooserMenu *menu,
gboolean show_tips);
static void set_recent_manager (GtkRecentChooserMenu *menu,
GtkRecentManager *manager);
static void item_activate_cb (GtkWidget *widget,
gpointer user_data);
static void manager_changed_cb (GtkRecentManager *manager,
gpointer user_data);
static void gtk_recent_chooser_activatable_iface_init (GtkActivatableIface *iface);
static void gtk_recent_chooser_update (GtkActivatable *activatable,
GtkAction *action,
const gchar *property_name);
static void gtk_recent_chooser_sync_action_properties (GtkActivatable *activatable,
GtkAction *action);
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
G_DEFINE_TYPE_WITH_CODE (GtkRecentChooserMenu,
gtk_recent_chooser_menu,
GTK_TYPE_MENU,
G_ADD_PRIVATE (GtkRecentChooserMenu)
G_IMPLEMENT_INTERFACE (GTK_TYPE_RECENT_CHOOSER,
gtk_recent_chooser_iface_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTIVATABLE,
gtk_recent_chooser_activatable_iface_init))
G_GNUC_END_IGNORE_DEPRECATIONS;
static void
gtk_recent_chooser_iface_init (GtkRecentChooserIface *iface)
{
iface->set_current_uri = gtk_recent_chooser_menu_set_current_uri;
iface->get_current_uri = gtk_recent_chooser_menu_get_current_uri;
iface->select_uri = gtk_recent_chooser_menu_select_uri;
iface->unselect_uri = gtk_recent_chooser_menu_unselect_uri;
iface->select_all = gtk_recent_chooser_menu_select_all;
iface->unselect_all = gtk_recent_chooser_menu_unselect_all;
iface->get_items = gtk_recent_chooser_menu_get_items;
iface->get_recent_manager = gtk_recent_chooser_menu_get_recent_manager;
iface->set_sort_func = gtk_recent_chooser_menu_set_sort_func;
iface->add_filter = gtk_recent_chooser_menu_add_filter;
iface->remove_filter = gtk_recent_chooser_menu_remove_filter;
iface->list_filters = gtk_recent_chooser_menu_list_filters;
}
static void
gtk_recent_chooser_activatable_iface_init (GtkActivatableIface *iface)
{
iface->update = gtk_recent_chooser_update;
iface->sync_action_properties = gtk_recent_chooser_sync_action_properties;
}
static void
gtk_recent_chooser_menu_class_init (GtkRecentChooserMenuClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->constructed = gtk_recent_chooser_menu_constructed;
gobject_class->dispose = gtk_recent_chooser_menu_dispose;
gobject_class->finalize = gtk_recent_chooser_menu_finalize;
gobject_class->set_property = gtk_recent_chooser_menu_set_property;
gobject_class->get_property = gtk_recent_chooser_menu_get_property;
_gtk_recent_chooser_install_properties (gobject_class);
/**
* GtkRecentChooserMenu:show-numbers:
*
* Whether the first ten items in the menu should be prepended by
* a number acting as a unique mnemonic.
*
* Since: 2.10
*/
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,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
g_object_class_override_property (gobject_class, PROP_ACTIVATABLE_RELATED_ACTION, "related-action");
g_object_class_override_property (gobject_class, PROP_ACTIVATABLE_USE_ACTION_APPEARANCE, "use-action-appearance");
}
static void
gtk_recent_chooser_menu_init (GtkRecentChooserMenu *menu)
{
GtkRecentChooserMenuPrivate *priv;
menu->priv = gtk_recent_chooser_menu_get_instance_private (menu);
priv = menu->priv;
priv->show_icons= TRUE;
priv->show_numbers = FALSE;
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->label_width = DEFAULT_LABEL_WIDTH;
priv->first_recent_item_pos = -1;
priv->placeholder = NULL;
priv->current_filter = NULL;
}
static void
gtk_recent_chooser_menu_finalize (GObject *object)
{
GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (object);
GtkRecentChooserMenuPrivate *priv = menu->priv;
priv->manager = NULL;
if (priv->sort_data_destroy)
{
priv->sort_data_destroy (priv->sort_data);
priv->sort_data_destroy = NULL;
}
priv->sort_data = NULL;
priv->sort_func = NULL;
G_OBJECT_CLASS (gtk_recent_chooser_menu_parent_class)->finalize (object);
}
static void
gtk_recent_chooser_menu_dispose (GObject *object)
{
GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (object);
GtkRecentChooserMenuPrivate *priv = menu->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->populate_id)
{
g_source_remove (priv->populate_id);
priv->populate_id = 0;
}
if (priv->current_filter)
{
g_object_unref (priv->current_filter);
priv->current_filter = NULL;
}
G_OBJECT_CLASS (gtk_recent_chooser_menu_parent_class)->dispose (object);
}
static void
gtk_recent_chooser_menu_constructed (GObject *object)
{
GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (object);
GtkRecentChooserMenuPrivate *priv = menu->priv;
G_OBJECT_CLASS (gtk_recent_chooser_menu_parent_class)->constructed (object);
g_assert (priv->manager);
/* we create a placeholder menuitem, to be used in case
* the menu is empty. this placeholder will stay around
* for the entire lifetime of the menu, and we just hide it
* when it's not used. we have to do this, and do it here,
* because we need a marker for the beginning of the recent
* items list, so that we can insert the new items at the
* right place when idly populating the menu in case the
* user appended or prepended custom menu items to the
* recent chooser menu widget.
*/
priv->placeholder = gtk_menu_item_new_with_label (_("No items found"));
gtk_widget_set_sensitive (priv->placeholder, FALSE);
g_object_set_data (G_OBJECT (priv->placeholder),
"gtk-recent-menu-placeholder",
GINT_TO_POINTER (TRUE));
gtk_menu_shell_insert (GTK_MENU_SHELL (menu), priv->placeholder, 0);
gtk_widget_set_no_show_all (priv->placeholder, TRUE);
gtk_widget_show (priv->placeholder);
/* (re)populate the menu */
gtk_recent_chooser_menu_populate (menu);
}
static void
gtk_recent_chooser_menu_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (object);
GtkRecentChooserMenuPrivate *priv = menu->priv;
switch (prop_id)
{
case PROP_SHOW_NUMBERS:
if (priv->show_numbers != g_value_get_boolean (value))
{
priv->show_numbers = g_value_get_boolean (value);
g_object_notify_by_pspec (object, pspec);
}
break;
case GTK_RECENT_CHOOSER_PROP_RECENT_MANAGER:
set_recent_manager (menu, g_value_get_object (value));
break;
case GTK_RECENT_CHOOSER_PROP_SHOW_PRIVATE:
if (priv->show_private != g_value_get_boolean (value))
{
priv->show_private = g_value_get_boolean (value);
g_object_notify_by_pspec (object, pspec);
}
break;
case GTK_RECENT_CHOOSER_PROP_SHOW_NOT_FOUND:
if (priv->show_not_found != g_value_get_boolean (value))
{
priv->show_not_found = g_value_get_boolean (value);
g_object_notify_by_pspec (object, pspec);
}
break;
case GTK_RECENT_CHOOSER_PROP_SHOW_TIPS:
gtk_recent_chooser_menu_set_show_tips (menu, g_value_get_boolean (value));
break;
case GTK_RECENT_CHOOSER_PROP_SHOW_ICONS:
if (priv->show_icons != g_value_get_boolean (value))
{
priv->show_icons = g_value_get_boolean (value);
g_object_notify_by_pspec (object, pspec);
}
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 (object));
break;
case GTK_RECENT_CHOOSER_PROP_LOCAL_ONLY:
if (priv->local_only != g_value_get_boolean (value))
{
priv->local_only = g_value_get_boolean (value);
g_object_notify_by_pspec (object, pspec);
}
break;
case GTK_RECENT_CHOOSER_PROP_LIMIT:
if (priv->limit != g_value_get_int (value))
{
priv->limit = g_value_get_int (value);
g_object_notify_by_pspec (object, pspec);
}
break;
case GTK_RECENT_CHOOSER_PROP_SORT_TYPE:
if (priv->sort_type != g_value_get_enum (value))
{
priv->sort_type = g_value_get_enum (value);
g_object_notify_by_pspec (object, pspec);
}
break;
case GTK_RECENT_CHOOSER_PROP_FILTER:
gtk_recent_chooser_menu_set_current_filter (menu, g_value_get_object (value));
break;
case PROP_ACTIVATABLE_RELATED_ACTION:
_gtk_recent_chooser_set_related_action (GTK_RECENT_CHOOSER (menu), g_value_get_object (value));
break;
case PROP_ACTIVATABLE_USE_ACTION_APPEARANCE:
_gtk_recent_chooser_set_use_action_appearance (GTK_RECENT_CHOOSER (menu), g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_recent_chooser_menu_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (object);
GtkRecentChooserMenuPrivate *priv = menu->priv;
switch (prop_id)
{
case PROP_SHOW_NUMBERS:
g_value_set_boolean (value, priv->show_numbers);
break;
case GTK_RECENT_CHOOSER_PROP_SHOW_TIPS:
g_value_set_boolean (value, priv->show_tips);
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_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_ICONS:
g_value_set_boolean (value, priv->show_icons);
break;
case GTK_RECENT_CHOOSER_PROP_SELECT_MULTIPLE:
g_value_set_boolean (value, FALSE);
break;
case GTK_RECENT_CHOOSER_PROP_FILTER:
g_value_set_object (value, priv->current_filter);
break;
case PROP_ACTIVATABLE_RELATED_ACTION:
g_value_set_object (value, _gtk_recent_chooser_get_related_action (GTK_RECENT_CHOOSER (menu)));
break;
case PROP_ACTIVATABLE_USE_ACTION_APPEARANCE:
g_value_set_boolean (value, _gtk_recent_chooser_get_use_action_appearance (GTK_RECENT_CHOOSER (menu)));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static gboolean
gtk_recent_chooser_menu_set_current_uri (GtkRecentChooser *chooser,
const gchar *uri,
GError **error)
{
GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (chooser);
GList *children, *l;
GtkWidget *menu_item = NULL;
gboolean found = FALSE;
gint i = 0;
children = gtk_container_get_children (GTK_CONTAINER (menu));
for (l = children; l != NULL; l = l->next, i++)
{
GtkRecentInfo *info;
menu_item = GTK_WIDGET (l->data);
info = g_object_get_data (G_OBJECT (menu_item), "gtk-recent-info");
if (!info)
continue;
if (strcmp (uri, gtk_recent_info_get_uri (info)) == 0)
{
gtk_menu_set_active (GTK_MENU (menu), i);
found = TRUE;
break;
}
}
g_list_free (children);
if (!found)
{
g_set_error (error, GTK_RECENT_CHOOSER_ERROR,
GTK_RECENT_CHOOSER_ERROR_NOT_FOUND,
_("No recently used resource found with URI '%s'"),
uri);
}
return found;
}
static gchar *
gtk_recent_chooser_menu_get_current_uri (GtkRecentChooser *chooser)
{
GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (chooser);
GtkWidget *menu_item;
GtkRecentInfo *info;
menu_item = gtk_menu_get_active (GTK_MENU (menu));
if (!menu_item)
return NULL;
info = g_object_get_data (G_OBJECT (menu_item), "gtk-recent-info");
if (!info)
return NULL;
return g_strdup (gtk_recent_info_get_uri (info));
}
static gboolean
gtk_recent_chooser_menu_select_uri (GtkRecentChooser *chooser,
const gchar *uri,
GError **error)
{
GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (chooser);
GList *children, *l;
GtkWidget *menu_item = NULL;
gboolean found = FALSE;
children = gtk_container_get_children (GTK_CONTAINER (menu));
for (l = children; l != NULL; l = l->next)
{
GtkRecentInfo *info;
menu_item = GTK_WIDGET (l->data);
info = g_object_get_data (G_OBJECT (menu_item), "gtk-recent-info");
if (!info)
continue;
if (0 == strcmp (uri, gtk_recent_info_get_uri (info)))
found = TRUE;
}
g_list_free (children);
if (!found)
{
g_set_error (error, GTK_RECENT_CHOOSER_ERROR,
GTK_RECENT_CHOOSER_ERROR_NOT_FOUND,
_("No recently used resource found with URI '%s'"),
uri);
return FALSE;
}
else
{
gtk_menu_shell_select_item (GTK_MENU_SHELL (menu), menu_item);
return TRUE;
}
}
static void
gtk_recent_chooser_menu_unselect_uri (GtkRecentChooser *chooser,
const gchar *uri)
{
GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (chooser);
gtk_menu_shell_deselect (GTK_MENU_SHELL (menu));
}
static void
gtk_recent_chooser_menu_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_chooser_menu_unselect_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_chooser_menu_set_sort_func (GtkRecentChooser *chooser,
GtkRecentSortFunc sort_func,
gpointer sort_data,
GDestroyNotify data_destroy)
{
GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (chooser);
GtkRecentChooserMenuPrivate *priv = menu->priv;
if (priv->sort_data_destroy)
{
priv->sort_data_destroy (priv->sort_data);
priv->sort_data_destroy = NULL;
}
priv->sort_func = NULL;
priv->sort_data = NULL;
priv->sort_data_destroy = NULL;
if (sort_func)
{
priv->sort_func = sort_func;
priv->sort_data = sort_data;
priv->sort_data_destroy = data_destroy;
}
}
static GList *
gtk_recent_chooser_menu_get_items (GtkRecentChooser *chooser)
{
GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (chooser);
GtkRecentChooserMenuPrivate *priv = menu->priv;
return _gtk_recent_chooser_get_items (chooser,
priv->current_filter,
priv->sort_func,
priv->sort_data);
}
static GtkRecentManager *
gtk_recent_chooser_menu_get_recent_manager (GtkRecentChooser *chooser)
{
GtkRecentChooserMenuPrivate *priv;
priv = GTK_RECENT_CHOOSER_MENU (chooser)->priv;
return priv->manager;
}
static void
gtk_recent_chooser_menu_add_filter (GtkRecentChooser *chooser,
GtkRecentFilter *filter)
{
GtkRecentChooserMenu *menu;
menu = GTK_RECENT_CHOOSER_MENU (chooser);
gtk_recent_chooser_menu_set_current_filter (menu, filter);
}
static void
gtk_recent_chooser_menu_remove_filter (GtkRecentChooser *chooser,
GtkRecentFilter *filter)
{
GtkRecentChooserMenu *menu;
menu = GTK_RECENT_CHOOSER_MENU (chooser);
if (filter == menu->priv->current_filter)
{
g_object_unref (menu->priv->current_filter);
menu->priv->current_filter = NULL;
g_object_notify (G_OBJECT (menu), "filter");
}
}
static GSList *
gtk_recent_chooser_menu_list_filters (GtkRecentChooser *chooser)
{
GtkRecentChooserMenu *menu;
GSList *retval = NULL;
menu = GTK_RECENT_CHOOSER_MENU (chooser);
if (menu->priv->current_filter)
retval = g_slist_prepend (retval, menu->priv->current_filter);
return retval;
}
static void
gtk_recent_chooser_menu_set_current_filter (GtkRecentChooserMenu *menu,
GtkRecentFilter *filter)
{
GtkRecentChooserMenuPrivate *priv;
priv = menu->priv;
if (priv->current_filter)
g_object_unref (G_OBJECT (priv->current_filter));
priv->current_filter = filter;
if (priv->current_filter)
g_object_ref_sink (priv->current_filter);
gtk_recent_chooser_menu_populate (menu);
g_object_notify (G_OBJECT (menu), "filter");
}
/* taken from libeel/eel-strings.c */
static gchar *
escape_underscores (const gchar *string)
{
gint underscores;
const gchar *p;
gchar *q;
gchar *escaped;
if (!string)
return NULL;
underscores = 0;
for (p = string; *p != '\0'; p++)
underscores += (*p == '_');
if (underscores == 0)
return g_strdup (string);
escaped = g_new (char, strlen (string) + underscores + 1);
for (p = string, q = escaped; *p != '\0'; p++, q++)
{
/* Add an extra underscore. */
if (*p == '_')
*q++ = '_';
*q = *p;
}
*q = '\0';
return escaped;
}
static void
gtk_recent_chooser_menu_add_tip (GtkRecentChooserMenu *menu,
GtkRecentInfo *info,
GtkWidget *item)
{
GtkRecentChooserMenuPrivate *priv;
gchar *path;
g_assert (info != NULL);
g_assert (item != NULL);
priv = menu->priv;
path = gtk_recent_info_get_uri_display (info);
if (path)
{
gchar *tip_text = g_strdup_printf (_("Open '%s'"), path);
gtk_widget_set_tooltip_text (item, tip_text);
gtk_widget_set_has_tooltip (item, priv->show_tips);
g_free (path);
g_free (tip_text);
}
}
static GtkWidget *
gtk_recent_chooser_menu_create_item (GtkRecentChooserMenu *menu,
GtkRecentInfo *info,
gint count)
{
GtkRecentChooserMenuPrivate *priv;
gchar *text;
GtkWidget *item, *image, *label;
GIcon *icon;
GtkWidget *box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
g_assert (info != NULL);
priv = menu->priv;
if (priv->show_numbers)
{
gchar *name, *escaped;
name = g_strdup (gtk_recent_info_get_display_name (info));
if (!name)
name = g_strdup (_("Unknown item"));
escaped = escape_underscores (name);
/* avoid clashing mnemonics */
if (count <= 10)
/* This is the label format that is used for the first 10 items
* in a recent files menu. The %d is the number of the item,
* the %s is the name of the item. Please keep the _ in front
* of the number to give these menu items a mnemonic.
*/
text = g_strdup_printf (C_("recent menu label", "_%d. %s"), count, escaped);
else
/* This is the format that is used for items in a recent files menu.
* The %d is the number of the item, the %s is the name of the item.
*/
text = g_strdup_printf (C_("recent menu label", "%d. %s"), count, escaped);
label = gtk_label_new_with_mnemonic (text);
g_free (escaped);
g_free (name);
}
else
{
text = g_strdup (gtk_recent_info_get_display_name (info));
label = gtk_label_new (text);
}
g_free (text);
/* ellipsize the menu item label, in case the recent document
* display name is huge.
*/
gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
gtk_label_set_max_width_chars (GTK_LABEL (label), priv->label_width);
if (priv->show_icons)
{
icon = gtk_recent_info_get_gicon (info);
image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_MENU);
if (icon)
g_object_unref (icon);
gtk_container_add (GTK_CONTAINER (box), image);
}
item = gtk_menu_item_new ();
gtk_container_add (GTK_CONTAINER (box), label);
gtk_widget_show_all (box);
gtk_container_add (GTK_CONTAINER (item), box);
g_signal_connect (item, "activate",
G_CALLBACK (item_activate_cb),
menu);
return item;
}
static void
gtk_recent_chooser_menu_insert_item (GtkRecentChooserMenu *menu,
GtkWidget *menuitem,
gint position)
{
GtkRecentChooserMenuPrivate *priv = menu->priv;
gint real_position;
if (priv->first_recent_item_pos == -1)
{
GList *children, *l;
children = gtk_container_get_children (GTK_CONTAINER (menu));
for (real_position = 0, l = children;
l != NULL;
real_position += 1, l = l->next)
{
GObject *child = l->data;
gboolean is_placeholder = FALSE;
is_placeholder =
GPOINTER_TO_INT (g_object_get_data (child, "gtk-recent-menu-placeholder"));
if (is_placeholder)
break;
}
g_list_free (children);
priv->first_recent_item_pos = real_position;
}
else
real_position = priv->first_recent_item_pos;
gtk_menu_shell_insert (GTK_MENU_SHELL (menu), menuitem,
real_position + position);
gtk_widget_show (menuitem);
}
/* removes the items we own from the menu */
static void
gtk_recent_chooser_menu_dispose_items (GtkRecentChooserMenu *menu)
{
GList *children, *l;
children = gtk_container_get_children (GTK_CONTAINER (menu));
for (l = children; l != NULL; l = l->next)
{
GtkWidget *menu_item = GTK_WIDGET (l->data);
gboolean has_mark = FALSE;
/* check for our mark, in order to remove just the items we own */
has_mark =
GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu_item), "gtk-recent-menu-mark"));
if (has_mark)
{
GtkRecentInfo *info;
/* destroy the attached RecentInfo struct, if found */
info = g_object_get_data (G_OBJECT (menu_item), "gtk-recent-info");
if (info)
g_object_set_data_full (G_OBJECT (menu_item), "gtk-recent-info",
NULL, NULL);
/* and finally remove the item from the menu */
gtk_container_remove (GTK_CONTAINER (menu), menu_item);
}
}
/* recalculate the position of the first recent item */
menu->priv->first_recent_item_pos = -1;
g_list_free (children);
}
typedef struct
{
GList *items;
gint n_items;
gint loaded_items;
gint displayed_items;
GtkRecentChooserMenu *menu;
GtkWidget *placeholder;
} MenuPopulateData;
static MenuPopulateData *
create_menu_populate_data (GtkRecentChooserMenu *menu)
{
MenuPopulateData *pdata;
pdata = g_slice_new (MenuPopulateData);
pdata->items = NULL;
pdata->n_items = 0;
pdata->loaded_items = 0;
pdata->displayed_items = 0;
pdata->menu = menu;
pdata->placeholder = g_object_ref (menu->priv->placeholder);
return pdata;
}
static void
free_menu_populate_data (MenuPopulateData *pdata)
{
if (pdata->placeholder)
g_object_unref (pdata->placeholder);
g_slice_free (MenuPopulateData, pdata);
}
static gboolean
idle_populate_func (gpointer data)
{
MenuPopulateData *pdata;
GtkRecentChooserMenuPrivate *priv;
GtkRecentInfo *info;
gboolean retval;
GtkWidget *item;
pdata = (MenuPopulateData *) data;
priv = pdata->menu->priv;
if (!pdata->items)
{
pdata->items = gtk_recent_chooser_get_items (GTK_RECENT_CHOOSER (pdata->menu));
if (!pdata->items)
{
/* show the placeholder here */
gtk_widget_show (pdata->placeholder);
pdata->displayed_items = 1;
priv->populate_id = 0;
return FALSE;
}
else
gtk_widget_hide (pdata->placeholder);
pdata->n_items = g_list_length (pdata->items);
pdata->loaded_items = 0;
}
info = g_list_nth_data (pdata->items, pdata->loaded_items);
item = gtk_recent_chooser_menu_create_item (pdata->menu,
info,
pdata->displayed_items);
if (!item)
goto check_and_return;
gtk_recent_chooser_menu_add_tip (pdata->menu, info, item);
gtk_recent_chooser_menu_insert_item (pdata->menu, item,
pdata->displayed_items);
pdata->displayed_items += 1;
/* mark the menu item as one of our own */
g_object_set_data (G_OBJECT (item),
"gtk-recent-menu-mark",
GINT_TO_POINTER (TRUE));
/* attach the RecentInfo object to the menu item, and own a reference
* to it, so that it will be destroyed with the menu item when it's
* not needed anymore.
*/
g_object_set_data_full (G_OBJECT (item), "gtk-recent-info",
gtk_recent_info_ref (info),
(GDestroyNotify) gtk_recent_info_unref);
check_and_return:
pdata->loaded_items += 1;
if (pdata->loaded_items == pdata->n_items)
{
g_list_free_full (pdata->items, (GDestroyNotify) gtk_recent_info_unref);
priv->populate_id = 0;
retval = FALSE;
}
else
retval = TRUE;
return retval;
}
static void
idle_populate_clean_up (gpointer data)
{
MenuPopulateData *pdata = data;
if (pdata->menu->priv->populate_id == 0)
{
/* show the placeholder in case no item survived
* the filtering process in the idle loop
*/
if (!pdata->displayed_items)
gtk_widget_show (pdata->placeholder);
}
free_menu_populate_data (pdata);
}
static void
gtk_recent_chooser_menu_populate (GtkRecentChooserMenu *menu)
{
MenuPopulateData *pdata;
GtkRecentChooserMenuPrivate *priv = menu->priv;
if (priv->populate_id)
return;
pdata = create_menu_populate_data (menu);
/* remove our menu items first */
gtk_recent_chooser_menu_dispose_items (menu);
priv->populate_id = gdk_threads_add_idle_full (G_PRIORITY_HIGH_IDLE + 30,
idle_populate_func,
pdata,
idle_populate_clean_up);
g_source_set_name_by_id (priv->populate_id, "[gtk+] idle_populate_func");
}
/* bounce activate signal from the recent menu item widget
* to the recent menu widget
*/
static void
item_activate_cb (GtkWidget *widget,
gpointer user_data)
{
GtkRecentChooser *chooser = GTK_RECENT_CHOOSER (user_data);
GtkRecentInfo *info = g_object_get_data (G_OBJECT (widget), "gtk-recent-info");
gtk_recent_chooser_menu_set_current_uri (chooser, gtk_recent_info_get_uri (info), NULL);
_gtk_recent_chooser_item_activated (chooser);
}
/* we force a redraw if the manager changes when we are showing */
static void
manager_changed_cb (GtkRecentManager *manager,
gpointer user_data)
{
GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (user_data);
gtk_recent_chooser_menu_populate (menu);
}
static void
set_recent_manager (GtkRecentChooserMenu *menu,
GtkRecentManager *manager)
{
GtkRecentChooserMenuPrivate *priv = menu->priv;
if (priv->manager)
{
if (priv->manager_changed_id)
{
g_signal_handler_disconnect (priv->manager, priv->manager_changed_id);
priv->manager_changed_id = 0;
}
if (priv->populate_id)
{
g_source_remove (priv->populate_id);
priv->populate_id = 0;
}
priv->manager = NULL;
}
if (manager)
priv->manager = manager;
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),
menu);
}
static void
foreach_set_shot_tips (GtkWidget *widget,
gpointer user_data)
{
GtkRecentChooserMenu *menu = user_data;
GtkRecentChooserMenuPrivate *priv = menu->priv;
gboolean has_mark;
/* toggle the tooltip only on the items we create */
has_mark =
GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), "gtk-recent-menu-mark"));
if (has_mark)
gtk_widget_set_has_tooltip (widget, priv->show_tips);
}
static void
gtk_recent_chooser_menu_set_show_tips (GtkRecentChooserMenu *menu,
gboolean show_tips)
{
GtkRecentChooserMenuPrivate *priv = menu->priv;
if (priv->show_tips == show_tips)
return;
priv->show_tips = show_tips;
gtk_container_foreach (GTK_CONTAINER (menu), foreach_set_shot_tips, menu);
g_object_notify (G_OBJECT (menu), "show-tips");
}
static void
gtk_recent_chooser_update (GtkActivatable *activatable,
GtkAction *action,
const gchar *property_name)
{
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
if (strcmp (property_name, "sensitive") == 0)
gtk_widget_set_sensitive (GTK_WIDGET (activatable), gtk_action_is_sensitive (action));
G_GNUC_END_IGNORE_DEPRECATIONS;
_gtk_recent_chooser_update (activatable, action, property_name);
}
static void
gtk_recent_chooser_sync_action_properties (GtkActivatable *activatable,
GtkAction *action)
{
if (!action)
return;
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
gtk_widget_set_sensitive (GTK_WIDGET (activatable), gtk_action_is_sensitive (action));
G_GNUC_END_IGNORE_DEPRECATIONS;
_gtk_recent_chooser_sync_action_properties (activatable, action);
}
/*
* Public API
*/
/**
* gtk_recent_chooser_menu_new:
*
* Creates a new #GtkRecentChooserMenu widget.
*
* This kind of widget shows the list of recently used resources as
* a menu, each item as a menu item. Each item inside the menu might
* have an icon, representing its MIME type, and a number, for mnemonic
* access.
*
* This widget implements the #GtkRecentChooser interface.
*
* This widget creates its own #GtkRecentManager object. See the
* gtk_recent_chooser_menu_new_for_manager() function to know how to create
* a #GtkRecentChooserMenu widget bound to another #GtkRecentManager object.
*
* Returns: a new #GtkRecentChooserMenu
*
* Since: 2.10
*/
GtkWidget *
gtk_recent_chooser_menu_new (void)
{
return g_object_new (GTK_TYPE_RECENT_CHOOSER_MENU,
"recent-manager", NULL,
NULL);
}
/**
* gtk_recent_chooser_menu_new_for_manager:
* @manager: a #GtkRecentManager
*
* Creates a new #GtkRecentChooserMenu widget using @manager as
* the underlying recently used resources manager.
*
* This is useful if you have implemented your own recent manager,
* or if you have a customized instance of a #GtkRecentManager
* object or if you wish to share a common #GtkRecentManager object
* among multiple #GtkRecentChooser widgets.
*
* Returns: a new #GtkRecentChooserMenu, bound to @manager.
*
* Since: 2.10
*/
GtkWidget *
gtk_recent_chooser_menu_new_for_manager (GtkRecentManager *manager)
{
g_return_val_if_fail (manager == NULL || GTK_IS_RECENT_MANAGER (manager), NULL);
return g_object_new (GTK_TYPE_RECENT_CHOOSER_MENU,
"recent-manager", manager,
NULL);
}
/**
* gtk_recent_chooser_menu_get_show_numbers:
* @menu: a #GtkRecentChooserMenu
*
* Returns the value set by gtk_recent_chooser_menu_set_show_numbers().
*
* Returns: %TRUE if numbers should be shown.
*
* Since: 2.10
*/
gboolean
gtk_recent_chooser_menu_get_show_numbers (GtkRecentChooserMenu *menu)
{
g_return_val_if_fail (GTK_IS_RECENT_CHOOSER_MENU (menu), FALSE);
return menu->priv->show_numbers;
}
/**
* gtk_recent_chooser_menu_set_show_numbers:
* @menu: a #GtkRecentChooserMenu
* @show_numbers: whether to show numbers
*
* Sets whether a number should be added to the items of @menu. The
* numbers are shown to provide a unique character for a mnemonic to
* be used inside ten menu items label. Only the first the items
* get a number to avoid clashes.
*
* Since: 2.10
*/
void
gtk_recent_chooser_menu_set_show_numbers (GtkRecentChooserMenu *menu,
gboolean show_numbers)
{
g_return_if_fail (GTK_IS_RECENT_CHOOSER_MENU (menu));
if (menu->priv->show_numbers == show_numbers)
return;
menu->priv->show_numbers = show_numbers;
g_object_notify (G_OBJECT (menu), "show-numbers");
}