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
This commit is contained in:
parent
42757a0c87
commit
a442dfea14
@ -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 \
|
||||
|
362
src/shell-doc-system.c
Normal file
362
src/shell-doc-system.c
Normal file
@ -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;
|
||||
}
|
46
src/shell-doc-system.h
Normal file
46
src/shell-doc-system.h
Normal file
@ -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__ */
|
Loading…
Reference in New Issue
Block a user