X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkrecentmanager.c;h=310efe077126223be5bb33dbbcedb2a548909a4d;hb=d484721b5ca9e82d6422cca8a3a40f001208f87b;hp=de5b396639bad53cfd07417ee0f7f305cb9d0317;hpb=13277b42e28a987c0e26f4e0ecb458b73d7f06ff;p=~andy%2Fgtk diff --git a/gtk/gtkrecentmanager.c b/gtk/gtkrecentmanager.c index de5b39663..310efe077 100644 --- a/gtk/gtkrecentmanager.c +++ b/gtk/gtkrecentmanager.c @@ -14,8 +14,76 @@ * 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, + * License along with this library. If not, see . + */ + +/** + * SECTION:gtkrecentmanager + * @Title: GtkRecentManager + * @short_description: Managing recently used files + * @See_Also: #GBookmarkFile, #GtkSettings, #GtkRecentChooser + * + * #GtkRecentManager provides a facility for adding, removing and + * looking up recently used files. Each recently used file is + * identified by its URI, and has meta-data associated to it, like + * the names and command lines of the applications that have + * registered it, the number of time each application has registered + * the same file, the mime type of the file and whether the file + * should be displayed only by the applications that have + * registered it. + * + * The recently used files list is per user. + * + * The #GtkRecentManager acts like a database of all the recently + * used files. You can create new #GtkRecentManager objects, but + * it is more efficient to use the default manager created by GTK+. + * + * Adding a new recently used file is as simple as: + * + * |[ + * GtkRecentManager *manager; + * + * manager = gtk_recent_manager_get_default (); + * gtk_recent_manager_add_item (manager, file_uri); + * ]| + * + * The #GtkRecentManager will try to gather all the needed information + * from the file itself through GIO. + * + * Looking up the meta-data associated with a recently used file + * given its URI requires calling gtk_recent_manager_lookup_item(): + * + * |[ + * GtkRecentManager *manager; + * GtkRecentInfo *info; + * GError *error = NULL; + * + * manager = gtk_recent_manager_get_default (); + * info = gtk_recent_manager_lookup_item (manager, file_uri, &error); + * if (error) + * { + * g_warning ("Could not find the file: %s", error->message); + * g_error_free (error); + * } + * else + * { + * /* Use the info object */ + * gtk_recent_info_unref (info); + * } + * ]| + * + * In order to retrieve the list of recently used files, you can use + * gtk_recent_manager_get_items(), which returns a list of #GtkRecentInfo + * structures. + * + * A #GtkRecentManager is the model used to populate the contents of + * one, or more #GtkRecentChooser implementations. + * + * The maximum age of the recently used files list is + * controllable through the #GtkSettings:gtk-recent-files-max-age + * property. + * + * Recently used files are supported since GTK+ 2.10. */ #include "config.h" @@ -30,26 +98,19 @@ #include #include #include +#include #include "gtkrecentmanager.h" #include "gtkintl.h" +#include "gtksettings.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 approximately every five seconds */ -#define POLL_DELTA 5 +#define GTK_RECENTLY_USED_FILE "recently-used.xbel" /* return all items by default */ #define DEFAULT_LIMIT -1 @@ -57,13 +118,6 @@ /* keep in sync with xdgmime */ #define GTK_RECENT_DEFAULT_MIME "application/octet-stream" -typedef struct -{ - GSourceFunc func; - gpointer data; - GDestroyNotify notify; -} ThreadsDispatch; - typedef struct { gchar *name; @@ -74,6 +128,17 @@ typedef struct time_t stamp; } RecentAppInfo; +/** + * GtkRecentInfo: + * + * #GtkRecentInfo is an opaque data structure + * whose members can only be accessed using the provided API. + * + * #GtkRecentInfo constains all the meta-data + * associated with an entry in the recently used files list. + * + * Since: 2.10 + */ struct _GtkRecentInfo { gchar *uri; @@ -104,16 +169,15 @@ struct _GtkRecentManagerPrivate gchar *filename; guint is_dirty : 1; - guint write_in_progress : 1; - guint read_in_progress : 1; - gint limit; gint size; GBookmarkFile *recent_items; - - time_t last_mtime; - guint poll_timeout; + + GFileMonitor *monitor; + + guint changed_timeout; + guint changed_age; }; enum @@ -125,33 +189,42 @@ enum PROP_SIZE }; -static void gtk_recent_manager_dispose (GObject *object); -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 void gtk_recent_manager_dispose (GObject *object); +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_add_item_query_info (GObject *source_object, + GAsyncResult *res, + gpointer user_data); +static void gtk_recent_manager_monitor_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data); +static void gtk_recent_manager_changed (GtkRecentManager *manager); +static void gtk_recent_manager_real_changed (GtkRecentManager *manager); +static void gtk_recent_manager_set_filename (GtkRecentManager *manager, + const gchar *filename); +static void gtk_recent_manager_clamp_to_age (GtkRecentManager *manager, + gint age); +static void gtk_recent_manager_enabled_changed (GtkRecentManager *manager); + + +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; @@ -197,47 +270,18 @@ gtk_recent_manager_error_quark (void) return g_quark_from_static_string ("gtk-recent-manager-error-quark"); } -static gboolean -threads_dispatch (gpointer data) -{ - ThreadsDispatch *dispatch = data; - gboolean res = FALSE; - - GDK_THREADS_ENTER (); - - if (!g_source_is_destroyed (g_main_current_source ())) - res = dispatch->func (dispatch->data); - - GDK_THREADS_LEAVE (); - - return res; -} - -static void -threads_free (gpointer data) -{ - ThreadsDispatch *dispatch = data; - - if (dispatch->notify) - dispatch->notify (dispatch->data); - - g_slice_free (ThreadsDispatch, dispatch); -} - 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->dispose = gtk_recent_manager_dispose; gobject_class->finalize = gtk_recent_manager_finalize; /** - * GtkRecentManager:filename + * GtkRecentManager:filename: * * The full path to the file to be used to store and read the recently * used resources list @@ -250,26 +294,10 @@ gtk_recent_manager_class_init (GtkRecentManagerClass *klass) 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)); + (G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE))); + /** - * GtkRecentManager:size + * GtkRecentManager:size: * * The size of the recently used resources list. * @@ -286,11 +314,12 @@ gtk_recent_manager_class_init (GtkRecentManagerClass *klass) G_PARAM_READABLE)); /** - * GtkRecentManager::changed + * GtkRecentManager::changed: * @recent_manager: the recent manager * * Emitted when the current recently used resources manager changes its - * contents. + * contents, either by calling gtk_recent_manager_add_item() or by another + * application. * * Since: 2.10 */ @@ -312,34 +341,19 @@ static void gtk_recent_manager_init (GtkRecentManager *manager) { GtkRecentManagerPrivate *priv; - ThreadsDispatch *dispatch; - - priv = g_type_instance_get_private ((GTypeInstance *) manager, - GTK_TYPE_RECENT_MANAGER); - manager->priv = priv; - - priv->limit = DEFAULT_LIMIT; + GtkSettings *settings; + + manager->priv = G_TYPE_INSTANCE_GET_PRIVATE (manager, + GTK_TYPE_RECENT_MANAGER, + GtkRecentManagerPrivate); + priv = manager->priv; + priv->size = 0; - - priv->is_dirty = FALSE; - priv->write_in_progress = FALSE; - priv->read_in_progress = FALSE; - - priv->filename = g_build_filename (g_get_home_dir (), - GTK_RECENTLY_USED_FILE, - NULL); - - dispatch = g_slice_new (ThreadsDispatch); - dispatch->func = gtk_recent_manager_poll_timeout; - dispatch->data = manager; - dispatch->notify = NULL; - priv->poll_timeout = g_timeout_add_seconds_full (G_PRIORITY_DEFAULT + 30, - POLL_DELTA, - threads_dispatch, - dispatch, - threads_free); + priv->filename = NULL; - build_recent_items_list (manager); + settings = gtk_settings_get_default (); + g_signal_connect_swapped (settings, "notify::gtk-recent-files-enabled", + G_CALLBACK (gtk_recent_manager_enabled_changed), manager); } static void @@ -354,9 +368,6 @@ gtk_recent_manager_set_property (GObject *object, { 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); @@ -377,9 +388,6 @@ gtk_recent_manager_get_property (GObject *object, 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; @@ -390,32 +398,56 @@ gtk_recent_manager_get_property (GObject *object, } static void -gtk_recent_manager_dispose (GObject *object) +gtk_recent_manager_finalize (GObject *object) { GtkRecentManager *manager = GTK_RECENT_MANAGER (object); GtkRecentManagerPrivate *priv = manager->priv; - if (priv->poll_timeout) - { - g_source_remove (priv->poll_timeout); - priv->poll_timeout = 0; - } + g_free (priv->filename); - G_OBJECT_CLASS (gtk_recent_manager_parent_class)->dispose (object); + if (priv->recent_items != NULL) + g_bookmark_file_free (priv->recent_items); + + G_OBJECT_CLASS (gtk_recent_manager_parent_class)->finalize (object); } static void -gtk_recent_manager_finalize (GObject *object) +gtk_recent_manager_dispose (GObject *gobject) { - GtkRecentManager *manager = GTK_RECENT_MANAGER (object); + GtkRecentManager *manager = GTK_RECENT_MANAGER (gobject); GtkRecentManagerPrivate *priv = manager->priv; - g_free (priv->filename); - - if (priv->recent_items) - g_bookmark_file_free (priv->recent_items); + if (priv->monitor != NULL) + { + g_signal_handlers_disconnect_by_func (priv->monitor, + G_CALLBACK (gtk_recent_manager_monitor_changed), + manager); + g_object_unref (priv->monitor); + priv->monitor = NULL; + } - G_OBJECT_CLASS (gtk_recent_manager_parent_class)->finalize (object); + if (priv->changed_timeout != 0) + { + g_source_remove (priv->changed_timeout); + priv->changed_timeout = 0; + priv->changed_age = 0; + } + + if (priv->is_dirty) + { + g_object_ref (manager); + g_signal_emit (manager, signal_changed, 0); + g_object_unref (manager); + } + + G_OBJECT_CLASS (gtk_recent_manager_parent_class)->dispose (gobject); +} + +static void +gtk_recent_manager_enabled_changed (GtkRecentManager *manager) +{ + manager->priv->is_dirty = TRUE; + gtk_recent_manager_changed (manager); } static void @@ -428,23 +460,39 @@ gtk_recent_manager_real_changed (GtkRecentManager *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) { + /* if no container object has been defined, we create a new + * empty container, and dump it + */ priv->recent_items = g_bookmark_file_new (); priv->size = 0; } + else + { + GtkSettings *settings = gtk_settings_get_default (); + gint age = 30; + gboolean enabled; + + g_object_get (G_OBJECT (settings), + "gtk-recent-files-max-age", &age, + "gtk-recent-files-enabled", &enabled, + NULL); + + if (age == 0 || !enabled) + { + g_bookmark_file_free (priv->recent_items); + priv->recent_items = g_bookmark_file_new (); + } + else if (age > 0) + gtk_recent_manager_clamp_to_age (manager, age); + } write_error = NULL; g_bookmark_file_to_file (priv->recent_items, priv->filename, &write_error); @@ -457,26 +505,14 @@ gtk_recent_manager_real_changed (GtkRecentManager *manager) g_error_free (write_error); } - priv->write_in_progress = FALSE; - - /* 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) - { - filename_warning ("Unable to stat() the recently used resources file " - "at `%s': %s.", - priv->filename, - g_strerror (errno)); - - g_object_thaw_notify (G_OBJECT (manager)); + if (g_chmod (priv->filename, 0600) < 0) + { + filename_warning ("Attempting to set the permissions of `%s', " + "but failed: %s", + priv->filename, + g_strerror (errno)); + } - return; - } - - priv->last_mtime = stat_buf.st_mtime; - /* mark us as clean */ priv->is_dirty = FALSE; } @@ -492,50 +528,50 @@ gtk_recent_manager_real_changed (GtkRecentManager *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) +static void +gtk_recent_manager_monitor_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data) { - GtkRecentManager *manager = GTK_RECENT_MANAGER (data); - GtkRecentManagerPrivate *priv = manager->priv; - time_t now; - struct stat stat_buf; - int stat_res; + GtkRecentManager *manager = user_data; - /* wait for the next timeout if we have a read/write in progress */ - if (priv->write_in_progress || priv->read_in_progress) - return TRUE; + switch (event_type) + { + case G_FILE_MONITOR_EVENT_CHANGED: + case G_FILE_MONITOR_EVENT_CREATED: + gdk_threads_enter (); + gtk_recent_manager_changed (manager); + gdk_threads_leave (); + break; - /* do not stat the file if we're within 5 seconds from the last stat() */ - now = time (NULL); - if (now < priv->last_mtime + 5); - return TRUE; + case G_FILE_MONITOR_EVENT_DELETED: + break; - 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; - - filename_warning ("Unable to stat() the recently used resources file " - "at `%s': %s.", - priv->filename, - g_strerror (errno)); - - return TRUE; + default: + break; } +} - /* the file didn't change from the last poll, so we bail out */ - if (stat_buf.st_mtime == priv->last_mtime) - return TRUE; +static gchar * +get_default_filename (void) +{ + if (g_mkdir_with_parents (g_get_user_data_dir (), 0755) == -1) + { + int saved_errno = errno; - /* the file has been changed, hence we emit the "changed" signal */ - gtk_recent_manager_changed (manager); + g_critical ("Unable to create user data directory '%s' for storing " + "the recently used files list: %s", + g_get_user_data_dir (), + g_strerror (saved_errno)); - return TRUE; + return NULL; + } + + return g_build_filename (g_get_user_data_dir (), + GTK_RECENTLY_USED_FILE, + NULL); } static void @@ -543,37 +579,67 @@ gtk_recent_manager_set_filename (GtkRecentManager *manager, const gchar *filename) { GtkRecentManagerPrivate *priv; - ThreadsDispatch *dispatch; + GFile *file; + GError *error; 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) + /* if a filename is already set and filename is not NULL, then copy + * it and reset the monitor; otherwise, if it's NULL we're being + * called from the finalization sequence, so we simply disconnect the + * monitoring and return. + * + * if no filename is set and filename is NULL, use the default. + */ + if (priv->filename) { - g_source_remove (manager->priv->poll_timeout); - manager->priv->poll_timeout = 0; + g_free (priv->filename); + + if (priv->monitor) + { + g_signal_handlers_disconnect_by_func (priv->monitor, + G_CALLBACK (gtk_recent_manager_monitor_changed), + manager); + g_object_unref (priv->monitor); + priv->monitor = NULL; + } + + if (!filename || *filename == '\0') + return; + else + priv->filename = g_strdup (filename); + } + else + { + if (!filename || *filename == '\0') + priv->filename = get_default_filename (); + else + priv->filename = g_strdup (filename); } - priv->filename = g_strdup (filename); + g_assert (priv->filename != NULL); + file = g_file_new_for_path (priv->filename); - dispatch = g_slice_new (ThreadsDispatch); - dispatch->func = gtk_recent_manager_poll_timeout; - dispatch->data = manager; - dispatch->notify = NULL; - priv->poll_timeout = g_timeout_add_seconds_full (G_PRIORITY_DEFAULT + 30, - POLL_DELTA, - threads_dispatch, - dispatch, - threads_free); + error = NULL; + priv->monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, &error); + if (error) + { + filename_warning ("Unable to monitor `%s': %s\n" + "The GtkRecentManager will not update its contents " + "if the file is changed from other instances", + priv->filename, + error->message); + g_error_free (error); + } + else + g_signal_connect (priv->monitor, "changed", + G_CALLBACK (gtk_recent_manager_monitor_changed), + manager); + + g_object_unref (file); - /* mark us clean, so that we can re-read the list - * of recently used resources - */ priv->is_dirty = FALSE; build_recent_items_list (manager); } @@ -586,14 +652,10 @@ gtk_recent_manager_set_filename (GtkRecentManager *manager, static void build_recent_items_list (GtkRecentManager *manager) { - GtkRecentManagerPrivate *priv; - struct stat stat_buf; - int stat_res; - gboolean res; + GtkRecentManagerPrivate *priv = manager->priv; GError *read_error; gint size; - priv = manager->priv; g_assert (priv->filename != NULL); if (!priv->recent_items) @@ -602,60 +664,41 @@ build_recent_items_list (GtkRecentManager *manager) 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 - { - filename_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); + g_bookmark_file_load_from_file (priv->recent_items, priv->filename, &read_error); if (read_error) { - filename_warning ("Attempting to read the recently used resources file " - "at `%s', but the parser failed: %s.", - priv->filename, - read_error->message); + /* if the file does not exist we just wait for the first write + * operation on this recent manager instance, to avoid creating + * empty files and leading to spurious file system events (Sabayon + * will not be happy about those) + */ + if (read_error->domain == G_FILE_ERROR && + read_error->code != G_FILE_ERROR_NOENT) + filename_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) + else { - priv->size = size; - - g_object_notify (G_OBJECT (manager), "size"); + 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; priv->is_dirty = FALSE; } @@ -690,10 +733,9 @@ gtk_recent_manager_new (void) * gtk_recent_manager_get_default: * * Gets a unique instance of #GtkRecentManager, that you can share - * in your application without caring about memory management. The - * returned instance will be freed when you application terminates. + * in your application without caring about memory management. * - * Return value: A unique #GtkRecentManager. Do not ref or unref it. + * Return value: (transfer none): A unique #GtkRecentManager. Do not ref or unref it. * * Since: 2.10 */ @@ -706,101 +748,68 @@ gtk_recent_manager_get_default (void) return recent_manager_singleton; } -/** - * 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. - * - * Deprecated: 2.12: This function has been deprecated and should - * not be used in newly written code. Calling this function is - * equivalent to calling gtk_recent_manager_get_default(). - * - * Since: 2.10 - */ -GtkRecentManager * -gtk_recent_manager_get_for_screen (GdkScreen *screen) -{ - return gtk_recent_manager_get_default (); -} -/** - * 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 - * - * Deprecated: 2.12: This function has been deprecated and should - * not be used in newly written code. Calling this function has - * no effect. - */ -void -gtk_recent_manager_set_screen (GtkRecentManager *manager, - GdkScreen *screen) +static void +gtk_recent_manager_add_item_query_info (GObject *source_object, + GAsyncResult *res, + gpointer user_data) { + GFile *file = G_FILE (source_object); + GtkRecentManager *manager = user_data; + GtkRecentData recent_data; + GFileInfo *file_info; + gchar *uri, *basename; -} + uri = g_file_get_uri (file); -/** - * gtk_recent_manager_set_limit: - * @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 *manager, - gint limit) -{ - GtkRecentManagerPrivate *priv; - - g_return_if_fail (GTK_IS_RECENT_MANAGER (manager)); - - priv = manager->priv; - priv->limit = limit; -} + file_info = g_file_query_info_finish (file, res, NULL); /* NULL-GError */ -/** - * gtk_recent_manager_get_limit: - * @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 *manager) -{ - GtkRecentManagerPrivate *priv; - - g_return_val_if_fail (GTK_IS_RECENT_MANAGER (manager), DEFAULT_LIMIT); - - priv = manager->priv; - return priv->limit; + recent_data.display_name = NULL; + recent_data.description = NULL; + + if (file_info) + { + gchar *content_type; + + content_type = g_file_info_get_attribute_as_string (file_info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE); + + if (G_LIKELY (content_type)) + recent_data.mime_type = g_content_type_get_mime_type (content_type); + else + recent_data.mime_type = g_strdup (GTK_RECENT_DEFAULT_MIME); + + g_free (content_type); + g_object_unref (file_info); + } + else + { + basename = g_file_get_basename (file); + recent_data.mime_type = g_content_type_guess (basename, NULL, 0, NULL); + g_free (basename); + } + + 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; + + gdk_threads_enter (); + + /* Ignore return value, this can't fail anyway since all required + * fields are set */ + gtk_recent_manager_add_full (manager, uri, &recent_data); + + manager->priv->is_dirty = TRUE; + gtk_recent_manager_changed (manager); + + gdk_threads_leave (); + + g_free (recent_data.mime_type); + g_free (recent_data.app_name); + g_free (recent_data.app_exec); + + g_object_unref (manager); + g_free (uri); } /** @@ -814,7 +823,7 @@ gtk_recent_manager_get_limit (GtkRecentManager *manager) * This function automatically retrieves 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 explicitly * define the metadata for the resource pointed by @uri. * @@ -827,51 +836,24 @@ gboolean gtk_recent_manager_add_item (GtkRecentManager *manager, const gchar *uri) { - GtkRecentData recent_data; - gboolean retval; + GFile* file; g_return_val_if_fail (GTK_IS_RECENT_MANAGER (manager), FALSE); g_return_val_if_fail (uri != NULL, FALSE); - recent_data.display_name = NULL; - recent_data.description = NULL; - recent_data.mime_type = NULL; + file = g_file_new_for_uri (uri); -#ifdef G_OS_UNIX - if (has_case_prefix (uri, "file:/")) - { - gchar *filename; - const gchar *mime_type; - - filename = g_filename_from_uri (uri, NULL, NULL); - if (filename) - { - mime_type = xdg_mime_get_mime_type_for_file (filename, NULL); - if (mime_type) - recent_data.mime_type = g_strdup (mime_type); - - g_free (filename); - } + g_file_query_info_async (file, + G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, + G_PRIORITY_DEFAULT, + G_FILE_QUERY_INFO_NONE, + NULL, + gtk_recent_manager_add_item_query_info, + g_object_ref (manager)); - if (!recent_data.mime_type) - recent_data.mime_type = g_strdup (GTK_RECENT_DEFAULT_MIME); - } - 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; - - retval = gtk_recent_manager_add_full (manager, uri, &recent_data); - - g_free (recent_data.mime_type); - g_free (recent_data.app_name); - g_free (recent_data.app_exec); + g_object_unref (file); - return retval; + return TRUE; } /** @@ -911,6 +893,8 @@ gtk_recent_manager_add_full (GtkRecentManager *manager, const GtkRecentData *data) { GtkRecentManagerPrivate *priv; + GtkSettings *settings; + gboolean enabled; g_return_val_if_fail (GTK_IS_RECENT_MANAGER (manager), FALSE); g_return_val_if_fail (uri != NULL, FALSE); @@ -963,7 +947,12 @@ gtk_recent_manager_add_full (GtkRecentManager *manager, uri); return FALSE; } - + + settings = gtk_settings_get_default (); + g_object_get (G_OBJECT (settings), "gtk-recent-files-enabled", &enabled, NULL); + if (!enabled) + return TRUE; + priv = manager->priv; if (!priv->recent_items) @@ -1003,7 +992,6 @@ gtk_recent_manager_add_full (GtkRecentManager *manager, * will dump our changes */ priv->is_dirty = TRUE; - gtk_recent_manager_changed (manager); return TRUE; @@ -1013,7 +1001,7 @@ gtk_recent_manager_add_full (GtkRecentManager *manager, * gtk_recent_manager_remove_item: * @manager: a #GtkRecentManager * @uri: the URI of the item you wish to remove - * @error: return location for a #GError, or %NULL + * @error: (allow-none): return location for a #GError, or %NULL * * Removes a resource pointed by @uri from the recently used resources * list handled by a recent manager. @@ -1053,13 +1041,17 @@ gtk_recent_manager_remove_item (GtkRecentManager *manager, g_bookmark_file_remove_item (priv->recent_items, uri, &remove_error); if (remove_error) { - g_propagate_error (error, remove_error); + g_error_free (remove_error); + + 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; } priv->is_dirty = TRUE; - gtk_recent_manager_changed (manager); return TRUE; @@ -1146,8 +1138,7 @@ build_recent_info (GBookmarkFile *bookmarks, app_info->count = count; app_info->stamp = stamp; - info->applications = g_slist_append (info->applications, - app_info); + info->applications = g_slist_prepend (info->applications, app_info); g_hash_table_replace (info->apps_lookup, app_info->name, app_info); } @@ -1158,7 +1149,7 @@ build_recent_info (GBookmarkFile *bookmarks, * gtk_recent_manager_lookup_item: * @manager: a #GtkRecentManager * @uri: a URI - * @error: a return location for a #GError, or %NULL + * @error: (allow-none): 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 @@ -1221,9 +1212,9 @@ gtk_recent_manager_lookup_item (GtkRecentManager *manager, * 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 + * @new_uri: (allow-none): 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 + * @error: (allow-none): a return location for a #GError, or %NULL * * Changes the location of a recently used resource from @uri to @new_uri. * @@ -1233,46 +1224,58 @@ gtk_recent_manager_lookup_item (GtkRecentManager *manager, * 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) + 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 (!priv->recent_items) + { + 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; + } + 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); + 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) + if (!g_bookmark_file_move_item (priv->recent_items, + uri, + new_uri, + &move_error)) { - g_propagate_error (error, move_error); + g_error_free (move_error); + + 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; } - - priv->is_dirty = TRUE; + priv->is_dirty = TRUE; gtk_recent_manager_changed (recent_manager); - + return TRUE; } @@ -1282,7 +1285,8 @@ gtk_recent_manager_move_item (GtkRecentManager *recent_manager, * * Gets the list of recently used resources. * - * Return value: a list of newly allocated #GtkRecentInfo objects. Use + * Return value: (element-type GtkRecentInfo) (transfer full): 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(). * @@ -1302,17 +1306,11 @@ gtk_recent_manager_get_items (GtkRecentManager *manager) if (!priv->recent_items) return NULL; - if (priv->limit == 0) - return NULL; - uris = g_bookmark_file_get_uris (priv->recent_items, &uris_len); for (i = 0; i < uris_len; i++) { GtkRecentInfo *info; - if (priv->limit != -1 && i == priv->limit) - break; - info = gtk_recent_info_new (uris[i]); build_recent_info (priv->recent_items, info); @@ -1330,24 +1328,22 @@ purge_recent_items_list (GtkRecentManager *manager, { GtkRecentManagerPrivate *priv = manager->priv; - if (!priv->recent_items) + if (priv->recent_items == NULL) 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 */ + priv->is_dirty = TRUE; gtk_recent_manager_changed (manager); } /** * gtk_recent_manager_purge_items: * @manager: a #GtkRecentManager - * @error: a return location for a #GError, or %NULL + * @error: (allow-none): a return location for a #GError, or %NULL * * Purges every item from the recently used resources list. * @@ -1380,27 +1376,83 @@ gtk_recent_manager_purge_items (GtkRecentManager *manager, return purged; } +static gboolean +emit_manager_changed (gpointer data) +{ + GtkRecentManager *manager = data; + + manager->priv->changed_age = 0; + manager->priv->changed_timeout = 0; + + g_signal_emit (manager, signal_changed, 0); + + return FALSE; +} + static void -gtk_recent_manager_changed (GtkRecentManager *recent_manager) +gtk_recent_manager_changed (GtkRecentManager *manager) { - g_signal_emit (recent_manager, signal_changed, 0); + /* coalesce consecutive changes + * + * we schedule a write in 250 msecs immediately; if we get more than one + * request per millisecond before the timeout has a chance to run, we + * schedule an emission immediately. + */ + if (manager->priv->changed_timeout == 0) + manager->priv->changed_timeout = gdk_threads_add_timeout (250, emit_manager_changed, manager); + else + { + manager->priv->changed_age += 1; + + if (manager->priv->changed_age > 250) + { + g_source_remove (manager->priv->changed_timeout); + g_signal_emit (manager, signal_changed, 0); + + manager->priv->changed_age = 0; + manager->priv->changed_timeout = 0; + } + } +} + +static void +gtk_recent_manager_clamp_to_age (GtkRecentManager *manager, + gint age) +{ + GtkRecentManagerPrivate *priv = manager->priv; + gchar **uris; + gsize n_uris, i; + time_t now; + + if (G_UNLIKELY (!priv->recent_items)) + return; + + now = time (NULL); + + uris = g_bookmark_file_get_uris (priv->recent_items, &n_uris); + + for (i = 0; i < n_uris; i++) + { + const gchar *uri = uris[i]; + time_t modified; + gint item_age; + + modified = g_bookmark_file_get_modified (priv->recent_items, uri, NULL); + item_age = (gint) ((now - modified) / (60 * 60 * 24)); + if (item_age > age) + g_bookmark_file_remove_item (priv->recent_items, uri, NULL); + } + + g_strfreev (uris); } /***************** * GtkRecentInfo * *****************/ -GType -gtk_recent_info_get_type (void) -{ - static GType info_type = 0; - - if (!info_type) - info_type = g_boxed_type_register_static (I_("GtkRecentInfo"), - (GBoxedCopyFunc) gtk_recent_info_ref, - (GBoxedFreeFunc) gtk_recent_info_unref); - return info_type; -} +G_DEFINE_BOXED_TYPE (GtkRecentInfo, gtk_recent_info, + gtk_recent_info_ref, + gtk_recent_info_unref) static GtkRecentInfo * gtk_recent_info_new (const gchar *uri) @@ -1516,7 +1568,7 @@ gtk_recent_info_unref (GtkRecentInfo *info) * * Since: 2.10 */ -G_CONST_RETURN gchar * +const gchar * gtk_recent_info_get_uri (GtkRecentInfo *info) { g_return_val_if_fail (info != NULL, NULL); @@ -1536,7 +1588,7 @@ gtk_recent_info_get_uri (GtkRecentInfo *info) * * Since: 2.10 */ -G_CONST_RETURN gchar * +const gchar * gtk_recent_info_get_display_name (GtkRecentInfo *info) { g_return_val_if_fail (info != NULL, NULL); @@ -1558,7 +1610,7 @@ gtk_recent_info_get_display_name (GtkRecentInfo *info) * * Since: 2.10 **/ -G_CONST_RETURN gchar * +const gchar * gtk_recent_info_get_description (GtkRecentInfo *info) { g_return_val_if_fail (info != NULL, NULL); @@ -1577,7 +1629,7 @@ gtk_recent_info_get_description (GtkRecentInfo *info) * * Since: 2.10 */ -G_CONST_RETURN gchar * +const gchar * gtk_recent_info_get_mime_type (GtkRecentInfo *info) { g_return_val_if_fail (info != NULL, NULL); @@ -1676,11 +1728,11 @@ recent_app_info_new (const gchar *app_name) g_assert (app_name != NULL); - app_info = g_new0 (RecentAppInfo, 1); + app_info = g_slice_new0 (RecentAppInfo); app_info->name = g_strdup (app_name); app_info->exec = NULL; app_info->count = 1; - app_info->stamp = time (NULL); + app_info->stamp = 0; return app_info; } @@ -1692,19 +1744,18 @@ recent_app_info_free (RecentAppInfo *app_info) return; g_free (app_info->name); - g_free (app_info->exec); - g_free (app_info); + g_slice_free (RecentAppInfo, 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 + * @app_exec: (transfer none) (out): return location for the string containing the command line + * @count: (out): return location for the number of times this item was registered + * @time_: (out): return location for the timestamp this item was last registered * for this application * * Gets the data regarding the application that has registered the resource @@ -1714,15 +1765,16 @@ recent_app_info_free (RecentAppInfo *app_info) * 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(). + * resource inside the recently used list, or %FALSE otherwise. The + * @app_exec string is owned by the #GtkRecentInfo and should not be + * modified or freed * * Since: 2.10 */ gboolean gtk_recent_info_get_application_info (GtkRecentInfo *info, const gchar *app_name, - gchar **app_exec, + const gchar **app_exec, guint *count, time_t *time_) { @@ -1757,15 +1809,16 @@ gtk_recent_info_get_application_info (GtkRecentInfo *info, /** * gtk_recent_info_get_applications: * @info: a #GtkRecentInfo - * @length: return location for the length of the returned list, or %NULL + * @length: (out) (allow-none): return location for the length of the returned list * * 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. + * Return value: (array length=length zero-terminated=1) (transfer full): + * 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) @@ -1867,52 +1920,31 @@ get_icon_for_mime_type (const char *mime_type, gint pixel_size) { GtkIconTheme *icon_theme; - const char *separator; - GString *icon_name; + char *content_type; + GIcon *icon; + GtkIconInfo *info; GdkPixbuf *pixbuf; - separator = strchr (mime_type, '/'); - if (!separator) + icon_theme = gtk_icon_theme_get_default (); + + content_type = g_content_type_from_mime_type (mime_type); + + if (!content_type) return NULL; - icon_theme = gtk_icon_theme_get_default (); + icon = g_content_type_get_icon (content_type); + info = gtk_icon_theme_lookup_by_gicon (icon_theme, + icon, + pixel_size, + GTK_ICON_LOOKUP_USE_BUILTIN); + g_free (content_type); + g_object_unref (icon); + + if (!info) + return NULL; - /* try with the three icon name variants for MIME types */ - - /* canonicalize MIME type: foo/x-bar -> foo-x-bar */ - icon_name = g_string_new (NULL); - 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 = gtk_icon_theme_load_icon (icon_theme, icon_name->str, - pixel_size, - 0, - NULL); - g_string_free (icon_name, TRUE); - if (pixbuf) - return pixbuf; - - /* canonicalize MIME type, and prepend "gnome-mime-" */ - 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 = gtk_icon_theme_load_icon (icon_theme, icon_name->str, - pixel_size, - 0, - NULL); - g_string_free (icon_name, TRUE); - if (pixbuf) - return pixbuf; - - /* try the MIME family icon */ - icon_name = g_string_new ("gnome-mime-"); - g_string_append_len (icon_name, mime_type, separator - mime_type); - pixbuf = gtk_icon_theme_load_icon (icon_theme, icon_name->str, - pixel_size, - 0, - NULL); - g_string_free (icon_name, TRUE); + pixbuf = gtk_icon_info_load_icon (info, NULL); + g_object_unref (info); return pixbuf; } @@ -1942,7 +1974,8 @@ get_icon_fallback (const gchar *icon_name, * * Retrieves the icon of size @size associated to the resource MIME type. * - * Return value: a #GdkPixbuf containing the icon, or %NULL. + * Return value: (transfer full): a #GdkPixbuf containing the icon, + * or %NULL. Use g_object_unref() when finished using the icon. * * Since: 2.10 */ @@ -1962,14 +1995,43 @@ gtk_recent_info_get_icon (GtkRecentInfo *info, { if (info->mime_type && strcmp (info->mime_type, "x-directory/normal") == 0) - retval = get_icon_fallback (GTK_STOCK_DIRECTORY, size); + retval = get_icon_fallback ("folder", size); else - retval = get_icon_fallback (GTK_STOCK_FILE, size); + retval = get_icon_fallback ("text-x-generic", size); } return retval; } +/** + * gtk_recent_info_get_gicon: + * @info: a #GtkRecentInfo + * + * Retrieves the icon associated to the resource MIME type. + * + * Return value: (transfer full): a #GIcon containing the icon, or %NULL. Use + * g_object_unref() when finished using the icon + * + * Since: 2.22 + */ +GIcon * +gtk_recent_info_get_gicon (GtkRecentInfo *info) +{ + GIcon *icon = NULL; + gchar *content_type; + + g_return_val_if_fail (info != NULL, NULL); + + if (info->mime_type != NULL && + (content_type = g_content_type_from_mime_type (info->mime_type)) != NULL) + { + icon = g_content_type_get_icon (content_type); + g_free (content_type); + } + + return icon; +} + /** * gtk_recent_info_is_local: * @info: a #GtkRecentInfo @@ -2004,7 +2066,7 @@ gboolean gtk_recent_info_exists (GtkRecentInfo *info) { gchar *filename; - struct stat stat_buf; + GStatBuf stat_buf; gboolean retval = FALSE; g_return_val_if_fail (info != NULL, FALSE); @@ -2016,7 +2078,7 @@ gtk_recent_info_exists (GtkRecentInfo *info) filename = g_filename_from_uri (info->uri, NULL, NULL); if (filename) { - if (stat (filename, &stat_buf) == 0) + if (g_stat (filename, &stat_buf) == 0) retval = TRUE; g_free (filename); @@ -2208,7 +2270,8 @@ gtk_recent_info_get_short_name (GtkRecentInfo *info) * is local, it returns a local path; if the resource is not local, * it returns the UTF-8 encoded content of gtk_recent_info_get_uri(). * - * Return value: a UTF-8 string containing the resource's URI or %NULL + * Return value: a newly allocated UTF-8 string containing the + * resource's URI or %NULL. Use g_free() when done using it. * * Since: 2.10 */ @@ -2271,14 +2334,15 @@ gtk_recent_info_get_age (GtkRecentInfo *info) /** * gtk_recent_info_get_groups: * @info: a #GtkRecentInfo - * @length: return location for the number of groups returned, or %NULL + * @length: (out) (allow-none): return location for the number of groups returned * * 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. + * Return value: (array length=length zero-terminated=1) (transfer full): + * a newly allocated %NULL terminated array of strings. + * Use g_strfreev() to free it. * * Since: 2.10 */ @@ -2357,6 +2421,72 @@ gtk_recent_info_has_group (GtkRecentInfo *info, return FALSE; } +/** + * gtk_recent_info_create_app_info: + * @info: a #GtkRecentInfo + * @app_name: (allow-none): the name of the application that should + * be mapped to a #GAppInfo; if %NULL is used then the default + * application for the MIME type is used + * @error: (allow-none): return location for a #GError, or %NULL + * + * Creates a #GAppInfo for the specified #GtkRecentInfo + * + * Return value: (transfer full): the newly created #GAppInfo, or %NULL. + * In case of error, @error will be set either with a + * %GTK_RECENT_MANAGER_ERROR or a %G_IO_ERROR + */ +GAppInfo * +gtk_recent_info_create_app_info (GtkRecentInfo *info, + const gchar *app_name, + GError **error) +{ + RecentAppInfo *ai; + GAppInfo *app_info; + GError *internal_error = NULL; + + g_return_val_if_fail (info != NULL, NULL); + + if (app_name == NULL || *app_name == '\0') + { + char *content_type; + + if (info->mime_type == NULL) + return NULL; + + content_type = g_content_type_from_mime_type (info->mime_type); + if (content_type == NULL) + return NULL; + + app_info = g_app_info_get_default_for_type (content_type, TRUE); + g_free (content_type); + + return app_info; + } + + ai = g_hash_table_lookup (info->apps_lookup, app_name); + if (ai == NULL) + { + g_set_error (error, GTK_RECENT_MANAGER_ERROR, + GTK_RECENT_MANAGER_ERROR_NOT_REGISTERED, + _("No registered application with name '%s' for item with URI '%s' found"), + app_name, + info->uri); + return NULL; + } + + internal_error = NULL; + app_info = g_app_info_create_from_commandline (ai->exec, ai->name, + G_APP_INFO_CREATE_NONE, + &internal_error); + if (internal_error != NULL) + { + g_propagate_error (error, internal_error); + return NULL; + } + + return app_info; +} + /* * _gtk_recent_manager_sync: * @@ -2372,6 +2502,3 @@ _gtk_recent_manager_sync (void) gtk_recent_manager_real_changed (recent_manager_singleton); } } - -#define __GTK_RECENT_MANAGER_C__ -#include "gtkaliasdef.c"