[gnome-shell] Add class for handling recent docs
- From: Colin Walters <walters src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [gnome-shell] Add class for handling recent docs
- Date: Tue, 15 Dec 2009 21:16:38 +0000 (UTC)
commit a442dfea1471c95e23fb8bd4b20ad39b146c243f
Author: Colin Walters <walters verbum org>
Date: Sun Nov 29 17:35:35 2009 -0500
Add class for handling recent docs
Push some of the JS docInfo down into C; crucially, this lets us
use the GIO async API.
https://bugzilla.gnome.org/show_bug.cgi?id=603522
src/Makefile.am | 2 +
src/shell-doc-system.c | 362 ++++++++++++++++++++++++++++++++++++++++++++++++
src/shell-doc-system.h | 46 ++++++
3 files changed, 410 insertions(+), 0 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 29c36cb..0b9ea5e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -63,6 +63,8 @@ libgnome_shell_la_SOURCES = \
shell-app-usage.h \
shell-arrow.c \
shell-arrow.h \
+ shell-doc-system.c \
+ shell-doc-system.h \
shell-drawing.c \
shell-drawing.h \
shell-embedded-window.c \
diff --git a/src/shell-doc-system.c b/src/shell-doc-system.c
new file mode 100644
index 0000000..c58b34b
--- /dev/null
+++ b/src/shell-doc-system.c
@@ -0,0 +1,362 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include "config.h"
+
+#include "shell-doc-system.h"
+
+#include "shell-global.h"
+#include "shell-texture-cache.h"
+
+
+/**
+ * SECTION:shell-doc-system
+ * @short_description: Track recently used documents
+ *
+ * Wraps #GtkRecentManager, caching recently used document information, and adds
+ * APIs for asynchronous queries.
+ */
+enum {
+ CHANGED,
+ DELETED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+struct _ShellDocSystemPrivate {
+ GtkRecentManager *manager;
+ GHashTable *infos_by_uri;
+ GSList *infos_by_timestamp;
+
+ guint idle_recent_changed_id;
+
+ GHashTable *deleted_infos;
+ guint idle_emit_deleted_id;
+};
+
+G_DEFINE_TYPE(ShellDocSystem, shell_doc_system, G_TYPE_OBJECT);
+
+/**
+ * shell_doc_system_get_all:
+ * @self: A #ShellDocSystem
+ *
+ * Returns the currently cached set of recent files. Recent files are read initially
+ * from the underlying #GtkRecentManager, and updated when it changes.
+ * This function does not perform I/O.
+ *
+ * Returns: (transfer none) (element-type GtkRecentInfo): Cached recent file infos
+ */
+GSList *
+shell_doc_system_get_all (ShellDocSystem *self)
+{
+ return self->priv->infos_by_timestamp;
+}
+
+/**
+ * @self: A #ShellDocSystem
+ * @uri: Url
+ *
+ * Returns: (transfer none): Recent file info corresponding to given @uri
+ */
+GtkRecentInfo *
+shell_doc_system_lookup_by_uri (ShellDocSystem *self,
+ const char *uri)
+{
+ return g_hash_table_lookup (self->priv->infos_by_uri, uri);
+}
+
+static gboolean
+shell_doc_system_idle_emit_deleted (gpointer data)
+{
+ ShellDocSystem *self = SHELL_DOC_SYSTEM (data);
+ GHashTableIter iter;
+ gpointer key, value;
+
+ self->priv->idle_emit_deleted_id = 0;
+
+ g_hash_table_iter_init (&iter, self->priv->deleted_infos);
+
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ GtkRecentInfo *info = key;
+ g_signal_emit (self, signals[DELETED], 0, info);
+ }
+
+ g_signal_emit (self, signals[CHANGED], 0);
+
+ return FALSE;
+}
+
+typedef struct {
+ ShellDocSystem *self;
+ GtkRecentInfo *info;
+} ShellDocSystemRecentQueryData;
+
+static void
+on_recent_file_query_result (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ ShellDocSystemRecentQueryData *data = user_data;
+ ShellDocSystem *self = data->self;
+ GError *error = NULL;
+ GFileInfo *fileinfo;
+
+ fileinfo = g_file_query_info_finish (G_FILE (source), result, &error);
+ if (fileinfo)
+ g_object_unref (fileinfo);
+ /* This is a strict error check; we don't want to cause recent files to
+ * vanish for anything potentially transient.
+ */
+ if (error != NULL && error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_FOUND)
+ {
+ self->priv->infos_by_timestamp = g_slist_remove (self->priv->infos_by_timestamp, data->info);
+ g_hash_table_remove (self->priv->infos_by_uri, gtk_recent_info_get_uri (data->info));
+
+ g_hash_table_insert (self->priv->deleted_infos, gtk_recent_info_ref (data->info), NULL);
+
+ if (self->priv->idle_emit_deleted_id == 0)
+ self->priv->idle_emit_deleted_id = g_timeout_add (0, shell_doc_system_idle_emit_deleted, self);
+ }
+ g_clear_error (&error);
+
+ gtk_recent_info_unref (data->info);
+ g_free (data);
+}
+
+/**
+ * shell_doc_system_queue_existence_check:
+ * @self: A #ShellDocSystem
+ * @n_items: Count of items to check for existence, starting from most recent
+ *
+ * Asynchronously start a check of a number of recent file for existence;
+ * any deleted files will be emitted from the #ShellDocSystem::deleted
+ * signal. Note that this function ignores non-local files; they
+ * will simply always appear to exist (until they are removed from
+ * the recent file list manually).
+ *
+ * The intent of this function is to be called after a #ShellDocSystem::changed
+ * signal has been emitted, and a display has shown a subset of those files.
+ */
+void
+shell_doc_system_queue_existence_check (ShellDocSystem *self,
+ guint n_items)
+{
+ GSList *iter;
+ guint i;
+
+ for (i = 0, iter = self->priv->infos_by_timestamp; i < n_items && iter; i++, iter = iter->next)
+ {
+ GtkRecentInfo *info = iter->data;
+ const char *uri;
+ GFile *file;
+ ShellDocSystemRecentQueryData *data;
+
+ if (!gtk_recent_info_is_local (info))
+ continue;
+
+ data = g_new0 (ShellDocSystemRecentQueryData, 1);
+ data->self = self;
+ data->info = gtk_recent_info_ref (info);
+
+ uri = gtk_recent_info_get_uri (info);
+ file = g_file_new_for_uri (uri);
+
+ g_file_query_info_async (file, "standard::type", G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT, NULL, on_recent_file_query_result, data);
+ g_object_unref (file);
+ }
+}
+
+static int
+sort_infos_by_timestamp_descending (gconstpointer a,
+ gconstpointer b)
+{
+ GtkRecentInfo *info_a = (GtkRecentInfo*)a;
+ GtkRecentInfo *info_b = (GtkRecentInfo*)b;
+ time_t modified_a, modified_b;
+
+ modified_a = gtk_recent_info_get_modified (info_a);
+ modified_b = gtk_recent_info_get_modified (info_b);
+
+ return modified_b - modified_a;
+}
+
+static gboolean
+idle_handle_recent_changed (gpointer data)
+{
+ ShellDocSystem *self = SHELL_DOC_SYSTEM (data);
+ GList *items, *iter;
+
+ self->priv->idle_recent_changed_id = 0;
+
+ g_hash_table_remove_all (self->priv->deleted_infos);
+ g_hash_table_remove_all (self->priv->infos_by_uri);
+ g_slist_free (self->priv->infos_by_timestamp);
+ self->priv->infos_by_timestamp = NULL;
+
+ items = gtk_recent_manager_get_items (self->priv->manager);
+ for (iter = items; iter; iter = iter->next)
+ {
+ GtkRecentInfo *info = iter->data;
+ const char *uri = gtk_recent_info_get_uri (info);
+
+ /* uri is owned by the info */
+ g_hash_table_insert (self->priv->infos_by_uri, (char*) uri, info);
+
+ self->priv->infos_by_timestamp = g_slist_prepend (self->priv->infos_by_timestamp, info);
+ }
+ g_list_free (items);
+
+ self->priv->infos_by_timestamp = g_slist_sort (self->priv->infos_by_timestamp, sort_infos_by_timestamp_descending);
+
+ g_signal_emit (self, signals[CHANGED], 0);
+
+ return FALSE;
+}
+
+static void
+shell_doc_system_on_recent_changed (GtkRecentManager *manager,
+ ShellDocSystem *self)
+{
+ if (self->priv->idle_recent_changed_id != 0)
+ return;
+ self->priv->idle_recent_changed_id = g_timeout_add (0, idle_handle_recent_changed, self);
+}
+
+/**
+ * shell_doc_system_open:
+ * @system: A #ShellDocSystem
+ * @info: A #GtkRecentInfo
+ *
+ * Launch the default application associated with the mime type of
+ * @info, using its uri.
+ */
+void
+shell_doc_system_open (ShellDocSystem *system,
+ GtkRecentInfo *info)
+{
+ GFile *file;
+ GAppInfo *app_info;
+ gboolean needs_uri;
+
+ file = g_file_new_for_uri (gtk_recent_info_get_uri (info));
+ needs_uri = g_file_get_path (file) == NULL;
+ g_object_unref (file);
+
+ app_info = g_app_info_get_default_for_type (gtk_recent_info_get_mime_type (info), needs_uri);
+ if (app_info != NULL)
+ {
+ GList *uris;
+ uris = g_list_prepend (NULL, (gpointer)gtk_recent_info_get_uri (info));
+ g_app_info_launch_uris (app_info, uris, shell_global_create_app_launch_context (shell_global_get ()), NULL);
+ g_list_free (uris);
+ }
+ else
+ {
+ char *app_name;
+ char *app_exec, *app_exec_quoted;
+ guint count;
+ time_t time;
+
+ app_name = gtk_recent_info_last_application (info);
+ if (gtk_recent_info_get_application_info (info, app_name, &app_exec, &count, &time))
+ {
+ GRegex *regex;
+ GAppLaunchContext *context;
+
+ /* TODO: Change this once better support for creating
+ GAppInfo is added to GtkRecentInfo, as right now
+ this relies on the fact that the file uri is
+ already a part of appExec, so we don't supply any
+ files to app_info.launch().
+
+ The 'command line' passed to
+ create_from_command_line is allowed to contain
+ '%<something>' macros that are expanded to file
+ name / icon name, etc, so we need to escape % as %%
+ */
+
+ regex = g_regex_new ("%", 0, 0, NULL);
+ app_exec_quoted = g_regex_replace (regex, app_exec, -1, 0, "%%", 0, NULL);
+ g_regex_unref (regex);
+
+ app_info = g_app_info_create_from_commandline (app_exec, NULL, 0, NULL);
+
+ /* The point of passing an app launch context to
+ launch() is mostly to get startup notification and
+ associated benefits like the app appearing on the
+ right desktop; but it doesn't really work for now
+ because with the way we create the appInfo we
+ aren't reading the application's desktop file, and
+ thus don't find the StartupNotify=true in it. So,
+ despite passing the app launch context, no startup
+ notification occurs.
+ */
+ context = shell_global_create_app_launch_context (shell_global_get ());
+ g_app_info_launch (app_info, NULL, context, NULL);
+ g_object_unref (context);
+ }
+
+ g_free (app_name);
+ }
+}
+
+static void
+shell_doc_system_class_init(ShellDocSystemClass *klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *)klass;
+
+ signals[CHANGED] =
+ g_signal_new ("changed",
+ SHELL_TYPE_DOC_SYSTEM,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[DELETED] =
+ g_signal_new ("deleted",
+ SHELL_TYPE_DOC_SYSTEM,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOXED,
+ G_TYPE_NONE, 1, GTK_TYPE_RECENT_INFO);
+
+ g_type_class_add_private (gobject_class, sizeof (ShellDocSystemPrivate));
+}
+
+static void
+shell_doc_system_init (ShellDocSystem *self)
+{
+ ShellDocSystemPrivate *priv;
+
+ self->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ SHELL_TYPE_DOC_SYSTEM,
+ ShellDocSystemPrivate);
+ self->priv->manager = gtk_recent_manager_get_default ();
+
+ self->priv->deleted_infos = g_hash_table_new_full (NULL, NULL, (GDestroyNotify)gtk_recent_info_unref, NULL);
+ self->priv->infos_by_uri = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)gtk_recent_info_unref);
+
+ g_signal_connect (self->priv->manager, "changed", G_CALLBACK(shell_doc_system_on_recent_changed), self);
+ shell_doc_system_on_recent_changed (self->priv->manager, self);
+}
+
+/**
+ * shell_doc_system_get_default:
+ *
+ * Return Value: (transfer none): The global #ShellDocSystem singleton
+ */
+ShellDocSystem *
+shell_doc_system_get_default ()
+{
+ static ShellDocSystem *instance = NULL;
+
+ if (instance == NULL)
+ instance = g_object_new (SHELL_TYPE_DOC_SYSTEM, NULL);
+
+ return instance;
+}
diff --git a/src/shell-doc-system.h b/src/shell-doc-system.h
new file mode 100644
index 0000000..0f2ad60
--- /dev/null
+++ b/src/shell-doc-system.h
@@ -0,0 +1,46 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_DOC_SYSTEM_H__
+#define __SHELL_DOC_SYSTEM_H__
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#define SHELL_TYPE_DOC_SYSTEM (shell_doc_system_get_type ())
+#define SHELL_DOC_SYSTEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SHELL_TYPE_DOC_SYSTEM, ShellDocSystem))
+#define SHELL_DOC_SYSTEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_DOC_SYSTEM, ShellDocSystemClass))
+#define SHELL_IS_DOC_SYSTEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SHELL_TYPE_DOC_SYSTEM))
+#define SHELL_IS_DOC_SYSTEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_DOC_SYSTEM))
+#define SHELL_DOC_SYSTEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_DOC_SYSTEM, ShellDocSystemClass))
+
+typedef struct _ShellDocSystem ShellDocSystem;
+typedef struct _ShellDocSystemClass ShellDocSystemClass;
+typedef struct _ShellDocSystemPrivate ShellDocSystemPrivate;
+
+struct _ShellDocSystem
+{
+ GObject parent;
+
+ ShellDocSystemPrivate *priv;
+};
+
+struct _ShellDocSystemClass
+{
+ GObjectClass parent_class;
+};
+
+GType shell_doc_system_get_type (void) G_GNUC_CONST;
+
+ShellDocSystem* shell_doc_system_get_default (void);
+
+GSList *shell_doc_system_get_all (ShellDocSystem *system);
+
+GtkRecentInfo *shell_doc_system_lookup_by_uri (ShellDocSystem *system,
+ const char *uri);
+
+void shell_doc_system_queue_existence_check (ShellDocSystem *system,
+ guint n_recent);
+
+void shell_doc_system_open (ShellDocSystem *system,
+ GtkRecentInfo *info);
+
+#endif /* __SHELL_DOC_SYSTEM_H__ */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]