diff --git a/gtk/Makefile.am b/gtk/Makefile.am index 4d24311b87..1a9f19a197 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -228,6 +228,12 @@ gtk_public_h_sources = \ gtkradiotoolbutton.h \ gtkrange.h \ gtkrc.h \ + gtkrecentchooser.h \ + gtkrecentchooserdialog.h \ + gtkrecentchoosermenu.h \ + gtkrecentchooserwidget.h \ + gtkrecentfilter.h \ + gtkrecentmanager.h \ gtkruler.h \ gtkscale.h \ gtkscrollbar.h \ @@ -310,6 +316,9 @@ gtk_private_h_sources = \ gtkpathbar.h \ gtkplugprivate.h \ gtkrbtree.h \ + gtkrecentchooserdefault.h \ + gtkrecentchooserprivate.h \ + gtkrecentchooserutils.h \ gtksequence.h \ gtksocketprivate.h \ gtktextbtree.h \ @@ -459,6 +468,14 @@ gtk_c_sources = \ gtkrange.c \ gtkrbtree.c \ gtkrc.c \ + gtkrecentchooserdefault.c \ + gtkrecentchooserdialog.c \ + gtkrecentchoosermenu.c \ + gtkrecentchooserwidget.c \ + gtkrecentchooserutils.c \ + gtkrecentchooser.c \ + gtkrecentfilter.c \ + gtkrecentmanager.c \ gtkruler.c \ gtkscale.c \ gtkscrollbar.c \ diff --git a/gtk/gtk.h b/gtk/gtk.h index e99aa165c7..2575238e7a 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -140,6 +140,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/gtk/gtk.symbols b/gtk/gtk.symbols index 24231aa871..0403fa2b47 100644 --- a/gtk/gtk.symbols +++ b/gtk/gtk.symbols @@ -2621,6 +2621,134 @@ gtk_rc_style_unref #endif #endif +#if IN_HEADER(__GTK_RECENT_CHOOSER_H__) +#if IN_FILE(__GTK_RECENT_CHOOSER_C__)) +gtk_recent_chooser_set_show_private +gtk_recent_chooser_get_show_private +gtk_recent_chooser_set_show_not_found +gtk_recent_chooser_get_show_not_found +gtk_recent_chooser_set_show_icons +gtk_recent_chooser_get_show_icons +gtk_recent_chooser_set_select_multiple +gtk_recent_chooser_get_select_multiple +gtk_recent_chooser_set_local_only +gtk_recent_chooser_get_local_only +gtk_recent_chooser_set_limit +gtk_recent_chooser_get_limit +gtk_recent_chooser_set_show_tips +gtk_recent_chooser_get_show_tips +gtk_recent_chooser_set_show_numbers +gtk_recent_chooser_get_show_numbers +gtk_recent_chooser_set_sort_type +gtk_recent_chooser_get_sort_type +gtk_recent_chooser_set_sort_func +gtk_recent_chooser_set_current_uri +gtk_recent_chooser_get_current_uri +gtk_recent_chooser_get_current_item +gtk_recent_chooser_select_uri +gtk_recent_chooser_unselect_uri +gtk_recent_chooser_select_all +gtk_recent_chooser_unselect_all +gtk_recent_chooser_get_items +gtk_recent_chooser_get_uris +gtk_recent_chooser_add_filter +gtk_recent_chooser_remove_filter +gtk_recent_chooser_list_filters +gtk_recent_chooser_set_filter +gtk_recent_chooser_get_filter +gtk_recent_chooser_get_type G_GNUC_CONST +gtk_recent_chooser_error_quark +#endif +#endif + +#if IN_HEADER(__GTK_RECENT_CHOOSER_DIALOG_H__) +#if IN_FILE(__GTK_RECENT_CHOOSER_DIALOG_C__) +gtk_recent_chooser_dialog_get_type G_GNUC_CONST +gtk_recent_chooser_dialog_new G_GNUC_NULL_TERMINATED +gtk_recent_chooser_dialog_new_for_manager G_GNUC_NULL_TERMINATED +#endif +#endif + +#if IN_HEADER(__GTK_RECENT_CHOOSER_MENU_H__) +#if IN_FILE(__GTK_RECENT_CHOOSER_MENU_C__) +gtk_recent_chooser_menu_get_type G_GNUC_CONST +gtk_recent_chooser_menu_new +gtk_recent_chooser_menu_new_for_manager +gtk_recent_chooser_menu_get_show_numbers +gtk_recent_chooser_menu_set_show_numbers +#endif +#endif + +#if IN_HEADER(__GTK_RECENT_CHOOSER_WIDGET_H__) +#if IN_FILE(__GTK_RECENT_CHOOSER_WIDGET_C__) +gtk_recent_chooser_widget_get_type G_GNUC_CONST +gtk_recent_chooser_widget_new +gtk_recent_chooser_widget_new_for_manager +#endif +#endif + +#if IN_HEADER(__GTK_RECENT_FILTER_H__) +#if IN_FILE(__GTK_RECENT_FILTER_C__) +gtk_recent_filter_get_type G_GNUC_CONST +gtk_recent_filter_new +gtk_recent_filter_set_name +gtk_recent_filter_get_name +gtk_recent_filter_add_mime_type +gtk_recent_filter_add_pattern +gtk_recent_filter_add_pixbuf_formats +gtk_recent_filter_add_application +gtk_recent_filter_add_group +gtk_recent_filter_add_age +gtk_recent_filter_add_custom +gtk_recent_filter_get_needed +gtk_recent_filter_filter +#endif +#endif + +#if IN_HEADER(__GTK_RECENT_MANAGER_H__) +#if IN_FILE(__GTK_RECENT_MANAGER_C__) +gtk_recent_manager_error_quark +gtk_recent_manager_get_type G_GNUC_CONST +gtk_recent_manager_new +gtk_recent_manager_get_default +gtk_recent_manager_get_for_screen +gtk_recent_manager_set_screen +gtk_recent_manager_add_item +gtk_recent_manager_add_full +gtk_recent_manager_remove_item +gtk_recent_manager_lookup_item +gtk_recent_manager_has_item +gtk_recent_manager_move_item +gtk_recent_manager_set_limit +gtk_recent_manager_get_limit +gtk_recent_manager_purge_items +gtk_recent_info_get_type G_GNUC_CONST +gtk_recent_info_ref +gtk_recent_info_unref +gtk_recent_info_get_uri +gtk_recent_info_get_display_name +gtk_recent_info_get_description +gtk_recent_info_get_mime_type +gtk_recent_info_get_added +gtk_recent_info_get_modified +gtk_recent_info_get_visited +gtk_recent_info_get_private_hint +gtk_recent_info_get_application_info +gtk_recent_info_get_applications G_GNUC_MALLOC +gtk_recent_info_last_application G_GNUC_MALLOC +gtk_recent_info_has_application +gtk_recent_info_get_groups G_GNUC_MALLOC +gtk_recent_info_has_group +gtk_recent_info_get_icon +gtk_recent_info_get_short_name G_GNUC_MALLOC +gtk_recent_info_get_uri_display G_GNUC_MALLOC +gtk_recent_info_get_age +gtk_recent_info_is_local +gtk_recent_info_exists +gtk_recent_info_match +#endif +#endif + #if IN_HEADER(__GTK_TEXT_BUFFER_RICH_TEXT_H__) #if IN_FILE(__GTK_TEXT_BUFFER_RICH_TEXT_C__) gtk_text_buffer_deserialize diff --git a/gtk/gtkrecentchooser.c b/gtk/gtkrecentchooser.c new file mode 100644 index 0000000000..873eee1d46 --- /dev/null +++ b/gtk/gtkrecentchooser.c @@ -0,0 +1,966 @@ +/* GTK - The GIMP Toolkit + * gtkrecentchooser.c - Abstract interface for recent file selectors GUIs + * + * Copyright (C) 2006, 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 "config.h" + +#include "gtkrecentchooser.h" +#include "gtkrecentchooserprivate.h" +#include "gtkrecentmanager.h" +#include "gtkintl.h" +#include "gtktypebuiltins.h" +#include "gtkprivate.h" +#include "gtkmarshalers.h" +#include "gtkalias.h" + + +enum +{ + ITEM_ACTIVATED, + SELECTION_CHANGED, + + LAST_SIGNAL +}; + +static void gtk_recent_chooser_class_init (gpointer g_iface); + +static guint chooser_signals[LAST_SIGNAL] = { 0, }; + +GType +gtk_recent_chooser_get_type (void) +{ + static GType chooser_type = 0; + + if (!chooser_type) + { + static const GTypeInfo chooser_info = + { + sizeof (GtkRecentChooserIface), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) gtk_recent_chooser_class_init, + }; + + chooser_type = g_type_register_static (G_TYPE_INTERFACE, + I_("GtkRecentChooser"), + &chooser_info, 0); + + g_type_interface_add_prerequisite (chooser_type, GTK_TYPE_OBJECT); + } + + return chooser_type; +} + +static void +gtk_recent_chooser_class_init (gpointer g_iface) +{ + GType iface_type = G_TYPE_FROM_INTERFACE (g_iface); + + /** + * GtkRecentChooser::selection-changed + * @chooser: the object which received the signal + * + * This signal is emitted when there is a change in the set of + * selected recently used resources. This can happen when a user + * modifies the selection with the mouse or the keyboard, or when + * explicitely calling functions to change the selection. + * + * Since: 2.10 + */ + chooser_signals[SELECTION_CHANGED] = + g_signal_new (I_("selection-changed"), + iface_type, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkRecentChooserIface, selection_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + /** + * GtkRecentChooser::item-activated + * @chooser: the object which received the signal + * + * This signal is emitted when the user "activates" a recent item + * in the recent chooser. This can happen by double-clicking on an item + * in the recently used resources list, or by pressing + * Enter. + * + * Since: 2.10 + */ + chooser_signals[ITEM_ACTIVATED] = + g_signal_new (I_("item-activated"), + iface_type, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkRecentChooserIface, item_activated), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + g_object_interface_install_property (g_iface, + g_param_spec_object ("recent-manager", + P_("Recent Manager"), + P_("The RecentManager object to use"), + GTK_TYPE_RECENT_MANAGER, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); + g_object_interface_install_property (g_iface, + g_param_spec_boolean ("show-private", + P_("Show Private"), + P_("Whether the private items should be displayed"), + FALSE, + G_PARAM_READWRITE)); + g_object_interface_install_property (g_iface, + g_param_spec_boolean ("show-tips", + P_("Show Tooltips"), + P_("Whether there should be a tooltip on the item"), + FALSE, + G_PARAM_READWRITE)); + g_object_interface_install_property (g_iface, + g_param_spec_boolean ("show-icons", + P_("Show Icons"), + P_("Whether there should be an icon near the item"), + TRUE, + G_PARAM_READWRITE)); + g_object_interface_install_property (g_iface, + g_param_spec_boolean ("show-not-found", + P_("Show Not Found"), + P_("Whether the items pointing to unavailable resources should be displayed"), + FALSE, + G_PARAM_READWRITE)); + g_object_interface_install_property (g_iface, + g_param_spec_boolean ("select-multiple", + P_("Select Multiple"), + P_("Whether to allow multiple items to be selected"), + FALSE, + G_PARAM_READWRITE)); + g_object_interface_install_property (g_iface, + g_param_spec_boolean ("local-only", + P_("Local only"), + P_("Whether the selected resource(s) should be limited to local file: URIs"), + TRUE, + G_PARAM_READWRITE)); + g_object_interface_install_property (g_iface, + g_param_spec_int ("limit", + P_("Limit"), + P_("The maximum number of items to be displayed"), + -1, + G_MAXINT, + -1, + G_PARAM_READWRITE)); + g_object_interface_install_property (g_iface, + g_param_spec_enum ("sort-type", + P_("Sort Type"), + P_("The sorting order of the items displayed"), + GTK_TYPE_RECENT_SORT_TYPE, + GTK_RECENT_SORT_NONE, + G_PARAM_READWRITE)); + g_object_interface_install_property (g_iface, + g_param_spec_object ("filter", + P_("Filter"), + P_("The current filter for selecting which resources are displayed"), + GTK_TYPE_RECENT_FILTER, + G_PARAM_READWRITE)); +} + +GQuark +gtk_recent_chooser_error_quark (void) +{ + static GQuark error_quark = 0; + if (!error_quark) + error_quark = g_quark_from_static_string ("gtk-recent-chooser-error-quark"); + return error_quark; +} + +/** + * _gtk_recent_chooser_get_recent_manager: + * @chooser: a #GtkRecentChooser + * + * Gets the #GtkRecentManager used by @chooser. + * + * Return value: the recent manager for @chooser. + * + * Since: 2.10 + */ +GtkRecentManager * +_gtk_recent_chooser_get_recent_manager (GtkRecentChooser *chooser) +{ + g_return_val_if_fail (GTK_IS_RECENT_CHOOSER (chooser), NULL); + + return GTK_RECENT_CHOOSER_GET_IFACE (chooser)->get_recent_manager (chooser); +} + +/** + * gtk_recent_chooser_set_show_private: + * @chooser: a #GtkRecentChooser + * @show_private: %TRUE to show private items, %FALSE otherwise + * + * Whether to show recently used resources marked registered as private. + * + * Since: 2.10 + */ +void +gtk_recent_chooser_set_show_private (GtkRecentChooser *chooser, + gboolean show_private) +{ + g_return_if_fail (GTK_IS_RECENT_CHOOSER (chooser)); + + g_object_set (chooser, "show-private", show_private, NULL); +} + +/** + * gtk_recent_chooser_get_show_private: + * @chooser: a #GtkRecentChooser + * + * Returns whether @chooser should display recently used resources + * registered as private. + * + * Return value: %TRUE if the recent chooser should show private items, + * %FALSE otherwise. + * + * Since: 2.10 + */ +gboolean +gtk_recent_chooser_get_show_private (GtkRecentChooser *chooser) +{ + gboolean show_private; + + g_return_val_if_fail (GTK_IS_RECENT_CHOOSER (chooser), FALSE); + + g_object_get (chooser, "show-private", &show_private, NULL); + + return show_private; +} + +/** + * gtk_recent_chooser_set_show_not_found: + * @chooser: a #GtkRecentChooser + * @show_not_found: whether to show the local items we didn't find + * + * Sets whether @chooser should display the recently used resources that + * it didn't find. This only applies to local resources. + * + * Since: 2.10 + */ +void +gtk_recent_chooser_set_show_not_found (GtkRecentChooser *chooser, + gboolean show_not_found) +{ + g_return_if_fail (GTK_IS_RECENT_CHOOSER (chooser)); + + g_object_set (chooser, "show-not-found", show_not_found, NULL); +} + +/** + * gtk_recent_chooser_get_show_not_found: + * @chooser: a #GtkRecentChooser + * + * Retrieves whether @chooser should show the recently used resources that + * were not found. + * + * Return value: %TRUE if the resources not found should be displayed, and + * %FALSE otheriwse. + * + * Since: 2.10 + */ +gboolean +gtk_recent_chooser_get_show_not_found (GtkRecentChooser *chooser) +{ + gboolean show_not_found; + + g_return_val_if_fail (GTK_IS_RECENT_CHOOSER (chooser), FALSE); + + g_object_get (chooser, "show-not-found", &show_not_found, NULL); + + return show_not_found; +} + +/** + * gtk_recent_chooser_set_show_icons: + * @chooser: a #GtkRecentChooser + * @show_icons: whether to show an icon near the resource + * + * Sets whether @chooser should show an icon near the resource when + * displaying it. + * + * Since: 2.10 + */ +void +gtk_recent_chooser_set_show_icons (GtkRecentChooser *chooser, + gboolean show_icons) +{ + g_return_if_fail (GTK_IS_RECENT_CHOOSER (chooser)); + + g_object_set (chooser, "show-icons", show_icons, NULL); +} + +/** + * gtk_recent_chooser_get_show_icons: + * @chooser: a #GtkRecentChooser + * + * Retrieves whether @chooser should show an icon near the resource. + * + * Return value: %TRUE if the icons should be displayed, %FALSE otherwise. + * + * Since: 2.10 + */ +gboolean +gtk_recent_chooser_get_show_icons (GtkRecentChooser *chooser) +{ + gboolean show_icons; + + g_return_val_if_fail (GTK_IS_RECENT_CHOOSER (chooser), FALSE); + + g_object_get (chooser, "show-icons", &show_icons, NULL); + + return show_icons; +} + +/** + * gtk_recent_chooser_set_select_multiple: + * @chooser: a #GtkRecentChooser + * @select_multiple: %TRUE if @chooser can select more than one item + * + * Sets whether @chooser can select multiple items. + * + * Since: 2.10 + */ +void +gtk_recent_chooser_set_select_multiple (GtkRecentChooser *chooser, + gboolean select_multiple) +{ + g_return_if_fail (GTK_IS_RECENT_CHOOSER (chooser)); + + g_object_set (chooser, "select-multiple", select_multiple, NULL); +} + +/** + * gtk_recent_chooser_get_select_multiple: + * @chooser: a #GtkRecentChooser + * + * Gets whether @chooser can select multiple items. + * + * Return value: %TRUE if @chooser can select more than one item. + * + * Since: 2.10 + */ +gboolean +gtk_recent_chooser_get_select_multiple (GtkRecentChooser *chooser) +{ + gboolean select_multiple; + + g_return_val_if_fail (GTK_IS_RECENT_CHOOSER (chooser), FALSE); + + g_object_get (chooser, "select-multiple", &select_multiple, NULL); + + return select_multiple; +} + +/** + * gtk_recent_chooser_set_local_only: + * @chooser: a #GtkRecentChooser + * @local_only: %TRUE if only local files can be shown + * + * Sets whether only local resources, that is resources using the file:// URI + * scheme, should be shown in the recently used resources selector. If + * @local_only is %TRUE (the default) then the shown resources are guaranteed + * to be accessible through the operating system native file system. + * + * Since: 2.10 + */ +void +gtk_recent_chooser_set_local_only (GtkRecentChooser *chooser, + gboolean local_only) +{ + g_return_if_fail (GTK_IS_RECENT_CHOOSER (chooser)); + + g_object_set (chooser, "local-only", local_only, NULL); +} + +/** + * gtk_recent_chooser_get_local_only: + * @chooser: a #GtkRecentChooser + * + * Gets whether only local resources should be shown in the recently used + * resources selector. See gtk_recent_chooser_set_local_only() + * + * Return value: %TRUE if only local resources should be shown. + * + * Since: 2.10 + */ +gboolean +gtk_recent_chooser_get_local_only (GtkRecentChooser *chooser) +{ + gboolean local_only; + + g_return_val_if_fail (GTK_IS_RECENT_CHOOSER (chooser), FALSE); + + g_object_get (chooser, "local-only", &local_only, NULL); + + return local_only; +} + +/** + * gtk_recent_chooser_set_limit: + * @chooser: a #GtkRecentChooser + * @limit: a positive integer, or -1 for all items + * + * Sets the number of items that should be returned by + * gtk_recent_chooser_get_items() and gtk_recent_chooser_get_uris(). + * + * Since: 2.10 + */ +void +gtk_recent_chooser_set_limit (GtkRecentChooser *chooser, + gint limit) +{ + g_return_if_fail (GTK_IS_RECENT_CHOOSER (chooser)); + + g_object_set (chooser, "limit", limit, NULL); +} + +/** + * gtk_recent_chooser_get_limit: + * @chooser: a #GtkRecentChooser + * + * Gets the number of items returned by gtk_recent_chooser_get_items() + * and gtk_recent_chooser_get_uris(). + * + * Return value: A positive integer, or -1 meaning that all items are + * returned. + * + * Since: 2.10 + */ +gint +gtk_recent_chooser_get_limit (GtkRecentChooser *chooser) +{ + gint limit; + + g_return_val_if_fail (GTK_IS_RECENT_CHOOSER (chooser), 10); + + g_object_get (chooser, "limit", &limit, NULL); + + return limit; +} + +/** + * gtk_recent_chooser_set_show_tips: + * @chooser: a #GtkRecentChooser + * @show_tips: %TRUE if tooltips should be shown + * + * Sets whether to show a tooltips on the widget. + * + * Since: 2.10 + */ +void +gtk_recent_chooser_set_show_tips (GtkRecentChooser *chooser, + gboolean show_tips) +{ + g_return_if_fail (GTK_IS_RECENT_CHOOSER (chooser)); + + g_object_set (chooser, "show-tips", show_tips, NULL); +} + +/** + * gtk_recent_chooser_get_show_tips: + * @chooser: a #GtkRecentChooser + * + * Gets whether @chooser should display tooltips. + * + * Return value: %TRUE if the recent chooser should show tooltips, + * %FALSE otherwise. + * + * Since: 2.10 + */ +gboolean +gtk_recent_chooser_get_show_tips (GtkRecentChooser *chooser) +{ + gboolean show_tips; + + g_return_val_if_fail (GTK_IS_RECENT_CHOOSER (chooser), FALSE); + + g_object_get (chooser, "show-tips", &show_tips, NULL); + + return show_tips; +} + +/** + * gtk_recent_chooser_set_show_numbers: + * @chooser: a #GtkRecentChooser + * @show_private: %TRUE to show numbers, %FALSE otherwise + * + * Whether to show recently used resources prepended by a unique number. + * + * Since: 2.10 + */ +void +gtk_recent_chooser_set_show_numbers (GtkRecentChooser *chooser, + gboolean show_numbers) +{ + g_return_if_fail (GTK_IS_RECENT_CHOOSER (chooser)); + + g_object_set (chooser, "show-numbers", show_numbers, NULL); +} + +/** + * gtk_recent_chooser_get_show_numbers: + * @chooser: a #GtkRecentChooser + * + * Returns whether @chooser should display recently used resources + * prepended by a unique number. + * + * Return value: %TRUE if the recent chooser should show display numbers, + * %FALSE otherwise. + * + * Since: 2.10 + */ +gboolean +gtk_recent_chooser_get_show_numbers (GtkRecentChooser *chooser) +{ + gboolean show_numbers; + + g_return_val_if_fail (GTK_IS_RECENT_CHOOSER (chooser), FALSE); + + g_object_get (chooser, "show-numbers", &show_numbers, NULL); + + return show_numbers; +} + + +/** + * gtk_recent_chooser_set_sort_type: + * @chooser: a #GtkRecentChooser + * @sort_type: sort order that the chooser should use + * + * Changes the sorting order of the recently used resources list displayed by + * @chooser. + * + * Since: 2.10 + */ +void +gtk_recent_chooser_set_sort_type (GtkRecentChooser *chooser, + GtkRecentSortType sort_type) +{ + g_return_if_fail (GTK_IS_RECENT_CHOOSER (chooser)); + + g_object_set (chooser, "sort-type", sort_type, NULL); +} + +/** + * gtk_recent_chooser_get_sort_type: + * @chooser: a #GtkRecentChooser + * + * Gets the value set by gtk_recent_chooser_set_sort_type(). + * + * Return value: the sorting order of the @chooser. + * + * Since: 2.10 + */ +GtkRecentSortType +gtk_recent_chooser_get_sort_type (GtkRecentChooser *chooser) +{ + GtkRecentSortType sort_type; + + g_return_val_if_fail (GTK_IS_RECENT_CHOOSER (chooser), GTK_RECENT_SORT_NONE); + + g_object_get (chooser, "sort-type", &sort_type, NULL); + + return sort_type; +} + +/** + * gtk_recent_chooser_set_sort_func: + * @chooser: a #GtkRecentChooser + * @sort_func: the comparison function + * @sort_data: user data to pass to @sort_func, or %NULL + * @destroy_data: destroy notifier for @sort_data, or %NULL + * + * Sets the comparison function used when sorting to be @sort_func. If + * the @chooser has the sort type set to #GTK_RECENT_SORT_CUSTOM then + * the chooser will sort using this function. + * + * To the comparison function will be passed two #GtkRecentInfo structs and + * @sort_data; @sort_func should return a positive integer if the first + * item comes before the second, zero if the two items are equal and + * a negative integer if the first item comes after the second. + * + * Since: 2.10 + */ +void +gtk_recent_chooser_set_sort_func (GtkRecentChooser *chooser, + GtkRecentSortFunc sort_func, + gpointer sort_data, + GDestroyNotify data_destroy) +{ + g_return_if_fail (GTK_IS_RECENT_CHOOSER (chooser)); + + GTK_RECENT_CHOOSER_GET_IFACE (chooser)->set_sort_func (chooser, + sort_func, + sort_data, + data_destroy); +} + +/** + * gtk_recent_chooser_set_current_uri: + * @chooser: a #GtkRecentChooser + * @uri: a URI + * @error: return location for a #GError, or %NULL + * + * Sets @uri as the current URI for @chooser. + * + * Return value: %TRUE if the URI was found. + * + * Since: 2.10 + */ +gboolean +gtk_recent_chooser_set_current_uri (GtkRecentChooser *chooser, + const gchar *uri, + GError **error) +{ + g_return_val_if_fail (GTK_IS_RECENT_CHOOSER (chooser), FALSE); + + return GTK_RECENT_CHOOSER_GET_IFACE (chooser)->set_current_uri (chooser, uri, error); +} + +/** + * gtk_recent_chooser_get_current_uri: + * @chooser: a #GtkRecentChooser + * + * Gets the URI currently selected by @chooser. + * + * Return value: a newly allocated string holding a URI. + * + * Since: 2.10 + */ +gchar * +gtk_recent_chooser_get_current_uri (GtkRecentChooser *chooser) +{ + g_return_val_if_fail (GTK_IS_RECENT_CHOOSER (chooser), NULL); + + return GTK_RECENT_CHOOSER_GET_IFACE (chooser)->get_current_uri (chooser); +} + +/** + * gtk_recent_chooser_get_current_item: + * @chooser: a #GtkRecentChooser + * + * Gets the #GtkRecentInfo currently selected by @chooser. + * + * Return value: a #GtkRecentInfo. Use gtk_recent_info_unref() when + * when you have finished using it. + * + * Since: 2.10 + */ +GtkRecentInfo * +gtk_recent_chooser_get_current_item (GtkRecentChooser *chooser) +{ + GtkRecentManager *manager; + GtkRecentInfo *retval; + gchar *uri; + + g_return_val_if_fail (GTK_IS_RECENT_CHOOSER (chooser), NULL); + + uri = gtk_recent_chooser_get_current_uri (chooser); + if (!uri) + return NULL; + + manager = _gtk_recent_chooser_get_recent_manager (chooser); + retval = gtk_recent_manager_lookup_item (manager, uri, NULL); + g_free (uri); + + return retval; +} + +/** + * gtk_recent_chooser_select_uri: + * @chooser: a #GtkRecentChooser + * @uri: a URI + * @error: return location for a #GError, or %NULL + * + * Selects @uri inside @chooser. + * + * Return value: %TRUE if @uri was found. + * + * Since: 2.10 + */ +gboolean +gtk_recent_chooser_select_uri (GtkRecentChooser *chooser, + const gchar *uri, + GError **error) +{ + g_return_val_if_fail (GTK_IS_RECENT_CHOOSER (chooser), FALSE); + + return GTK_RECENT_CHOOSER_GET_IFACE (chooser)->select_uri (chooser, uri, error); +} + +/** + * gtk_recent_chooser_unselect_uri: + * @chooser: a #GtkRecentChooser + * @uri: a URI + * + * Unselects @uri inside @chooser. + * + * Since: 2.10 + */ +void +gtk_recent_chooser_unselect_uri (GtkRecentChooser *chooser, + const gchar *uri) +{ + g_return_if_fail (GTK_IS_RECENT_CHOOSER (chooser)); + + GTK_RECENT_CHOOSER_GET_IFACE (chooser)->unselect_uri (chooser, uri); +} + +/** + * gtk_recent_chooser_select_all: + * @chooser: a #GtkRecentChooser + * + * Selects all the items inside @chooser, if the @chooser supports + * multiple selection. + * + * Since: 2.10 + */ +void +gtk_recent_chooser_select_all (GtkRecentChooser *chooser) +{ + g_return_if_fail (GTK_IS_RECENT_CHOOSER (chooser)); + + GTK_RECENT_CHOOSER_GET_IFACE (chooser)->select_all (chooser); +} + +/** + * gtk_recent_chooser_unselect_all: + * @chooser: a #GtkRecentChooser + * + * Unselects all the items inside @chooser. + * + * Since: 2.10 + */ +void +gtk_recent_chooser_unselect_all (GtkRecentChooser *chooser) +{ + g_return_if_fail (GTK_IS_RECENT_CHOOSER (chooser)); + + GTK_RECENT_CHOOSER_GET_IFACE (chooser)->unselect_all (chooser); +} + +/** + * gtk_recent_chooser_get_items: + * @chooser: a #GtkRecentChooser + * + * Gets the list of recently used resources in form of #GtkRecentInfo objects. + * + * The return value of this function is affected by the "sort-type" and + * "limit" properties of @chooser. + * + * Return value: A newly allocated list of #GtkRecentInfo objects. You should + * use gtk_recent_info_unref() on every item of the list, and then free + * the list itself using g_list_free(). + * + * Since: 2.10 + */ +GList * +gtk_recent_chooser_get_items (GtkRecentChooser *chooser) +{ + g_return_val_if_fail (GTK_IS_RECENT_CHOOSER (chooser), NULL); + + return GTK_RECENT_CHOOSER_GET_IFACE (chooser)->get_items (chooser); +} + +/** + * gtk_recent_chooser_get_uris: + * @chooser: a #GtkRecentChooser + * @length: return location for a the length of the URI list, or %NULL + * + * Gets the URI of the recently used resources. + * + * The return value of this function is affected by the "sort-type" and "limit" + * properties of @chooser. + * + * Since the returned array is %NULL terminated, @length may be %NULL. + * + * Return value: A newly allocated, %NULL terminated array of strings. Use + * g_strfreev() to free it. + * + * Since: 2.10 + */ +gchar ** +gtk_recent_chooser_get_uris (GtkRecentChooser *chooser, + gsize *length) +{ + GList *items, *l; + gchar **retval; + gsize n_items, i; + + items = gtk_recent_chooser_get_items (chooser); + if (!items) + return NULL; + + n_items = g_list_length (items); + retval = g_new0 (gchar *, n_items + 1); + + for (l = items, i = 0; l != NULL; l = l->next) + { + GtkRecentInfo *info = (GtkRecentInfo *) l->data; + const gchar *uri; + + g_assert (info != NULL); + + uri = gtk_recent_info_get_uri (info); + g_assert (uri != NULL); + + retval[i++] = g_strdup (uri); + } + retval[i] = NULL; + + if (length) + *length = i; + + g_list_foreach (items, + (GFunc) gtk_recent_info_unref, + NULL); + g_list_free (items); + + return retval; +} + +/** + * gtk_recent_chooser_add_filter: + * @chooser: a #GtkRecentChooser + * @filter: a #GtkRecentFilter + * + * Adds @filter to the list of #GtkRecentFilter objects held by @chooser. + * + * If no previous filter objects were defined, this function will call + * gtk_recent_chooser_set_filter(). + * + * Since: 2.10 + */ +void +gtk_recent_chooser_add_filter (GtkRecentChooser *chooser, + GtkRecentFilter *filter) +{ + g_return_if_fail (GTK_IS_RECENT_CHOOSER (chooser)); + + GTK_RECENT_CHOOSER_GET_IFACE (chooser)->add_filter (chooser, filter); +} + +/** + * gtk_recent_chooser_remove_filter: + * @chooser: a #GtkRecentChooser + * @filter: a #GtkRecentFilter + * + * Removes @filter from the list of #GtkRecentFilter objects held by @chooser. + * + * Since: 2.10 + */ +void +gtk_recent_chooser_remove_filter (GtkRecentChooser *chooser, + GtkRecentFilter *filter) +{ + g_return_if_fail (GTK_IS_RECENT_CHOOSER (chooser)); + + GTK_RECENT_CHOOSER_GET_IFACE (chooser)->remove_filter (chooser, filter); +} + +/** + * gtk_recent_chooser_list_filters: + * @chooser: a #GtkRecentChooser + * + * Gets the #GtkRecentFilter objects held by @chooser. + * + * Return value: A singly linked list of #GtkRecentFilter objects. You + * should just free the returned list using g_slist_free(). + * + * Since: 2.10 + */ +GSList * +gtk_recent_chooser_list_filters (GtkRecentChooser *chooser) +{ + g_return_val_if_fail (GTK_IS_RECENT_CHOOSER (chooser), NULL); + + return GTK_RECENT_CHOOSER_GET_IFACE (chooser)->list_filters (chooser); +} + +/** + * gtk_recent_chooser_set_filter: + * @chooser: a #GtkRecentChooser + * @filter: a #GtkRecentFilter + * + * Sets @filter as the current #GtkRecentFilter object used by @chooser + * to affect the displayed recently used resources. + * + * Since: 2.10 + */ +void +gtk_recent_chooser_set_filter (GtkRecentChooser *chooser, + GtkRecentFilter *filter) +{ + g_return_if_fail (GTK_IS_RECENT_CHOOSER (chooser)); + + g_object_set (G_OBJECT (chooser), "filter", filter, NULL); +} + +/** + * gtk_recent_chooser_get_filter: + * @chooser: a #GtkRecentChooser + * + * Gets the #GtkRecentFilter object currently used by @chooser to affect + * the display of the recently used resources. + * + * Return value: a #GtkRecentFilter object. + * + * Since: 2.10 + */ +GtkRecentFilter * +gtk_recent_chooser_get_filter (GtkRecentChooser *chooser) +{ + GtkRecentFilter *filter; + + g_return_val_if_fail (GTK_IS_RECENT_CHOOSER (chooser), NULL); + + g_object_get (G_OBJECT (chooser), "filter", &filter, NULL); + + /* we need this hack because g_object_get() increases the refcount + * of the returned object; see also gtk_file_chooser_get_filter() + * inside gtkfilechooser.c + */ + if (filter) + g_object_unref (filter); + + return filter; +} + +void +_gtk_recent_chooser_item_activated (GtkRecentChooser *chooser) +{ + g_return_if_fail (GTK_IS_RECENT_CHOOSER (chooser)); + + g_signal_emit (chooser, chooser_signals[ITEM_ACTIVATED], 0); +} + +void +_gtk_recent_chooser_selection_changed (GtkRecentChooser *chooser) +{ + g_return_if_fail (GTK_IS_RECENT_CHOOSER (chooser)); + + g_signal_emit (chooser, chooser_signals[SELECTION_CHANGED], 0); +} + +#define __GTK_RECENT_CHOOSER_H__ +#include "gtkaliasdef.c" diff --git a/gtk/gtkrecentchooser.h b/gtk/gtkrecentchooser.h new file mode 100644 index 0000000000..2362667a95 --- /dev/null +++ b/gtk/gtkrecentchooser.h @@ -0,0 +1,186 @@ +/* GTK - The GIMP Toolkit + * gtkrecentchooser.h - Abstract interface for recent file selectors GUIs + * + * Copyright (C) 2006, 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_CHOOSER_H__ +#define __GTK_RECENT_CHOOSER_H__ + +#include + +#include "gtkrecentmanager.h" +#include "gtkrecentfilter.h" + +G_BEGIN_DECLS + +#define GTK_TYPE_RECENT_CHOOSER (gtk_recent_chooser_get_type ()) +#define GTK_RECENT_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_RECENT_CHOOSER, GtkRecentChooser)) +#define GTK_IS_RECENT_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_RECENT_CHOOSER)) +#define GTK_RECENT_CHOOSER_GET_IFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GTK_TYPE_RECENT_CHOOSER, GtkRecentChooserIface)) + +/** + * GtkRecentSortType: + * @GTK_RECENT_SORT_NONE: Do not sort the returned list of recently used + * resources. + * @GTK_RECENT_SORT_MRU: Sort the returned list with the most recently used + * items first. + * @GTK_RECENT_SORT_LRU: Sort the returned list with the least recently used + * items first. + * @GTK_RECENT_SORT_CUSTOM: Sort the returned list using a custom sorting + * function passed using gtk_recent_manager_set_sort_func(). + * + * Used to specify the sorting method to be applyed to the recently + * used resource list. + **/ +typedef enum +{ + GTK_RECENT_SORT_NONE = 0, + GTK_RECENT_SORT_MRU, + GTK_RECENT_SORT_LRU, + GTK_RECENT_SORT_CUSTOM +} GtkRecentSortType; + +typedef gint (*GtkRecentSortFunc) (GtkRecentInfo *a, + GtkRecentInfo *b, + gpointer user_data); + + +typedef struct _GtkRecentChooser GtkRecentChooser; /* dummy */ +typedef struct _GtkRecentChooserIface GtkRecentChooserIface; + +#define GTK_RECENT_CHOOSER_ERROR (gtk_recent_chooser_error_quark ()) + +typedef enum +{ + GTK_RECENT_CHOOSER_ERROR_NOT_FOUND, + GTK_RECENT_CHOOSER_ERROR_INVALID_URI +} GtkRecentChooserError; + +GQuark gtk_recent_chooser_error_quark (void); + + +struct _GtkRecentChooserIface +{ + GTypeInterface base_iface; + + /* + * Methods + */ + gboolean (* set_current_uri) (GtkRecentChooser *chooser, + const gchar *uri, + GError **error); + gchar * (* get_current_uri) (GtkRecentChooser *chooser); + gboolean (* select_uri) (GtkRecentChooser *chooser, + const gchar *uri, + GError **error); + void (* unselect_uri) (GtkRecentChooser *chooser, + const gchar *uri); + void (* select_all) (GtkRecentChooser *chooser); + void (* unselect_all) (GtkRecentChooser *chooser); + GList * (* get_items) (GtkRecentChooser *chooser); + GtkRecentManager *(* get_recent_manager) (GtkRecentChooser *chooser); + void (* add_filter) (GtkRecentChooser *chooser, + GtkRecentFilter *filter); + void (* remove_filter) (GtkRecentChooser *chooser, + GtkRecentFilter *filter); + GSList * (* list_filters) (GtkRecentChooser *chooser); + void (* set_sort_func) (GtkRecentChooser *chooser, + GtkRecentSortFunc sort_func, + gpointer data, + GDestroyNotify destroy); + + /* + * Signals + */ + void (* item_activated) (GtkRecentChooser *chooser); + void (* selection_changed) (GtkRecentChooser *chooser); +}; + +GType gtk_recent_chooser_get_type (void) G_GNUC_CONST; + +/* + * Configuration + */ +void gtk_recent_chooser_set_show_private (GtkRecentChooser *chooser, + gboolean show_private); +gboolean gtk_recent_chooser_get_show_private (GtkRecentChooser *chooser); +void gtk_recent_chooser_set_show_not_found (GtkRecentChooser *chooser, + gboolean show_not_found); +gboolean gtk_recent_chooser_get_show_not_found (GtkRecentChooser *chooser); +void gtk_recent_chooser_set_select_multiple (GtkRecentChooser *chooser, + gboolean select_multiple); +gboolean gtk_recent_chooser_get_select_multiple (GtkRecentChooser *chooser); +void gtk_recent_chooser_set_limit (GtkRecentChooser *chooser, + gint limit); +gint gtk_recent_chooser_get_limit (GtkRecentChooser *chooser); +void gtk_recent_chooser_set_local_only (GtkRecentChooser *chooser, + gboolean local_only); +gboolean gtk_recent_chooser_get_local_only (GtkRecentChooser *chooser); +void gtk_recent_chooser_set_show_tips (GtkRecentChooser *chooser, + gboolean show_tips); +gboolean gtk_recent_chooser_get_show_tips (GtkRecentChooser *chooser); +void gtk_recent_chooser_set_show_numbers (GtkRecentChooser *chooser, + gboolean show_numbers); +gboolean gtk_recent_chooser_get_show_numbers (GtkRecentChooser *chooser); +void gtk_recent_chooser_set_show_icons (GtkRecentChooser *chooser, + gboolean show_icons); +gboolean gtk_recent_chooser_get_show_icons (GtkRecentChooser *chooser); +void gtk_recent_chooser_set_sort_type (GtkRecentChooser *chooser, + GtkRecentSortType sort_type); +GtkRecentSortType gtk_recent_chooser_get_sort_type (GtkRecentChooser *chooser); +void gtk_recent_chooser_set_sort_func (GtkRecentChooser *chooser, + GtkRecentSortFunc sort_func, + gpointer sort_data, + GDestroyNotify data_destroy); + +/* + * Items handling + */ +gboolean gtk_recent_chooser_set_current_uri (GtkRecentChooser *chooser, + const gchar *uri, + GError **error); +gchar * gtk_recent_chooser_get_current_uri (GtkRecentChooser *chooser); +GtkRecentInfo *gtk_recent_chooser_get_current_item (GtkRecentChooser *chooser); +gboolean gtk_recent_chooser_select_uri (GtkRecentChooser *chooser, + const gchar *uri, + GError **error); +void gtk_recent_chooser_unselect_uri (GtkRecentChooser *chooser, + const gchar *uri); +void gtk_recent_chooser_select_all (GtkRecentChooser *chooser); +void gtk_recent_chooser_unselect_all (GtkRecentChooser *chooser); +GList * gtk_recent_chooser_get_items (GtkRecentChooser *chooser); +gchar ** gtk_recent_chooser_get_uris (GtkRecentChooser *chooser, + gsize *length); + +/* + * Filters + */ +void gtk_recent_chooser_add_filter (GtkRecentChooser *chooser, + GtkRecentFilter *filter); +void gtk_recent_chooser_remove_filter (GtkRecentChooser *chooser, + GtkRecentFilter *filter); +GSList * gtk_recent_chooser_list_filters (GtkRecentChooser *chooser); +void gtk_recent_chooser_set_filter (GtkRecentChooser *chooser, + GtkRecentFilter *filter); +GtkRecentFilter *gtk_recent_chooser_get_filter (GtkRecentChooser *chooser); + + +G_END_DECLS + +#endif /* __GTK_RECENT_CHOOSER_H__ */ diff --git a/gtk/gtkrecentchooserdefault.c b/gtk/gtkrecentchooserdefault.c new file mode 100644 index 0000000000..e830e96cbf --- /dev/null +++ b/gtk/gtkrecentchooserdefault.c @@ -0,0 +1,2073 @@ +/* GTK - The GIMP Toolkit + * gtkrecentchooserdefault.c + * Copyright (C) 2005-2006, 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 "config.h" + +#include +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#include + +#include "gtkstock.h" +#include "gtkicontheme.h" +#include "gtkiconfactory.h" +#include "gtksettings.h" +#include "gtktreeview.h" +#include "gtkliststore.h" +#include "gtkbutton.h" +#include "gtkcelllayout.h" +#include "gtkcellrendererpixbuf.h" +#include "gtkcellrenderertext.h" +#include "gtkcheckmenuitem.h" +#include "gtkclipboard.h" +#include "gtkcombobox.h" +#include "gtkentry.h" +#include "gtkeventbox.h" +#include "gtkexpander.h" +#include "gtkframe.h" +#include "gtkhbox.h" +#include "gtkhpaned.h" +#include "gtkimage.h" +#include "gtkimagemenuitem.h" +#include "gtkintl.h" +#include "gtklabel.h" +#include "gtkmenuitem.h" +#include "gtkmessagedialog.h" +#include "gtkscrolledwindow.h" +#include "gtkseparatormenuitem.h" +#include "gtksizegroup.h" +#include "gtktable.h" +#include "gtktreeview.h" +#include "gtktreemodelsort.h" +#include "gtktreemodelfilter.h" +#include "gtktreeselection.h" +#include "gtktreestore.h" +#include "gtktooltips.h" +#include "gtktypebuiltins.h" +#include "gtkvbox.h" +#include "gtkalias.h" + +#include "gtkrecentmanager.h" +#include "gtkrecentfilter.h" +#include "gtkrecentchooser.h" +#include "gtkrecentchooserprivate.h" +#include "gtkrecentchooserutils.h" +#include "gtkrecentchooserdefault.h" + + + +struct _GtkRecentChooserDefault +{ + GtkVBox parent_instance; + + GtkRecentManager *manager; + gulong manager_changed_id; + guint local_manager : 1; + + gint icon_size; + + /* RecentChooser properties */ + gint limit; + GtkRecentSortType sort_type; + guint show_private : 1; + guint show_not_found : 1; + guint select_multiple : 1; + guint show_numbers : 1; + guint show_tips : 1; + guint show_icons : 1; + guint local_only : 1; + + GSList *filters; + GtkRecentFilter *current_filter; + GtkWidget *filter_combo_hbox; + GtkWidget *filter_combo; + + GtkRecentSortFunc sort_func; + gpointer sort_data; + GDestroyNotify sort_data_destroy; + + GtkTooltips *tooltips; + + GtkIconTheme *icon_theme; + + GtkWidget *recent_view; + GtkListStore *recent_store; + GtkTreeModel *recent_store_filter; + GtkTreeViewColumn *icon_column; + GtkTreeViewColumn *meta_column; + GtkCellRenderer *meta_renderer; + GtkTreeSelection *selection; + + GtkWidget *recent_popup_menu; + GtkWidget *recent_popup_menu_copy_item; + GtkWidget *recent_popup_menu_remove_item; + GtkWidget *recent_popup_menu_clear_item; + GtkWidget *recent_popup_menu_show_private_item; + + guint load_id; + GList *recent_items; + gint n_recent_items; + gint loaded_items; + guint load_state; +}; + +typedef struct _GtkRecentChooserDefaultClass +{ + GtkVBoxClass parent_class; +} GtkRecentChooserDefaultClass; + +enum { + RECENT_URI_COLUMN, + RECENT_DISPLAY_NAME_COLUMN, + RECENT_INFO_COLUMN, + + N_RECENT_COLUMNS +}; + +enum { + LOAD_EMPTY, /* initial state: the model is empty */ + LOAD_PRELOAD, /* the model is loading and not inserted in the tree yet */ + LOAD_LOADING, /* the model is fully loaded but not inserted */ + LOAD_FINISHED /* the model is fully loaded and inserted */ +}; + +enum { + TEXT_URI_LIST +}; + +/* Target types for DnD from the file list */ +static const GtkTargetEntry recent_list_source_targets[] = { + { "text/uri-list", 0, TEXT_URI_LIST } +}; + +static const int num_recent_list_source_targets = (sizeof (recent_list_source_targets) + / sizeof (recent_list_source_targets[0])); + +/* Icon size for if we can't get it from the theme */ +#define FALLBACK_ICON_SIZE 48 +#define FALLBACK_ITEM_LIMIT 20 + +#define NUM_CHARS 40 +#define NUM_LINES 9 + + + +/* GObject */ +static void gtk_recent_chooser_default_class_init (GtkRecentChooserDefaultClass *klass); +static void gtk_recent_chooser_default_init (GtkRecentChooserDefault *impl); +static GObject *gtk_recent_chooser_default_constructor (GType type, + guint n_construct_prop, + GObjectConstructParam *construct_params); +static void gtk_recent_chooser_default_finalize (GObject *object); +static void gtk_recent_chooser_default_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gtk_recent_chooser_default_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +/* GtkRecentChooserIface */ +static void gtk_recent_chooser_iface_init (GtkRecentChooserIface *iface); +static gboolean gtk_recent_chooser_default_set_current_uri (GtkRecentChooser *chooser, + const gchar *uri, + GError **error); +static gchar * gtk_recent_chooser_default_get_current_uri (GtkRecentChooser *chooser); +static gboolean gtk_recent_chooser_default_select_uri (GtkRecentChooser *chooser, + const gchar *uri, + GError **error); +static void gtk_recent_chooser_default_unselect_uri (GtkRecentChooser *chooser, + const gchar *uri); +static void gtk_recent_chooser_default_select_all (GtkRecentChooser *chooser); +static void gtk_recent_chooser_default_unselect_all (GtkRecentChooser *chooser); +static GList * gtk_recent_chooser_default_get_items (GtkRecentChooser *chooser); +static GtkRecentManager *gtk_recent_chooser_default_get_recent_manager (GtkRecentChooser *chooser); +static void gtk_recent_chooser_default_set_sort_func (GtkRecentChooser *chooser, + GtkRecentSortFunc sort_func, + gpointer sort_data, + GDestroyNotify data_destroy); +static void gtk_recent_chooser_default_add_filter (GtkRecentChooser *chooser, + GtkRecentFilter *filter); +static void gtk_recent_chooser_default_remove_filter (GtkRecentChooser *chooser, + GtkRecentFilter *filter); +static GSList * gtk_recent_chooser_default_list_filters (GtkRecentChooser *chooser); + + +static void gtk_recent_chooser_default_map (GtkWidget *widget); +static void gtk_recent_chooser_default_show_all (GtkWidget *widget); + +static void set_current_filter (GtkRecentChooserDefault *impl, + GtkRecentFilter *filter); + +static GtkIconTheme *get_icon_theme_for_widget (GtkWidget *widget); +static gint get_icon_size_for_widget (GtkWidget *widget, + GtkIconSize icon_size); + +static void reload_recent_items (GtkRecentChooserDefault *impl); +static void chooser_set_model (GtkRecentChooserDefault *impl); + +static void set_recent_manager (GtkRecentChooserDefault *impl, + GtkRecentManager *manager); + +static void chooser_set_sort_type (GtkRecentChooserDefault *impl, + GtkRecentSortType sort_type); + +static gboolean recent_store_filter_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer user_data); + +static void recent_manager_changed_cb (GtkRecentManager *manager, + gpointer user_data); +static void recent_icon_data_func (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer user_data); +static void recent_meta_data_func (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer user_data); + +static void selection_changed_cb (GtkTreeSelection *z, + gpointer user_data); +static void row_activated_cb (GtkTreeView *tree_view, + GtkTreePath *tree_path, + GtkTreeViewColumn *tree_column, + gpointer user_data); +static void filter_combo_changed_cb (GtkComboBox *combo_box, + gpointer user_data); + +static void remove_all_activated_cb (GtkMenuItem *menu_item, + gpointer user_data); +static void remove_item_activated_cb (GtkMenuItem *menu_item, + gpointer user_data); +static void show_private_toggled_cb (GtkCheckMenuItem *menu_item, + gpointer user_data); + +static gboolean recent_view_popup_menu_cb (GtkWidget *widget, + gpointer user_data); +static gboolean recent_view_button_press_cb (GtkWidget *widget, + GdkEventButton *event, + gpointer user_data); + +static void recent_view_drag_begin_cb (GtkWidget *widget, + GdkDragContext *context, + gpointer user_data); +static void recent_view_drag_data_get_cb (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint32 time_, + gpointer data); + +G_DEFINE_TYPE_WITH_CODE (GtkRecentChooserDefault, + gtk_recent_chooser_default, + GTK_TYPE_VBOX, + G_IMPLEMENT_INTERFACE (GTK_TYPE_RECENT_CHOOSER, + gtk_recent_chooser_iface_init)); + + + + +static void +gtk_recent_chooser_iface_init (GtkRecentChooserIface *iface) +{ + iface->set_current_uri = gtk_recent_chooser_default_set_current_uri; + iface->get_current_uri = gtk_recent_chooser_default_get_current_uri; + iface->select_uri = gtk_recent_chooser_default_select_uri; + iface->unselect_uri = gtk_recent_chooser_default_unselect_uri; + iface->select_all = gtk_recent_chooser_default_select_all; + iface->unselect_all = gtk_recent_chooser_default_unselect_all; + iface->get_items = gtk_recent_chooser_default_get_items; + iface->get_recent_manager = gtk_recent_chooser_default_get_recent_manager; + iface->set_sort_func = gtk_recent_chooser_default_set_sort_func; + iface->add_filter = gtk_recent_chooser_default_add_filter; + iface->remove_filter = gtk_recent_chooser_default_remove_filter; + iface->list_filters = gtk_recent_chooser_default_list_filters; +} + +static void +gtk_recent_chooser_default_class_init (GtkRecentChooserDefaultClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gobject_class->constructor = gtk_recent_chooser_default_constructor; + gobject_class->finalize = gtk_recent_chooser_default_finalize; + gobject_class->set_property = gtk_recent_chooser_default_set_property; + gobject_class->get_property = gtk_recent_chooser_default_get_property; + + widget_class->map = gtk_recent_chooser_default_map; + widget_class->show_all = gtk_recent_chooser_default_show_all; + + _gtk_recent_chooser_install_properties (gobject_class); +} + +static void +gtk_recent_chooser_default_init (GtkRecentChooserDefault *impl) +{ + gtk_box_set_spacing (GTK_BOX (impl), 12); + + /* by default, we use the global manager */ + impl->local_manager = FALSE; + + impl->limit = FALLBACK_ITEM_LIMIT; + impl->sort_type = GTK_RECENT_SORT_NONE; + + impl->show_icons = TRUE; + impl->show_private = FALSE; + impl->show_not_found = FALSE; + impl->show_tips = TRUE; + impl->select_multiple = FALSE; + impl->local_only = TRUE; + + impl->icon_size = FALLBACK_ICON_SIZE; + impl->icon_theme = NULL; + + impl->current_filter = NULL; + + impl->tooltips = gtk_tooltips_new (); + g_object_ref_sink (impl->tooltips); + + impl->recent_items = NULL; + impl->n_recent_items = 0; + impl->loaded_items = 0; + + impl->load_state = LOAD_EMPTY; +} + +static GObject * +gtk_recent_chooser_default_constructor (GType type, + guint n_construct_prop, + GObjectConstructParam *construct_params) +{ + GtkRecentChooserDefault *impl; + GObject *object; + + GtkWidget *scrollw; + GtkCellRenderer *renderer; + + object = G_OBJECT_CLASS (gtk_recent_chooser_default_parent_class)->constructor (type, n_construct_prop, construct_params); + + impl = GTK_RECENT_CHOOSER_DEFAULT (object); + + g_assert (impl->manager); + + gtk_widget_push_composite_child (); + + scrollw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrollw), + GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrollw), + GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_box_pack_start (GTK_BOX (impl), scrollw, TRUE, TRUE, 0); + gtk_widget_show (scrollw); + + impl->recent_view = gtk_tree_view_new (); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (impl->recent_view), FALSE); + g_signal_connect (impl->recent_view, "row-activated", + G_CALLBACK (row_activated_cb), impl); + g_signal_connect (impl->recent_view, "popup-menu", + G_CALLBACK (recent_view_popup_menu_cb), impl); + g_signal_connect (impl->recent_view, "button-press-event", + G_CALLBACK (recent_view_button_press_cb), impl); + g_signal_connect (impl->recent_view, "drag-begin", + G_CALLBACK (recent_view_drag_begin_cb), impl); + g_signal_connect (impl->recent_view, "drag-data-get", + G_CALLBACK (recent_view_drag_data_get_cb), impl); + + g_object_set_data (G_OBJECT (impl->recent_view), "GtkRecentChooserDefault", impl); + + gtk_container_add (GTK_CONTAINER (scrollw), impl->recent_view); + gtk_widget_show (impl->recent_view); + + impl->icon_column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_expand (impl->icon_column, FALSE); + gtk_tree_view_column_set_resizable (impl->icon_column, FALSE); + + renderer = gtk_cell_renderer_pixbuf_new (); + gtk_tree_view_column_pack_start (impl->icon_column, renderer, FALSE); + gtk_tree_view_column_set_cell_data_func (impl->icon_column, + renderer, + recent_icon_data_func, + impl, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (impl->recent_view), + impl->icon_column); + + impl->meta_column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_expand (impl->meta_column, TRUE); + gtk_tree_view_column_set_resizable (impl->meta_column, FALSE); + + impl->meta_renderer = gtk_cell_renderer_text_new (); + g_object_set (G_OBJECT (impl->meta_renderer), + "ellipsize", PANGO_ELLIPSIZE_END, + NULL); + gtk_tree_view_column_pack_start (impl->meta_column, impl->meta_renderer, TRUE); + gtk_tree_view_column_set_cell_data_func (impl->meta_column, + impl->meta_renderer, + recent_meta_data_func, + impl, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (impl->recent_view), + impl->meta_column); + + impl->selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->recent_view)); + gtk_tree_selection_set_mode (impl->selection, GTK_SELECTION_SINGLE); + g_signal_connect (impl->selection, "changed", G_CALLBACK (selection_changed_cb), impl); + + /* drag and drop */ + gtk_drag_source_set (impl->recent_view, + GDK_BUTTON1_MASK, + recent_list_source_targets, + num_recent_list_source_targets, + GDK_ACTION_COPY); + + impl->filter_combo_hbox = gtk_hbox_new (FALSE, 12); + + impl->filter_combo = gtk_combo_box_new_text (); + gtk_combo_box_set_focus_on_click (GTK_COMBO_BOX (impl->filter_combo), FALSE); + g_signal_connect (impl->filter_combo, "changed", + G_CALLBACK (filter_combo_changed_cb), impl); + gtk_tooltips_set_tip (impl->tooltips, + impl->filter_combo, + _("Select which type of documents are shown"), + NULL); + + gtk_box_pack_end (GTK_BOX (impl->filter_combo_hbox), + impl->filter_combo, + FALSE, FALSE, 0); + gtk_widget_show (impl->filter_combo); + + gtk_box_pack_end (GTK_BOX (impl), impl->filter_combo_hbox, FALSE, FALSE, 0); + + gtk_widget_pop_composite_child (); + + impl->recent_store = gtk_list_store_new (N_RECENT_COLUMNS, + G_TYPE_STRING, /* uri */ + G_TYPE_STRING, /* display_name */ + GTK_TYPE_RECENT_INFO /* info */); + + return object; +} + +static void +gtk_recent_chooser_default_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkRecentChooserDefault *impl = GTK_RECENT_CHOOSER_DEFAULT (object); + + switch (prop_id) + { + case GTK_RECENT_CHOOSER_PROP_RECENT_MANAGER: + set_recent_manager (impl, g_value_get_object (value)); + break; + case GTK_RECENT_CHOOSER_PROP_SHOW_PRIVATE: + impl->show_private = g_value_get_boolean (value); + + if (impl->recent_store && impl->recent_store_filter) + gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (impl->recent_store_filter)); + + if (impl->recent_popup_menu_show_private_item) + { + GtkCheckMenuItem *item = GTK_CHECK_MENU_ITEM (impl->recent_popup_menu_show_private_item); + g_signal_handlers_block_by_func (item, G_CALLBACK (show_private_toggled_cb), impl); + gtk_check_menu_item_set_active (item, impl->show_private); + g_signal_handlers_unblock_by_func (item, G_CALLBACK (show_private_toggled_cb), impl); + } + break; + case GTK_RECENT_CHOOSER_PROP_SHOW_NOT_FOUND: + impl->show_not_found = g_value_get_boolean (value); + + if (impl->recent_store && impl->recent_store_filter) + gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (impl->recent_store_filter)); + break; + case GTK_RECENT_CHOOSER_PROP_SHOW_TIPS: + impl->show_tips = g_value_get_boolean (value); + + if (impl->show_tips) + gtk_tooltips_enable (impl->tooltips); + else + gtk_tooltips_disable (impl->tooltips); + break; + case GTK_RECENT_CHOOSER_PROP_SHOW_ICONS: + impl->show_icons = g_value_get_boolean (value); + gtk_tree_view_column_set_visible (impl->icon_column, impl->show_icons); + break; + case GTK_RECENT_CHOOSER_PROP_SELECT_MULTIPLE: + impl->select_multiple = g_value_get_boolean (value); + + if (impl->select_multiple) + gtk_tree_selection_set_mode (impl->selection, GTK_SELECTION_MULTIPLE); + else + gtk_tree_selection_set_mode (impl->selection, GTK_SELECTION_SINGLE); + break; + case GTK_RECENT_CHOOSER_PROP_LOCAL_ONLY: + impl->local_only = g_value_get_boolean (value); + break; + case GTK_RECENT_CHOOSER_PROP_LIMIT: + impl->limit = g_value_get_int (value); + break; + case GTK_RECENT_CHOOSER_PROP_SORT_TYPE: + chooser_set_sort_type (impl, g_value_get_enum (value)); + break; + case GTK_RECENT_CHOOSER_PROP_FILTER: + set_current_filter (impl, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_recent_chooser_default_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkRecentChooserDefault *impl = GTK_RECENT_CHOOSER_DEFAULT (object); + + switch (prop_id) + { + case GTK_RECENT_CHOOSER_PROP_LIMIT: + g_value_set_int (value, impl->limit); + break; + case GTK_RECENT_CHOOSER_PROP_SORT_TYPE: + g_value_set_enum (value, impl->sort_type); + break; + case GTK_RECENT_CHOOSER_PROP_SHOW_PRIVATE: + g_value_set_boolean (value, impl->show_private); + break; + case GTK_RECENT_CHOOSER_PROP_SHOW_ICONS: + g_value_set_boolean (value, impl->show_icons); + break; + case GTK_RECENT_CHOOSER_PROP_SHOW_NOT_FOUND: + g_value_set_boolean (value, impl->show_not_found); + break; + case GTK_RECENT_CHOOSER_PROP_SHOW_TIPS: + g_value_set_boolean (value, impl->show_tips); + break; + case GTK_RECENT_CHOOSER_PROP_LOCAL_ONLY: + g_value_set_boolean (value, impl->local_only); + break; + case GTK_RECENT_CHOOSER_PROP_SELECT_MULTIPLE: + g_value_set_boolean (value, impl->select_multiple); + break; + case GTK_RECENT_CHOOSER_PROP_FILTER: + g_value_set_object (value, impl->current_filter); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_recent_chooser_default_finalize (GObject *object) +{ + GtkRecentChooserDefault *impl = GTK_RECENT_CHOOSER_DEFAULT (object); + + if (impl->recent_items) + { + g_list_foreach (impl->recent_items, + (GFunc) gtk_recent_info_unref, + NULL); + g_list_free (impl->recent_items); + impl->recent_items = NULL; + } + + if (impl->manager_changed_id) + { + g_signal_handler_disconnect (impl->manager, impl->manager_changed_id); + impl->manager_changed_id = 0; + } + + impl->manager = NULL; + + if (impl->sort_data_destroy) + { + impl->sort_data_destroy (impl->sort_data); + + impl->sort_data_destroy = NULL; + impl->sort_data = NULL; + impl->sort_func = NULL; + } + + if (impl->filters) + { + g_slist_foreach (impl->filters, + (GFunc) g_object_unref, + NULL); + g_slist_free (impl->filters); + } + + if (impl->current_filter) + g_object_unref (impl->current_filter); + + if (impl->recent_store_filter) + g_object_unref (impl->recent_store_filter); + + if (impl->recent_store) + g_object_unref (impl->recent_store); + + if (impl->tooltips) + g_object_unref (impl->tooltips); + + G_OBJECT_CLASS (gtk_recent_chooser_default_parent_class)->finalize (object); +} + +/* override GtkWidget::show_all since we have internal widgets we wish to keep + * hidden unless we decide otherwise, like the filter combo box. + */ +static void +gtk_recent_chooser_default_show_all (GtkWidget *widget) +{ + gtk_widget_show (widget); +} + + + +/* Shows an error dialog set as transient for the specified window */ +static void +error_message_with_parent (GtkWindow *parent, + const gchar *msg, + const gchar *detail) +{ + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (parent, + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + "%s", + msg); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + "%s", detail); + + if (parent->group) + gtk_window_group_add_window (parent->group, GTK_WINDOW (dialog)); + + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +/* Returns a toplevel GtkWindow, or NULL if none */ +static GtkWindow * +get_toplevel (GtkWidget *widget) +{ + GtkWidget *toplevel; + + toplevel = gtk_widget_get_toplevel (widget); + if (!GTK_WIDGET_TOPLEVEL (toplevel)) + return NULL; + else + return GTK_WINDOW (toplevel); +} + +/* Shows an error dialog for the file chooser */ +static void +error_message (GtkRecentChooserDefault *impl, + const gchar *msg, + const gchar *detail) +{ + error_message_with_parent (get_toplevel (GTK_WIDGET (impl)), msg, detail); +} + +static void +set_busy_cursor (GtkRecentChooserDefault *impl, + gboolean show_busy_cursor) +{ + GtkWindow *toplevel; + GdkDisplay *display; + GdkCursor *cursor; + + toplevel = get_toplevel (GTK_WIDGET (impl)); + if (!toplevel || !GTK_WIDGET_REALIZED (toplevel)) + return; + + display = gtk_widget_get_display (GTK_WIDGET (toplevel)); + + cursor = NULL; + if (show_busy_cursor) + cursor = gdk_cursor_new_for_display (display, GDK_WATCH); + + gdk_window_set_cursor (GTK_WIDGET (toplevel)->window, cursor); + gdk_display_flush (display); + + if (cursor) + gdk_cursor_unref (cursor); +} + +static void +chooser_set_model (GtkRecentChooserDefault *impl) +{ + g_assert (impl->recent_store != NULL); + g_assert (impl->recent_store_filter == NULL); + g_assert (impl->load_state == LOAD_LOADING); + + impl->recent_store_filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (impl->recent_store), NULL); + gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (impl->recent_store_filter), + recent_store_filter_func, + impl, + NULL); + + gtk_tree_view_set_model (GTK_TREE_VIEW (impl->recent_view), + impl->recent_store_filter); + gtk_tree_view_columns_autosize (GTK_TREE_VIEW (impl->recent_view)); + gtk_tree_view_set_enable_search (GTK_TREE_VIEW (impl->recent_view), TRUE); + gtk_tree_view_set_search_column (GTK_TREE_VIEW (impl->recent_view), + RECENT_DISPLAY_NAME_COLUMN); + + impl->load_state = LOAD_FINISHED; +} + +static gboolean +load_recent_items (gpointer user_data) +{ + GtkRecentChooserDefault *impl; + GtkRecentInfo *info; + GtkTreeIter iter; + const gchar *uri, *name; + gboolean retval; + + GDK_THREADS_ENTER (); + + impl = GTK_RECENT_CHOOSER_DEFAULT (user_data); + + g_assert ((impl->load_state == LOAD_EMPTY) || + (impl->load_state == LOAD_PRELOAD)); + + /* store the items for multiple runs */ + if (!impl->recent_items) + { + impl->recent_items = gtk_recent_chooser_get_items (GTK_RECENT_CHOOSER (impl)); + if (!impl->recent_items) + { + GDK_THREADS_LEAVE (); + + impl->load_state = LOAD_FINISHED; + + return FALSE; + } + + impl->n_recent_items = g_list_length (impl->recent_items); + impl->loaded_items = 0; + impl->load_state = LOAD_PRELOAD; + } + + info = (GtkRecentInfo *) g_list_nth_data (impl->recent_items, + impl->loaded_items); + g_assert (info); + + uri = gtk_recent_info_get_uri (info); + name = gtk_recent_info_get_display_name (info); + + /* at this point, everything goes inside the model; operations on the + * visualization of items inside the model are done in the cell data + * funcs (remember that there are two of those: one for the icon and + * one for the text), while the filtering is done only when a filter + * is actually loaded. */ + gtk_list_store_append (impl->recent_store, &iter); + gtk_list_store_set (impl->recent_store, &iter, + RECENT_URI_COLUMN, uri, /* uri */ + RECENT_DISPLAY_NAME_COLUMN, name, /* display_name */ + RECENT_INFO_COLUMN, info, /* info */ + -1); + + impl->loaded_items += 1; + + if (impl->loaded_items == impl->n_recent_items) + { + /* we have finished loading, so we remove the items cache */ + impl->load_state = LOAD_LOADING; + + g_list_foreach (impl->recent_items, + (GFunc) gtk_recent_info_unref, + NULL); + g_list_free (impl->recent_items); + + impl->recent_items = NULL; + impl->n_recent_items = 0; + impl->loaded_items = 0; + + if (impl->recent_store_filter) + { + g_object_unref (impl->recent_store_filter); + impl->recent_store_filter = NULL; + } + + /* load the filled up model */ + chooser_set_model (impl); + + retval = FALSE; + } + else + { + /* we did not finish, so continue loading */ + retval = TRUE; + } + + GDK_THREADS_LEAVE (); + + return retval; +} + +static void +cleanup_after_load (gpointer user_data) +{ + GtkRecentChooserDefault *impl; + + GDK_THREADS_ENTER (); + + impl = GTK_RECENT_CHOOSER_DEFAULT (user_data); + + if (impl->load_id != 0) + { + g_assert ((impl->load_state == LOAD_PRELOAD) || + (impl->load_state == LOAD_LOADING) || + (impl->load_state == LOAD_FINISHED)); + + /* we have officialy finished loading all the items, + * so we can reset the state machine + */ + g_source_remove (impl->load_id); + impl->load_id = 0; + impl->load_state = LOAD_EMPTY; + } + else + g_assert ((impl->load_state == LOAD_EMPTY) || + (impl->load_state == LOAD_LOADING) || + (impl->load_state == LOAD_FINISHED)); + + set_busy_cursor (impl, FALSE); + + GDK_THREADS_LEAVE (); +} + +/* clears the current model and reloads the recently used resources */ +static void +reload_recent_items (GtkRecentChooserDefault *impl) +{ + /* reload is already in progress - do not disturb */ + if (impl->load_id) + return; + + gtk_tree_view_set_model (GTK_TREE_VIEW (impl->recent_view), NULL); + gtk_list_store_clear (impl->recent_store); + + if (!impl->icon_theme) + impl->icon_theme = get_icon_theme_for_widget (GTK_WIDGET (impl)); + + impl->icon_size = get_icon_size_for_widget (GTK_WIDGET (impl), + GTK_ICON_SIZE_BUTTON); + + set_busy_cursor (impl, TRUE); + + impl->load_state = LOAD_EMPTY; + impl->load_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE + 30, + load_recent_items, + impl, + cleanup_after_load); +} + +/* taken form gtkfilechooserdialog.c */ +static void +set_default_size (GtkRecentChooserDefault *impl) +{ + GtkWidget *widget; + gint width, height; + gint font_size; + GdkScreen *screen; + gint monitor_num; + GtkRequisition req; + GdkRectangle monitor; + + widget = GTK_WIDGET (impl); + + /* Size based on characters and the icon size */ + font_size = pango_font_description_get_size (widget->style->font_desc); + font_size = PANGO_PIXELS (font_size); + + width = impl->icon_size + font_size * NUM_CHARS; + height = (impl->icon_size + font_size) * NUM_LINES; + + /* Use at least the requisition size... */ + gtk_widget_size_request (widget, &req); + width = MAX (width, req.width); + height = MAX (height, req.height); + + /* ... but no larger than the monitor */ + screen = gtk_widget_get_screen (widget); + monitor_num = gdk_screen_get_monitor_at_window (screen, widget->window); + + gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor); + + width = MIN (width, monitor.width * 3 / 4); + height = MIN (height, monitor.height * 3 / 4); + + /* Set size */ + gtk_widget_set_size_request (impl->recent_view, width, height); +} + +static void +gtk_recent_chooser_default_map (GtkWidget *widget) +{ + GtkRecentChooserDefault *impl = GTK_RECENT_CHOOSER_DEFAULT (widget); + + if (GTK_WIDGET_CLASS (gtk_recent_chooser_default_parent_class)->map) + GTK_WIDGET_CLASS (gtk_recent_chooser_default_parent_class)->map (widget); + + /* reloads everything */ + reload_recent_items (impl); + + set_default_size (impl); +} + +static void +recent_icon_data_func (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer user_data) +{ + GtkRecentChooserDefault *impl = GTK_RECENT_CHOOSER_DEFAULT (user_data); + GtkRecentInfo *info = NULL; + GdkPixbuf *pixbuf; + + gtk_tree_model_get (model, iter, + RECENT_INFO_COLUMN, &info, + -1); + g_assert (info != NULL); + + pixbuf = gtk_recent_info_get_icon (info, impl->icon_size); + + g_object_set (cell, + "pixbuf", pixbuf, + NULL); + + if (pixbuf) + g_object_unref (pixbuf); +} + +static void +recent_meta_data_func (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer user_data) +{ + GtkRecentInfo *info = NULL; + gchar *uri; + gchar *name; + GString *data; + + data = g_string_new (NULL); + + gtk_tree_model_get (model, iter, + RECENT_DISPLAY_NAME_COLUMN, &name, + RECENT_INFO_COLUMN, &info, + -1); + g_assert (info != NULL); + + uri = gtk_recent_info_get_uri_display (info); + + if (!name) + name = gtk_recent_info_get_short_name (info); + + g_string_append_printf (data, + "%s\n" + "Location: %s", + name, + uri); + + g_object_set (cell, + "markup", data->str, + "sensitive", gtk_recent_info_exists (info), + NULL); + + g_string_free (data, TRUE); + g_free (uri); + g_free (name); + gtk_recent_info_unref (info); +} + + +static gchar * +gtk_recent_chooser_default_get_current_uri (GtkRecentChooser *chooser) +{ + GtkRecentChooserDefault *impl = GTK_RECENT_CHOOSER_DEFAULT (chooser); + + g_assert (impl->selection != NULL); + + if (!impl->select_multiple) + { + GtkTreeModel *model; + GtkTreeIter iter; + gchar *uri = NULL; + + if (!gtk_tree_selection_get_selected (impl->selection, &model, &iter)) + return NULL; + + gtk_tree_model_get (model, &iter, RECENT_URI_COLUMN, &uri, -1); + + return uri; + } + + return NULL; +} + +typedef struct +{ + guint found : 1; + guint do_select : 1; + guint do_activate : 1; + + gchar *uri; + + GtkRecentChooserDefault *impl; +} SelectURIData; + +static gboolean +scan_for_uri_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer user_data) +{ + SelectURIData *select_data = (SelectURIData *) user_data; + gchar *uri; + + if (!select_data) + return TRUE; + + if (select_data->found) + return TRUE; + + gtk_tree_model_get (model, iter, RECENT_URI_COLUMN, &uri, -1); + if (uri && (0 == strcmp (uri, select_data->uri))) + { + select_data->found = TRUE; + + if (select_data->do_activate) + { + gtk_tree_view_row_activated (GTK_TREE_VIEW (select_data->impl->recent_view), + path, + select_data->impl->meta_column); + + return TRUE; + } + + if (select_data->do_select) + gtk_tree_selection_select_iter (select_data->impl->selection, iter); + else + gtk_tree_selection_unselect_iter (select_data->impl->selection, iter); + + return TRUE; + } + + return FALSE; +} + +static gboolean +gtk_recent_chooser_default_set_current_uri (GtkRecentChooser *chooser, + const gchar *uri, + GError **error) +{ + GtkRecentChooserDefault *impl = GTK_RECENT_CHOOSER_DEFAULT (chooser); + SelectURIData *data; + + data = g_new0 (SelectURIData, 1); + data->uri = g_strdup (uri); + data->impl = impl; + data->found = FALSE; + data->do_activate = TRUE; + data->do_select = TRUE; + + gtk_tree_model_foreach (GTK_TREE_MODEL (impl->recent_store), + scan_for_uri_cb, + data); + + if (!data->found) + { + g_free (data->uri); + g_free (data); + + g_set_error (error, GTK_RECENT_CHOOSER_ERROR, + GTK_RECENT_CHOOSER_ERROR_NOT_FOUND, + _("No item for URI '%s' found"), + uri); + return FALSE; + } + + g_free (data->uri); + g_free (data); + + return TRUE; +} + +static gboolean +gtk_recent_chooser_default_select_uri (GtkRecentChooser *chooser, + const gchar *uri, + GError **error) +{ + GtkRecentChooserDefault *impl = GTK_RECENT_CHOOSER_DEFAULT (chooser); + SelectURIData *data; + + data = g_new0 (SelectURIData, 1); + data->uri = g_strdup (uri); + data->impl = impl; + data->found = FALSE; + data->do_activate = FALSE; + data->do_select = TRUE; + + gtk_tree_model_foreach (GTK_TREE_MODEL (impl->recent_store), + scan_for_uri_cb, + data); + + if (!data->found) + { + g_free (data->uri); + g_free (data); + + g_set_error (error, GTK_RECENT_CHOOSER_ERROR, + GTK_RECENT_CHOOSER_ERROR_NOT_FOUND, + _("No item for URI '%s' found"), + uri); + return FALSE; + } + + g_free (data->uri); + g_free (data); + + return TRUE; +} + +static void +gtk_recent_chooser_default_unselect_uri (GtkRecentChooser *chooser, + const gchar *uri) +{ + GtkRecentChooserDefault *impl = GTK_RECENT_CHOOSER_DEFAULT (chooser); + SelectURIData *data; + + data = g_new0 (SelectURIData, 1); + data->uri = g_strdup (uri); + data->impl = impl; + data->found = FALSE; + data->do_activate = FALSE; + data->do_select = FALSE; + + gtk_tree_model_foreach (GTK_TREE_MODEL (impl->recent_store), + scan_for_uri_cb, + data); + + g_free (data->uri); + g_free (data); +} + +static void +gtk_recent_chooser_default_select_all (GtkRecentChooser *chooser) +{ + GtkRecentChooserDefault *impl = GTK_RECENT_CHOOSER_DEFAULT (chooser); + + if (!impl->select_multiple) + return; + + gtk_tree_selection_select_all (impl->selection); +} + +static void +gtk_recent_chooser_default_unselect_all (GtkRecentChooser *chooser) +{ + GtkRecentChooserDefault *impl = GTK_RECENT_CHOOSER_DEFAULT (chooser); + + gtk_tree_selection_unselect_all (impl->selection); +} + +static void +gtk_recent_chooser_default_set_sort_func (GtkRecentChooser *chooser, + GtkRecentSortFunc sort_func, + gpointer sort_data, + GDestroyNotify data_destroy) +{ + GtkRecentChooserDefault *impl = GTK_RECENT_CHOOSER_DEFAULT (chooser); + + if (impl->sort_data_destroy) + { + impl->sort_data_destroy (impl->sort_data); + + impl->sort_func = NULL; + impl->sort_data = NULL; + impl->sort_data_destroy = NULL; + } + + if (sort_func) + { + impl->sort_func = sort_func; + impl->sort_data = sort_data; + impl->sort_data_destroy = data_destroy; + } +} + +static gint +sort_recent_items_mru (GtkRecentInfo *a, + GtkRecentInfo *b, + gpointer unused) +{ + g_assert (a != NULL && b != NULL); + + return (gtk_recent_info_get_modified (a) < gtk_recent_info_get_modified (b)); +} + +static gint +sort_recent_items_lru (GtkRecentInfo *a, + GtkRecentInfo *b, + gpointer unused) +{ + g_assert (a != NULL && b != NULL); + + return (gtk_recent_info_get_modified (a) > gtk_recent_info_get_modified (b)); +} + +/* our proxy sorting function */ +static gint +sort_recent_items_proxy (gpointer *a, + gpointer *b, + gpointer user_data) +{ + GtkRecentInfo *info_a = (GtkRecentInfo *) a; + GtkRecentInfo *info_b = (GtkRecentInfo *) b; + GtkRecentChooserDefault *impl = GTK_RECENT_CHOOSER_DEFAULT (user_data); + + if (impl->sort_func) + return (* impl->sort_func) (info_a, + info_b, + impl->sort_data); + + /* fallback */ + return 0; +} + +static void +chooser_set_sort_type (GtkRecentChooserDefault *impl, + GtkRecentSortType sort_type) +{ + if (impl->sort_type == sort_type) + return; + + impl->sort_type = sort_type; +} + +static GList * +gtk_recent_chooser_default_get_items (GtkRecentChooser *chooser) +{ + GtkRecentChooserDefault *impl; + gint limit; + GtkRecentSortType sort_type; + GList *items; + GCompareDataFunc compare_func; + gint length; + + impl = GTK_RECENT_CHOOSER_DEFAULT (chooser); + + if (!impl->manager) + return NULL; + + items = gtk_recent_manager_get_items (impl->manager); + if (!items) + return NULL; + + limit = gtk_recent_chooser_get_limit (chooser); + sort_type = gtk_recent_chooser_get_sort_type (chooser); + + switch (sort_type) + { + case GTK_RECENT_SORT_NONE: + compare_func = NULL; + break; + case GTK_RECENT_SORT_MRU: + compare_func = (GCompareDataFunc) sort_recent_items_mru; + break; + case GTK_RECENT_SORT_LRU: + compare_func = (GCompareDataFunc) sort_recent_items_lru; + break; + case GTK_RECENT_SORT_CUSTOM: + compare_func = (GCompareDataFunc) sort_recent_items_proxy; + break; + default: + g_assert_not_reached (); + break; + } + + /* sort the items; the filtering will be dealt with using + * the treeview's own filter object + */ + if (compare_func) + items = g_list_sort_with_data (items, compare_func, impl); + + length = g_list_length (items); + if ((limit != -1) && (length > limit)) + { + GList *clamp, *l; + + clamp = g_list_nth (items, limit - 1); + + if (!clamp) + return items; + + l = clamp->next; + clamp->next = NULL; + + g_list_foreach (l, (GFunc) gtk_recent_info_unref, NULL); + g_list_free (l); + } + + return items; +} + +static GtkRecentManager * +gtk_recent_chooser_default_get_recent_manager (GtkRecentChooser *chooser) +{ + GtkRecentChooserDefault *impl = GTK_RECENT_CHOOSER_DEFAULT (chooser); + + return impl->manager; +} + +static void +show_filters (GtkRecentChooserDefault *impl, + gboolean show) +{ + if (show) + gtk_widget_show (impl->filter_combo_hbox); + else + gtk_widget_hide (impl->filter_combo_hbox); +} + +static void +gtk_recent_chooser_default_add_filter (GtkRecentChooser *chooser, + GtkRecentFilter *filter) +{ + GtkRecentChooserDefault *impl; + const gchar *name; + + impl = GTK_RECENT_CHOOSER_DEFAULT (chooser); + + if (g_slist_find (impl->filters, filter)) + { + g_warning ("gtk_recent_chooser_add_filter() called on filter already in list\n"); + return; + } + + g_object_ref_sink (filter); + impl->filters = g_slist_append (impl->filters, filter); + + /* display new filter */ + name = gtk_recent_filter_get_name (filter); + if (!name) + name = "Untitled filter"; + + gtk_combo_box_append_text (GTK_COMBO_BOX (impl->filter_combo), name); + + if (!g_slist_find (impl->filters, impl->current_filter)) + set_current_filter (impl, filter); + + show_filters (impl, TRUE); +} + +static void +gtk_recent_chooser_default_remove_filter (GtkRecentChooser *chooser, + GtkRecentFilter *filter) +{ + GtkRecentChooserDefault *impl = GTK_RECENT_CHOOSER_DEFAULT (chooser); + GtkTreeModel *model; + GtkTreeIter iter; + gint filter_idx; + + filter_idx = g_slist_index (impl->filters, filter); + + if (filter_idx < 0) + { + g_warning ("gtk_recent_chooser_remove_filter() called on filter not in list\n"); + return; + } + + impl->filters = g_slist_remove (impl->filters, filter); + + if (filter == impl->current_filter) + { + if (impl->filters) + set_current_filter (impl, impl->filters->data); + else + set_current_filter (impl, NULL); + } + + model = gtk_combo_box_get_model (GTK_COMBO_BOX (impl->filter_combo)); + gtk_tree_model_iter_nth_child (model, &iter, NULL, filter_idx); + gtk_list_store_remove (GTK_LIST_STORE (model), &iter); + + g_object_unref (filter); + + if (!impl->filters) + show_filters (impl, FALSE); +} + +static GSList * +gtk_recent_chooser_default_list_filters (GtkRecentChooser *chooser) +{ + GtkRecentChooserDefault *impl = GTK_RECENT_CHOOSER_DEFAULT (chooser); + + return g_slist_copy (impl->filters); +} + +static gboolean +get_is_recent_filtered (GtkRecentChooserDefault *impl, + GtkRecentInfo *info) +{ + GtkRecentFilter *current_filter; + GtkRecentFilterInfo filter_info; + GtkRecentFilterFlags needed; + gboolean retval; + + g_assert (info != NULL); + + if (!impl->current_filter) + return FALSE; + + current_filter = impl->current_filter; + needed = gtk_recent_filter_get_needed (current_filter); + + filter_info.contains = GTK_RECENT_FILTER_URI | GTK_RECENT_FILTER_MIME_TYPE; + + filter_info.uri = gtk_recent_info_get_uri (info); + filter_info.mime_type = gtk_recent_info_get_mime_type (info); + + if (needed & GTK_RECENT_FILTER_DISPLAY_NAME) + { + filter_info.display_name = gtk_recent_info_get_display_name (info); + filter_info.contains |= GTK_RECENT_FILTER_DISPLAY_NAME; + } + else + filter_info.uri = NULL; + + if (needed & GTK_RECENT_FILTER_APPLICATION) + { + filter_info.applications = (const gchar **) gtk_recent_info_get_applications (info, NULL); + filter_info.contains |= GTK_RECENT_FILTER_APPLICATION; + } + else + filter_info.applications = NULL; + + if (needed & GTK_RECENT_FILTER_GROUP) + { + filter_info.groups = (const gchar **) gtk_recent_info_get_groups (info, NULL); + filter_info.contains |= GTK_RECENT_FILTER_GROUP; + } + else + filter_info.groups = NULL; + + if (needed & GTK_RECENT_FILTER_AGE) + { + filter_info.age = gtk_recent_info_get_age (info); + filter_info.contains |= GTK_RECENT_FILTER_AGE; + } + else + filter_info.age = -1; + + retval = gtk_recent_filter_filter (current_filter, &filter_info); + + /* this we own */ + if (filter_info.applications) + g_strfreev ((gchar **) filter_info.applications); + + return !retval; +} + +static gboolean +recent_store_filter_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer user_data) +{ + GtkRecentChooserDefault *impl = GTK_RECENT_CHOOSER_DEFAULT (user_data); + GtkRecentInfo *info = NULL; + + if (!impl->current_filter) + return TRUE; + + gtk_tree_model_get (model, iter, + RECENT_INFO_COLUMN, &info, + -1); + if (!info) + return TRUE; + + if (get_is_recent_filtered (impl, info)) + return FALSE; + + if (impl->local_only && !gtk_recent_info_is_local (info)) + return FALSE; + + if ((!impl->show_private) && gtk_recent_info_get_private_hint (info)) + return FALSE; + + if ((!impl->show_not_found) && !gtk_recent_info_exists (info)) + return FALSE; + + return TRUE; +} + +static void +set_current_filter (GtkRecentChooserDefault *impl, + GtkRecentFilter *filter) +{ + if (impl->current_filter != filter) + { + gint filter_idx; + + filter_idx = g_slist_index (impl->filters, filter); + if (impl->filters && filter && filter_idx < 0) + return; + + if (impl->current_filter) + g_object_unref (impl->current_filter); + + impl->current_filter = filter; + + if (impl->current_filter) + { + g_object_ref_sink (impl->current_filter); + } + + if (impl->filters) + gtk_combo_box_set_active (GTK_COMBO_BOX (impl->filter_combo), + filter_idx); + + if (impl->recent_store && impl->recent_store_filter) + { + gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (impl->recent_store_filter)); + } + + g_object_notify (G_OBJECT (impl), "filter"); + } +} + +static GtkIconTheme * +get_icon_theme_for_widget (GtkWidget *widget) +{ + if (gtk_widget_has_screen (widget)) + return gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget)); + + return gtk_icon_theme_get_default (); +} + +static gint +get_icon_size_for_widget (GtkWidget *widget, + GtkIconSize icon_size) +{ + GtkSettings *settings; + gint width, height; + + if (gtk_widget_has_screen (widget)) + settings = gtk_settings_get_for_screen (gtk_widget_get_screen (widget)); + else + settings = gtk_settings_get_default (); + + if (gtk_icon_size_lookup_for_settings (settings, icon_size, + &width, &height)) + return MAX (width, height); + + return FALLBACK_ICON_SIZE; +} + + +static void +recent_manager_changed_cb (GtkRecentManager *manager, + gpointer user_data) +{ + GtkRecentChooserDefault *impl = GTK_RECENT_CHOOSER_DEFAULT (user_data); + + reload_recent_items (impl); +} + +static void +selection_changed_cb (GtkTreeSelection *selection, + gpointer user_data) +{ + _gtk_recent_chooser_selection_changed (GTK_RECENT_CHOOSER (user_data)); +} + +static void +row_activated_cb (GtkTreeView *tree_view, + GtkTreePath *tree_path, + GtkTreeViewColumn *tree_column, + gpointer user_data) +{ + _gtk_recent_chooser_item_activated (GTK_RECENT_CHOOSER (user_data)); +} + +static void +filter_combo_changed_cb (GtkComboBox *combo_box, + gpointer user_data) +{ + GtkRecentChooserDefault *impl; + gint new_index; + GtkRecentFilter *filter; + + impl = GTK_RECENT_CHOOSER_DEFAULT (user_data); + + new_index = gtk_combo_box_get_active (combo_box); + filter = g_slist_nth_data (impl->filters, new_index); + + set_current_filter (impl, filter); +} + +static GdkPixbuf * +get_drag_pixbuf (GtkRecentChooserDefault *impl) +{ + GtkRecentInfo *info; + GdkPixbuf *retval; + gint size; + + g_assert (GTK_IS_RECENT_CHOOSER_DEFAULT (impl)); + + info = gtk_recent_chooser_get_current_item (GTK_RECENT_CHOOSER (impl)); + if (!info) + return NULL; + + size = get_icon_size_for_widget (GTK_WIDGET (impl), GTK_ICON_SIZE_DND); + + retval = gtk_recent_info_get_icon (info, size); + gtk_recent_info_unref (info); + + return retval; +} + +static void +recent_view_drag_begin_cb (GtkWidget *widget, + GdkDragContext *context, + gpointer user_data) +{ + GtkRecentChooserDefault *impl = GTK_RECENT_CHOOSER_DEFAULT (user_data); + GdkPixbuf *pixbuf; + + pixbuf = get_drag_pixbuf (impl); + if (pixbuf) + { + gtk_drag_set_icon_pixbuf (context, pixbuf, 0, 0); + g_object_unref (pixbuf); + } + else + gtk_drag_set_icon_default (context); +} + +typedef struct +{ + gchar **uri_list; + gsize next_pos; +} DragData; + +static void +append_uri_to_urilist (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer user_data) +{ + DragData *drag_data = (DragData *) user_data; + GtkTreeModel *child_model; + GtkTreeIter child_iter; + gchar *uri = NULL; + gsize pos; + + child_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (model)); + gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model), + &child_iter, + iter); + gtk_tree_model_get (child_model, &child_iter, + RECENT_URI_COLUMN, &uri, + -1); + g_assert (uri != NULL); + + pos = drag_data->next_pos; + drag_data->uri_list[pos] = g_strdup (uri); + drag_data->next_pos = pos + 1; +} + +static void +recent_view_drag_data_get_cb (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint32 time_, + gpointer data) +{ + GtkRecentChooserDefault *impl = GTK_RECENT_CHOOSER_DEFAULT (data); + DragData *drag_data; + gsize n_uris; + + n_uris = gtk_tree_selection_count_selected_rows (impl->selection); + if (n_uris == 0) + return; + + drag_data = g_new (DragData, 1); + drag_data->uri_list = g_new0 (gchar *, n_uris + 1); + drag_data->next_pos = 0; + + gtk_tree_selection_selected_foreach (impl->selection, + append_uri_to_urilist, + drag_data); + + gtk_selection_data_set_uris (selection_data, drag_data->uri_list); + + g_strfreev (drag_data->uri_list); + g_free (drag_data); +} + + + +static void +remove_selected_from_list (GtkRecentChooserDefault *impl) +{ + gchar *uri; + GError *err; + + if (impl->select_multiple) + return; + + uri = gtk_recent_chooser_get_current_uri (GTK_RECENT_CHOOSER (impl)); + if (!uri) + return; + + err = NULL; + if (!gtk_recent_manager_remove_item (impl->manager, uri, &err)) + { + gchar *msg; + + msg = strdup (_("Could not remove item")); + error_message (impl, msg, err->message); + + g_free (msg); + g_error_free (err); + } + + g_free (uri); +} + +static void +copy_activated_cb (GtkMenuItem *menu_item, + gpointer user_data) +{ + GtkRecentChooserDefault *impl = GTK_RECENT_CHOOSER_DEFAULT (user_data); + GtkRecentInfo *info; + gchar *utf8_uri; + + info = gtk_recent_chooser_get_current_item (GTK_RECENT_CHOOSER (impl)); + if (!info) + return; + + utf8_uri = gtk_recent_info_get_uri_display (info); + + gtk_clipboard_set_text (gtk_widget_get_clipboard (GTK_WIDGET (impl), + GDK_SELECTION_CLIPBOARD), + utf8_uri, -1); + + g_free (utf8_uri); +} + +static void +remove_all_activated_cb (GtkMenuItem *menu_item, + gpointer user_data) +{ + GtkRecentChooserDefault *impl = GTK_RECENT_CHOOSER_DEFAULT (user_data); + GError *err = NULL; + + gtk_recent_manager_purge_items (impl->manager, &err); + if (err) + { + gchar *msg; + + msg = g_strdup (_("Could not clear list")); + + error_message (impl, msg, err->message); + + g_free (msg); + g_error_free (err); + } +} + +static void +remove_item_activated_cb (GtkMenuItem *menu_item, + gpointer user_data) +{ + GtkRecentChooserDefault *impl = GTK_RECENT_CHOOSER_DEFAULT (user_data); + + remove_selected_from_list (impl); +} + +static void +show_private_toggled_cb (GtkCheckMenuItem *menu_item, + gpointer user_data) +{ + GtkRecentChooserDefault *impl = GTK_RECENT_CHOOSER_DEFAULT (user_data); + + g_object_set (G_OBJECT (impl), + "show-private", gtk_check_menu_item_get_active (menu_item), + NULL); +} + +static void +recent_popup_menu_detach_cb (GtkWidget *attach_widget, + GtkMenu *menu) +{ + GtkRecentChooserDefault *impl; + + impl = g_object_get_data (G_OBJECT (attach_widget), "GtkRecentChooserDefault"); + g_assert (GTK_IS_RECENT_CHOOSER_DEFAULT (impl)); + + impl->recent_popup_menu = NULL; + impl->recent_popup_menu_remove_item = NULL; + impl->recent_popup_menu_copy_item = NULL; + impl->recent_popup_menu_clear_item = NULL; + impl->recent_popup_menu_show_private_item = NULL; +} + +static void +recent_view_menu_ensure_state (GtkRecentChooserDefault *impl) +{ + gint count; + + g_assert (GTK_IS_RECENT_CHOOSER_DEFAULT (impl)); + g_assert (impl->recent_popup_menu != NULL); + + if (!impl->manager) + count = 0; + else + g_object_get (G_OBJECT (impl->manager), "size", &count, NULL); + + if (count == 0) + { + gtk_widget_set_sensitive (impl->recent_popup_menu_remove_item, FALSE); + gtk_widget_set_sensitive (impl->recent_popup_menu_copy_item, FALSE); + gtk_widget_set_sensitive (impl->recent_popup_menu_clear_item, FALSE); + gtk_widget_set_sensitive (impl->recent_popup_menu_show_private_item, FALSE); + } +} + +static void +recent_view_menu_build (GtkRecentChooserDefault *impl) +{ + GtkWidget *item; + + if (impl->recent_popup_menu) + { + recent_view_menu_ensure_state (impl); + + return; + } + + impl->recent_popup_menu = gtk_menu_new (); + gtk_menu_attach_to_widget (GTK_MENU (impl->recent_popup_menu), + impl->recent_view, + recent_popup_menu_detach_cb); + + item = gtk_image_menu_item_new_with_mnemonic (_("Copy _Location")); + impl->recent_popup_menu_copy_item = item; + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), + gtk_image_new_from_stock (GTK_STOCK_COPY, GTK_ICON_SIZE_MENU)); + g_signal_connect (item, "activate", + G_CALLBACK (copy_activated_cb), impl); + gtk_widget_show (item); + gtk_menu_shell_append (GTK_MENU_SHELL (impl->recent_popup_menu), item); + + item = gtk_separator_menu_item_new (); + gtk_widget_show (item); + gtk_menu_shell_append (GTK_MENU_SHELL (impl->recent_popup_menu), item); + + item = gtk_image_menu_item_new_with_mnemonic (_("_Remove From List")); + impl->recent_popup_menu_remove_item = item; + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), + gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU)); + g_signal_connect (item, "activate", + G_CALLBACK (remove_item_activated_cb), impl); + gtk_widget_show (item); + gtk_menu_shell_append (GTK_MENU_SHELL (impl->recent_popup_menu), item); + + item = gtk_image_menu_item_new_with_mnemonic (_("_Clear List")); + impl->recent_popup_menu_clear_item = item; + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), + gtk_image_new_from_stock (GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU)); + g_signal_connect (item, "activate", + G_CALLBACK (remove_all_activated_cb), impl); + + gtk_widget_show (item); + gtk_menu_shell_append (GTK_MENU_SHELL (impl->recent_popup_menu), item); + + item = gtk_separator_menu_item_new (); + gtk_widget_show (item); + gtk_menu_shell_append (GTK_MENU_SHELL (impl->recent_popup_menu), item); + + item = gtk_check_menu_item_new_with_mnemonic (_("Show _Private Resources")); + impl->recent_popup_menu_show_private_item = item; + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), impl->show_private); + g_signal_connect (item, "toggled", + G_CALLBACK (show_private_toggled_cb), impl); + gtk_widget_show (item); + gtk_menu_shell_append (GTK_MENU_SHELL (impl->recent_popup_menu), item); + + recent_view_menu_ensure_state (impl); +} + +/* taken from gtkfilechooserdefault.c */ +static void +popup_position_func (GtkMenu *menu, + gint *x, + gint *y, + gboolean *push_in, + gpointer user_data) +{ + GtkWidget *widget = GTK_WIDGET (user_data); + GdkScreen *screen = gtk_widget_get_screen (widget); + GtkRequisition req; + gint monitor_num; + GdkRectangle monitor; + + if (G_UNLIKELY (!GTK_WIDGET_REALIZED (widget))) + return; + + gdk_window_get_origin (widget->window, x, y); + + gtk_widget_size_request (GTK_WIDGET (menu), &req); + + *x += (widget->allocation.width - req.width) / 2; + *y += (widget->allocation.height - req.height) / 2; + + monitor_num = gdk_screen_get_monitor_at_point (screen, *x, *y); + gtk_menu_set_monitor (menu, monitor_num); + gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor); + + *x = CLAMP (*x, monitor.x, monitor.x + MAX (0, monitor.width - req.width)); + *y = CLAMP (*y, monitor.y, monitor.y + MAX (0, monitor.height - req.height)); + + *push_in = FALSE; +} + + +static void +recent_view_menu_popup (GtkRecentChooserDefault *impl, + GdkEventButton *event) +{ + recent_view_menu_build (impl); + + if (event) + gtk_menu_popup (GTK_MENU (impl->recent_popup_menu), + NULL, NULL, NULL, NULL, + event->button, event->time); + else + { + gtk_menu_popup (GTK_MENU (impl->recent_popup_menu), + NULL, NULL, + popup_position_func, impl->recent_view, + 0, GDK_CURRENT_TIME); + gtk_menu_shell_select_first (GTK_MENU_SHELL (impl->recent_popup_menu), + FALSE); + } +} + +static gboolean +recent_view_popup_menu_cb (GtkWidget *widget, + gpointer user_data) +{ + recent_view_menu_popup (GTK_RECENT_CHOOSER_DEFAULT (user_data), NULL); + return TRUE; +} + +static gboolean +recent_view_button_press_cb (GtkWidget *widget, + GdkEventButton *event, + gpointer user_data) +{ + GtkRecentChooserDefault *impl = GTK_RECENT_CHOOSER_DEFAULT (user_data); + + if (event->button == 3) + { + GtkTreePath *path; + gboolean res; + + if (event->window != gtk_tree_view_get_bin_window (GTK_TREE_VIEW (impl->recent_view))) + return FALSE; + + res = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (impl->recent_view), + event->x, event->y, + &path, + NULL, NULL, NULL); + if (!res) + return FALSE; + + /* select the path before creating the popup menu */ + gtk_tree_selection_select_path (impl->selection, path); + gtk_tree_path_free (path); + + recent_view_menu_popup (impl, event); + + return TRUE; + } + + return FALSE; +} + +static void +set_recent_manager (GtkRecentChooserDefault *impl, + GtkRecentManager *manager) +{ + if (impl->manager) + { + g_signal_handler_disconnect (impl, impl->manager_changed_id); + impl->manager_changed_id = 0; + + impl->manager = NULL; + } + + if (manager) + impl->manager = manager; + else + impl->manager = gtk_recent_manager_get_default (); + + if (impl->manager) + impl->manager_changed_id = g_signal_connect (impl->manager, "changed", + G_CALLBACK (recent_manager_changed_cb), + impl); +} + +GtkWidget * +_gtk_recent_chooser_default_new (GtkRecentManager *manager) +{ + return g_object_new (GTK_TYPE_RECENT_CHOOSER_DEFAULT, + "recent-manager", manager, + NULL); +} + +#define __GTK_RECENT_CHOOSER_DEFAULT_C__ +#include "gtkaliasdef.c" diff --git a/gtk/gtkrecentchooserdefault.h b/gtk/gtkrecentchooserdefault.h new file mode 100644 index 0000000000..852f4014a1 --- /dev/null +++ b/gtk/gtkrecentchooserdefault.h @@ -0,0 +1,42 @@ +/* GTK - The GIMP Toolkit + * gtkrecentchooserdefault.h + * Copyright (C) 2006 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_CHOOSER_DEFAULT_H__ +#define __GTK_RECENT_CHOOSER_DEFAULT_H__ + +#include + +G_BEGIN_DECLS + + +#define GTK_TYPE_RECENT_CHOOSER_DEFAULT (gtk_recent_chooser_default_get_type ()) +#define GTK_RECENT_CHOOSER_DEFAULT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_RECENT_CHOOSER_DEFAULT, GtkRecentChooserDefault)) +#define GTK_IS_RECENT_CHOOSER_DEFAULT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_RECENT_CHOOSER_DEFAULT)) + + +typedef struct _GtkRecentChooserDefault GtkRecentChooserDefault; + +GType _gtk_recent_chooser_default_get_type (void) G_GNUC_CONST; +GtkWidget *_gtk_recent_chooser_default_new (GtkRecentManager *recent_manager); + + +G_END_DECLS + +#endif /* __GTK_RECENT_CHOOSER_DEFAULT_H__ */ diff --git a/gtk/gtkrecentchooserdialog.c b/gtk/gtkrecentchooserdialog.c new file mode 100644 index 0000000000..a047789a9a --- /dev/null +++ b/gtk/gtkrecentchooserdialog.c @@ -0,0 +1,376 @@ +/* GTK - The GIMP Toolkit + * gtkrecentchooserdialog.c: Recent files selector dialog + * Copyright (C) 2006 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 "gtkrecentchooserdialog.h" +#include "gtkrecentchooserwidget.h" +#include "gtkrecentchooserutils.h" +#include "gtkrecentmanager.h" +#include "gtktypebuiltins.h" +#include "gtkalias.h" + +#include + +struct _GtkRecentChooserDialogPrivate +{ + GtkRecentManager *manager; + + GtkWidget *chooser; +}; + +#define GTK_RECENT_CHOOSER_DIALOG_GET_PRIVATE(obj) (GTK_RECENT_CHOOSER_DIALOG (obj)->priv) + +static void gtk_recent_chooser_dialog_class_init (GtkRecentChooserDialogClass *klass); +static void gtk_recent_chooser_dialog_init (GtkRecentChooserDialog *dialog); +static void gtk_recent_chooser_dialog_finalize (GObject *object); + +static GObject *gtk_recent_chooser_dialog_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params); + +static void gtk_recent_chooser_dialog_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gtk_recent_chooser_dialog_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +static void gtk_recent_chooser_dialog_map (GtkWidget *widget); +static void gtk_recent_chooser_dialog_unmap (GtkWidget *widget); +static void gtk_recent_chooser_dialog_style_set (GtkWidget *widget, + GtkStyle *old_style); + + +G_DEFINE_TYPE_WITH_CODE (GtkRecentChooserDialog, + gtk_recent_chooser_dialog, + GTK_TYPE_DIALOG, + G_IMPLEMENT_INTERFACE (GTK_TYPE_RECENT_CHOOSER, + _gtk_recent_chooser_delegate_iface_init)); + +static void +gtk_recent_chooser_dialog_class_init (GtkRecentChooserDialogClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gobject_class->set_property = gtk_recent_chooser_dialog_set_property; + gobject_class->get_property = gtk_recent_chooser_dialog_get_property; + gobject_class->constructor = gtk_recent_chooser_dialog_constructor; + gobject_class->finalize = gtk_recent_chooser_dialog_finalize; + + widget_class->map = gtk_recent_chooser_dialog_map; + widget_class->unmap = gtk_recent_chooser_dialog_unmap; + widget_class->style_set = gtk_recent_chooser_dialog_style_set; + + _gtk_recent_chooser_install_properties (gobject_class); + + g_type_class_add_private (klass, sizeof (GtkRecentChooserDialogPrivate)); +} + +static void +gtk_recent_chooser_dialog_init (GtkRecentChooserDialog *dialog) +{ + GtkRecentChooserDialogPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (dialog, + GTK_TYPE_RECENT_CHOOSER_DIALOG, + GtkRecentChooserDialogPrivate); + + dialog->priv = priv; + + gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE); +} + +/* we intercept the GtkRecentChooser::item_activated signal and try to + * make the dialog emit a valid response signal + */ +static void +gtk_recent_chooser_item_activated_cb (GtkRecentChooser *chooser, + gpointer user_data) +{ + GtkRecentChooserDialog *dialog; + GList *children, *l; + + dialog = GTK_RECENT_CHOOSER_DIALOG (user_data); + + if (gtk_window_activate_default (GTK_WINDOW (dialog))) + return; + + children = gtk_container_get_children (GTK_CONTAINER (GTK_DIALOG (dialog)->action_area)); + + for (l = children; l; l = l->next) + { + GtkWidget *widget; + gint response_id; + + widget = GTK_WIDGET (l->data); + response_id = gtk_dialog_get_response_for_widget (GTK_DIALOG (dialog), widget); + + if (response_id == GTK_RESPONSE_ACCEPT || + response_id == GTK_RESPONSE_OK || + response_id == GTK_RESPONSE_YES || + response_id == GTK_RESPONSE_APPLY) + { + g_list_free (children); + + gtk_dialog_response (GTK_DIALOG (dialog), response_id); + + return; + } + } + + g_list_free (children); +} + +static GObject * +gtk_recent_chooser_dialog_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GtkRecentChooserDialogPrivate *priv; + + object = G_OBJECT_CLASS (gtk_recent_chooser_dialog_parent_class)->constructor (type, + n_construct_properties, + construct_params); + priv = GTK_RECENT_CHOOSER_DIALOG_GET_PRIVATE (object); + + gtk_widget_push_composite_child (); + + if (priv->manager) + priv->chooser = g_object_new (GTK_TYPE_RECENT_CHOOSER_WIDGET, + "recent-manager", priv->manager, + NULL); + else + priv->chooser = g_object_new (GTK_TYPE_RECENT_CHOOSER_WIDGET, NULL); + + g_signal_connect (priv->chooser, "item-activated", + G_CALLBACK (gtk_recent_chooser_item_activated_cb), + object); + + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (object)->vbox), + priv->chooser, TRUE, TRUE, 0); + gtk_widget_show (priv->chooser); + + _gtk_recent_chooser_set_delegate (GTK_RECENT_CHOOSER (object), + GTK_RECENT_CHOOSER (priv->chooser)); + + gtk_widget_pop_composite_child (); + + return object; +} + +static void +gtk_recent_chooser_dialog_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkRecentChooserDialogPrivate *priv; + + priv = GTK_RECENT_CHOOSER_DIALOG_GET_PRIVATE (object); + + switch (prop_id) + { + case GTK_RECENT_CHOOSER_PROP_RECENT_MANAGER: + priv->manager = g_value_get_object (value); + break; + default: + g_object_set_property (G_OBJECT (priv->chooser), pspec->name, value); + break; + } +} + +static void +gtk_recent_chooser_dialog_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkRecentChooserDialogPrivate *priv; + + priv = GTK_RECENT_CHOOSER_DIALOG_GET_PRIVATE (object); + + g_object_get_property (G_OBJECT (priv->chooser), pspec->name, value); +} + +static void +gtk_recent_chooser_dialog_finalize (GObject *object) +{ + GtkRecentChooserDialog *dialog = GTK_RECENT_CHOOSER_DIALOG (object); + + dialog->priv->manager = NULL; + + G_OBJECT_CLASS (gtk_recent_chooser_dialog_parent_class)->finalize (object); +} + +static void +gtk_recent_chooser_dialog_map (GtkWidget *widget) +{ + GtkRecentChooserDialog *dialog = GTK_RECENT_CHOOSER_DIALOG (widget); + GtkRecentChooserDialogPrivate *priv = dialog->priv; + + if (!GTK_WIDGET_MAPPED (priv->chooser)) + gtk_widget_map (priv->chooser); + + GTK_WIDGET_CLASS (gtk_recent_chooser_dialog_parent_class)->map (widget); +} + +static void +gtk_recent_chooser_dialog_unmap (GtkWidget *widget) +{ + GtkRecentChooserDialog *dialog = GTK_RECENT_CHOOSER_DIALOG (widget); + GtkRecentChooserDialogPrivate *priv = dialog->priv; + + GTK_WIDGET_CLASS (gtk_recent_chooser_dialog_parent_class)->unmap (widget); + + gtk_widget_unmap (priv->chooser); +} + +/* taken from gtkfilechooserdialog.c */ +static void +gtk_recent_chooser_dialog_style_set (GtkWidget *widget, + GtkStyle *old_style) +{ + GtkDialog *dialog; + + dialog = GTK_DIALOG (widget); + + /* Override the style properties with HIG-compliant spacings. Ugh. + * http://developer.gnome.org/projects/gup/hig/1.0/layout.html#layout-dialogs + * http://developer.gnome.org/projects/gup/hig/1.0/windows.html#alert-spacing + */ + + gtk_container_set_border_width (GTK_CONTAINER (dialog->vbox), 12); + gtk_box_set_spacing (GTK_BOX (dialog->vbox), 24); + + gtk_container_set_border_width (GTK_CONTAINER (dialog->action_area), 0); + gtk_box_set_spacing (GTK_BOX (dialog->action_area), 6); + + if (GTK_WIDGET_CLASS (gtk_recent_chooser_dialog_parent_class)->style_set) + GTK_WIDGET_CLASS (gtk_recent_chooser_dialog_parent_class)->style_set (widget, old_style); +} + +static GtkWidget * +gtk_recent_chooser_dialog_new_valist (const gchar *title, + GtkWindow *parent, + GtkRecentManager *manager, + const gchar *first_button_text, + va_list varargs) +{ + GtkWidget *result; + const char *button_text = first_button_text; + gint response_id; + + result = g_object_new (GTK_TYPE_RECENT_CHOOSER_DIALOG, + "title", title, + "recent-manager", manager, + NULL); + + if (parent) + gtk_window_set_transient_for (GTK_WINDOW (result), parent); + + while (button_text) + { + response_id = va_arg (varargs, gint); + gtk_dialog_add_button (GTK_DIALOG (result), button_text, response_id); + button_text = va_arg (varargs, const gchar *); + } + + return result; +} + +/** + * gtk_recent_chooser_dialog_new: + * @title: Title of the dialog, or %NULL + * @parent: Transient parent of the dialog, or %NULL, + * @first_button_text: stock ID or text to go in the first button, or %NULL + * @Varargs: response ID for the first button, then additional (button, id) + * pairs, ending with %NULL + * + * Creates a new #GtkRecentChooserDialog. This function is analogous to + * gtk_dialog_new_with_buttons(). + * + * Return value: a new #GtkRecentChooserDialog + * + * Since: 2.10 + */ +GtkWidget * +gtk_recent_chooser_dialog_new (const gchar *title, + GtkWindow *parent, + const gchar *first_button_text, + ...) +{ + GtkWidget *result; + va_list varargs; + + va_start (varargs, first_button_text); + result = gtk_recent_chooser_dialog_new_valist (title, + parent, + NULL, + first_button_text, + varargs); + va_end (varargs); + + return result; +} + +/** + * gtk_recent_chooser_dialog_new_for_manager: + * @title: Title of the dialog, or %NULL + * @parent: Transient parent of the dialog, or %NULL, + * @manager: a #GtkRecentManager + * @first_button_text: stock ID or text to go in the first button, or %NULL + * @Varargs: response ID for the first button, then additional (button, id) + * pairs, ending with %NULL + * + * Creates a new #GtkRecentChooserDialog with a specified recent manager. + * + * This is useful if you have implemented your own recent manager, or if you + * have a customized instance of a #GtkRecentManager object. + * + * Return value: a new #GtkRecentChooserDialog + * + * Since: 2.10 + */ +GtkWidget * +gtk_recent_chooser_dialog_new_for_manager (const gchar *title, + GtkWindow *parent, + GtkRecentManager *manager, + const gchar *first_button_text, + ...) +{ + GtkWidget *result; + va_list varargs; + + va_start (varargs, first_button_text); + result = gtk_recent_chooser_dialog_new_valist (title, + parent, + manager, + first_button_text, + varargs); + va_end (varargs); + + return result; +} + +#define __GTK_RECENT_CHOOSER_DIALOG_C__ +#include "gtkaliasdef.c" diff --git a/gtk/gtkrecentchooserdialog.h b/gtk/gtkrecentchooserdialog.h new file mode 100644 index 0000000000..22be218d0b --- /dev/null +++ b/gtk/gtkrecentchooserdialog.h @@ -0,0 +1,70 @@ +/* GTK - The GIMP Toolkit + * gtkrecentchooserdialog.h: Recent files selector dialog + * Copyright (C) 2006 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_CHOOSER_DIALOG_H__ +#define __GTK_RECENT_CHOOSER_DIALOG_H__ + +#include +#include "gtkrecentchooser.h" + +G_BEGIN_DECLS + +#define GTK_TYPE_RECENT_CHOOSER_DIALOG (gtk_recent_chooser_dialog_get_type ()) +#define GTK_RECENT_CHOOSER_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_RECENT_CHOOSER_DIALOG, GtkRecentChooserDialog)) +#define GTK_IS_RECENT_CHOOSER_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_RECENT_CHOOSER_DIALOG)) +#define GTK_RECENT_CHOOSER_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_RECENT_CHOOSER_DIALOG, GtkRecentChooserDialogClass)) +#define GTK_IS_RECENT_CHOOSER_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_RECENT_CHOOSER_DIALOG)) +#define GTK_RECENT_CHOOSER_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_RECENT_CHOOSER_DIALOG, GtkRecentChooserDialogClass)) + +typedef struct _GtkRecentChooserDialog GtkRecentChooserDialog; +typedef struct _GtkRecentChooserDialogClass GtkRecentChooserDialogClass; + +typedef struct _GtkRecentChooserDialogPrivate GtkRecentChooserDialogPrivate; + + +struct _GtkRecentChooserDialog +{ + /*< private >*/ + GtkDialog parent_instance; + + GtkRecentChooserDialogPrivate *priv; +}; + +struct _GtkRecentChooserDialogClass +{ + GtkDialogClass parent_class; +}; + + +GType gtk_recent_chooser_dialog_get_type (void) G_GNUC_CONST; + +GtkWidget *gtk_recent_chooser_dialog_new (const gchar *title, + GtkWindow *parent, + const gchar *first_button_text, + ...) G_GNUC_NULL_TERMINATED; +GtkWidget *gtk_recent_chooser_dialog_new_for_manager (const gchar *title, + GtkWindow *parent, + GtkRecentManager *manager, + const gchar *first_button_text, + ...) G_GNUC_NULL_TERMINATED; + +G_END_DECLS + +#endif /* __GTK_RECENT_CHOOSER_DIALOG_H__ */ diff --git a/gtk/gtkrecentchoosermenu.c b/gtk/gtkrecentchoosermenu.c new file mode 100644 index 0000000000..90542d91b5 --- /dev/null +++ b/gtk/gtkrecentchoosermenu.c @@ -0,0 +1,1225 @@ +/* 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" + +#include + +#include + +#include "gtkstock.h" +#include "gtkicontheme.h" +#include "gtkiconfactory.h" +#include "gtkintl.h" +#include "gtksettings.h" +#include "gtkmenushell.h" +#include "gtkmenuitem.h" +#include "gtkimagemenuitem.h" +#include "gtkseparatormenuitem.h" +#include "gtkmenu.h" +#include "gtkimage.h" +#include "gtkobject.h" +#include "gtktooltips.h" +#include "gtktypebuiltins.h" +#include "gtkalias.h" + +#include "gtkrecentmanager.h" +#include "gtkrecentfilter.h" +#include "gtkrecentchooser.h" +#include "gtkrecentchooserutils.h" +#include "gtkrecentchooserprivate.h" +#include "gtkrecentchoosermenu.h" + +struct _GtkRecentChooserMenuPrivate +{ + /* the recent manager object */ + GtkRecentManager *manager; + + /* size of the icons of the menu items */ + gint icon_size; + + /* 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; + + /* tooltips for our bookmark items*/ + GtkTooltips *tooltips; +}; + +enum { + PROP_0, + + PROP_SHOW_NUMBERS +}; + +#define FALLBACK_ICON_SIZE 32 +#define FALLBACK_ITEM_LIMIT 10 + +#define GTK_RECENT_CHOOSER_MENU_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_RECENT_CHOOSER_MENU, GtkRecentChooserMenuPrivate)) + +static void gtk_recent_chooser_menu_finalize (GObject *object); +static GObject *gtk_recent_chooser_menu_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params); + +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_map (GtkWidget *widget); +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 chooser_set_sort_type (GtkRecentChooserMenu *menu, + GtkRecentSortType sort_type); + +static gint get_icon_size_for_widget (GtkWidget *widget); + +static void item_activate_cb (GtkWidget *widget, + gpointer user_data); +static void manager_changed_cb (GtkRecentManager *manager, + gpointer user_data); + +G_DEFINE_TYPE_WITH_CODE (GtkRecentChooserMenu, + gtk_recent_chooser_menu, + GTK_TYPE_MENU, + G_IMPLEMENT_INTERFACE (GTK_TYPE_RECENT_CHOOSER, + gtk_recent_chooser_iface_init)); + + +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_menu_class_init (GtkRecentChooserMenuClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gobject_class->constructor = gtk_recent_chooser_menu_constructor; + 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; + + widget_class->map = gtk_recent_chooser_menu_map; + + _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, + G_PARAM_READWRITE)); + + g_type_class_add_private (klass, sizeof (GtkRecentChooserMenuPrivate)); +} + +static void +gtk_recent_chooser_menu_init (GtkRecentChooserMenu *menu) +{ + GtkRecentChooserMenuPrivate *priv; + + priv = GTK_RECENT_CHOOSER_MENU_GET_PRIVATE (menu); + + menu->priv = priv; + + priv->show_icons= TRUE; + priv->show_numbers = FALSE; + priv->show_tips = FALSE; + priv->show_not_found = FALSE; + priv->show_private = FALSE; + priv->local_only = TRUE; + + priv->limit = FALLBACK_ITEM_LIMIT; + + priv->sort_type = GTK_RECENT_SORT_NONE; + + priv->icon_size = FALLBACK_ICON_SIZE; + + priv->current_filter = NULL; + + priv->tooltips = gtk_tooltips_new (); + g_object_ref_sink (priv->tooltips); +} + +static void +gtk_recent_chooser_menu_finalize (GObject *object) +{ + GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (object); + GtkRecentChooserMenuPrivate *priv = menu->priv; + + g_signal_handler_disconnect (priv->manager, priv->manager_changed_id); + priv->manager_changed_id = 0; + + 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; + } + + if (priv->tooltips) + g_object_unref (priv->tooltips); + + if (priv->current_filter) + g_object_unref (priv->current_filter); + + G_OBJECT_CLASS (gtk_recent_chooser_menu_parent_class)->finalize; +} + +static GObject * +gtk_recent_chooser_menu_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GtkRecentChooserMenu *menu; + GObject *object; + + object = G_OBJECT_CLASS (gtk_recent_chooser_menu_parent_class)->constructor (type, + n_construct_properties, + construct_params); + menu = GTK_RECENT_CHOOSER_MENU (object); + + g_assert (menu->priv->manager); + + return object; +} + +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); + + switch (prop_id) + { + case PROP_SHOW_NUMBERS: + menu->priv->show_numbers = g_value_get_boolean (value); + 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: + menu->priv->show_private = g_value_get_boolean (value); + break; + case GTK_RECENT_CHOOSER_PROP_SHOW_NOT_FOUND: + menu->priv->show_not_found = g_value_get_boolean (value); + 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: + menu->priv->show_icons = g_value_get_boolean (value); + break; + case GTK_RECENT_CHOOSER_PROP_SELECT_MULTIPLE: + g_warning ("%s: RecentChoosers of type `%s' do not support " + "selecting multiple items.", + G_STRFUNC, + G_OBJECT_TYPE_NAME (object)); + break; + case GTK_RECENT_CHOOSER_PROP_LOCAL_ONLY: + menu->priv->local_only = g_value_get_boolean (value); + break; + case GTK_RECENT_CHOOSER_PROP_LIMIT: + menu->priv->limit = g_value_get_int (value); + break; + case GTK_RECENT_CHOOSER_PROP_SORT_TYPE: + chooser_set_sort_type (menu, g_value_get_enum (value)); + break; + case GTK_RECENT_CHOOSER_PROP_FILTER: + gtk_recent_chooser_menu_set_current_filter (menu, g_value_get_object (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); + + switch (prop_id) + { + case PROP_SHOW_NUMBERS: + g_value_set_boolean (value, menu->priv->show_numbers); + break; + case GTK_RECENT_CHOOSER_PROP_SHOW_TIPS: + g_value_set_boolean (value, menu->priv->show_tips); + break; + case GTK_RECENT_CHOOSER_PROP_LIMIT: + g_value_set_int (value, menu->priv->limit); + break; + case GTK_RECENT_CHOOSER_PROP_LOCAL_ONLY: + g_value_set_boolean (value, menu->priv->local_only); + case GTK_RECENT_CHOOSER_PROP_SORT_TYPE: + g_value_set_enum (value, menu->priv->sort_type); + break; + case GTK_RECENT_CHOOSER_PROP_SHOW_PRIVATE: + g_value_set_boolean (value, menu->priv->show_private); + break; + case GTK_RECENT_CHOOSER_PROP_SHOW_NOT_FOUND: + g_value_set_boolean (value, menu->priv->show_not_found); + break; + case GTK_RECENT_CHOOSER_PROP_SHOW_ICONS: + g_value_set_boolean (value, menu->priv->show_icons); + break; + case GTK_RECENT_CHOOSER_PROP_SELECT_MULTIPLE: + g_warning ("%s: Recent Choosers of type `%s' do not support " + "selecting multiple items.", + G_STRFUNC, + G_OBJECT_TYPE_NAME (object)); + break; + case GTK_RECENT_CHOOSER_PROP_FILTER: + g_value_set_object (value, menu->priv->current_filter); + 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; + + 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_activate_item (GTK_MENU_SHELL (menu), menu_item, TRUE); + + return TRUE; + } +} + +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_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 gint +sort_recent_items_mru (GtkRecentInfo *a, + GtkRecentInfo *b, + gpointer unused) +{ + g_assert (a != NULL && b != NULL); + + return (gtk_recent_info_get_modified (a) < gtk_recent_info_get_modified (b)); +} + +static gint +sort_recent_items_lru (GtkRecentInfo *a, + GtkRecentInfo *b, + gpointer unused) +{ + g_assert (a != NULL && b != NULL); + + return (gtk_recent_info_get_modified (a) > gtk_recent_info_get_modified (b)); +} + +/* our proxy sorting function */ +static gint +sort_recent_items_proxy (gpointer *a, + gpointer *b, + gpointer user_data) +{ + GtkRecentInfo *info_a = (GtkRecentInfo *) a; + GtkRecentInfo *info_b = (GtkRecentInfo *) b; + GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (user_data); + + if (menu->priv->sort_func) + return (* menu->priv->sort_func) (info_a, + info_b, + menu->priv->sort_data); + + /* fallback */ + return 0; +} + +static void +chooser_set_sort_type (GtkRecentChooserMenu *menu, + GtkRecentSortType sort_type) +{ + if (menu->priv->sort_type == sort_type) + return; + + menu->priv->sort_type = sort_type; +} + + +static GList * +gtk_recent_chooser_menu_get_items (GtkRecentChooser *chooser) +{ + GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (chooser); + GtkRecentChooserMenuPrivate *priv; + gint limit; + GtkRecentSortType sort_type; + GList *items; + GCompareDataFunc compare_func; + gint length; + + priv = menu->priv; + + if (!priv->manager) + return NULL; + + limit = gtk_recent_chooser_get_limit (chooser); + sort_type = gtk_recent_chooser_get_sort_type (chooser); + + switch (sort_type) + { + case GTK_RECENT_SORT_NONE: + compare_func = NULL; + break; + case GTK_RECENT_SORT_MRU: + compare_func = (GCompareDataFunc) sort_recent_items_mru; + break; + case GTK_RECENT_SORT_LRU: + compare_func = (GCompareDataFunc) sort_recent_items_lru; + break; + case GTK_RECENT_SORT_CUSTOM: + compare_func = (GCompareDataFunc) sort_recent_items_proxy; + break; + default: + g_assert_not_reached (); + break; + } + + items = gtk_recent_manager_get_items (priv->manager); + if (!items) + return NULL; + + if (compare_func) + items = g_list_sort_with_data (items, compare_func, menu); + + length = g_list_length (items); + if ((limit != -1) && (length > limit)) + { + GList *clamp, *l; + + clamp = g_list_nth (items, limit - 1); + + l = clamp->next; + clamp->next = NULL; + + g_list_foreach (l, (GFunc) gtk_recent_info_unref, NULL); + g_list_free (l); + } + + return items; +} + +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) +{ + 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_remove_filter (GtkRecentChooser *chooser, + GtkRecentFilter *filter) +{ + g_warning (_("This function is not implemented for " + "widgets of class '%s'"), + g_type_name (G_OBJECT_TYPE (chooser))); +} + +static GSList * +gtk_recent_chooser_menu_list_filters (GtkRecentChooser *chooser) +{ + g_warning (_("This function is not implemented for " + "widgets of class '%s'"), + g_type_name (G_OBJECT_TYPE (chooser))); + + return NULL; +} + +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; + g_object_ref_sink (priv->current_filter); + + g_object_notify (G_OBJECT (menu), "filter"); +} + +static gboolean +get_is_recent_filtered (GtkRecentChooserMenu *menu, + GtkRecentInfo *info) +{ + GtkRecentChooserMenuPrivate *priv; + GtkRecentFilter *current_filter; + GtkRecentFilterInfo filter_info; + GtkRecentFilterFlags needed; + gboolean retval; + + g_assert (info != NULL); + + priv = menu->priv; + + if (!priv->current_filter) + return FALSE; + + current_filter = priv->current_filter; + needed = gtk_recent_filter_get_needed (current_filter); + + filter_info.contains = GTK_RECENT_FILTER_URI | GTK_RECENT_FILTER_MIME_TYPE; + + filter_info.uri = gtk_recent_info_get_uri (info); + filter_info.mime_type = gtk_recent_info_get_mime_type (info); + + if (needed & GTK_RECENT_FILTER_DISPLAY_NAME) + { + filter_info.display_name = gtk_recent_info_get_display_name (info); + filter_info.contains |= GTK_RECENT_FILTER_DISPLAY_NAME; + } + else + filter_info.uri = NULL; + + if (needed & GTK_RECENT_FILTER_APPLICATION) + { + filter_info.applications = (const gchar **) gtk_recent_info_get_applications (info, NULL); + filter_info.contains |= GTK_RECENT_FILTER_APPLICATION; + } + else + filter_info.applications = NULL; + + if (needed & GTK_RECENT_FILTER_GROUP) + { + filter_info.groups = (const gchar **) gtk_recent_info_get_groups (info, NULL); + filter_info.contains |= GTK_RECENT_FILTER_GROUP; + } + else + filter_info.groups = NULL; + + if (needed & GTK_RECENT_FILTER_AGE) + { + filter_info.age = gtk_recent_info_get_age (info); + filter_info.contains |= GTK_RECENT_FILTER_AGE; + } + else + filter_info.age = -1; + + retval = gtk_recent_filter_filter (current_filter, &filter_info); + + /* this we own */ + if (filter_info.applications) + g_strfreev ((gchar **) filter_info.applications); + + return !retval; +} + +/* 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, *tip_text; + + g_assert (info != NULL); + g_assert (item != NULL); + + priv = menu->priv; + + if (!priv->tooltips) + return; + + path = gtk_recent_info_get_uri_display (info); + + tip_text = g_strdup_printf (_("Open '%s'"), path); + + gtk_tooltips_set_tip (priv->tooltips, + item, + tip_text, + NULL); + + g_free (path); + g_free (tip_text); +} + +static GtkWidget * +gtk_recent_chooser_menu_create_item (GtkRecentChooserMenu *menu, + GtkRecentInfo *info, + gint count) +{ + GtkRecentChooserMenuPrivate *priv; + gchar *label; + GtkWidget *item, *image; + GdkPixbuf *icon; + + 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) + label = g_strdup_printf ("_%d. %s", count, escaped); + else + label = g_strdup_printf ("%d. %s", count, escaped); + + item = gtk_image_menu_item_new_with_mnemonic (label); + + g_free (escaped); + g_free (name); + } + else + { + label = g_strdup (gtk_recent_info_get_display_name (info)); + item = gtk_image_menu_item_new_with_label (label); + } + + if (priv->show_icons) + { + icon = gtk_recent_info_get_icon (info, priv->icon_size); + + image = gtk_image_new_from_pixbuf (icon); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image); + } + + if (!gtk_recent_info_exists (info)) + { + gtk_widget_set_sensitive (item, FALSE); + + goto out; + } + + g_signal_connect (item, "activate", + G_CALLBACK (item_activate_cb), + menu); + +out: + g_free (label); + + return item; +} + +/* 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); + gint mark = 0; + + /* check for our mark, in order to remove just the items we own */ + mark = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu_item), + "gtk-recent-menu-mark")); + if (mark == 1) + { + 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); + } + } + + g_list_free (children); +} + +/* GtkWidget::map method override + * + * We override this method in order to populate the menu with our + * menu items linked to the recently used resources. + */ +static void +gtk_recent_chooser_menu_map (GtkWidget *widget) +{ + GtkRecentChooserMenu *menu = GTK_RECENT_CHOOSER_MENU (widget); + GtkRecentChooserMenuPrivate *priv = menu->priv; + GList *items, *l; + gint count; + gboolean has_items = FALSE; + + if (GTK_WIDGET_CLASS (gtk_recent_chooser_menu_parent_class)->map) + GTK_WIDGET_CLASS (gtk_recent_chooser_menu_parent_class)->map (widget); + + priv->icon_size = get_icon_size_for_widget (widget); + + /* dispose our menu items first */ + gtk_recent_chooser_menu_dispose_items (menu); + + items = gtk_recent_chooser_get_items (GTK_RECENT_CHOOSER (menu)); + + count = g_list_length (items); + items = g_list_reverse (items); + + for (l = items; l != NULL; l = l->next) + { + GtkRecentInfo *info = (GtkRecentInfo *) l->data; + GtkWidget *item; + + g_assert (info != NULL); + + /* skip non-local items on request */ + if (priv->local_only && !gtk_recent_info_is_local (info)) + continue; + + /* skip private items on request */ + if (!priv->show_private && gtk_recent_info_get_private_hint (info)) + continue; + + /* skip non-existing items on request */ + if (!priv->show_not_found && !gtk_recent_info_exists (info)) + continue; + + /* filter items based on the currently set filter object */ + if (get_is_recent_filtered (menu, info)) + continue; + + item = gtk_recent_chooser_menu_create_item (menu, info, count); + if (!item) + continue; + + gtk_recent_chooser_menu_add_tip (menu, info, item); + + /* FIXME + * + * We should really place our items taking into account user + * defined menu items; this would also remove the need of + * reverting the scan order. + */ + gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item); + gtk_widget_show (item); + + /* mark the menu item as one of our own */ + g_object_set_data (G_OBJECT (item), "gtk-recent-menu-mark", + GINT_TO_POINTER (1)); + + /* 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); + + /* we have at least one item */ + if (!has_items) + has_items = TRUE; + } + + /* now, the RecentInfo objects are bound to the lifetime of the menu */ + if (items) + { + g_list_foreach (items, + (GFunc) gtk_recent_info_unref, + NULL); + g_list_free (items); + } + + /* no recently used resources were found, or they were filtered out, so + * we build an item stating that no recently used resources were found + * (as night follows the day...). + */ + if (!has_items) + { + GtkWidget *item; + + item = gtk_menu_item_new_with_label ("No items found"); + gtk_widget_set_sensitive (item, FALSE); + + /* we also mark this item, so that it gets removed when rebuilding + * the menu on the next map event + */ + g_object_set_data (G_OBJECT (item), "gtk-recent-menu-mark", + GINT_TO_POINTER (1)); + + gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item); + gtk_widget_show (item); + } +} + +/* 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); + + _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) +{ + gtk_widget_queue_draw (GTK_WIDGET (user_data)); +} + +static void +set_recent_manager (GtkRecentChooserMenu *menu, + GtkRecentManager *manager) +{ + if (menu->priv->manager) + g_signal_handler_disconnect (menu, menu->priv->manager_changed_id); + + menu->priv->manager = NULL; + + if (manager) + menu->priv->manager = manager; + else + menu->priv->manager = gtk_recent_manager_get_default (); + + if (menu->priv->manager) + menu->priv->manager_changed_id = g_signal_connect (menu->priv->manager, "changed", + G_CALLBACK (manager_changed_cb), + menu); +} + +static gint +get_icon_size_for_widget (GtkWidget *widget) +{ + GtkSettings *settings; + gint width, height; + + if (gtk_widget_has_screen (widget)) + settings = gtk_settings_get_for_screen (gtk_widget_get_screen (widget)); + else + settings = gtk_settings_get_default (); + + if (gtk_icon_size_lookup_for_settings (settings, GTK_ICON_SIZE_MENU, + &width, &height)) + return MAX (width, height); + + return FALLBACK_ICON_SIZE; +} + +static void +gtk_recent_chooser_menu_set_show_tips (GtkRecentChooserMenu *menu, + gboolean show_tips) +{ + if (menu->priv->show_tips == show_tips) + return; + + g_assert (menu->priv->tooltips != NULL); + + if (show_tips) + gtk_tooltips_enable (menu->priv->tooltips); + else + gtk_tooltips_disable (menu->priv->tooltips); + + menu->priv->show_tips = show_tips; +} + +/* + * 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. + * + * Return value: 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. + * + * Return value: a new #GtkRecentChooserMenu, bound to @manager. + * + * Since: 2.10 + */ +GtkWidget * +gtk_recent_chooser_menu_new_for_manager (GtkRecentManager *manager) +{ + g_return_val_if_fail (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(). + * + * Return value: %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 the menu item's 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"); +} + +#define __GTK_RECENT_CHOOSER_MENU_C__ +#include "gtkaliasdef.c" diff --git a/gtk/gtkrecentchoosermenu.h b/gtk/gtkrecentchoosermenu.h new file mode 100644 index 0000000000..200233f9ee --- /dev/null +++ b/gtk/gtkrecentchoosermenu.h @@ -0,0 +1,70 @@ +/* GTK - The GIMP Toolkit + * gtkrecentchoosermenu.h - Recently used items menu widget + * Copyright (C) 2006, 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_CHOOSER_MENU_H__ +#define __GTK_RECENT_CHOOSER_MENU_H__ + +#include +#include "gtkrecentchooser.h" + +G_BEGIN_DECLS + +#define GTK_TYPE_RECENT_CHOOSER_MENU (gtk_recent_chooser_menu_get_type ()) +#define GTK_RECENT_CHOOSER_MENU(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_RECENT_CHOOSER_MENU, GtkRecentChooserMenu)) +#define GTK_IS_RECENT_CHOOSER_MENU(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_RECENT_CHOOSER_MENU)) +#define GTK_RECENT_CHOOSER_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_RECENT_CHOOSER_MENU, GtkRecentChooserMenuClass)) +#define GTK_IS_RECENT_CHOOSER_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_RECENT_CHOOSER_MENU)) +#define GTK_RECENT_CHOOSER_MENU_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_RECENT_CHOOSER_MENU, GtkRecentChooserMenuClass)) + +typedef struct _GtkRecentChooserMenu GtkRecentChooserMenu; +typedef struct _GtkRecentChooserMenuClass GtkRecentChooserMenuClass; +typedef struct _GtkRecentChooserMenuPrivate GtkRecentChooserMenuPrivate; + +struct _GtkRecentChooserMenu +{ + /*< private >*/ + GtkMenu parent_instance; + + GtkRecentChooserMenuPrivate *priv; +}; + +struct _GtkRecentChooserMenuClass +{ + GtkMenuClass parent_class; + + /* padding for future expansion */ + void (* gtk_recent1) (void); + void (* gtk_recent2) (void); + void (* gtk_recent3) (void); + void (* gtk_recent4) (void); +}; + +GType gtk_recent_chooser_menu_get_type (void) G_GNUC_CONST; + +GtkWidget *gtk_recent_chooser_menu_new (void); +GtkWidget *gtk_recent_chooser_menu_new_for_manager (GtkRecentManager *manager); + +gboolean gtk_recent_chooser_menu_get_show_numbers (GtkRecentChooserMenu *menu); +void gtk_recent_chooser_menu_set_show_numbers (GtkRecentChooserMenu *menu, + gboolean show_numbers); + +G_END_DECLS + +#endif /* ! __GTK_RECENT_CHOOSER_MENU_H__ */ diff --git a/gtk/gtkrecentchooserprivate.h b/gtk/gtkrecentchooserprivate.h new file mode 100644 index 0000000000..fcd3c832d6 --- /dev/null +++ b/gtk/gtkrecentchooserprivate.h @@ -0,0 +1,42 @@ +/* gtkrecentprivatechooser.h - Interface definitions for recent selectors UI + * + * Copyright (C) 2006 Emmanuele Bassi + * + * All rights reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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_CHOOSER_PRIVATE_H__ +#define __GTK_RECENT_CHOOSER_PRIVATE_H__ + +#include + +#include "gtkrecentmanager.h" +#include "gtkrecentchooser.h" + +G_BEGIN_DECLS + +#define GTK_DEFAULT_RECENT_MANAGER "gtk-recent-manager-default" + +GtkRecentManager *_gtk_recent_chooser_get_recent_manager (GtkRecentChooser *chooser); + +void _gtk_recent_chooser_item_activated (GtkRecentChooser *chooser); +void _gtk_recent_chooser_selection_changed (GtkRecentChooser *chooser); + +G_END_DECLS + +#endif /* ! __GTK_RECENT_CHOOSER_PRIVATE_H__ */ diff --git a/gtk/gtkrecentchooserutils.c b/gtk/gtkrecentchooserutils.c new file mode 100644 index 0000000000..b7fa81d5a0 --- /dev/null +++ b/gtk/gtkrecentchooserutils.c @@ -0,0 +1,298 @@ +/* gtkrecentchooserutils.h - Private utility functions for implementing a + * GtkRecentChooser interface + * + * Copyright (C) 2006 Emmanuele Bassi + * + * All rights reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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. + * + * Based on gtkfilechooserutils.c: + * Copyright (C) 2003 Red Hat, Inc. + */ + +#include "config.h" + +#include "gtkrecentchooserutils.h" + +/* Methods */ +static void delegate_set_sort_func (GtkRecentChooser *chooser, + GtkRecentSortFunc sort_func, + gpointer sort_data, + GDestroyNotify data_destroy); +static void delegate_add_filter (GtkRecentChooser *chooser, + GtkRecentFilter *filter); +static void delegate_remove_filter (GtkRecentChooser *chooser, + GtkRecentFilter *filter); +static GSList *delegate_list_filters (GtkRecentChooser *chooser); +static gboolean delegate_select_uri (GtkRecentChooser *chooser, + const gchar *uri, + GError **error); +static void delegate_unselect_uri (GtkRecentChooser *chooser, + const gchar *uri); +static GList *delegate_get_items (GtkRecentChooser *chooser); +static GtkRecentManager *delegate_get_recent_manager (GtkRecentChooser *chooser); +static void delegate_select_all (GtkRecentChooser *chooser); +static void delegate_unselect_all (GtkRecentChooser *chooser); +static gboolean delegate_set_current_uri (GtkRecentChooser *chooser, + const gchar *uri, + GError **error); +static gchar * delegate_get_current_uri (GtkRecentChooser *chooser); + +/* Signals */ +static void delegate_notify (GObject *object, + GParamSpec *pspec, + gpointer user_data); +static void delegate_selection_changed (GtkRecentChooser *receiver, + gpointer user_data); +static void delegate_item_activated (GtkRecentChooser *receiver, + gpointer user_data); + +/** + * _gtk_recent_chooser_install_properties: + * @klass: the class structure for a type deriving from #GObject + * + * Installs the necessary properties for a class implementing + * #GtkRecentChooser. A #GtkParamSpecOverride property is installed + * for each property, using the values from the #GtkRecentChooserProp + * enumeration. The caller must make sure itself that the enumeration + * values don't collide with some other property values they + * are using. + */ +void +_gtk_recent_chooser_install_properties (GObjectClass *klass) +{ + g_object_class_override_property (klass, + GTK_RECENT_CHOOSER_PROP_RECENT_MANAGER, + "recent-manager"); + g_object_class_override_property (klass, + GTK_RECENT_CHOOSER_PROP_SHOW_PRIVATE, + "show-private"); + g_object_class_override_property (klass, + GTK_RECENT_CHOOSER_PROP_SHOW_TIPS, + "show-tips"); + g_object_class_override_property (klass, + GTK_RECENT_CHOOSER_PROP_SHOW_ICONS, + "show-icons"); + g_object_class_override_property (klass, + GTK_RECENT_CHOOSER_PROP_SHOW_NOT_FOUND, + "show-not-found"); + g_object_class_override_property (klass, + GTK_RECENT_CHOOSER_PROP_SELECT_MULTIPLE, + "select-multiple"); + g_object_class_override_property (klass, + GTK_RECENT_CHOOSER_PROP_LIMIT, + "limit"); + g_object_class_override_property (klass, + GTK_RECENT_CHOOSER_PROP_LOCAL_ONLY, + "local-only"); + g_object_class_override_property (klass, + GTK_RECENT_CHOOSER_PROP_SORT_TYPE, + "sort-type"); + g_object_class_override_property (klass, + GTK_RECENT_CHOOSER_PROP_FILTER, + "filter"); +} + +/** + * _gtk_recent_chooser_delegate_iface_init: + * @iface: a #GtkRecentChooserIface + * + * An interface-initialization function for use in cases where + * an object is simply delegating the methods, signals of + * the #GtkRecentChooser interface to another object. + * _gtk_recent_chooser_set_delegate() must be called on each + * instance of the object so that the delegate object can + * be found. + */ +void +_gtk_recent_chooser_delegate_iface_init (GtkRecentChooserIface *iface) +{ + iface->set_current_uri = delegate_set_current_uri; + iface->get_current_uri = delegate_get_current_uri; + iface->select_uri = delegate_select_uri; + iface->unselect_uri = delegate_unselect_uri; + iface->select_all = delegate_select_all; + iface->unselect_all = delegate_unselect_all; + iface->get_items = delegate_get_items; + iface->get_recent_manager = delegate_get_recent_manager; + iface->set_sort_func = delegate_set_sort_func; + iface->add_filter = delegate_add_filter; + iface->remove_filter = delegate_remove_filter; + iface->list_filters = delegate_list_filters; +} + +/** + * _gtk_recent_chooser_set_delegate: + * @receiver: a #GObject implementing #GtkRecentChooser + * @delegate: another #GObject implementing #GtkRecentChooser + * + * Establishes that calls on @receiver for #GtkRecentChooser + * methods should be delegated to @delegate, and that + * #GtkRecentChooser signals emitted on @delegate should be + * forwarded to @receiver. Must be used in conjunction with + * _gtk_recent_chooser_delegate_iface_init(). + */ +void +_gtk_recent_chooser_set_delegate (GtkRecentChooser *receiver, + GtkRecentChooser *delegate) +{ + g_return_if_fail (GTK_IS_RECENT_CHOOSER (receiver)); + g_return_if_fail (GTK_IS_RECENT_CHOOSER (delegate)); + + g_object_set_data (G_OBJECT (receiver), + "gtk-recent-chooser-delegate", delegate); + + g_signal_connect (delegate, "notify", + G_CALLBACK (delegate_notify), receiver); + g_signal_connect (delegate, "selection-changed", + G_CALLBACK (delegate_selection_changed), receiver); + g_signal_connect (delegate, "item-activated", + G_CALLBACK (delegate_item_activated), receiver); +} + +GQuark +_gtk_recent_chooser_delegate_get_quark (void) +{ + static GQuark quark = 0; + + if (G_UNLIKELY (quark == 0)) + quark = g_quark_from_static_string ("gtk-recent-chooser-delegate"); + + return quark; +} + +static GtkRecentChooser * +get_delegate (GtkRecentChooser *receiver) +{ + return g_object_get_qdata (G_OBJECT (receiver), + GTK_RECENT_CHOOSER_DELEGATE_QUARK); +} + +static void +delegate_set_sort_func (GtkRecentChooser *chooser, + GtkRecentSortFunc sort_func, + gpointer sort_data, + GDestroyNotify data_destroy) +{ + gtk_recent_chooser_set_sort_func (get_delegate (chooser), + sort_func, + sort_data, + data_destroy); +} + +static void +delegate_add_filter (GtkRecentChooser *chooser, + GtkRecentFilter *filter) +{ + gtk_recent_chooser_add_filter (get_delegate (chooser), filter); +} + +static void +delegate_remove_filter (GtkRecentChooser *chooser, + GtkRecentFilter *filter) +{ + gtk_recent_chooser_remove_filter (get_delegate (chooser), filter); +} + +static GSList * +delegate_list_filters (GtkRecentChooser *chooser) +{ + return gtk_recent_chooser_list_filters (get_delegate (chooser)); +} + +static gboolean +delegate_select_uri (GtkRecentChooser *chooser, + const gchar *uri, + GError **error) +{ + return gtk_recent_chooser_select_uri (get_delegate (chooser), uri, error); +} + +static void +delegate_unselect_uri (GtkRecentChooser *chooser, + const gchar *uri) +{ + return gtk_recent_chooser_unselect_uri (get_delegate (chooser), uri); +} + +static GList * +delegate_get_items (GtkRecentChooser *chooser) +{ + return gtk_recent_chooser_get_items (get_delegate (chooser)); +} + +static GtkRecentManager * +delegate_get_recent_manager (GtkRecentChooser *chooser) +{ + return _gtk_recent_chooser_get_recent_manager (get_delegate (chooser)); +} + +static void +delegate_select_all (GtkRecentChooser *chooser) +{ + gtk_recent_chooser_select_all (get_delegate (chooser)); +} + +static void +delegate_unselect_all (GtkRecentChooser *chooser) +{ + gtk_recent_chooser_unselect_all (get_delegate (chooser)); +} + +static gboolean +delegate_set_current_uri (GtkRecentChooser *chooser, + const gchar *uri, + GError **error) +{ + return gtk_recent_chooser_set_current_uri (get_delegate (chooser), uri, error); +} + +static gchar * +delegate_get_current_uri (GtkRecentChooser *chooser) +{ + return gtk_recent_chooser_get_current_uri (get_delegate (chooser)); +} + +static void +delegate_notify (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + gpointer iface; + + iface = g_type_interface_peek (g_type_class_peek (G_OBJECT_TYPE (object)), + gtk_recent_chooser_get_type ()); + if (g_object_interface_find_property (iface, pspec->name)) + g_object_notify (user_data, pspec->name); +} + +static void +delegate_selection_changed (GtkRecentChooser *receiver, + gpointer user_data) +{ + _gtk_recent_chooser_selection_changed (GTK_RECENT_CHOOSER (user_data)); +} + +static void +delegate_item_activated (GtkRecentChooser *receiver, + gpointer user_data) +{ + _gtk_recent_chooser_item_activated (GTK_RECENT_CHOOSER (user_data)); +} + +#define __GTK_RECENT_CHOOSER_UTILS_H__ +#include "gtkaliasdef.c" diff --git a/gtk/gtkrecentchooserutils.h b/gtk/gtkrecentchooserutils.h new file mode 100644 index 0000000000..79adca301a --- /dev/null +++ b/gtk/gtkrecentchooserutils.h @@ -0,0 +1,63 @@ +/* gtkrecentchooserutils.h - Private utility functions for implementing a + * GtkRecentChooser interface + * + * Copyright (C) 2006 Emmanuele Bassi + * + * All rights reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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. + * + * Based on gtkfilechooserutils.h: + * Copyright (C) 2003 Red Hat, Inc. + */ + +#ifndef __GTK_RECENT_CHOOSER_UTILS_H__ +#define __GTK_RECENT_CHOOSER_UTILS_H__ + +#include "gtkrecentchooserprivate.h" + +G_BEGIN_DECLS + + +#define GTK_RECENT_CHOOSER_DELEGATE_QUARK (_gtk_recent_chooser_delegate_get_quark ()) + +typedef enum { + GTK_RECENT_CHOOSER_PROP_FIRST = 0x3000, + GTK_RECENT_CHOOSER_PROP_RECENT_MANAGER, + GTK_RECENT_CHOOSER_PROP_SHOW_PRIVATE, + GTK_RECENT_CHOOSER_PROP_SHOW_NOT_FOUND, + GTK_RECENT_CHOOSER_PROP_SHOW_TIPS, + GTK_RECENT_CHOOSER_PROP_SHOW_ICONS, + GTK_RECENT_CHOOSER_PROP_SELECT_MULTIPLE, + GTK_RECENT_CHOOSER_PROP_LIMIT, + GTK_RECENT_CHOOSER_PROP_LOCAL_ONLY, + GTK_RECENT_CHOOSER_PROP_SORT_TYPE, + GTK_RECENT_CHOOSER_PROP_FILTER, + GTK_RECENT_CHOOSER_PROP_LAST +} GtkRecentChooserProp; + +void _gtk_recent_chooser_install_properties (GObjectClass *klass); + +void _gtk_recent_chooser_delegate_iface_init (GtkRecentChooserIface *iface); +void _gtk_recent_chooser_set_delegate (GtkRecentChooser *receiver, + GtkRecentChooser *delegate); + +GQuark _gtk_recent_chooser_delegate_get_quark (void) G_GNUC_CONST; + + +G_END_DECLS + +#endif /* __GTK_RECENT_CHOOSER_UTILS_H__ */ diff --git a/gtk/gtkrecentchooserwidget.c b/gtk/gtkrecentchooserwidget.c new file mode 100644 index 0000000000..b3c2115694 --- /dev/null +++ b/gtk/gtkrecentchooserwidget.c @@ -0,0 +1,194 @@ +/* GTK - The GIMP Toolkit + * gtkrecentchooserwidget.c: embeddable recently used resources chooser widget + * Copyright (C) 2006 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 "gtkrecentchooserwidget.h" +#include "gtkrecentchooserdefault.h" +#include "gtkrecentchooserutils.h" +#include "gtktypebuiltins.h" +#include "gtkalias.h" + +struct _GtkRecentChooserWidgetPrivate +{ + GtkRecentManager *manager; + + GtkWidget *chooser; +}; + +#define GTK_RECENT_CHOOSER_WIDGET_GET_PRIVATE(obj) (GTK_RECENT_CHOOSER_WIDGET (obj)->priv) + +static GObject *gtk_recent_chooser_widget_constructor (GType type, + guint n_params, + GObjectConstructParam *params); +static void gtk_recent_chooser_widget_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gtk_recent_chooser_widget_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void gtk_recent_chooser_widget_finalize (GObject *object); + + +G_DEFINE_TYPE_WITH_CODE (GtkRecentChooserWidget, + gtk_recent_chooser_widget, + GTK_TYPE_VBOX, + G_IMPLEMENT_INTERFACE (GTK_TYPE_RECENT_CHOOSER, + _gtk_recent_chooser_delegate_iface_init)); + +static void +gtk_recent_chooser_widget_class_init (GtkRecentChooserWidgetClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->constructor = gtk_recent_chooser_widget_constructor; + gobject_class->set_property = gtk_recent_chooser_widget_set_property; + gobject_class->get_property = gtk_recent_chooser_widget_get_property; + gobject_class->finalize = gtk_recent_chooser_widget_finalize; + + _gtk_recent_chooser_install_properties (gobject_class); + + g_type_class_add_private (klass, sizeof (GtkRecentChooserWidgetPrivate)); +} + + +static void +gtk_recent_chooser_widget_init (GtkRecentChooserWidget *widget) +{ + widget->priv = G_TYPE_INSTANCE_GET_PRIVATE (widget, GTK_TYPE_RECENT_CHOOSER_WIDGET, + GtkRecentChooserWidgetPrivate); +} + +static GObject * +gtk_recent_chooser_widget_constructor (GType type, + guint n_params, + GObjectConstructParam *params) +{ + GObject *object; + GtkRecentChooserWidgetPrivate *priv; + + object = G_OBJECT_CLASS (gtk_recent_chooser_widget_parent_class)->constructor (type, + n_params, + params); + + priv = GTK_RECENT_CHOOSER_WIDGET_GET_PRIVATE (object); + priv->chooser = _gtk_recent_chooser_default_new (priv->manager); + + + gtk_container_add (GTK_CONTAINER (object), priv->chooser); + gtk_widget_show (priv->chooser); + _gtk_recent_chooser_set_delegate (GTK_RECENT_CHOOSER (object), + GTK_RECENT_CHOOSER (priv->chooser)); + + return object; +} + +static void +gtk_recent_chooser_widget_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkRecentChooserWidgetPrivate *priv; + + priv = GTK_RECENT_CHOOSER_WIDGET_GET_PRIVATE (object); + + switch (prop_id) + { + case GTK_RECENT_CHOOSER_PROP_RECENT_MANAGER: + priv->manager = g_value_get_object (value); + break; + default: + g_object_set_property (G_OBJECT (priv->chooser), pspec->name, value); + break; + } +} + +static void +gtk_recent_chooser_widget_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkRecentChooserWidgetPrivate *priv; + + priv = GTK_RECENT_CHOOSER_WIDGET_GET_PRIVATE (object); + + g_object_get_property (G_OBJECT (priv->chooser), pspec->name, value); +} + +static void +gtk_recent_chooser_widget_finalize (GObject *object) +{ + GtkRecentChooserWidgetPrivate *priv; + + priv = GTK_RECENT_CHOOSER_WIDGET_GET_PRIVATE (object); + priv->manager = NULL; + + G_OBJECT_CLASS (gtk_recent_chooser_widget_parent_class)->finalize (object); +} + +/* + * Public API + */ + +/** + * gtk_recent_chooser_widget_new: + * + * Creates a new #GtkRecentChooserWidget object. This is an embeddable widget + * used to access the recently used resources list. + * + * Return value: a new #GtkRecentChooserWidget + * + * Since: 2.10 + */ +GtkWidget * +gtk_recent_chooser_widget_new (void) +{ + return g_object_new (GTK_TYPE_RECENT_CHOOSER_WIDGET, NULL); +} + +/** + * gtk_recent_chooser_widget_new_for_manager: + * @manager: a #GtkRecentManager + * + * Creates a new #GtkRecentChooserWidget with a specified recent manager. + * + * This is useful if you have implemented your own recent manager, or if you + * have a customized instance of a #GtkRecentManager object. + * + * Return value: a new #GtkRecentChooserWidget + * + * Since: 2.10 + */ +GtkWidget * +gtk_recent_chooser_widget_new_for_manager (GtkRecentManager *manager) +{ + g_return_val_if_fail (GTK_IS_RECENT_MANAGER (manager), NULL); + + return g_object_new (GTK_TYPE_RECENT_CHOOSER_WIDGET, + "recent-manager", manager, + NULL); +} + +#define __GTK_RECENT_CHOOSER_WIDGET_C__ +#include "gtkaliasdef.c" diff --git a/gtk/gtkrecentchooserwidget.h b/gtk/gtkrecentchooserwidget.h new file mode 100644 index 0000000000..96aea95f1f --- /dev/null +++ b/gtk/gtkrecentchooserwidget.h @@ -0,0 +1,60 @@ +/* GTK - The GIMP Toolkit + * gtkrecentchooserwidget.h: embeddable recently used resources chooser widget + * Copyright (C) 2006 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_CHOOSER_WIDGET_H__ +#define __GTK_RECENT_CHOOSER_WIDGET_H__ + +#include "gtkrecentchooser.h" +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_RECENT_CHOOSER_WIDGET (gtk_recent_chooser_widget_get_type ()) +#define GTK_RECENT_CHOOSER_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_RECENT_CHOOSER_WIDGET, GtkRecentChooserWidget)) +#define GTK_IS_RECENT_CHOOSER_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_RECENT_CHOOSER_WIDGET)) +#define GTK_RECENT_CHOOSER_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_RECENT_CHOOSER_WIDGET, GtkRecentChooserWidgetClass)) +#define GTK_IS_RECENT_CHOOSER_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_RECENT_CHOOSER_WIDGET)) +#define GTK_RECENT_CHOOSER_WIDGET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_RECENT_CHOOSER_WIDGET, GtkRecentChooserWidgetClass)) + +typedef struct _GtkRecentChooserWidget GtkRecentChooserWidget; +typedef struct _GtkRecentChooserWidgetClass GtkRecentChooserWidgetClass; + +typedef struct _GtkRecentChooserWidgetPrivate GtkRecentChooserWidgetPrivate; + +struct _GtkRecentChooserWidget +{ + /*< private >*/ + GtkVBox parent_instance; + + GtkRecentChooserWidgetPrivate *priv; +}; + +struct _GtkRecentChooserWidgetClass +{ + GtkVBoxClass parent_class; +}; + +GType gtk_recent_chooser_widget_get_type (void) G_GNUC_CONST; +GtkWidget *gtk_recent_chooser_widget_new (void); +GtkWidget *gtk_recent_chooser_widget_new_for_manager (GtkRecentManager *manager); + +G_END_DECLS + +#endif /* __GTK_RECENT_CHOOSER_WIDGET_H__ */ diff --git a/gtk/gtkrecentfilter.c b/gtk/gtkrecentfilter.c new file mode 100644 index 0000000000..58476dead8 --- /dev/null +++ b/gtk/gtkrecentfilter.c @@ -0,0 +1,579 @@ +/* GTK - The GIMP Toolkit + * gtkrecentfilter.h - Filter object for recently used resources + * Copyright (C) 2006, 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 "config.h" +#include + +#include "gtkrecentfilter.h" +#include "gtkobject.h" +#include "gtkintl.h" +#include "gtkprivate.h" + +#include "gtkalias.h" + +#ifdef G_OS_UNIX +#define XDG_PREFIX _gtk_xdg +#include "xdgmime/xdgmime.h" +#endif + +typedef struct _GtkRecentFilterClass GtkRecentFilterClass; +typedef struct _FilterRule FilterRule; + +typedef enum { + FILTER_RULE_URI, + FILTER_RULE_DISPLAY_NAME, + FILTER_RULE_MIME_TYPE, + FILTER_RULE_PIXBUF_FORMATS, + FILTER_RULE_APPLICATION, + FILTER_RULE_AGE, + FILTER_RULE_GROUP, + FILTER_RULE_CUSTOM, +} FilterRuleType; + +struct _GtkRecentFilter +{ + GtkObject parent_instance; + + gchar *name; + GSList *rules; + + GtkRecentFilterFlags needed; +}; + +struct _GtkRecentFilterClass +{ + GtkObjectClass parent_class; +}; + +struct _FilterRule +{ + FilterRuleType type; + GtkRecentFilterFlags needed; + + union { + gchar *uri; + gchar *pattern; + gchar *mime_type; + GSList *pixbuf_formats; + gchar *application; + gchar *group; + gint age; + struct { + GtkRecentFilterFunc func; + gpointer data; + GDestroyNotify data_destroy; + } custom; + } u; +}; + +G_DEFINE_TYPE (GtkRecentFilter, gtk_recent_filter, GTK_TYPE_OBJECT); + + +static void +filter_rule_free (FilterRule *rule) +{ + switch (rule->type) + { + case FILTER_RULE_MIME_TYPE: + g_free (rule->u.mime_type); + break; + case FILTER_RULE_URI: + g_free (rule->u.uri); + break; + case FILTER_RULE_DISPLAY_NAME: + g_free (rule->u.pattern); + break; + case FILTER_RULE_PIXBUF_FORMATS: + g_slist_free (rule->u.pixbuf_formats); + break; + case FILTER_RULE_AGE: + break; + case FILTER_RULE_APPLICATION: + g_free (rule->u.application); + break; + case FILTER_RULE_GROUP: + g_free (rule->u.group); + break; + case FILTER_RULE_CUSTOM: + if (rule->u.custom.data_destroy) + rule->u.custom.data_destroy (rule->u.custom.data); + break; + default: + g_assert_not_reached (); + break; + } + + g_free (rule); +} + +static void +gtk_recent_filter_finalize (GObject *object) +{ + GtkRecentFilter *filter = GTK_RECENT_FILTER (object); + + if (filter->name) + g_free (filter->name); + + if (filter->rules) + { + g_slist_foreach (filter->rules, + (GFunc) filter_rule_free, + NULL); + g_slist_free (filter->rules); + } + + G_OBJECT_CLASS (gtk_recent_filter_parent_class)->finalize (object); +} + +static void +gtk_recent_filter_class_init (GtkRecentFilterClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = gtk_recent_filter_finalize; +} + +static void +gtk_recent_filter_init (GtkRecentFilter *filter) +{ + +} + +/* + * Public API + */ + +/** + * gtk_recent_filter_new: + * + * Creates a new #GtkRecentFilter with no rules added to it. + * Such filter does not accept any recently used resources, so is not + * particularly useful until you add rules with + * gtk_recent_filter_add_pattern(), gtk_recent_filter_add_mime_type(), + * gtk_recent_filter_add_application(), gtk_recent_filter_add_age(). + * To create a filter that accepts any recently used resource, use: + * + * + * GtkRecentFilter *filter = gtk_recent_filter_new (); + * gtk_recent_filter_add_pattern (filter, "*"); + * + * + * Return value: a new #GtkRecentFilter + * + * Since: 2.10 + */ +GtkRecentFilter * +gtk_recent_filter_new (void) +{ + return g_object_new (GTK_TYPE_RECENT_FILTER, NULL); +} + +/** + * gtk_recent_filter_set_name: + * @filter: a #GtkRecentFilter + * @name: then human readable name of @filter + * + * Sets the human-readable name of the filter; this is the string + * that will be displayed in the recently used resources selector + * user interface if there is a selectable list of filters. + * + * Since: 2.10 + */ +void +gtk_recent_filter_set_name (GtkRecentFilter *filter, + const gchar *name) +{ + g_return_if_fail (GTK_IS_RECENT_FILTER (filter)); + + if (filter->name) + g_free (filter->name); + + if (name) + filter->name = g_strdup (name); +} + +/** + * gtk_recent_filter_get_name: + * @filter: a #GtkRecentFilter + * + * Gets the human-readable name for the filter. + * See gtk_recent_filter_set_name(). + * + * Return value: the name of the filter, or %NULL. The returned string + * is owned by the filter object and should not be freed. + * + * Since: 2.10 + */ +G_CONST_RETURN gchar * +gtk_recent_filter_get_name (GtkRecentFilter *filter) +{ + g_return_val_if_fail (GTK_IS_RECENT_FILTER (filter), NULL); + + return filter->name; +} + +/** + * gtk_recent_filter_get_needed: + * @filter: a #GtkRecentFilter + * + * Gets the fields that need to be filled in for the structure + * passed to gtk_recent_filter_filter() + * + * This function will not typically be used by applications; it + * is intended principally for use in the implementation of + * #GtkRecentChooser. + * + * Return value: bitfield of flags indicating needed fields when + * calling gtk_recent_filter_filter() + * + * Since: 2.10 + */ +GtkRecentFilterFlags +gtk_recent_filter_get_needed (GtkRecentFilter *filter) +{ + return filter->needed; +} + +static void +recent_filter_add_rule (GtkRecentFilter *filter, + FilterRule *rule) +{ + filter->needed |= rule->needed; + filter->rules = g_slist_append (filter->rules, rule); +} + +/** + * gtk_recent_filter_add_mime_type: + * @filter: a #GtkRecentFilter + * @mime_type: a MIME type + * + * Adds a rule that allows resources based on their registered MIME type. + * + * Since: 2.10 + */ +void +gtk_recent_filter_add_mime_type (GtkRecentFilter *filter, + const gchar *mime_type) +{ + FilterRule *rule; + + g_return_if_fail (GTK_IS_RECENT_FILTER (filter)); + g_return_if_fail (mime_type != NULL); + + rule = g_new0 (FilterRule, 1); + rule->type = FILTER_RULE_MIME_TYPE; + rule->needed = GTK_RECENT_FILTER_MIME_TYPE; + rule->u.mime_type = g_strdup (mime_type); + + recent_filter_add_rule (filter, rule); +} + +/** + * gtk_recent_filter_add_pattern: + * @filter: a #GtkRecentFilter + * @pattern: a file pattern + * + * Adds a rule that allows resources based on a pattern matching their + * display name. + * + * Since: 2.10 + */ +void +gtk_recent_filter_add_pattern (GtkRecentFilter *filter, + const gchar *pattern) +{ + FilterRule *rule; + + g_return_if_fail (GTK_IS_RECENT_FILTER (filter)); + g_return_if_fail (pattern != NULL); + + rule = g_new0 (FilterRule, 1); + rule->type = FILTER_RULE_DISPLAY_NAME; + rule->needed = GTK_RECENT_FILTER_DISPLAY_NAME; + rule->u.pattern = g_strdup (pattern); + + recent_filter_add_rule (filter, rule); +} + +/** + * gtk_recent_filter_add_pixbuf_formats: + * @filter: a #GtkRecentFilter + * + * Adds a rule allowing image files in the formats supported + * by GdkPixbuf. + * + * Since: 2.10 + */ +void +gtk_recent_filter_add_pixbuf_formats (GtkRecentFilter *filter) +{ + FilterRule *rule; + + g_return_if_fail (GTK_IS_RECENT_FILTER (filter)); + + rule = g_new0 (FilterRule, 1); + rule->type = FILTER_RULE_PIXBUF_FORMATS; + rule->needed = GTK_RECENT_FILTER_MIME_TYPE; + rule->u.pixbuf_formats = gdk_pixbuf_get_formats (); + + recent_filter_add_rule (filter, rule); +} + +/** + * gtk_recent_filter_add_application: + * @filter: a #GtkRecentFilter + * @application: an application name + * + * Adds a rule that allows resources based on the name of the application + * that has registered them. + * + * Since: 2.10 + */ +void +gtk_recent_filter_add_application (GtkRecentFilter *filter, + const gchar *application) +{ + FilterRule *rule; + + g_return_if_fail (GTK_IS_RECENT_FILTER (filter)); + g_return_if_fail (application != NULL); + + rule = g_new0 (FilterRule, 1); + rule->type = FILTER_RULE_APPLICATION; + rule->needed = GTK_RECENT_FILTER_APPLICATION; + rule->u.application = g_strdup (application); + + recent_filter_add_rule (filter, rule); +} + +/** + * gtk_recent_filter_add_group: + * @filter: a #GtkRecentFilter + * @group: a group name + * + * Adds a rule that allows resources based on the name of the group + * to which they belong + * + * Since: 2.10 + */ +void +gtk_recent_filter_add_group (GtkRecentFilter *filter, + const gchar *group) +{ + FilterRule *rule; + + g_return_if_fail (GTK_IS_RECENT_FILTER (filter)); + g_return_if_fail (group != NULL); + + rule = g_new0 (FilterRule, 1); + rule->type = FILTER_RULE_GROUP; + rule->needed = GTK_RECENT_FILTER_GROUP; + rule->u.group = g_strdup (group); + + recent_filter_add_rule (filter, rule); +} + +/** + * gtk_recent_filter_add_mime_type: + * @filter: a #GtkRecentFilter + * @days: number of days + * + * Adds a rule that allows resources based on their age - that is, the number + * of days elapsed since they were last modified. + * + * Since: 2.10 + */ +void +gtk_recent_filter_add_age (GtkRecentFilter *filter, + gint days) +{ + FilterRule *rule; + + g_return_if_fail (GTK_IS_RECENT_FILTER (filter)); + + rule = g_new0 (FilterRule, 1); + rule->type = FILTER_RULE_AGE; + rule->needed = GTK_RECENT_FILTER_AGE; + rule->u.age = days; + + recent_filter_add_rule (filter, rule); +} + +/** + * gtk_recent_filter_add_custom: + * @filter: a #GtkRecentFilter + * @needed: bitfield of flags indicating the information that the custom + * filter function needs. + * @func: callback function; if the function returns %TRUE, then + * the file will be displayed. + * @data: data to pass to @func + * @data_destroy: function to call to free @data when it is no longer needed. + * + * Adds a rule to a filter that allows resources based on a custom callback + * function. The bitfield @needed which is passed in provides information + * about what sorts of information that the filter function needs; + * this allows GTK+ to avoid retrieving expensive information when + * it isn't needed by the filter. + * + * Since: 2.10 + **/ +void +gtk_recent_filter_add_custom (GtkRecentFilter *filter, + GtkRecentFilterFlags needed, + GtkRecentFilterFunc func, + gpointer data, + GDestroyNotify data_destroy) +{ + FilterRule *rule; + + g_return_if_fail (GTK_IS_RECENT_FILTER (filter)); + g_return_if_fail (func != NULL); + + rule = g_new0 (FilterRule, 1); + rule->type = FILTER_RULE_CUSTOM; + rule->needed = needed; + rule->u.custom.func = func; + rule->u.custom.data = data; + rule->u.custom.data_destroy = data_destroy; + + recent_filter_add_rule (filter, rule); +} + + +/** + * gtk_recent_filter_filter: + * @filter: a #GtkRecentFilter + * @filter_info: a #GtkRecentFilterInfo structure containing information + * about a recently used resource + * + * Tests whether a file should be displayed according to @filter. + * The #GtkRecentFilterInfo structure @filter_info should include + * the fields returned from gtk_recent_filter_get_needed(). + * + * This function will not typically be used by applications; it + * is intended principally for use in the implementation of + * #GtkRecentChooser. + * + * Return value: %TRUE if the file should be displayed + */ +gboolean +gtk_recent_filter_filter (GtkRecentFilter *filter, + const GtkRecentFilterInfo *filter_info) +{ + GSList *l; + + g_return_val_if_fail (GTK_IS_RECENT_FILTER (filter), FALSE); + g_return_val_if_fail (filter_info != NULL, FALSE); + + for (l = filter->rules; l != NULL; l = l->next) + { + FilterRule *rule = (FilterRule *) l->data; + + if ((filter_info->contains & rule->needed) != rule->needed) + continue; + + switch (rule->type) + { + case FILTER_RULE_MIME_TYPE: + if ((filter_info->mime_type != NULL) +#ifdef G_OS_UNIX + && (xdg_mime_mime_type_subclass (filter_info->mime_type, rule->u.mime_type))) +#else + && (strcmp (filter_info->mime_type, rule->u.mime_type) == 0)) +#endif + return TRUE; + break; + case FILTER_RULE_APPLICATION: + if (filter_info->applications) + { + gint i; + + for (i = 0; filter_info->applications[i] != NULL; i++) + { + if (strcmp (filter_info->applications[i], rule->u.application) == 0) + return TRUE; + } + } + break; + case FILTER_RULE_GROUP: + if (filter_info->groups) + { + gint i; + + for (i = 0; filter_info->groups[i] != NULL; i++) + { + if (strcmp (filter_info->groups[i], rule->u.group) == 0) + return TRUE; + } + } + break; + case FILTER_RULE_PIXBUF_FORMATS: + { + GSList *list; + if (!filter_info->mime_type) + break; + + for (list = rule->u.pixbuf_formats; list; list = list->next) + { + gint i; + gchar **mime_types; + + mime_types = gdk_pixbuf_format_get_mime_types (list->data); + + for (i = 0; mime_types[i] != NULL; i++) + { + if (strcmp (mime_types[i], filter_info->mime_type) == 0) + { + g_strfreev (mime_types); + return TRUE; + } + } + + g_strfreev (mime_types); + } + break; + } + case FILTER_RULE_URI: + if ((filter_info->uri != NULL) && + _gtk_fnmatch (rule->u.uri, filter_info->uri, FALSE)) + return TRUE; + break; + case FILTER_RULE_DISPLAY_NAME: + if ((filter_info->display_name != NULL) && + _gtk_fnmatch (rule->u.pattern, filter_info->display_name, FALSE)) + return TRUE; + break; + case FILTER_RULE_AGE: + if ((filter_info->age != -1) && + (filter_info->age < rule->u.age)) + return TRUE; + break; + case FILTER_RULE_CUSTOM: + if (rule->u.custom.func (filter_info, rule->u.custom.data)) + return TRUE; + break; + } + } + + return FALSE; +} + +#define __GTK_RECENT_FILTER_C__ +#include "gtkaliasdef.c" diff --git a/gtk/gtkrecentfilter.h b/gtk/gtkrecentfilter.h new file mode 100644 index 0000000000..28f6fe7aa7 --- /dev/null +++ b/gtk/gtkrecentfilter.h @@ -0,0 +1,90 @@ +/* GTK - The GIMP Toolkit + * gtkrecentfilter.h - Filter object for recently used resources + * Copyright (C) 2006, 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_FILTER_H__ +#define __GTK_RECENT_FILTER_H__ + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_RECENT_FILTER (gtk_recent_filter_get_type ()) +#define GTK_RECENT_FILTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_RECENT_FILTER, GtkRecentFilter)) +#define GTK_IS_RECENT_FILTER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_RECENT_FILTER)) + +typedef struct _GtkRecentFilter GtkRecentFilter; +typedef struct _GtkRecentFilterInfo GtkRecentFilterInfo; + +typedef enum { + GTK_RECENT_FILTER_URI = 1 << 0, + GTK_RECENT_FILTER_DISPLAY_NAME = 1 << 1, + GTK_RECENT_FILTER_MIME_TYPE = 1 << 2, + GTK_RECENT_FILTER_APPLICATION = 1 << 3, + GTK_RECENT_FILTER_GROUP = 1 << 4, + GTK_RECENT_FILTER_AGE = 1 << 5 +} GtkRecentFilterFlags; + +typedef gboolean (*GtkRecentFilterFunc) (const GtkRecentFilterInfo *filter_info, + gpointer user_data); + +struct _GtkRecentFilterInfo +{ + GtkRecentFilterFlags contains; + + const gchar *uri; + const gchar *display_name; + const gchar *mime_type; + const gchar **applications; + const gchar **groups; + + gint age; +}; + +GType gtk_recent_filter_get_type (void) G_GNUC_CONST; + +GtkRecentFilter * gtk_recent_filter_new (void); +void gtk_recent_filter_set_name (GtkRecentFilter *filter, + const gchar *name); +G_CONST_RETURN gchar *gtk_recent_filter_get_name (GtkRecentFilter *filter); + +void gtk_recent_filter_add_mime_type (GtkRecentFilter *filter, + const gchar *mime_type); +void gtk_recent_filter_add_pattern (GtkRecentFilter *filter, + const gchar *pattern); +void gtk_recent_filter_add_pixbuf_formats (GtkRecentFilter *filter); +void gtk_recent_filter_add_application (GtkRecentFilter *filter, + const gchar *application); +void gtk_recent_filter_add_group (GtkRecentFilter *filter, + const gchar *group); +void gtk_recent_filter_add_age (GtkRecentFilter *filter, + gint days); +void gtk_recent_filter_add_custom (GtkRecentFilter *filter, + GtkRecentFilterFlags needed, + GtkRecentFilterFunc func, + gpointer data, + GDestroyNotify data_destroy); + +GtkRecentFilterFlags gtk_recent_filter_get_needed (GtkRecentFilter *filter); +gboolean gtk_recent_filter_filter (GtkRecentFilter *filter, + const GtkRecentFilterInfo *filter_info); + +G_END_DECLS + +#endif /* ! __GTK_RECENT_FILTER_H__ */ diff --git a/gtk/gtkrecentmanager.c b/gtk/gtkrecentmanager.c new file mode 100644 index 0000000000..82e3760fc2 --- /dev/null +++ b/gtk/gtkrecentmanager.c @@ -0,0 +1,2424 @@ +/* GTK - The GIMP Toolkit + * gtkrecentmanager.c: a manager for the recently used resources + * + * Copyright (C) 2006 Emmanuele Bassi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + */ + +#include "config.h" + +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include +#include +#include +#include + +#include "gtkrecentmanager.h" +#include "gtkintl.h" +#include "gtkstock.h" +#include "gtkicontheme.h" +#include "gtktypebuiltins.h" +#include "gtkprivate.h" +#include "gtkmarshalers.h" +#include "gtkalias.h" + +#ifdef G_OS_UNIX +#define XDG_PREFIX _gtk_xdg +#include "xdgmime/xdgmime.h" +#endif + +/* the file where we store the recently used items */ +#define GTK_RECENTLY_USED_FILE ".recently-used.xbel" + +/* a poll per second should be enough */ +#define POLL_DELTA 1000 + +/* return all items by default */ +#define DEFAULT_LIMIT -1 + +/* keep in sync with xdgmime */ +#define GTK_RECENT_DEFAULT_MIME "application/octet-stream" + +typedef struct +{ + gchar *name; + gchar *exec; + + guint count; + + time_t stamp; +} RecentAppInfo; + +struct _GtkRecentInfo +{ + gchar *uri; + + gchar *display_name; + gchar *description; + + time_t added; + time_t modified; + time_t visited; + + gchar *mime_type; + + GSList *applications; + GHashTable *apps_lookup; + + GSList *groups; + + gboolean is_private; + + GdkPixbuf *icon; + + gint ref_count; +}; + +struct _GtkRecentManagerPrivate +{ + gchar *filename; + + guint is_screen_singleton : 1; + guint is_dirty : 1; + guint write_in_progress : 1; + guint read_in_progress : 1; + + gint limit; + gint size; + + GdkScreen *screen; + + GBookmarkFile *recent_items; + + time_t last_mtime; + guint poll_timeout; +}; + +enum +{ + PROP_0, + + PROP_FILENAME, + PROP_LIMIT, + PROP_SIZE +}; + +static void gtk_recent_manager_finalize (GObject *object); + +static void gtk_recent_manager_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gtk_recent_manager_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void gtk_recent_manager_changed (GtkRecentManager *manager); + +static void gtk_recent_manager_real_changed (GtkRecentManager *manager); +static gboolean gtk_recent_manager_poll_timeout (gpointer data); +static void gtk_recent_manager_set_filename (GtkRecentManager *manager, + const gchar *filename); + +static void build_recent_items_list (GtkRecentManager *manager); +static void purge_recent_items_list (GtkRecentManager *manager, + GError **error); + +static RecentAppInfo *recent_app_info_new (const gchar *app_name); +static void recent_app_info_free (RecentAppInfo *app_info); + +static GtkRecentInfo *gtk_recent_info_new (const gchar *uri); +static void gtk_recent_info_free (GtkRecentInfo *recent_info); + +static guint signal_changed = 0; + +G_DEFINE_TYPE (GtkRecentManager, gtk_recent_manager, G_TYPE_OBJECT); + +GQuark +gtk_recent_manager_error_quark (void) +{ + static GQuark quark = 0; + if (quark == 0) + quark = g_quark_from_static_string ("gtk-recent-manager-error-quark"); + return quark; +} + + +static void +gtk_recent_manager_class_init (GtkRecentManagerClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gtk_recent_manager_parent_class = g_type_class_peek_parent (klass); + + gobject_class->set_property = gtk_recent_manager_set_property; + gobject_class->get_property = gtk_recent_manager_get_property; + gobject_class->finalize = gtk_recent_manager_finalize; + + /** + * GtkRecentManager:filename + * + * The full path to the file to be used to store and read the recently + * used resources list + * + * Since: 2.10 + */ + g_object_class_install_property (gobject_class, + PROP_FILENAME, + g_param_spec_string ("filename", + P_("Filename"), + P_("The full path to the file to be used to store and read the list"), + NULL, + (G_PARAM_CONSTRUCT_ONLY | G_PARAM_READABLE | G_PARAM_WRITABLE))); + /** + * GtkRecentManager:limit + * + * The maximum number of items to be returned by the + * gtk_recent_manager_get_items() function. + * + * Since: 2.10 + */ + g_object_class_install_property (gobject_class, + PROP_LIMIT, + g_param_spec_int ("limit", + P_("Limit"), + P_("The maximum number of items to be returned by gtk_recent_manager_get_items()"), + -1, + G_MAXINT, + DEFAULT_LIMIT, + G_PARAM_READWRITE)); + /** + * GtkRecentManager:size + * + * The size of the recently used resources list. + * + * Since: 2.10 + */ + g_object_class_install_property (gobject_class, + PROP_SIZE, + g_param_spec_int ("size", + P_("Size"), + P_("The size of the recently used resources list"), + -1, + G_MAXINT, + 0, + G_PARAM_READABLE)); + + /** + * GtkRecentManager::changed + * @recent_manager: the recent manager + * + * Emitted when the current recently used resources manager changes its + * contents. + * + * Since: 2.10 + */ + signal_changed = + g_signal_new (I_("changed"), + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GtkRecentManagerClass, changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + klass->changed = gtk_recent_manager_real_changed; + + g_type_class_add_private (klass, sizeof (GtkRecentManagerPrivate)); +} + +static void +gtk_recent_manager_init (GtkRecentManager *manager) +{ + GtkRecentManagerPrivate *priv; + + priv = g_type_instance_get_private ((GTypeInstance *) manager, + GTK_TYPE_RECENT_MANAGER); + manager->priv = priv; + + priv->filename = g_build_filename (g_get_home_dir (), + GTK_RECENTLY_USED_FILE, + NULL); + + priv->limit = DEFAULT_LIMIT; + priv->size = 0; + + priv->is_screen_singleton = FALSE; + priv->is_dirty = FALSE; + priv->write_in_progress = FALSE; + priv->read_in_progress = FALSE; + + priv->screen = NULL; +} + +static void +gtk_recent_manager_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkRecentManager *recent_manager = GTK_RECENT_MANAGER (object); + + switch (prop_id) + { + case PROP_FILENAME: + gtk_recent_manager_set_filename (recent_manager, g_value_get_string (value)); + break; + case PROP_LIMIT: + gtk_recent_manager_set_limit (recent_manager, g_value_get_int (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_recent_manager_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkRecentManager *recent_manager = GTK_RECENT_MANAGER (object); + + switch (prop_id) + { + case PROP_FILENAME: + g_value_set_string (value, recent_manager->priv->filename); + break; + case PROP_LIMIT: + g_value_set_int (value, recent_manager->priv->limit); + break; + case PROP_SIZE: + g_value_set_int (value, recent_manager->priv->size); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_recent_manager_finalize (GObject *object) +{ + GtkRecentManager *manager = GTK_RECENT_MANAGER (object); + GtkRecentManagerPrivate *priv = manager->priv; + + /* remove the poll timeout */ + if (priv->poll_timeout) + g_source_remove (priv->poll_timeout); + + if (priv->filename) + g_free (priv->filename); + + if (priv->recent_items) + g_bookmark_file_free (priv->recent_items); + + /* chain up parent's finalize method */ + G_OBJECT_CLASS (gtk_recent_manager_parent_class)->finalize (object); +} + +static void +gtk_recent_manager_real_changed (GtkRecentManager *manager) +{ + GtkRecentManagerPrivate *priv = manager->priv; + + g_object_freeze_notify (G_OBJECT (manager)); + + if (priv->is_dirty) + { + GError *write_error; + struct stat stat_buf; + + /* we are marked as dirty, so we dump the content of our + * recently used items list + */ + g_assert (priv->filename != NULL); + + priv->write_in_progress = TRUE; + + /* if no container object has been defined, we create a new + * empty container, and dump it + */ + if (!priv->recent_items) + { + priv->recent_items = g_bookmark_file_new (); + priv->size = 0; + } + + write_error = NULL; + g_bookmark_file_to_file (priv->recent_items, + priv->filename, + &write_error); + if (write_error) + { + g_warning ("Attempting to store changes into `%s', " + "but failed: %s", + priv->filename, + write_error->message); + g_error_free (write_error); + } + + /* we have sync'ed our list with the storage file, so we + * update the file mtime in order to skip the timed check + * and spare us from a re-read. + */ + if (g_stat (priv->filename, &stat_buf) < 0) + { + g_warning ("Unable to stat() the recently used resources file " + "at `%s': %s.", + priv->filename, + g_strerror (errno)); + + priv->write_in_progress = FALSE; + + g_object_thaw_notify (G_OBJECT (manager)); + + return; + } + + priv->last_mtime = stat_buf.st_mtime; + + /* mark us as clean */ + priv->is_dirty = FALSE; + } + else + { + /* we are not marked as dirty, so we have been called + * because the recently used resources file has been + * changed (and not from us). + */ + build_recent_items_list (manager); + } + + g_object_thaw_notify (G_OBJECT (manager)); +} + +/* timed poll()-ing of the recently used resources file. + * an event-based system would be more efficient. + */ +static gboolean +gtk_recent_manager_poll_timeout (gpointer data) +{ + GtkRecentManager *manager = GTK_RECENT_MANAGER (data); + GtkRecentManagerPrivate *priv = manager->priv; + struct stat stat_buf; + int stat_res; + + /* wait for the next timeout if we have a read/write in progress */ + if (priv->write_in_progress || priv->read_in_progress) + return TRUE; + + stat_res = g_stat (priv->filename, &stat_buf); + if (stat_res < 0) + { + /* the file does not exist, yet, so we wait */ + if (errno == ENOENT) + return TRUE; + + g_warning ("Unable to stat() the recently used resources file " + "at `%s': %s.", + priv->filename, + g_strerror (errno)); + + return TRUE; + } + + /* the file didn't change from the last poll(), so we bail out */ + if (stat_buf.st_mtime == priv->last_mtime) + return TRUE; + + /* the file has been changed, hence we emit the "changed" signal */ + gtk_recent_manager_changed (manager); + + return TRUE; +} + +static void +gtk_recent_manager_set_filename (GtkRecentManager *manager, + const gchar *filename) +{ + GtkRecentManagerPrivate *priv; + + g_assert (GTK_IS_RECENT_MANAGER (manager)); + priv = manager->priv; + + if (!filename || filename[0] == '\0') + return; + + g_free (manager->priv->filename); + + if (manager->priv->poll_timeout) + { + g_source_remove (manager->priv->poll_timeout); + manager->priv->poll_timeout = 0; + } + + build_recent_items_list (manager); + + priv->filename = g_strdup (filename); + priv->poll_timeout = g_timeout_add (POLL_DELTA, + gtk_recent_manager_poll_timeout, + manager); + + priv->is_dirty = FALSE; +} + +/* reads the recently used resources file and builds the items list. + * we keep the items list inside the parser object, and build the + * RecentInfo object only on user's demand to avoid useless replication. + */ +static void +build_recent_items_list (GtkRecentManager *manager) +{ + GtkRecentManagerPrivate *priv; + struct stat stat_buf; + int stat_res; + gboolean res; + GError *read_error; + gint size; + + priv = manager->priv; + g_assert (priv->filename != NULL); + + if (!priv->recent_items) + { + priv->recent_items = g_bookmark_file_new (); + priv->size = 0; + } + + stat_res = g_stat (priv->filename, &stat_buf); + if (stat_res < 0) + { + /* the file doesn't exists, so we bail out and wait for the first + * write operation + */ + + if (errno == ENOENT) + return; + else + { + g_warning ("Attempting to read the recently used resources file " + "at `%s', but an error occurred: %s. Aborting.", + priv->filename, + g_strerror (errno)); + + return; + } + } + + /* record the last mtime, for later use */ + priv->last_mtime = stat_buf.st_mtime; + + priv->read_in_progress = TRUE; + + /* the file exists, and it's valid (we hope); if not, destroy the container + * object and hope for a better result when the next "changed" signal is + * fired. */ + read_error = NULL; + res = g_bookmark_file_load_from_file (priv->recent_items, + priv->filename, + &read_error); + if (read_error) + { + g_warning ("Attempting to read the recently used resources file " + "at `%s', but the parser failed: %s.", + priv->filename, + read_error->message); + + g_bookmark_file_free (priv->recent_items); + priv->recent_items = NULL; + + g_error_free (read_error); + } + + size = g_bookmark_file_get_size (priv->recent_items); + if (priv->size != size) + { + priv->size = size; + + g_object_notify (G_OBJECT (manager), "size"); + } + + priv->read_in_progress = FALSE; +} + + +/******************** + * GtkRecentManager * + ********************/ + + +/** + * gtk_recent_manager_new: + * + * Creates a new recent manager object. Recent manager objects are used to + * handle the list of recently used resources. A #GtkRecentManager object + * monitors the recently used resources list, and emits the "changed" signal + * each time something inside the list changes. + * + * #GtkRecentManager objects are expansive: be sure to create them only when + * needed. You should use the gtk_recent_manager_new_for_screen() or the + * gtk_recent_manager_get_default() functions instead. + * + * Return value: A newly created #GtkRecentManager object. + * + * Since: 2.10 + */ +GtkRecentManager * +gtk_recent_manager_new (void) +{ + GtkRecentManager *retval; + gchar *filename; + + filename = g_build_filename (g_get_home_dir (), + GTK_RECENTLY_USED_FILE, + NULL); + + retval = g_object_new (GTK_TYPE_RECENT_MANAGER, + "filename", filename, + NULL); + + g_free (filename); + + return retval; +} + +/** + * gtk_recent_manager_get_default: + * + * Gets the recent manager for the default screen. See + * gtk_recent_manager_get_for_screen(). + * + * Return value: A unique #GtkRecentManager associated with the + * default screen. This recent manager is associated to the + * screen and can be used as long as the screen is open. + * Do no ref or unref it. + * + * Since: 2.10 + */ +GtkRecentManager * +gtk_recent_manager_get_default (void) +{ + return gtk_recent_manager_get_for_screen (gdk_screen_get_default ()); +} + +/** + * gtk_recent_manager_get_for_screen: + * @screen: a #GdkScreen + * + * Gets the recent manager object associated with @screen; if this + * function has not previously been called for the given screen, + * a new recent manager object will be created and associated with + * the screen. Recent manager objects are fairly expensive to create, + * so using this function is usually a better choice than calling + * gtk_recent_manager_new() and setting the screen yourself; by using + * this function a single recent manager object will be shared between + * users. + * + * Return value: A unique #GtkRecentManager associated with the given + * screen. This recent manager is associated to the with the screen + * and can be used as long as the screen is open. Do not ref or + * unref it. + * + * Since: 2.10 + */ +GtkRecentManager * +gtk_recent_manager_get_for_screen (GdkScreen *screen) +{ + GtkRecentManager *manager; + + g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL); + g_return_val_if_fail (!screen->closed, NULL); + + manager = g_object_get_data (G_OBJECT (screen), "gtk-recent-manager-default"); + if (!manager) + { + GtkRecentManagerPrivate *priv; + + manager = gtk_recent_manager_new (); + gtk_recent_manager_set_screen (manager, screen); + + priv = manager->priv; + priv->is_screen_singleton = TRUE; + + g_object_set_data (G_OBJECT (screen), I_("gtk-recent-manager-default"), manager); + } + + return manager; +} + +static void +display_closed (GdkDisplay *display, + gboolean is_error, + GtkRecentManager *manager) +{ + GtkRecentManagerPrivate *priv = manager->priv; + GdkScreen *screen = priv->screen; + gboolean was_screen_singleton = priv->is_screen_singleton; + + if (was_screen_singleton) + { + g_object_set_data (G_OBJECT (screen), I_("gtk-recent-manager-error-quark"), NULL); + priv->is_screen_singleton = FALSE; + } + + gtk_recent_manager_set_screen (manager, NULL); + + if (was_screen_singleton) + g_object_unref (manager); +} + +static void +unset_screen (GtkRecentManager *manager) +{ + GtkRecentManagerPrivate *priv = manager->priv; + GdkDisplay *display; + + if (priv->screen) + { + display = gdk_screen_get_display (priv->screen); + + g_signal_handlers_disconnect_by_func (display, + (gpointer) display_closed, + manager); + + priv->screen = NULL; + } +} + +/** + * gtk_recent_manager_set_screen: + * @manager: a #GtkRecentManager + * @screen: a #GdkScreen + * + * Sets the screen for a recent manager; the screen is used to + * track the user's currently configured recently used documents + * storage. + * + * Since: 2.10 + */ +void +gtk_recent_manager_set_screen (GtkRecentManager *manager, + GdkScreen *screen) +{ + GtkRecentManagerPrivate *priv; + GdkDisplay *display; + + g_return_if_fail (GTK_IS_RECENT_MANAGER (manager)); + g_return_if_fail (screen == NULL || GDK_IS_SCREEN (screen)); + + priv = manager->priv; + + unset_screen (manager); + + if (screen) + { + display = gdk_screen_get_display (screen); + + priv->screen = screen; + + g_signal_connect (display, "closed", + G_CALLBACK (display_closed), manager); + } +} + +/** + * gtk_recent_manager_set_limit: + * @recent_manager: a #GtkRecentManager + * @limit: the maximum number of items to return, or -1. + * + * Sets the maximum number of item that the gtk_recent_manager_get_items() + * function should return. If @limit is set to -1, then return all the + * items. + * + * Since: 2.10 + */ +void +gtk_recent_manager_set_limit (GtkRecentManager *recent_manager, + gint limit) +{ + GtkRecentManagerPrivate *priv; + + g_return_if_fail (GTK_IS_RECENT_MANAGER (recent_manager)); + + priv = recent_manager->priv; + priv->limit = limit; +} + +/** + * gtk_recent_manager_get_limit: + * @recent_manager: a #GtkRecentManager + * + * Gets the maximum number of items that the gtk_recent_manager_get_items() + * function should return. + * + * Return value: the number of items to return, or -1 for every item. + * + * Since: 2.10 + */ +gint +gtk_recent_manager_get_limit (GtkRecentManager *recent_manager) +{ + GtkRecentManagerPrivate *priv; + + g_return_val_if_fail (GTK_IS_RECENT_MANAGER (recent_manager), DEFAULT_LIMIT); + + priv = recent_manager->priv; + return priv->limit; +} + +/** + * gtk_recent_manager_add_item: + * @recent_manager: a #GtkRecentManager + * @uri: a valid URI + * @error: return location for a #GError, or %NULL + * + * Adds a new resource, pointed by @uri, into the recently used + * resources list. + * + * This function automatically retrieving some of the needed + * metadata and setting other metadata to common default values; it + * then feeds the data to gtk_recent_manager_add_full(). + * + * See gtk_recent_manager_add_full() if you want to explicitely + * define the metadata for the resource pointed by @uri. + * + * Return value: %TRUE if the new item was successfully added + * to the recently used resources list + * + * Since: 2.10 + */ +gboolean +gtk_recent_manager_add_item (GtkRecentManager *recent_manager, + const gchar *uri, + GError **error) +{ + GtkRecentData *recent_data; + GError *add_error; + gboolean retval; + + g_return_val_if_fail (GTK_IS_RECENT_MANAGER (recent_manager), FALSE); + g_return_val_if_fail (uri != NULL, FALSE); + + recent_data = g_slice_new (GtkRecentData); + + recent_data->display_name = NULL; + recent_data->description = NULL; + +#ifdef G_OS_UNIX + if (g_str_has_prefix (uri, "file://")) + { + gchar *filename; + const gchar *mime_type; + + filename = g_filename_from_uri (uri, NULL, NULL); + mime_type = xdg_mime_get_mime_type_for_file (filename, NULL); + if (!mime_type) + recent_data->mime_type = g_strdup (GTK_RECENT_DEFAULT_MIME); + else + recent_data->mime_type = g_strdup (mime_type); + + g_free (filename); + } + else +#endif + recent_data->mime_type = g_strdup (GTK_RECENT_DEFAULT_MIME); + + recent_data->app_name = g_strdup (g_get_application_name ()); + recent_data->app_exec = g_strjoin (" ", g_get_prgname (), "%u", NULL); + + recent_data->groups = NULL; + + recent_data->is_private = FALSE; + + add_error = NULL; + retval = gtk_recent_manager_add_full (recent_manager, uri, recent_data, &add_error); + + g_free (recent_data->mime_type); + g_free (recent_data->app_name); + g_free (recent_data->app_exec); + + g_slice_free (GtkRecentData, recent_data); + + if (!retval) + { + g_propagate_error (error, add_error); + + return FALSE; + } + + return retval; +} + +/** + * gtk_recent_manager_add_full: + * @recent_manager: a #GtkRecentManager + * @uri: a valid URI + * @recent_data: metadata of the resource + * @error: return location for a #GError, or %NULL + * + * Adds a new resource, pointed by @uri, into the recently used + * resources list, using the metadata specified inside the #GtkRecentData + * structure passed in @recent_data. + * + * The passed URI will be used to identify this resource inside the + * list. + * + * In order to register the new recently used resource, metadata about + * the resource must be passed as well as the URI; the metadata is + * stored in a #GtkRecentData structure, which must contain the MIME + * type of the resource pointed by the URI; the name of the application + * that is registering the item, and a command line to be used when + * launching the item. + * + * Optionally, a #GtkRecentData structure might contain a UTF-8 string + * to be used when viewing the item instead of the last component of the + * URI; a short description of the item; whether the item should be + * considered private - that is, should be displayed only by the + * applications that have registered it. + * + * Return value: %TRUE if the new item was successfully added to the + * recently used resources list, %FALSE otherwise. + * + * Since: 2.10 + */ +gboolean +gtk_recent_manager_add_full (GtkRecentManager *recent_manager, + const gchar *uri, + const GtkRecentData *data, + GError **error) +{ + GtkRecentManagerPrivate *priv; + + g_return_val_if_fail (GTK_IS_RECENT_MANAGER (recent_manager), FALSE); + g_return_val_if_fail (uri != NULL, FALSE); + g_return_val_if_fail (data != NULL, FALSE); + + /* sanity checks */ + if ((data->display_name) && + (!g_utf8_validate (data->display_name, -1, NULL))) + { + g_set_error (error, GTK_RECENT_MANAGER_ERROR, + GTK_RECENT_MANAGER_ERROR_INVALID_ENCODING, + _("The display name of the recently used resource " + "must be a valid UTF-8 encoded string.")); + return FALSE; + } + + if ((data->description) && + (!g_utf8_validate (data->description, -1, NULL))) + { + g_set_error (error, GTK_RECENT_MANAGER_ERROR, + GTK_RECENT_MANAGER_ERROR_INVALID_ENCODING, + _("The description of the recently used resource " + "must by a valid UTF-8 encoded string.")); + return FALSE; + } + + + if (!data->mime_type) + { + g_set_error (error, GTK_RECENT_MANAGER_ERROR, + GTK_RECENT_MANAGER_ERROR_INVALID_MIME, + _("You must specify the MIME type of the " + "resource pointed by `%s'"), + uri); + return FALSE; + } + + if (!data->app_name) + { + g_set_error (error, GTK_RECENT_MANAGER_ERROR, + GTK_RECENT_MANAGER_ERROR_NOT_REGISTERED, + _("You must specify the name of the application " + "that is registering the recently used resource " + "pointed by `%s'"), + uri); + return FALSE; + } + + if (!data->app_exec) + { + g_set_error (error, GTK_RECENT_MANAGER_ERROR, + GTK_RECENT_MANAGER_ERROR_BAD_EXEC_STRING, + _("You must specify a command line to " + "be used when launching the resource " + "pointed by `%s'"), + uri); + return FALSE; + } + + priv = recent_manager->priv; + + if (!priv->recent_items) + { + priv->recent_items = g_bookmark_file_new (); + priv->size = 0; + } + + if (data->display_name) + g_bookmark_file_set_title (priv->recent_items, uri, data->display_name); + + if (data->description) + g_bookmark_file_set_description (priv->recent_items, uri, data->description); + + g_bookmark_file_set_mime_type (priv->recent_items, uri, data->mime_type); + + if (data->groups && data->groups[0] != '\0') + { + gint j; + + for (j = 0; (data->groups)[j] != NULL; j++) + g_bookmark_file_add_group (priv->recent_items, uri, (data->groups)[j]); + } + + /* register the application; this will take care of updating the + * registration count and time in case the application has + * already registered the same document inside the list + */ + g_bookmark_file_add_application (priv->recent_items, uri, + data->app_name, + data->app_exec); + + g_bookmark_file_set_is_private (priv->recent_items, uri, + data->is_private); + + /* mark us as dirty, so that when emitting the "changed" signal we + * will dump our changes + */ + priv->is_dirty = TRUE; + + gtk_recent_manager_changed (recent_manager); + + return TRUE; +} + +/** + * gtk_recent_manager_remove_item: + * @recent_manager: a #GtkRecentManager + * @uri: the URI of the item you wish to remove + * @error: return location for a #GError, or %NULL + * + * Removes a resource pointed by @uri from the recently used resources + * list handled by a recent manager. + * + * Return value: %TRUE if the item pointed by @uri has been successfully + * removed by the recently used resources list, and %FALSE otherwise. + * + * Since: 2.10 + */ +gboolean +gtk_recent_manager_remove_item (GtkRecentManager *recent_manager, + const gchar *uri, + GError **error) +{ + GtkRecentManagerPrivate *priv; + GError *remove_error = NULL; + + g_return_val_if_fail (GTK_IS_RECENT_MANAGER (recent_manager), FALSE); + g_return_val_if_fail (uri != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + priv = recent_manager->priv; + + if (!priv->recent_items) + { + priv->recent_items = g_bookmark_file_new (); + priv->size = 0; + + g_set_error (error, GTK_RECENT_MANAGER_ERROR, + GTK_RECENT_MANAGER_ERROR_NOT_FOUND, + _("Unable to find an item with URI '%s'"), + uri); + + return FALSE; + } + + g_bookmark_file_remove_item (priv->recent_items, uri, &remove_error); + if (remove_error) + { + g_propagate_error (error, remove_error); + + return FALSE; + } + + priv->is_dirty = TRUE; + + gtk_recent_manager_changed (recent_manager); + + return TRUE; +} + +/** + * gtk_recent_manager_has_item: + * @recent_manager: a #GtkRecentManager + * @uri: a URI + * + * Checks whether there is a recently used resource registered + * with @uri inside the recent manager. + * + * Return value: %TRUE if the resource was found, %FALSE otherwise. + * + * Since: 2.10 + */ +gboolean +gtk_recent_manager_has_item (GtkRecentManager *recent_manager, + const gchar *uri) +{ + GtkRecentManagerPrivate *priv; + + g_return_val_if_fail (GTK_IS_RECENT_MANAGER (recent_manager), FALSE); + g_return_val_if_fail (uri != NULL, FALSE); + + priv = recent_manager->priv; + g_return_val_if_fail (priv->recent_items != NULL, FALSE); + + return g_bookmark_file_has_item (priv->recent_items, uri); +} + +static gboolean +build_recent_info (GBookmarkFile *bookmarks, + GtkRecentInfo *info) +{ + gchar **apps, **groups; + gsize apps_len, groups_len, i; + + g_assert (bookmarks != NULL); + g_assert (info != NULL); + + info->display_name = g_bookmark_file_get_title (bookmarks, info->uri, NULL); + info->description = g_bookmark_file_get_description (bookmarks, info->uri, NULL); + info->mime_type = g_bookmark_file_get_mime_type (bookmarks, info->uri, NULL); + + info->is_private = g_bookmark_file_get_is_private (bookmarks, info->uri, NULL); + + info->added = g_bookmark_file_get_added (bookmarks, info->uri, NULL); + info->modified = g_bookmark_file_get_modified (bookmarks, info->uri, NULL); + info->visited = g_bookmark_file_get_visited (bookmarks, info->uri, NULL); + + groups = g_bookmark_file_get_groups (bookmarks, info->uri, &groups_len, NULL); + for (i = 0; i < groups_len; i++) + { + gchar *group_name = g_strdup (groups[i]); + + info->groups = g_slist_append (info->groups, group_name); + } + + g_strfreev (groups); + + apps = g_bookmark_file_get_applications (bookmarks, info->uri, &apps_len, NULL); + for (i = 0; i < apps_len; i++) + { + gchar *app_name, *app_exec; + guint count; + time_t stamp; + RecentAppInfo *app_info; + gboolean res; + + app_name = apps[i]; + + res = g_bookmark_file_get_app_info (bookmarks, info->uri, app_name, + &app_exec, + &count, + &stamp, + NULL); + if (!res) + continue; + + app_info = recent_app_info_new (app_name); + app_info->exec = app_exec; + app_info->count = count; + app_info->stamp = stamp; + + info->applications = g_slist_append (info->applications, + app_info); + g_hash_table_replace (info->apps_lookup, app_info->name, app_info); + } + + g_strfreev (apps); + + return TRUE; +} + +/** + * gtk_recent_manager_lookup_item: + * @recent_manager: a #GtkRecentManager + * @uri: a URI + * @error: a return location for a #GError, or %NULL + * + * Searches for a URI inside the recently used resources list, and + * returns a structure containing informations about the resource + * like its MIME type, or its display name. + * + * Return value: a #GtkRecentInfo structure containing information + * about the resource pointed by @uri, or %NULL if the URI was + * not registered in the recently used resources list. Free with + * gtk_recent_info_unref(). + **/ +GtkRecentInfo * +gtk_recent_manager_lookup_item (GtkRecentManager *recent_manager, + const gchar *uri, + GError **error) +{ + GtkRecentManagerPrivate *priv; + GtkRecentInfo *info = NULL; + gboolean res; + + g_return_val_if_fail (GTK_IS_RECENT_MANAGER (recent_manager), NULL); + g_return_val_if_fail (uri != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + priv = recent_manager->priv; + if (!priv->recent_items) + { + priv->recent_items = g_bookmark_file_new (); + priv->size = 0; + + g_set_error (error, GTK_RECENT_MANAGER_ERROR, + GTK_RECENT_MANAGER_ERROR_NOT_FOUND, + _("Unable to find an item with URI '%s'"), + uri); + + return NULL; + } + + if (!g_bookmark_file_has_item (priv->recent_items, uri)) + { + g_set_error (error, GTK_RECENT_MANAGER_ERROR, + GTK_RECENT_MANAGER_ERROR_NOT_FOUND, + _("Unable to find an item with URI '%s'"), + uri); + return NULL; + } + + info = gtk_recent_info_new (uri); + g_return_val_if_fail (info != NULL, NULL); + + /* fill the RecentInfo structure with the data retrieved by our + * parser object from the storage file + */ + res = build_recent_info (priv->recent_items, info); + if (!res) + { + gtk_recent_info_free (info); + + return NULL; + } + + return gtk_recent_info_ref (info); +} + +/** + * gtk_recent_manager_move_item: + * @manager: a #GtkRecentManager + * @uri: the URI of a recently used resource + * @new_uri: the new URI of the recently used resource, or %NULL to + * remove the item pointed by @uri in the list + * @error: a return location for a #GError, or %NULL + * + * Changes the location of a recently used resource from @uri to @new_uri. + * + * Please note that this function will not affect the resource pointed + * by the URIs, but only the URI used in the recently used resources list. + * + * Return value: %TRUE on success. + * + * Since: 2.10 + */ +gboolean +gtk_recent_manager_move_item (GtkRecentManager *recent_manager, + const gchar *uri, + const gchar *new_uri, + GError **error) +{ + GtkRecentManagerPrivate *priv; + GError *move_error; + gboolean res; + + g_return_val_if_fail (GTK_IS_RECENT_MANAGER (recent_manager), FALSE); + g_return_val_if_fail (uri != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + priv = recent_manager->priv; + + if (!g_bookmark_file_has_item (priv->recent_items, uri)) + { + g_set_error (error, GTK_RECENT_MANAGER_ERROR, + GTK_RECENT_MANAGER_ERROR_NOT_FOUND, + _("Unable to find an item with URI '%s'"), + uri); + return FALSE; + } + + move_error = NULL; + res = g_bookmark_file_move_item (priv->recent_items, + uri, new_uri, + &move_error); + if (move_error) + { + g_propagate_error (error, move_error); + return FALSE; + } + + priv->is_dirty = TRUE; + + gtk_recent_manager_changed (recent_manager); + + return TRUE; +} + +/** + * gtk_recent_manager_get_items: + * @recent_manager: a #GtkRecentManager + * + * Gets the list of recently used resources. + * + * Return value: a list of newly allocated #GtkRecentInfo objects. Use + * gtk_recent_info_unref() on each item inside the list, and then + * free the list itself using g_list_free(). + * + * Since: 2.10 + */ +GList * +gtk_recent_manager_get_items (GtkRecentManager *recent_manager) +{ + GtkRecentManagerPrivate *priv; + GList *retval = NULL; + gchar **uris; + gsize uris_len, i; + + g_return_val_if_fail (GTK_IS_RECENT_MANAGER (recent_manager), NULL); + + priv = recent_manager->priv; + if (!priv->recent_items) + return NULL; + + uris = g_bookmark_file_get_uris (priv->recent_items, &uris_len); + for (i = 0; i < uris_len; i++) + { + GtkRecentInfo *info; + gboolean res; + + info = gtk_recent_info_new (uris[i]); + res = build_recent_info (priv->recent_items, info); + if (!res) + { + g_warning ("Unable to create a RecentInfo object for " + "item with URI `%s'", + uris[i]); + gtk_recent_info_free (info); + + continue; + } + + retval = g_list_prepend (retval, info); + } + + g_strfreev (uris); + + /* clamp the list, if a limit is present */ + if ((priv->limit != -1) && + (g_list_length (retval) > priv->limit)) + { + GList *clamp, *l; + + clamp = g_list_nth (retval, priv->limit - 1); + + if (!clamp) + return retval; + + l = clamp->next; + clamp->next = NULL; + + g_list_foreach (l, (GFunc) gtk_recent_info_free, NULL); + g_list_free (l); + } + + return retval; +} + +static void +purge_recent_items_list (GtkRecentManager *manager, + GError **error) +{ + GtkRecentManagerPrivate *priv = manager->priv; + + if (!priv->recent_items) + return; + + g_bookmark_file_free (priv->recent_items); + priv->recent_items = NULL; + + priv->recent_items = g_bookmark_file_new (); + priv->size = 0; + priv->is_dirty = TRUE; + + /* emit the changed signal, to ensure that the purge is written */ + gtk_recent_manager_changed (manager); +} + +/** + * gtk_recent_manager_purge_items: + * @recent_manager: a #GtkRecentManager + * @error: a return location for a #GError, or %NULL + * + * Purges every item from the recently used resources list. + * + * Return value: the number of items that have been removed from the + * recently used resources list. + * + * Since: 2.10 + */ +gint +gtk_recent_manager_purge_items (GtkRecentManager *recent_manager, + GError **error) +{ + GtkRecentManagerPrivate *priv; + gint count, purged; + + g_return_val_if_fail (GTK_IS_RECENT_MANAGER (recent_manager), -1); + + priv = recent_manager->priv; + if (!priv->recent_items) + return 0; + + count = g_bookmark_file_get_size (priv->recent_items); + if (!count) + return 0; + + purge_recent_items_list (recent_manager, error); + + purged = count - g_bookmark_file_get_size (priv->recent_items); + + return purged; +} + +static void +gtk_recent_manager_changed (GtkRecentManager *recent_manager) +{ + g_signal_emit (recent_manager, signal_changed, 0); +} + +/***************** + * GtkRecentInfo * + *****************/ + +GType +gtk_recent_info_get_type (void) +{ + static GType info_type = 0; + + if (!info_type) + info_type = g_boxed_type_register_static ("GtkRecentInfo", + (GBoxedCopyFunc) gtk_recent_info_ref, + (GBoxedFreeFunc) gtk_recent_info_unref); + return info_type; +} + +static GtkRecentInfo * +gtk_recent_info_new (const gchar *uri) +{ + GtkRecentInfo *info; + + g_assert (uri != NULL); + + info = g_new0 (GtkRecentInfo, 1); + info->uri = g_strdup (uri); + + info->applications = NULL; + info->apps_lookup = g_hash_table_new (g_str_hash, g_str_equal); + + info->groups = NULL; + + info->ref_count = 1; + + return info; +} + +static void +gtk_recent_info_free (GtkRecentInfo *recent_info) +{ + if (!recent_info) + return; + + g_free (recent_info->uri); + g_free (recent_info->display_name); + g_free (recent_info->description); + g_free (recent_info->mime_type); + + if (recent_info->applications) + { + g_slist_foreach (recent_info->applications, + (GFunc) recent_app_info_free, + NULL); + g_slist_free (recent_info->applications); + + recent_info->applications = NULL; + } + + if (recent_info->apps_lookup) + g_hash_table_destroy (recent_info->apps_lookup); + + if (recent_info->groups) + { + g_slist_foreach (recent_info->groups, + (GFunc) g_free, + NULL); + g_slist_free (recent_info->groups); + + recent_info->groups = NULL; + } + + if (recent_info->icon) + g_object_unref (recent_info->icon); + + g_free (recent_info); +} + +/** + * gtk_recent_info_ref: + * @info: a #GtkRecentInfo + * + * Increases the reference count of @recent_info by one. + * + * Return value: the recent info object with its reference count increased + * by one. + * + * Since: 2.10 + */ +GtkRecentInfo * +gtk_recent_info_ref (GtkRecentInfo *info) +{ + g_return_val_if_fail (info != NULL, NULL); + g_return_val_if_fail (info->ref_count > 0, NULL); + + info->ref_count += 1; + + return info; +} + +/** + * gtk_recent_info_unref: + * @info: a #GtkRecentInfo + * + * Decreases the reference count of @info by one. If the reference + * count reaches zero, @info is deallocated, and the memory freed. + * + * Since: 2.10 + */ +void +gtk_recent_info_unref (GtkRecentInfo *info) +{ + g_return_if_fail (info != NULL); + g_return_if_fail (info->ref_count > 0); + + info->ref_count -= 1; + + if (info->ref_count == 0) + gtk_recent_info_free (info); +} + +/** + * gtk_recent_info_get_uri: + * @info: a #GtkRecentInfo + * + * Gets the URI of the resource. + * + * Return value: the URI of the resource. The returned string is + * owned by the recent manager, and should not be freed. + * + * Since: 2.10 + */ +G_CONST_RETURN gchar * +gtk_recent_info_get_uri (GtkRecentInfo *info) +{ + g_return_val_if_fail (info != NULL, NULL); + + return info->uri; +} + +/** + * gtk_recent_info_get_display_name: + * @info: a #GtkRecentInfo + * + * Gets the name of the resource. If none has been defined, the basename + * of the resource is obtained. + * + * Return value: the display name of the resource. The returned string + * is owned by the recent manager, and should not be freed. + * + * Since: 2.10 + */ +G_CONST_RETURN gchar * +gtk_recent_info_get_display_name (GtkRecentInfo *info) +{ + g_return_val_if_fail (info != NULL, NULL); + + if (!info->display_name) + info->display_name = gtk_recent_info_get_short_name (info); + + return info->display_name; +} + +/** + * gtk_recent_info_get_description: + * @info: a #GtkRecentInfo + * + * Gets the (short) description of the resource. + * + * Return value: the description of the resource. The returned string + * is owned by the recent manager, and should not be freed. + * + * Since: 2.10 + **/ +G_CONST_RETURN gchar * +gtk_recent_info_get_description (GtkRecentInfo *info) +{ + g_return_val_if_fail (info != NULL, NULL); + + return info->description; +} + +/** + * gtk_recent_info_get_mime_type: + * @info: a #GtkRecentInfo + * + * Gets the MIME type of the resource. + * + * Return value: the MIME type of the resource. The returned string + * is owned by the recent manager, and should not be freed. + * + * Since: 2.10 + */ +G_CONST_RETURN gchar * +gtk_recent_info_get_mime_type (GtkRecentInfo *info) +{ + g_return_val_if_fail (info != NULL, NULL); + + if (!info->mime_type) + info->mime_type = g_strdup (GTK_RECENT_DEFAULT_MIME); + + return info->mime_type; +} + +/** + * gtk_recent_info_get_added: + * @info: a #GtkRecentInfo + * + * Gets the timestamp (seconds from system's Epoch) when the resource + * was added to the recently used resources list. + * + * Return value: the number of seconds elapsed from system's Epoch when + * the resource was added to the list, or -1 on failure. + * + * Since: 2.10 + */ +time_t +gtk_recent_info_get_added (GtkRecentInfo *info) +{ + g_return_val_if_fail (info != NULL, (time_t) -1); + + return info->added; +} + +/** + * gtk_recent_info_get_modified: + * @info: a #GtkRecentInfo + * + * Gets the timestamp (seconds from system's Epoch) when the resource + * was last modified. + * + * Return value: the number of seconds elapsed from system's Epoch when + * the resource was last modified, or -1 on failure. + * + * Since: 2.10 + */ +time_t +gtk_recent_info_get_modified (GtkRecentInfo *info) +{ + g_return_val_if_fail (info != NULL, (time_t) -1); + + return info->modified; +} + +/** + * gtk_recent_info_get_visited: + * @info: a #GtkRecentInfo + * + * Gets the timestamp (seconds from system's Epoch) when the resource + * was last visited. + * + * Return value: the number of seconds elapsed from system's Epoch when + * the resource was last visited, or -1 on failure. + * + * Since: 2.10 + */ +time_t +gtk_recent_info_get_visited (GtkRecentInfo *info) +{ + g_return_val_if_fail (info != NULL, (time_t) -1); + + return info->visited; +} + +/** + * gtk_recent_info_get_private_hint: + * @info: a #GtkRecentInfo + * + * Gets the value of the "private" flag. Resources in the recently used + * list that have this flag set to %TRUE should only be displayed by the + * applications that have registered them. + * + * Return value: %TRUE if the private flag was found, %FALSE otherwise. + * + * Since: 2.10 + */ +gboolean +gtk_recent_info_get_private_hint (GtkRecentInfo *info) +{ + g_return_val_if_fail (info != NULL, FALSE); + + return info->is_private; +} + + +static RecentAppInfo * +recent_app_info_new (const gchar *app_name) +{ + RecentAppInfo *app_info; + + g_assert (app_name != NULL); + + app_info = g_new0 (RecentAppInfo, 1); + app_info->name = g_strdup (app_name); + app_info->exec = NULL; + app_info->count = 1; + app_info->stamp = time (NULL); + + return app_info; +} + +static void +recent_app_info_free (RecentAppInfo *app_info) +{ + if (!app_info) + return; + + if (app_info->name) + g_free (app_info->name); + + if (app_info->exec) + g_free (app_info->exec); + + g_free (app_info); +} + +/** + * gtk_recent_info_get_application_info: + * @info: a #GtkRecentInfo + * @app_name: the name of the application that has registered this item + * @app_exec: return location for the string containing the command line + * @count: return location for the number of times this item was registered + * @time: return location for the timestamp this item was last registered + * for this application + * + * Gets the data regarding the application that has registered the resource + * pointed by @info. + * + * If the command line contains any escape characters defined inside the + * storage specification, they will be expanded. + * + * Return value: %TRUE if an application with @app_name has registered this + * resource inside the recently used list, or %FALSE otherwise. You should + * free the returned command line using g_free(). + * + * Since: 2.10 + */ +gboolean +gtk_recent_info_get_application_info (GtkRecentInfo *info, + const gchar *app_name, + gchar **app_exec, + guint *count, + time_t *time) +{ + RecentAppInfo *ai; + + g_return_val_if_fail (info != NULL, FALSE); + g_return_val_if_fail (app_name != NULL, FALSE); + + ai = (RecentAppInfo *) g_hash_table_lookup (info->apps_lookup, + app_name); + if (!ai) + { + g_warning ("No registered application with name '%s' " + "for item with URI '%s' found", + app_name, + info->uri); + return FALSE; + } + + if (app_exec) + *app_exec = ai->exec; + + if (count) + *count = ai->count; + + if (time) + *time = ai->stamp; + + return TRUE; +} + +/** + * gtk_recent_info_get_applications: + * @info: a #GtkRecentInfo + * @length: return location for the length of the returned list, or %NULL + * + * Retrieves the list of applications that have registered this resource. + * + * Return value: a newly allocated %NULL-terminated array of strings. + * Use g_strfreev() to free it. + * + * Since: 2.10 + */ +gchar ** +gtk_recent_info_get_applications (GtkRecentInfo *info, + gsize *length) +{ + GSList *l; + gchar **retval; + gsize n_apps, i; + + g_return_val_if_fail (info != NULL, NULL); + + if (!info->applications) + { + if (length) + *length = 0; + + return NULL; + } + + n_apps = g_slist_length (info->applications); + + retval = g_new0 (gchar *, n_apps + 1); + + for (l = info->applications, i = 0; + l != NULL; + l = l->next) + { + RecentAppInfo *ai = (RecentAppInfo *) l->data; + + g_assert (ai != NULL); + + retval[i++] = g_strdup (ai->name); + } + retval[i] = NULL; + + if (length) + *length = i; + + return retval; +} + +/** + * gtk_recent_info_has_application: + * @info: a #GtkRecentInfo + * @app_name: a string containing an application name + * + * Checks whether an application registered this resource using @app_name. + * + * Return value: %TRUE if an application with name @app_name was found, + * %FALSE otherwise. + * + * Since: 2.10 + */ +gboolean +gtk_recent_info_has_application (GtkRecentInfo *info, + const gchar *app_name) +{ + g_return_val_if_fail (info != NULL, FALSE); + g_return_val_if_fail (app_name != NULL, FALSE); + + return (NULL != g_hash_table_lookup (info->apps_lookup, app_name)); +} + +/** + * gtk_recent_info_last_application: + * @info: a #GtkRecentInfo + * + * Gets the name of the last application that have registered the + * recently used resource represented by @info. + * + * Return value: an application name. Use g_free() to free it. + * + * Since: 2.10 + */ +gchar * +gtk_recent_info_last_application (GtkRecentInfo *info) +{ + GSList *l; + time_t last_stamp = (time_t) -1; + gchar *name = NULL; + + g_return_val_if_fail (info != NULL, NULL); + + for (l = info->applications; l != NULL; l = l->next) + { + RecentAppInfo *ai = (RecentAppInfo *) l->data; + + if (ai->stamp > last_stamp) + name = ai->name; + } + + return g_strdup (name); +} + +typedef struct +{ + gint size; + GdkPixbuf *pixbuf; +} IconCacheElement; + +static void +icon_cache_element_free (IconCacheElement *element) +{ + if (element->pixbuf) + g_object_unref (element->pixbuf); + g_free (element); +} + +static void +icon_theme_changed (GtkIconTheme *icon_theme) +{ + GHashTable *cache; + + /* Difference from the initial creation is that we don't + * reconnect the signal + */ + cache = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify)g_free, + (GDestroyNotify)icon_cache_element_free); + g_object_set_data_full (G_OBJECT (icon_theme), "gtk-recent-icon-cache", + cache, (GDestroyNotify)g_hash_table_destroy); +} + +/* TODO: use the GtkFileChooser's icon cache instead of our own to reduce + * the memory footprint + */ +static GdkPixbuf * +get_cached_icon (const gchar *name, + gint pixel_size) +{ + GtkIconTheme *icon_theme; + GHashTable *cache; + IconCacheElement *element; + + icon_theme = gtk_icon_theme_get_default (); + cache = g_object_get_data (G_OBJECT (icon_theme), "gtk-recent-icon-cache"); + + if (!cache) + { + cache = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify)g_free, + (GDestroyNotify)icon_cache_element_free); + + g_object_set_data_full (G_OBJECT (icon_theme), "gtk-recent-icon-cache", + cache, (GDestroyNotify)g_hash_table_destroy); + g_signal_connect (icon_theme, "changed", + G_CALLBACK (icon_theme_changed), NULL); + } + + element = g_hash_table_lookup (cache, name); + if (!element) + { + element = g_new0 (IconCacheElement, 1); + g_hash_table_insert (cache, g_strdup (name), element); + } + + if (element->size != pixel_size) + { + if (element->pixbuf) + g_object_unref (element->pixbuf); + + element->size = pixel_size; + element->pixbuf = gtk_icon_theme_load_icon (icon_theme, name, + pixel_size, 0, NULL); + } + + return element->pixbuf ? g_object_ref (element->pixbuf) : NULL; +} + + +static GdkPixbuf * +get_icon_for_mime_type (const char *mime_type, + gint pixel_size) +{ + const char *separator; + GString *icon_name; + GdkPixbuf *pixbuf; + + separator = strchr (mime_type, '/'); + if (!separator) + return NULL; /* maybe we should return a GError with "invalid MIME-type" */ + + icon_name = g_string_new ("gnome-mime-"); + g_string_append_len (icon_name, mime_type, separator - mime_type); + g_string_append_c (icon_name, '-'); + g_string_append (icon_name, separator + 1); + pixbuf = get_cached_icon (icon_name->str, pixel_size); + g_string_free (icon_name, TRUE); + if (pixbuf) + return pixbuf; + + icon_name = g_string_new ("gnome-mime-"); + g_string_append_len (icon_name, mime_type, separator - mime_type); + pixbuf = get_cached_icon (icon_name->str, pixel_size); + g_string_free (icon_name, TRUE); + + return pixbuf; +} + +static GdkPixbuf * +get_icon_fallback (const gchar *icon_name, + gint size) +{ + GtkIconTheme *icon_theme; + GdkPixbuf *retval; + + icon_theme = gtk_icon_theme_get_default (); + + retval = gtk_icon_theme_load_icon (icon_theme, icon_name, + size, + GTK_ICON_LOOKUP_USE_BUILTIN, + NULL); + g_assert (retval != NULL); + + return retval; +} + +/** + * gtk_recent_info_get_icon: + * @info: a #GtkRecentInfo + * @size: the size of the icon in pixels + * + * Retrieves the icon of size @size associated to the resource MIME type. + * + * Return value: a #GdkPixbuf containing the icon, or %NULL. + * + * Since: 2.10 + */ +GdkPixbuf * +gtk_recent_info_get_icon (GtkRecentInfo *info, + gint size) +{ + GdkPixbuf *retval = NULL; + + g_return_val_if_fail (info != NULL, NULL); + + if (info->mime_type) + retval = get_icon_for_mime_type (info->mime_type, size); + + /* this should never fail */ + if (!retval) + retval = get_icon_fallback (GTK_STOCK_FILE, size); + + return retval; +} + +/** + * gtk_recent_info_is_local: + * @info: a #GtkRecentInfo + * + * Checks whether the resource is local or not by looking at the + * scheme of its URI. + * + * Return value: %TRUE if the resource is local. + * + * Since: 2.10 + */ +gboolean +gtk_recent_info_is_local (GtkRecentInfo *info) +{ + g_return_val_if_fail (info != NULL, FALSE); + + return g_str_has_prefix (info->uri, "file://"); +} + +/** + * gtk_recent_info_exists: + * @info: a #GtkRecentInfo + * + * Checks whether the resource pointed by @info still exists. At + * the moment this check is done only on resources pointing to local files. + * + * Return value: %TRUE if the resource exists + * + * Since: 2.10 + */ +gboolean +gtk_recent_info_exists (GtkRecentInfo *info) +{ + gchar *filename; + struct stat stat_buf; + gboolean retval = FALSE; + + g_return_val_if_fail (info != NULL, FALSE); + + /* we guarantee only local resources */ + if (!gtk_recent_info_is_local (info)) + return FALSE; + + filename = g_filename_from_uri (info->uri, NULL, NULL); + if (filename) + { + if (stat (filename, &stat_buf) == 0) + retval = TRUE; + + g_free (filename); + } + + return retval; +} + +/** + * gtk_recent_info_match: + * @a: a #GtkRecentInfo + * @b: a #GtkRecentInfo + * + * Checks whether two #GtkRecentInfo structures point to the same + * resource. + * + * Return value: %TRUE if both #GtkRecentInfo structures point to se same + * resource, %FALSE otherwise. + * + * Since: 2.10 + */ +gboolean +gtk_recent_info_match (GtkRecentInfo *a, + GtkRecentInfo *b) +{ + g_return_val_if_fail (a != NULL, FALSE); + g_return_val_if_fail (b != NULL, FALSE); + + return (0 == strcmp (a->uri, b->uri)); +} + +/* taken from gnome-vfs-uri.c */ +static const gchar * +get_method_string (const gchar *substring, gchar **method_string) +{ + const gchar *p; + char *method; + + for (p = substring; + g_ascii_isalnum (*p) || *p == '+' || *p == '-' || *p == '.'; + p++) + ; + + if (*p == ':' +#ifdef G_OS_WIN32 + && + !(p == substring + 1 && g_ascii_isalpha (*substring)) +#endif + ) + { + /* Found toplevel method specification. */ + method = g_strndup (substring, p - substring); + *method_string = g_ascii_strdown (method, -1); + g_free (method); + p++; + } + else + { + *method_string = g_strdup ("file"); + p = substring; + } + + return p; +} + +/* Stolen from gnome_vfs_make_valid_utf8() */ +static char * +make_valid_utf8 (const char *name) +{ + GString *string; + const char *remainder, *invalid; + int remaining_bytes, valid_bytes; + + string = NULL; + remainder = name; + remaining_bytes = name ? strlen (name) : 0; + + while (remaining_bytes != 0) + { + if (g_utf8_validate (remainder, remaining_bytes, &invalid)) + break; + + valid_bytes = invalid - remainder; + + if (string == NULL) + string = g_string_sized_new (remaining_bytes); + + g_string_append_len (string, remainder, valid_bytes); + g_string_append_c (string, '?'); + + remaining_bytes -= valid_bytes + 1; + remainder = invalid + 1; + } + + if (string == NULL) + return g_strdup (name); + + g_string_append (string, remainder); + g_assert (g_utf8_validate (string->str, -1, NULL)); + + return g_string_free (string, FALSE); +} + +static gchar * +get_uri_shortname_for_display (const gchar *uri) +{ + gchar *name = NULL; + gboolean validated = FALSE; + + if (g_str_has_prefix (uri, "file://")) + { + gchar *local_file; + + local_file = g_filename_from_uri (uri, NULL, NULL); + + if (local_file != NULL) + { + name = g_filename_display_basename (local_file); + validated = TRUE; + } + + g_free (local_file); + } + else + { + gchar *method; + gchar *local_file; + const gchar *rest; + + rest = get_method_string (uri, &method); + local_file = g_filename_display_basename (rest); + + name = g_strdup_printf ("%s: %s", method, local_file); + + g_free (local_file); + g_free (method); + } + + g_assert (name != NULL); + + if (!validated && !g_utf8_validate (name, -1, NULL)) + { + gchar *utf8_name; + + utf8_name = make_valid_utf8 (name); + g_free (name); + + name = utf8_name; + } + + return name; +} + +/** + * gtk_recent_info_get_short_name: + * @info: an #GtkRecentInfo + * + * Computes a valid UTF-8 string that can be used as the name of the item in a + * menu or list. For example, calling this function on an item that refers to + * "file:///foo/bar.txt" will yield "bar.txt". + * + * Return value: A newly-allocated string in UTF-8 encoding; free it with + * g_free(). + * + * Since: 2.10 + */ +gchar * +gtk_recent_info_get_short_name (GtkRecentInfo *info) +{ + gchar *short_name; + + g_return_val_if_fail (info != NULL, NULL); + + if (info->uri == NULL) + return NULL; + + short_name = get_uri_shortname_for_display (info->uri); + + return short_name; +} + +/** + * gtk_recent_info_get_uri_display: + * @info: a #GtkRecentInfo + * + * Gets a displayable version of the resource's URI. + * + * Return value: a UTF-8 string containing the resource's URI or %NULL + * + * Since: 2.10 + */ +gchar * +gtk_recent_info_get_uri_display (GtkRecentInfo *info) +{ + gchar *filename, *filename_utf8; + + g_return_val_if_fail (info != NULL, NULL); + + filename = g_filename_from_uri (info->uri, NULL, NULL); + if (!filename) + return NULL; + + filename_utf8 = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL); + g_free (filename); + + return filename_utf8; +} + +/** + * gtk_recent_info_get_age: + * @info: a #GtkRecentInfo + * + * Gets the number of days elapsed since the last update of the resource + * pointed by @info. + * + * Return value: a positive integer containing the number of days elapsed + * since the time this resource was last modified. On failure, -1 is + * returned. + * + * Since: 2.10 + */ +gint +gtk_recent_info_get_age (GtkRecentInfo *info) +{ + time_t now, delta; + gint retval; + + g_return_val_if_fail (info != NULL, -1); + + now = time (NULL); + + delta = now - info->modified; + g_assert (delta >= 0); + + retval = (gint) (delta / (60 * 60 * 24)); + + return retval; +} + +/** + * gtk_recent_info_get_groups: + * @info: a #GtkRecentInfo + * @length: return location for the number of groups returned, or %NULL + * + * Returns all groups registered for the recently used item @info. The + * array of returned group names will be %NULL terminated, so length might + * optionally be %NULL. + * + * Return value: a newly allocated %NULL terminated array of strings. Use + * g_strfreev() to free it. + * + * Since: 2.10 + */ +gchar ** +gtk_recent_info_get_groups (GtkRecentInfo *info, + gsize *length) +{ + GSList *l; + gchar **retval; + gsize n_groups, i; + + g_return_val_if_fail (info != NULL, NULL); + + if (!info->groups) + { + if (length) + *length = 0; + + return NULL; + } + + n_groups = g_slist_length (info->groups); + + retval = g_new0 (gchar *, n_groups + 1); + + for (l = info->groups, i = 0; + l != NULL; + l = l->next) + { + gchar *group_name = (gchar *) l->data; + + g_assert (group_name != NULL); + + retval[i++] = g_strdup (group_name); + } + retval[i] = NULL; + + if (length) + *length = i; + + return retval; +} + +/** + * gtk_recent_info_has_group: + * @info: a #GtkRecentInfo + * @group_name: name of a group + * + * Checks whether @group_name appears inside the groups registered for the + * recently used item @info. + * + * Return value: %TRUE if the group was found. + * + * Since: 2.10 + */ +gboolean +gtk_recent_info_has_group (GtkRecentInfo *info, + const gchar *group_name) +{ + GSList *l; + + g_return_val_if_fail (info != NULL, FALSE); + g_return_val_if_fail (group_name != NULL, FALSE); + + if (!info->groups) + return FALSE; + + for (l = info->groups; l != NULL; l = l->next) + { + gchar *g = (gchar *) l->data; + + if (strcmp (g, group_name) == 0) + return TRUE; + } + + return FALSE; +} + +#define __GTK_RECENT_MANAGER_C__ +#include "gtkaliasdef.c" diff --git a/gtk/gtkrecentmanager.h b/gtk/gtkrecentmanager.h new file mode 100644 index 0000000000..f41e2dab2b --- /dev/null +++ b/gtk/gtkrecentmanager.h @@ -0,0 +1,214 @@ +/* GTK - The GIMP Toolkit + * gtkrecentmanager.h: a manager for the recently used resources + * + * Copyright (C) 2006 Emmanuele Bassi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + */ + +#ifndef __GTK_RECENT_MANAGER_H__ +#define __GTK_RECENT_MANAGER_H__ + +#include +#include +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_RECENT_INFO (gtk_recent_info_get_type ()) + +#define GTK_TYPE_RECENT_MANAGER (gtk_recent_manager_get_type ()) +#define GTK_RECENT_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_RECENT_MANAGER, GtkRecentManager)) +#define GTK_IS_RECENT_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_RECENT_MANAGER)) +#define GTK_RECENT_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_RECENT_MANAGER, GtkRecentManagerClass)) +#define GTK_IS_RECENT_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_RECENT_MANAGER)) +#define GTK_RECENT_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_RECENT_MANAGER, GtkRecentManagerClass)) + +typedef struct _GtkRecentInfo GtkRecentInfo; +typedef struct _GtkRecentData GtkRecentData; +typedef struct _GtkRecentManager GtkRecentManager; +typedef struct _GtkRecentManagerClass GtkRecentManagerClass; +typedef struct _GtkRecentManagerPrivate GtkRecentManagerPrivate; + +/** + * GtkRecentData: + * + * @display_name: a UTF-8 encoded string, containing the name of the recently + * used resource to be displayed, or %NULL; + * @description: a UTF-8 encoded string, containing a short description of + * the resource, or %NULL; + * @mime_type: the MIME type of the resource; + * @app_name: the name of the application that is registering this recently + * used resource; + * @app_exec: command line used to launch this resource; may contain the + * "%f" and "%u" escape characters which will be expanded to the resource + * file path and URI respectively when the command line is retrieved; + * @groups: a vector of strings containing groups names; + * @is_private: whether this resource should be displayed only by the + * applications that have registered it or not. + * + * Meta-data to be passed to gtk_recent_manager_add_full() when + * registering a recently used resource. + **/ +struct _GtkRecentData +{ + gchar *display_name; + gchar *description; + + gchar *mime_type; + + gchar *app_name; + gchar *app_exec; + + gchar **groups; + + gboolean is_private; +}; + +struct _GtkRecentManager +{ + /*< private >*/ + GObject parent_instance; + + GtkRecentManagerPrivate *priv; +}; + +struct _GtkRecentManagerClass +{ + /*< private >*/ + GObjectClass parent_class; + + void (*changed) (GtkRecentManager *manager); + + /* padding for future expansion */ + void (*_gtk_recent1) (void); + void (*_gtk_recent2) (void); + void (*_gtk_recent3) (void); + void (*_gtk_recent4) (void); +}; + +/** + * GtkRecentManagerError: + * @GTK_RECENT_MANAGER_ERROR_NOT_FOUND: the URI specified does not exists in + * the recently used resources list. + * @GTK_RECENT_MANAGER_ERROR_INVALID_URI: the URI specified is not valid. + * @GTK_RECENT_MANAGER_ERROR_INVALID_MIME: the MIME type specified is not + * valid. + * @GTK_RECENT_MANAGER_ERROR_INVALID_ENCODING: the supplied string is not + * UTF-8 encoded. + * @GTK_RECENT_MANAGER_ERROR_NOT_REGISTERED: no application has registered + * the specified item. + * @GTK_RECENT_MANAGER_ERROR_READ: failure while reading the recently used + * resources file. + * @GTK_RECENT_MANAGER_ERROR_WRITE: failure while writing the recently used + * resources file. + * @GTK_RECENT_MANAGER_ERROR_UNKNOWN: unspecified error. + * + * Error codes for GtkRecentManager operations + **/ +typedef enum +{ + GTK_RECENT_MANAGER_ERROR_NOT_FOUND, + GTK_RECENT_MANAGER_ERROR_INVALID_URI, + GTK_RECENT_MANAGER_ERROR_INVALID_MIME, + GTK_RECENT_MANAGER_ERROR_INVALID_ENCODING, + GTK_RECENT_MANAGER_ERROR_NOT_REGISTERED, + GTK_RECENT_MANAGER_ERROR_BAD_EXEC_STRING, + GTK_RECENT_MANAGER_ERROR_READ, + GTK_RECENT_MANAGER_ERROR_WRITE, + GTK_RECENT_MANAGER_ERROR_UNKNOWN +} GtkRecentManagerError; + +#define GTK_RECENT_MANAGER_ERROR (gtk_recent_manager_error_quark ()) +GQuark gtk_recent_manager_error_quark (void); + + +GType gtk_recent_manager_get_type (void) G_GNUC_CONST; + +GtkRecentManager *gtk_recent_manager_new (void); +GtkRecentManager *gtk_recent_manager_get_default (void); +GtkRecentManager *gtk_recent_manager_get_for_screen (GdkScreen *screen); + +void gtk_recent_manager_set_screen (GtkRecentManager *manager, + GdkScreen *screen); + +gboolean gtk_recent_manager_add_item (GtkRecentManager *manager, + const gchar *uri, + GError **error); +gboolean gtk_recent_manager_add_full (GtkRecentManager *manager, + const gchar *uri, + const GtkRecentData *recent_data, + GError **error); +gboolean gtk_recent_manager_remove_item (GtkRecentManager *manager, + const gchar *uri, + GError **error); +GtkRecentInfo * gtk_recent_manager_lookup_item (GtkRecentManager *manager, + const gchar *uri, + GError **error); +gboolean gtk_recent_manager_has_item (GtkRecentManager *manager, + const gchar *uri); +gboolean gtk_recent_manager_move_item (GtkRecentManager *manager, + const gchar *uri, + const gchar *new_uri, + GError **error); +void gtk_recent_manager_set_limit (GtkRecentManager *manager, + gint limit); +gint gtk_recent_manager_get_limit (GtkRecentManager *manager); +GList * gtk_recent_manager_get_items (GtkRecentManager *manager); +gint gtk_recent_manager_purge_items (GtkRecentManager *manager, + GError **error); + + +GType gtk_recent_info_get_type (void) G_GNUC_CONST; + +GtkRecentInfo * gtk_recent_info_ref (GtkRecentInfo *info); +void gtk_recent_info_unref (GtkRecentInfo *info); + +G_CONST_RETURN gchar *gtk_recent_info_get_uri (GtkRecentInfo *info); +G_CONST_RETURN gchar *gtk_recent_info_get_display_name (GtkRecentInfo *info); +G_CONST_RETURN gchar *gtk_recent_info_get_description (GtkRecentInfo *info); +G_CONST_RETURN gchar *gtk_recent_info_get_mime_type (GtkRecentInfo *info); +time_t gtk_recent_info_get_added (GtkRecentInfo *info); +time_t gtk_recent_info_get_modified (GtkRecentInfo *info); +time_t gtk_recent_info_get_visited (GtkRecentInfo *info); +gboolean gtk_recent_info_get_private_hint (GtkRecentInfo *info); +gboolean gtk_recent_info_get_application_info (GtkRecentInfo *info, + const gchar *app_name, + gchar **app_exec, + guint *count, + time_t *time); +gchar ** gtk_recent_info_get_applications (GtkRecentInfo *info, + gsize *length) G_GNUC_MALLOC; +gchar * gtk_recent_info_last_application (GtkRecentInfo *info) G_GNUC_MALLOC; +gboolean gtk_recent_info_has_application (GtkRecentInfo *info, + const gchar *app_name); +gchar ** gtk_recent_info_get_groups (GtkRecentInfo *info, + gsize *length) G_GNUC_MALLOC; +gboolean gtk_recent_info_has_group (GtkRecentInfo *info, + const gchar *group_name); +GdkPixbuf * gtk_recent_info_get_icon (GtkRecentInfo *info, + gint size); +gchar * gtk_recent_info_get_short_name (GtkRecentInfo *info) G_GNUC_MALLOC; +gchar * gtk_recent_info_get_uri_display (GtkRecentInfo *info) G_GNUC_MALLOC; +gint gtk_recent_info_get_age (GtkRecentInfo *info); +gboolean gtk_recent_info_is_local (GtkRecentInfo *info); +gboolean gtk_recent_info_exists (GtkRecentInfo *info); +gboolean gtk_recent_info_match (GtkRecentInfo *info_a, + GtkRecentInfo *info_b); + +G_END_DECLS + +#endif /* __GTK_RECENT_MANAGER_H__ */