[gnome-shell] Add class for handling recent docs



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]