Process some GFileMonitor events
So far, it's rather crude.
This commit is contained in:
parent
2caebb7d19
commit
200485246b
156
fiv-browser.c
156
fiv-browser.c
@ -32,6 +32,7 @@
|
||||
#include "fiv-collection.h"
|
||||
#include "fiv-context-menu.h"
|
||||
#include "fiv-io.h"
|
||||
#include "fiv-io-model.h"
|
||||
#include "fiv-thumbnail.h"
|
||||
|
||||
// --- Widget ------------------------------------------------------------------
|
||||
@ -78,7 +79,7 @@ struct _FivBrowser {
|
||||
gboolean show_labels; ///< Show labels underneath items
|
||||
|
||||
FivIoModel *model; ///< Filesystem model
|
||||
GArray *entries; ///< []Entry
|
||||
GPtrArray *entries; ///< []*Entry
|
||||
GArray *layouted_rows; ///< []Row
|
||||
const Entry *selected; ///< Selected entry or NULL
|
||||
|
||||
@ -112,14 +113,25 @@ struct entry {
|
||||
FivIoModelEntry *e; ///< Reference to model entry
|
||||
cairo_surface_t *thumbnail; ///< Prescaled thumbnail
|
||||
GIcon *icon; ///< If no thumbnail, use this icon
|
||||
|
||||
gboolean removed; ///< Model announced removal
|
||||
};
|
||||
|
||||
static Entry *
|
||||
entry_new(FivIoModelEntry *e)
|
||||
{
|
||||
Entry *self = g_slice_alloc0(sizeof *self);
|
||||
self->e = e;
|
||||
return self;
|
||||
}
|
||||
|
||||
static void
|
||||
entry_free(Entry *self)
|
||||
entry_destroy(Entry *self)
|
||||
{
|
||||
fiv_io_model_entry_unref(self->e);
|
||||
g_clear_pointer(&self->thumbnail, cairo_surface_destroy);
|
||||
g_clear_object(&self->icon);
|
||||
g_slice_free1(sizeof *self, self);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
@ -202,7 +214,7 @@ relayout(FivBrowser *self, int width)
|
||||
GArray *items = g_array_new(TRUE, TRUE, sizeof(Item));
|
||||
int x = 0, y = padding.top;
|
||||
for (guint i = 0; i < self->entries->len; i++) {
|
||||
const Entry *entry = &g_array_index(self->entries, Entry, i);
|
||||
const Entry *entry = self->entries->pdata[i];
|
||||
if (!entry->thumbnail)
|
||||
continue;
|
||||
|
||||
@ -414,6 +426,15 @@ draw_row(FivBrowser *self, cairo_t *cr, const Row *row)
|
||||
// the whole rectangle with the selection color.
|
||||
}
|
||||
|
||||
// TODO(p): Come up with a better rendition.
|
||||
if (item->entry->removed) {
|
||||
cairo_move_to(cr, 0, border.top + extents.height + border.bottom);
|
||||
cairo_line_to(cr, border.left + extents.width + border.right, 0);
|
||||
cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
|
||||
cairo_set_line_width(cr, 5);
|
||||
cairo_stroke(cr);
|
||||
}
|
||||
|
||||
if (self->show_labels) {
|
||||
gtk_style_context_save(style);
|
||||
gtk_style_context_add_class(style, "label");
|
||||
@ -522,14 +543,9 @@ entry_set_surface_user_data(const Entry *self)
|
||||
NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
entry_add_thumbnail(gpointer data, gpointer user_data)
|
||||
static cairo_surface_t *
|
||||
entry_lookup_thumbnail(Entry *self, FivBrowser *browser)
|
||||
{
|
||||
Entry *self = data;
|
||||
g_clear_object(&self->icon);
|
||||
g_clear_pointer(&self->thumbnail, cairo_surface_destroy);
|
||||
|
||||
FivBrowser *browser = FIV_BROWSER(user_data);
|
||||
cairo_surface_t *cached =
|
||||
g_hash_table_lookup(browser->thumbnail_cache, self->e->uri);
|
||||
if (cached &&
|
||||
@ -537,18 +553,39 @@ entry_add_thumbnail(gpointer data, gpointer user_data)
|
||||
&fiv_browser_key_mtime_msec) == (intptr_t) self->e->mtime_msec &&
|
||||
(uintptr_t) cairo_surface_get_user_data(cached,
|
||||
&fiv_browser_key_filesize) == (uintptr_t) self->e->filesize) {
|
||||
self->thumbnail = cairo_surface_reference(cached);
|
||||
// TODO(p): If this hit is low-quality, see if a high-quality thumbnail
|
||||
// hasn't been produced without our knowledge (avoid launching a minion
|
||||
// unnecessarily; we might also shift the concern there).
|
||||
} else {
|
||||
cairo_surface_t *found = fiv_thumbnail_lookup(
|
||||
entry_system_wide_uri(self), self->e->mtime_msec, self->e->filesize,
|
||||
browser->item_size);
|
||||
self->thumbnail = rescale_thumbnail(found, browser->item_height);
|
||||
return cairo_surface_reference(cached);
|
||||
}
|
||||
|
||||
if (self->thumbnail) {
|
||||
cairo_surface_t *found = fiv_thumbnail_lookup(
|
||||
entry_system_wide_uri(self), self->e->mtime_msec, self->e->filesize,
|
||||
browser->item_size);
|
||||
return rescale_thumbnail(found, browser->item_height);
|
||||
}
|
||||
|
||||
static void
|
||||
entry_add_thumbnail(gpointer data, gpointer user_data)
|
||||
{
|
||||
Entry *self = data;
|
||||
FivBrowser *browser = FIV_BROWSER(user_data);
|
||||
if (self->removed) {
|
||||
// Keep whatever size of thumbnail we had at the time up until reload.
|
||||
// g_file_query_info() fails for removed files, so keep the icon, too.
|
||||
if (self->icon) {
|
||||
g_clear_pointer(&self->thumbnail, cairo_surface_destroy);
|
||||
} else {
|
||||
self->thumbnail =
|
||||
rescale_thumbnail(self->thumbnail, browser->item_height);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
g_clear_object(&self->icon);
|
||||
g_clear_pointer(&self->thumbnail, cairo_surface_destroy);
|
||||
|
||||
if ((self->thumbnail = entry_lookup_thumbnail(self, browser))) {
|
||||
// Yes, this is a pointless action in case it's been found in the cache.
|
||||
entry_set_surface_user_data(self);
|
||||
return;
|
||||
@ -624,15 +661,15 @@ reload_thumbnails(FivBrowser *self)
|
||||
GThreadPool *pool = g_thread_pool_new(
|
||||
entry_add_thumbnail, self, g_get_num_processors(), FALSE, NULL);
|
||||
for (guint i = 0; i < self->entries->len; i++)
|
||||
g_thread_pool_push(pool, &g_array_index(self->entries, Entry, i), NULL);
|
||||
g_thread_pool_push(pool, self->entries->pdata[i], NULL);
|
||||
g_thread_pool_free(pool, FALSE, TRUE);
|
||||
|
||||
// Once a URI disappears from the model, its thumbnail is forgotten.
|
||||
g_hash_table_remove_all(self->thumbnail_cache);
|
||||
|
||||
for (guint i = 0; i < self->entries->len; i++) {
|
||||
Entry *entry = &g_array_index(self->entries, Entry, i);
|
||||
if (entry->thumbnail) {
|
||||
Entry *entry = self->entries->pdata[i];
|
||||
if (!entry->removed && entry->thumbnail) {
|
||||
g_hash_table_insert(self->thumbnail_cache, g_strdup(entry->e->uri),
|
||||
cairo_surface_reference(entry->thumbnail));
|
||||
}
|
||||
@ -790,7 +827,10 @@ thumbnailers_start(FivBrowser *self)
|
||||
|
||||
GQueue lq = G_QUEUE_INIT;
|
||||
for (guint i = 0; i < self->entries->len; i++) {
|
||||
Entry *entry = &g_array_index(self->entries, Entry, i);
|
||||
Entry *entry = self->entries->pdata[i];
|
||||
if (entry->removed)
|
||||
continue;
|
||||
|
||||
if (entry->icon)
|
||||
g_queue_push_tail(&self->thumbnailers_queue, entry);
|
||||
else if (cairo_surface_get_user_data(
|
||||
@ -868,7 +908,7 @@ fiv_browser_finalize(GObject *gobject)
|
||||
{
|
||||
FivBrowser *self = FIV_BROWSER(gobject);
|
||||
thumbnailers_abort(self);
|
||||
g_array_free(self->entries, TRUE);
|
||||
g_ptr_array_free(self->entries, TRUE);
|
||||
g_array_free(self->layouted_rows, TRUE);
|
||||
if (self->model) {
|
||||
g_signal_handlers_disconnect_by_data(self->model, self);
|
||||
@ -1774,8 +1814,8 @@ fiv_browser_init(FivBrowser *self)
|
||||
gtk_widget_set_can_focus(GTK_WIDGET(self), TRUE);
|
||||
gtk_widget_set_has_tooltip(GTK_WIDGET(self), TRUE);
|
||||
|
||||
self->entries = g_array_new(FALSE, TRUE, sizeof(Entry));
|
||||
g_array_set_clear_func(self->entries, (GDestroyNotify) entry_free);
|
||||
self->entries =
|
||||
g_ptr_array_new_with_free_func((GDestroyNotify) entry_destroy);
|
||||
self->layouted_rows = g_array_new(FALSE, TRUE, sizeof(Row));
|
||||
g_array_set_clear_func(self->layouted_rows, (GDestroyNotify) row_free);
|
||||
abort_button_tracking(self);
|
||||
@ -1810,9 +1850,8 @@ fiv_browser_init(FivBrowser *self)
|
||||
|
||||
// --- Public interface --------------------------------------------------------
|
||||
|
||||
// TODO(p): Later implement any arguments of this FivIoModel signal.
|
||||
static void
|
||||
on_model_files_changed(FivIoModel *model, FivBrowser *self)
|
||||
on_model_reloaded(FivIoModel *model, FivBrowser *self)
|
||||
{
|
||||
g_return_if_fail(model == self->model);
|
||||
|
||||
@ -1821,14 +1860,14 @@ on_model_files_changed(FivIoModel *model, FivBrowser *self)
|
||||
selected_uri = g_strdup(self->selected->e->uri);
|
||||
|
||||
thumbnailers_abort(self);
|
||||
g_array_set_size(self->entries, 0);
|
||||
g_array_set_size(self->layouted_rows, 0);
|
||||
g_ptr_array_set_size(self->entries, 0);
|
||||
|
||||
gsize len = 0;
|
||||
FivIoModelEntry *const *files = fiv_io_model_get_files(self->model, &len);
|
||||
for (gsize i = 0; i < len; i++) {
|
||||
Entry e = {.e = fiv_io_model_entry_ref(files[i])};
|
||||
g_array_append_val(self->entries, e);
|
||||
g_ptr_array_add(
|
||||
self->entries, entry_new(fiv_io_model_entry_ref(files[i])));
|
||||
}
|
||||
|
||||
fiv_browser_select(self, selected_uri);
|
||||
@ -1838,6 +1877,55 @@ on_model_files_changed(FivIoModel *model, FivBrowser *self)
|
||||
thumbnailers_start(self);
|
||||
}
|
||||
|
||||
static void
|
||||
on_model_changed(FivIoModel *model, FivIoModelEntry *old, FivIoModelEntry *new,
|
||||
FivBrowser *self)
|
||||
{
|
||||
g_return_if_fail(model == self->model);
|
||||
|
||||
// Add new entries to the end, so as to not disturb the layout.
|
||||
if (!old) {
|
||||
g_ptr_array_add(
|
||||
self->entries, entry_new(fiv_io_model_entry_ref(new)));
|
||||
|
||||
// TODO(p): Only process this one item, not everything at once.
|
||||
// (This mainly has an effect on thumbnail-less entries.)
|
||||
reload_thumbnails(self);
|
||||
thumbnailers_start(self);
|
||||
return;
|
||||
}
|
||||
|
||||
Entry *found = NULL;
|
||||
for (guint i = 0; i < self->entries->len; i++) {
|
||||
Entry *entry = self->entries->pdata[i];
|
||||
if (entry->e == old) {
|
||||
found = entry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
return;
|
||||
|
||||
// Rename entries in place, so as to not disturb the layout.
|
||||
// XXX: This behaves differently from FivIoModel, and by extension fiv.c.
|
||||
if (new) {
|
||||
fiv_io_model_entry_unref(found->e);
|
||||
found->e = fiv_io_model_entry_ref(new);
|
||||
found->removed = FALSE;
|
||||
|
||||
// TODO(p): If there is a URI mismatch, don't reload thumbnails,
|
||||
// so that there's no jumping around. Or, a bit more properly,
|
||||
// move the thumbnail cache entry to the new URI.
|
||||
// TODO(p): Only process this one item, not everything at once.
|
||||
// (This mainly has an effect on thumbnail-less entries.)
|
||||
reload_thumbnails(self);
|
||||
thumbnailers_start(self);
|
||||
} else {
|
||||
found->removed = TRUE;
|
||||
gtk_widget_queue_draw(GTK_WIDGET(self));
|
||||
}
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
fiv_browser_new(FivIoModel *model)
|
||||
{
|
||||
@ -1846,9 +1934,11 @@ fiv_browser_new(FivIoModel *model)
|
||||
FivBrowser *self = g_object_new(FIV_TYPE_BROWSER, NULL);
|
||||
self->model = g_object_ref(model);
|
||||
|
||||
g_signal_connect(
|
||||
self->model, "files-changed", G_CALLBACK(on_model_files_changed), self);
|
||||
on_model_files_changed(self->model, self);
|
||||
g_signal_connect(self->model, "reloaded",
|
||||
G_CALLBACK(on_model_reloaded), self);
|
||||
g_signal_connect(self->model, "files-changed",
|
||||
G_CALLBACK(on_model_changed), self);
|
||||
on_model_reloaded(self->model, self);
|
||||
return GTK_WIDGET(self);
|
||||
}
|
||||
|
||||
@ -1877,7 +1967,7 @@ fiv_browser_select(FivBrowser *self, const char *uri)
|
||||
return;
|
||||
|
||||
for (guint i = 0; i < self->entries->len; i++) {
|
||||
const Entry *entry = &g_array_index(self->entries, Entry, i);
|
||||
const Entry *entry = self->entries->pdata[i];
|
||||
if (!g_strcmp0(entry->e->uri, uri)) {
|
||||
self->selected = entry;
|
||||
scroll_to_selection(self);
|
||||
|
396
fiv-io-model.c
396
fiv-io-model.c
@ -19,12 +19,109 @@
|
||||
#include "fiv-io-model.h"
|
||||
#include "xdg.h"
|
||||
|
||||
static GPtrArray *
|
||||
model_entry_array_new(void)
|
||||
GType
|
||||
fiv_io_model_sort_get_type(void)
|
||||
{
|
||||
return g_ptr_array_new_with_free_func(g_rc_box_release);
|
||||
static gsize guard;
|
||||
if (g_once_init_enter(&guard)) {
|
||||
#define XX(name) {FIV_IO_MODEL_SORT_ ## name, \
|
||||
"FIV_IO_MODEL_SORT_" #name, #name},
|
||||
static const GEnumValue values[] = {FIV_IO_MODEL_SORTS(XX) {}};
|
||||
#undef XX
|
||||
GType type = g_enum_register_static(
|
||||
g_intern_static_string("FivIoModelSort"), values);
|
||||
g_once_init_leave(&guard, type);
|
||||
}
|
||||
return guard;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
G_DEFINE_BOXED_TYPE(FivIoModelEntry, fiv_io_model_entry,
|
||||
fiv_io_model_entry_ref, fiv_io_model_entry_unref)
|
||||
|
||||
FivIoModelEntry *
|
||||
fiv_io_model_entry_ref(FivIoModelEntry *self)
|
||||
{
|
||||
return g_rc_box_acquire(self);
|
||||
}
|
||||
|
||||
void
|
||||
fiv_io_model_entry_unref(FivIoModelEntry *self)
|
||||
{
|
||||
g_rc_box_release(self);
|
||||
}
|
||||
|
||||
static size_t
|
||||
entry_strsize(const char *string)
|
||||
{
|
||||
if (!string)
|
||||
return 0;
|
||||
|
||||
return strlen(string) + 1;
|
||||
}
|
||||
|
||||
static char *
|
||||
entry_strappend(char **p, const char *string, size_t size)
|
||||
{
|
||||
if (!string)
|
||||
return NULL;
|
||||
|
||||
char *destination = memcpy(*p, string, size);
|
||||
*p += size;
|
||||
return destination;
|
||||
}
|
||||
|
||||
// See model_load_attributes for a (superset of a) list of required attributes.
|
||||
static FivIoModelEntry *
|
||||
entry_new(GFile *file, GFileInfo *info)
|
||||
{
|
||||
gchar *uri = g_file_get_uri(file);
|
||||
const gchar *target_uri = g_file_info_get_attribute_string(
|
||||
info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);
|
||||
const gchar *display_name = g_file_info_get_display_name(info);
|
||||
|
||||
// TODO(p): Make it possible to use g_utf8_collate_key() instead,
|
||||
// which does not use natural sorting.
|
||||
gchar *parse_name = g_file_get_parse_name(file);
|
||||
gchar *collate_key = g_utf8_collate_key_for_filename(parse_name, -1);
|
||||
g_free(parse_name);
|
||||
|
||||
// The entries are immutable. Packing them into the structure
|
||||
// should help memory usage as well as performance.
|
||||
size_t size_uri = entry_strsize(uri);
|
||||
size_t size_target_uri = entry_strsize(target_uri);
|
||||
size_t size_display_name = entry_strsize(display_name);
|
||||
size_t size_collate_key = entry_strsize(collate_key);
|
||||
|
||||
FivIoModelEntry *entry = g_rc_box_alloc0(sizeof *entry +
|
||||
size_uri +
|
||||
size_target_uri +
|
||||
size_display_name +
|
||||
size_collate_key);
|
||||
|
||||
gchar *p = (gchar *) entry + sizeof *entry;
|
||||
entry->uri = entry_strappend(&p, uri, size_uri);
|
||||
entry->target_uri = entry_strappend(&p, target_uri, size_target_uri);
|
||||
entry->display_name = entry_strappend(&p, display_name, size_display_name);
|
||||
entry->collate_key = entry_strappend(&p, collate_key, size_collate_key);
|
||||
|
||||
entry->filesize = (guint64) g_file_info_get_size(info);
|
||||
|
||||
GDateTime *mtime = g_file_info_get_modification_date_time(info);
|
||||
if (mtime) {
|
||||
entry->mtime_msec = g_date_time_to_unix(mtime) * 1000 +
|
||||
g_date_time_get_microsecond(mtime) / 1000;
|
||||
g_date_time_unref(mtime);
|
||||
}
|
||||
|
||||
g_free(uri);
|
||||
g_free(collate_key);
|
||||
return entry;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
struct _FivIoModel {
|
||||
GObject parent_instance;
|
||||
GPatternSpec **supported_patterns;
|
||||
@ -51,6 +148,7 @@ enum {
|
||||
static GParamSpec *model_properties[N_PROPERTIES];
|
||||
|
||||
enum {
|
||||
RELOADED,
|
||||
FILES_CHANGED,
|
||||
SUBDIRECTORIES_CHANGED,
|
||||
LAST_SIGNAL,
|
||||
@ -61,6 +159,13 @@ static guint model_signals[LAST_SIGNAL];
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static GPtrArray *
|
||||
model_entry_array_new(void)
|
||||
{
|
||||
return g_ptr_array_new_with_free_func(
|
||||
(GDestroyNotify) fiv_io_model_entry_unref);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
model_supports(FivIoModel *self, const char *filename)
|
||||
{
|
||||
@ -124,71 +229,28 @@ model_compare(gconstpointer a, gconstpointer b, gpointer user_data)
|
||||
return result;
|
||||
}
|
||||
|
||||
static size_t
|
||||
model_strsize(const char *string)
|
||||
{
|
||||
if (!string)
|
||||
return 0;
|
||||
static const char *model_load_attributes =
|
||||
G_FILE_ATTRIBUTE_STANDARD_TYPE ","
|
||||
G_FILE_ATTRIBUTE_STANDARD_NAME ","
|
||||
G_FILE_ATTRIBUTE_STANDARD_SIZE ","
|
||||
G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME ","
|
||||
G_FILE_ATTRIBUTE_STANDARD_TARGET_URI ","
|
||||
G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN ","
|
||||
G_FILE_ATTRIBUTE_TIME_MODIFIED ","
|
||||
G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC;
|
||||
|
||||
return strlen(string) + 1;
|
||||
}
|
||||
|
||||
static char *
|
||||
model_strappend(char **p, const char *string, size_t size)
|
||||
static GPtrArray *
|
||||
model_decide_placement(
|
||||
FivIoModel *self, GFileInfo *info, GPtrArray *subdirs, GPtrArray *files)
|
||||
{
|
||||
if (!string)
|
||||
if (self->filtering && g_file_info_get_is_hidden(info))
|
||||
return NULL;
|
||||
|
||||
char *destination = memcpy(*p, string, size);
|
||||
*p += size;
|
||||
return destination;
|
||||
}
|
||||
|
||||
static FivIoModelEntry *
|
||||
model_entry_new(GFile *file, GFileInfo *info)
|
||||
{
|
||||
gchar *uri = g_file_get_uri(file);
|
||||
const gchar *target_uri = g_file_info_get_attribute_string(
|
||||
info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);
|
||||
const gchar *display_name = g_file_info_get_display_name(info);
|
||||
|
||||
// TODO(p): Make it possible to use g_utf8_collate_key() instead,
|
||||
// which does not use natural sorting.
|
||||
gchar *parse_name = g_file_get_parse_name(file);
|
||||
gchar *collate_key = g_utf8_collate_key_for_filename(parse_name, -1);
|
||||
g_free(parse_name);
|
||||
|
||||
// The entries are immutable. Packing them into the structure
|
||||
// should help memory usage as well as performance.
|
||||
size_t size_uri = model_strsize(uri);
|
||||
size_t size_target_uri = model_strsize(target_uri);
|
||||
size_t size_display_name = model_strsize(display_name);
|
||||
size_t size_collate_key = model_strsize(collate_key);
|
||||
|
||||
FivIoModelEntry *entry = g_rc_box_alloc0(sizeof *entry +
|
||||
size_uri +
|
||||
size_target_uri +
|
||||
size_display_name +
|
||||
size_collate_key);
|
||||
|
||||
gchar *p = (gchar *) entry + sizeof *entry;
|
||||
entry->uri = model_strappend(&p, uri, size_uri);
|
||||
entry->target_uri = model_strappend(&p, target_uri, size_target_uri);
|
||||
entry->display_name = model_strappend(&p, display_name, size_display_name);
|
||||
entry->collate_key = model_strappend(&p, collate_key, size_collate_key);
|
||||
|
||||
entry->filesize = (guint64) g_file_info_get_size(info);
|
||||
|
||||
GDateTime *mtime = g_file_info_get_modification_date_time(info);
|
||||
if (mtime) {
|
||||
entry->mtime_msec = g_date_time_to_unix(mtime) * 1000 +
|
||||
g_date_time_get_microsecond(mtime) / 1000;
|
||||
g_date_time_unref(mtime);
|
||||
}
|
||||
|
||||
g_free(uri);
|
||||
g_free(collate_key);
|
||||
return entry;
|
||||
if (g_file_info_get_file_type(info) == G_FILE_TYPE_DIRECTORY)
|
||||
return subdirs;
|
||||
if (!self->filtering ||
|
||||
model_supports(self, g_file_info_get_name(info)))
|
||||
return files;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
@ -200,16 +262,8 @@ model_reload_to(FivIoModel *self, GFile *directory,
|
||||
if (files)
|
||||
g_ptr_array_set_size(files, 0);
|
||||
|
||||
GFileEnumerator *enumerator = g_file_enumerate_children(directory,
|
||||
G_FILE_ATTRIBUTE_STANDARD_TYPE ","
|
||||
G_FILE_ATTRIBUTE_STANDARD_NAME ","
|
||||
G_FILE_ATTRIBUTE_STANDARD_SIZE ","
|
||||
G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME ","
|
||||
G_FILE_ATTRIBUTE_STANDARD_TARGET_URI ","
|
||||
G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN ","
|
||||
G_FILE_ATTRIBUTE_TIME_MODIFIED ","
|
||||
G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC,
|
||||
G_FILE_QUERY_INFO_NONE, NULL, error);
|
||||
GFileEnumerator *enumerator = g_file_enumerate_children(
|
||||
directory, model_load_attributes, G_FILE_QUERY_INFO_NONE, NULL, error);
|
||||
if (!enumerator)
|
||||
return FALSE;
|
||||
|
||||
@ -225,18 +279,11 @@ model_reload_to(FivIoModel *self, GFile *directory,
|
||||
}
|
||||
if (!info)
|
||||
break;
|
||||
if (self->filtering && g_file_info_get_is_hidden(info))
|
||||
continue;
|
||||
|
||||
GPtrArray *target = NULL;
|
||||
if (g_file_info_get_file_type(info) == G_FILE_TYPE_DIRECTORY)
|
||||
target = subdirs;
|
||||
else if (!self->filtering ||
|
||||
model_supports(self, g_file_info_get_name(info)))
|
||||
target = files;
|
||||
|
||||
GPtrArray *target =
|
||||
model_decide_placement(self, info, subdirs, files);
|
||||
if (target)
|
||||
g_ptr_array_add(target, model_entry_new(child, info));
|
||||
g_ptr_array_add(target, entry_new(child, info));
|
||||
}
|
||||
g_object_unref(enumerator);
|
||||
|
||||
@ -253,9 +300,7 @@ model_reload(FivIoModel *self, GError **error)
|
||||
// Note that this will clear all entries on failure.
|
||||
gboolean result = model_reload_to(
|
||||
self, self->directory, self->subdirs, self->files, error);
|
||||
|
||||
g_signal_emit(self, model_signals[FILES_CHANGED], 0);
|
||||
g_signal_emit(self, model_signals[SUBDIRECTORIES_CHANGED], 0);
|
||||
g_signal_emit(self, model_signals[RELOADED], 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -264,9 +309,144 @@ model_resort(FivIoModel *self)
|
||||
{
|
||||
g_ptr_array_sort_with_data(self->subdirs, model_compare, self);
|
||||
g_ptr_array_sort_with_data(self->files, model_compare, self);
|
||||
g_signal_emit(self, model_signals[RELOADED], 0);
|
||||
}
|
||||
|
||||
g_signal_emit(self, model_signals[FILES_CHANGED], 0);
|
||||
g_signal_emit(self, model_signals[SUBDIRECTORIES_CHANGED], 0);
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static gint
|
||||
model_find(const GPtrArray *target, GFile *file, FivIoModelEntry **entry)
|
||||
{
|
||||
for (guint i = 0; i < target->len; i++) {
|
||||
FivIoModelEntry *e = target->pdata[i];
|
||||
GFile *f = g_file_new_for_uri(e->uri);
|
||||
gboolean match = g_file_equal(f, file);
|
||||
g_object_unref(f);
|
||||
if (match) {
|
||||
*entry = e;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void
|
||||
on_monitor_changed(G_GNUC_UNUSED GFileMonitor *monitor, GFile *file,
|
||||
GFile *other_file, GFileMonitorEvent event_type, gpointer user_data)
|
||||
{
|
||||
FivIoModel *self = user_data;
|
||||
|
||||
FivIoModelEntry *old_entry = NULL;
|
||||
gint files_index = model_find(self->files, file, &old_entry);
|
||||
gint subdirs_index = model_find(self->subdirs, file, &old_entry);
|
||||
|
||||
enum { NONE, CHANGING, RENAMING, REMOVING, ADDING } action = NONE;
|
||||
GFile *new_entry_file = NULL;
|
||||
switch (event_type) {
|
||||
case G_FILE_MONITOR_EVENT_CHANGED:
|
||||
case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
|
||||
action = CHANGING;
|
||||
new_entry_file = file;
|
||||
break;
|
||||
case G_FILE_MONITOR_EVENT_RENAMED:
|
||||
action = RENAMING;
|
||||
new_entry_file = other_file;
|
||||
break;
|
||||
case G_FILE_MONITOR_EVENT_DELETED:
|
||||
case G_FILE_MONITOR_EVENT_MOVED_OUT:
|
||||
action = REMOVING;
|
||||
break;
|
||||
case G_FILE_MONITOR_EVENT_CREATED:
|
||||
case G_FILE_MONITOR_EVENT_MOVED_IN:
|
||||
action = ADDING;
|
||||
old_entry = NULL;
|
||||
new_entry_file = file;
|
||||
break;
|
||||
|
||||
case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
|
||||
// TODO(p): Figure out if we can't make use of _CHANGES_DONE_HINT.
|
||||
case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
|
||||
case G_FILE_MONITOR_EVENT_UNMOUNTED:
|
||||
// TODO(p): Figure out how to handle _UNMOUNTED sensibly.
|
||||
case G_FILE_MONITOR_EVENT_MOVED:
|
||||
return;
|
||||
}
|
||||
|
||||
FivIoModelEntry *new_entry = NULL;
|
||||
GPtrArray *new_target = NULL;
|
||||
if (new_entry_file) {
|
||||
GError *error = NULL;
|
||||
GFileInfo *info = g_file_query_info(new_entry_file,
|
||||
model_load_attributes, G_FILE_QUERY_INFO_NONE, NULL, &error);
|
||||
if (error) {
|
||||
g_debug("monitor: %s", error->message);
|
||||
g_error_free(error);
|
||||
goto run;
|
||||
}
|
||||
|
||||
if ((new_target =
|
||||
model_decide_placement(self, info, self->subdirs, self->files)))
|
||||
new_entry = entry_new(new_entry_file, info);
|
||||
g_object_unref(info);
|
||||
|
||||
if ((files_index != -1 && new_target == self->subdirs) ||
|
||||
(subdirs_index != -1 && new_target == self->files)) {
|
||||
g_debug("monitor: ignoring transfer between files and subdirs");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
run:
|
||||
// Keep a reference alive so that signal handlers see the new arrays.
|
||||
if (old_entry)
|
||||
fiv_io_model_entry_ref(old_entry);
|
||||
|
||||
if (files_index != -1 || new_target == self->files) {
|
||||
if (action == CHANGING) {
|
||||
g_assert(new_entry != NULL);
|
||||
fiv_io_model_entry_unref(self->files->pdata[files_index]);
|
||||
self->files->pdata[files_index] =
|
||||
fiv_io_model_entry_ref(new_entry);
|
||||
}
|
||||
if (action == REMOVING || action == RENAMING)
|
||||
g_ptr_array_remove_index(self->files, files_index);
|
||||
if (action == RENAMING || action == ADDING) {
|
||||
g_assert(new_entry != NULL);
|
||||
g_ptr_array_add(self->files, fiv_io_model_entry_ref(new_entry));
|
||||
}
|
||||
|
||||
g_signal_emit(self, model_signals[FILES_CHANGED],
|
||||
0, old_entry, new_entry);
|
||||
}
|
||||
if (subdirs_index != -1 || new_target == self->subdirs) {
|
||||
if (action == CHANGING) {
|
||||
g_assert(new_entry != NULL);
|
||||
fiv_io_model_entry_unref(self->subdirs->pdata[subdirs_index]);
|
||||
self->subdirs->pdata[subdirs_index] =
|
||||
fiv_io_model_entry_ref(new_entry);
|
||||
}
|
||||
if (action == REMOVING || action == RENAMING)
|
||||
g_ptr_array_remove_index(self->subdirs, subdirs_index);
|
||||
if (action == RENAMING || action == ADDING) {
|
||||
g_assert(new_entry != NULL);
|
||||
g_ptr_array_add(self->subdirs, fiv_io_model_entry_ref(new_entry));
|
||||
}
|
||||
|
||||
g_signal_emit(self, model_signals[SUBDIRECTORIES_CHANGED],
|
||||
0, old_entry, new_entry);
|
||||
}
|
||||
|
||||
// NOTE: It would make sense to do
|
||||
// g_ptr_array_sort_with_data(self->{files,subdirs}, model_compare, self);
|
||||
// but then the iteration behaviour of fiv.c would differ from what's shown
|
||||
// in the browser. Perhaps we need to use an index-based, fully-synchronized
|
||||
// interface similar to GListModel::items-changed.
|
||||
|
||||
if (old_entry)
|
||||
fiv_io_model_entry_unref(old_entry);
|
||||
out:
|
||||
if (new_entry)
|
||||
fiv_io_model_entry_unref(new_entry);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
@ -406,7 +586,7 @@ fiv_io_model_get_property(
|
||||
g_value_set_boolean(value, self->filtering);
|
||||
break;
|
||||
case PROP_SORT_FIELD:
|
||||
g_value_set_int(value, self->sort_field);
|
||||
g_value_set_enum(value, self->sort_field);
|
||||
break;
|
||||
case PROP_SORT_DESCENDING:
|
||||
g_value_set_boolean(value, self->sort_descending);
|
||||
@ -430,8 +610,8 @@ fiv_io_model_set_property(
|
||||
}
|
||||
break;
|
||||
case PROP_SORT_FIELD:
|
||||
if ((int) self->sort_field != g_value_get_int(value)) {
|
||||
self->sort_field = g_value_get_int(value);
|
||||
if ((int) self->sort_field != g_value_get_enum(value)) {
|
||||
self->sort_field = g_value_get_enum(value);
|
||||
g_object_notify_by_pspec(object, model_properties[property_id]);
|
||||
model_resort(self);
|
||||
}
|
||||
@ -459,24 +639,28 @@ fiv_io_model_class_init(FivIoModelClass *klass)
|
||||
model_properties[PROP_FILTERING] = g_param_spec_boolean(
|
||||
"filtering", "Filtering", "Only show non-hidden, supported entries",
|
||||
TRUE, G_PARAM_READWRITE);
|
||||
// TODO(p): GObject enumerations are annoying, but this should be one.
|
||||
model_properties[PROP_SORT_FIELD] = g_param_spec_int(
|
||||
model_properties[PROP_SORT_FIELD] = g_param_spec_enum(
|
||||
"sort-field", "Sort field", "Sort order",
|
||||
FIV_IO_MODEL_SORT_MIN, FIV_IO_MODEL_SORT_MAX,
|
||||
FIV_IO_MODEL_SORT_NAME, G_PARAM_READWRITE);
|
||||
FIV_TYPE_IO_MODEL_SORT, FIV_IO_MODEL_SORT_NAME, G_PARAM_READWRITE);
|
||||
model_properties[PROP_SORT_DESCENDING] = g_param_spec_boolean(
|
||||
"sort-descending", "Sort descending", "Use reverse sort order",
|
||||
FALSE, G_PARAM_READWRITE);
|
||||
g_object_class_install_properties(
|
||||
object_class, N_PROPERTIES, model_properties);
|
||||
|
||||
// TODO(p): Arguments something like: index, added, removed.
|
||||
// All entries might have changed.
|
||||
model_signals[RELOADED] =
|
||||
g_signal_new("reloaded", G_TYPE_FROM_CLASS(klass), 0, 0,
|
||||
NULL, NULL, NULL, G_TYPE_NONE, 0);
|
||||
|
||||
model_signals[FILES_CHANGED] =
|
||||
g_signal_new("files-changed", G_TYPE_FROM_CLASS(klass), 0, 0,
|
||||
NULL, NULL, NULL, G_TYPE_NONE, 0);
|
||||
NULL, NULL, NULL,
|
||||
G_TYPE_NONE, 2, FIV_TYPE_IO_MODEL_ENTRY, FIV_TYPE_IO_MODEL_ENTRY);
|
||||
model_signals[SUBDIRECTORIES_CHANGED] =
|
||||
g_signal_new("subdirectories-changed", G_TYPE_FROM_CLASS(klass), 0, 0,
|
||||
NULL, NULL, NULL, G_TYPE_NONE, 0);
|
||||
NULL, NULL, NULL,
|
||||
G_TYPE_NONE, 2, FIV_TYPE_IO_MODEL_ENTRY, FIV_TYPE_IO_MODEL_ENTRY);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
@ -511,9 +695,15 @@ fiv_io_model_open(FivIoModel *self, GFile *directory, GError **error)
|
||||
g_clear_object(&self->monitor);
|
||||
self->directory = g_object_ref(directory);
|
||||
|
||||
// TODO(p): Process the ::changed signal.
|
||||
self->monitor = g_file_monitor_directory(
|
||||
directory, G_FILE_MONITOR_WATCH_MOVES, NULL, NULL /* error */);
|
||||
GError *e = NULL;
|
||||
if ((self->monitor = g_file_monitor_directory(
|
||||
directory, G_FILE_MONITOR_WATCH_MOVES, NULL, &e))) {
|
||||
g_signal_connect(self->monitor, "changed",
|
||||
G_CALLBACK(on_monitor_changed), self);
|
||||
} else {
|
||||
g_debug("directory monitoring failed: %s", e->message);
|
||||
g_error_free(e);
|
||||
}
|
||||
return model_reload(self, error);
|
||||
}
|
||||
|
||||
|
@ -20,15 +20,39 @@
|
||||
#include <gio/gio.h>
|
||||
#include <glib.h>
|
||||
|
||||
// Avoid glib-mkenums.
|
||||
typedef enum _FivIoModelSort {
|
||||
FIV_IO_MODEL_SORT_NAME,
|
||||
FIV_IO_MODEL_SORT_MTIME,
|
||||
FIV_IO_MODEL_SORT_COUNT,
|
||||
|
||||
FIV_IO_MODEL_SORT_MIN = 0,
|
||||
FIV_IO_MODEL_SORT_MAX = FIV_IO_MODEL_SORT_COUNT - 1
|
||||
#define FIV_IO_MODEL_SORTS(XX) \
|
||||
XX(NAME) \
|
||||
XX(MTIME)
|
||||
#define XX(name) FIV_IO_MODEL_SORT_ ## name,
|
||||
FIV_IO_MODEL_SORTS(XX)
|
||||
#undef XX
|
||||
FIV_IO_MODEL_SORT_COUNT
|
||||
} FivIoModelSort;
|
||||
|
||||
GType fiv_io_model_sort_get_type(void) G_GNUC_CONST;
|
||||
#define FIV_TYPE_IO_MODEL_SORT (fiv_io_model_sort_get_type())
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
typedef struct {
|
||||
const char *uri; ///< GIO URI
|
||||
const char *target_uri; ///< GIO URI for any target
|
||||
const char *display_name; ///< Label for the file
|
||||
const char *collate_key; ///< Collate key for the filename
|
||||
guint64 filesize; ///< Filesize in bytes
|
||||
gint64 mtime_msec; ///< Modification time in milliseconds
|
||||
} FivIoModelEntry;
|
||||
|
||||
GType fiv_io_model_entry_get_type(void) G_GNUC_CONST;
|
||||
#define FIV_TYPE_IO_MODEL_ENTRY (fiv_io_model_entry_get_type())
|
||||
|
||||
FivIoModelEntry *fiv_io_model_entry_ref(FivIoModelEntry *self);
|
||||
void fiv_io_model_entry_unref(FivIoModelEntry *self);
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
#define FIV_TYPE_IO_MODEL (fiv_io_model_get_type())
|
||||
G_DECLARE_FINAL_TYPE(FivIoModel, fiv_io_model, FIV, IO_MODEL, GObject)
|
||||
|
||||
@ -44,18 +68,5 @@ GFile *fiv_io_model_get_previous_directory(FivIoModel *self);
|
||||
/// Returns the next VFS directory in order, or NULL.
|
||||
GFile *fiv_io_model_get_next_directory(FivIoModel *self);
|
||||
|
||||
// These objects are reference-counted using GRcBox.
|
||||
typedef struct {
|
||||
const char *uri; ///< GIO URI
|
||||
const char *target_uri; ///< GIO URI for any target
|
||||
const char *display_name; ///< Label for the file
|
||||
const char *collate_key; ///< Collate key for the filename
|
||||
guint64 filesize; ///< Filesize in bytes
|
||||
gint64 mtime_msec; ///< Modification time in milliseconds
|
||||
} FivIoModelEntry;
|
||||
|
||||
#define fiv_io_model_entry_ref(e) g_rc_box_acquire(e)
|
||||
#define fiv_io_model_entry_unref(e) g_rc_box_release(e)
|
||||
|
||||
FivIoModelEntry *const *fiv_io_model_get_files(FivIoModel *self, gsize *len);
|
||||
FivIoModelEntry *const *fiv_io_model_get_subdirs(FivIoModel *self, gsize *len);
|
||||
|
@ -623,9 +623,9 @@ fiv_sidebar_new(FivIoModel *model)
|
||||
gtk_container_set_focus_vadjustment(GTK_CONTAINER(sidebar_port),
|
||||
gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(self)));
|
||||
|
||||
// TODO(p): There should be an extra signal to watch location changes only.
|
||||
// TODO(p): Also connect to and process the subdirectories-changed signal.
|
||||
self->model = g_object_ref(model);
|
||||
g_signal_connect_swapped(self->model, "subdirectories-changed",
|
||||
g_signal_connect_swapped(self->model, "reloaded",
|
||||
G_CALLBACK(update_location), self);
|
||||
|
||||
return GTK_WIDGET(self);
|
||||
|
@ -21,7 +21,7 @@
|
||||
#include <gio/gio.h>
|
||||
#include <glib.h>
|
||||
|
||||
// And this is how you avoid glib-mkenums.
|
||||
// Avoid glib-mkenums.
|
||||
typedef enum _FivThumbnailSize {
|
||||
#define FIV_THUMBNAIL_SIZES(XX) \
|
||||
XX(SMALL, 128, "normal") \
|
||||
|
18
fiv.c
18
fiv.c
@ -733,7 +733,7 @@ load_directory_without_switching(const char *uri)
|
||||
GError *error = NULL;
|
||||
GFile *file = g_file_new_for_uri(g.directory);
|
||||
if (fiv_io_model_open(g.model, file, &error)) {
|
||||
// This is handled by our ::files-changed callback.
|
||||
// This is handled by our ::reloaded callback.
|
||||
} else if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) {
|
||||
g_error_free(error);
|
||||
} else {
|
||||
@ -797,7 +797,7 @@ go_forward(void)
|
||||
}
|
||||
|
||||
static void
|
||||
on_model_files_changed(FivIoModel *model, G_GNUC_UNUSED gpointer user_data)
|
||||
on_model_reloaded(FivIoModel *model, G_GNUC_UNUSED gpointer user_data)
|
||||
{
|
||||
g_return_if_fail(model == g.model);
|
||||
|
||||
@ -810,6 +810,13 @@ on_model_files_changed(FivIoModel *model, G_GNUC_UNUSED gpointer user_data)
|
||||
gtk_widget_set_sensitive(g.toolbar[TOOLBAR_FILE_NEXT], files_len > 1);
|
||||
}
|
||||
|
||||
static void
|
||||
on_model_files_changed(FivIoModel *model, G_GNUC_UNUSED FivIoModelEntry *old,
|
||||
G_GNUC_UNUSED FivIoModelEntry *new, G_GNUC_UNUSED gpointer user_data)
|
||||
{
|
||||
on_model_reloaded(model, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
on_sidebar_toggled(GtkToggleButton *button, G_GNUC_UNUSED gpointer user_data)
|
||||
{
|
||||
@ -838,7 +845,8 @@ on_sort_field(G_GNUC_UNUSED GtkToggleButton *button, gpointer data)
|
||||
if (!active)
|
||||
return;
|
||||
|
||||
int old = -1, new = (int) (intptr_t) data;
|
||||
FivIoModelSort old = FIV_IO_MODEL_SORT_COUNT;
|
||||
FivIoModelSort new = (FivIoModelSort) (intptr_t) data;
|
||||
g_object_get(g.model, "sort-field", &old, NULL);
|
||||
if (old != new)
|
||||
g_object_set(g.model, "sort-field", new, NULL);
|
||||
@ -1206,7 +1214,7 @@ static void
|
||||
on_notify_thumbnail_size(
|
||||
GObject *object, GParamSpec *param_spec, G_GNUC_UNUSED gpointer user_data)
|
||||
{
|
||||
FivThumbnailSize size = 0;
|
||||
FivThumbnailSize size = FIV_THUMBNAIL_SIZE_COUNT;
|
||||
g_object_get(object, g_param_spec_get_name(param_spec), &size, NULL);
|
||||
gtk_widget_set_sensitive(
|
||||
g.browsebar[BROWSEBAR_PLUS], size < FIV_THUMBNAIL_SIZE_MAX);
|
||||
@ -2253,6 +2261,8 @@ main(int argc, char *argv[])
|
||||
fiv_collection_register();
|
||||
|
||||
g.model = g_object_new(FIV_TYPE_IO_MODEL, NULL);
|
||||
g_signal_connect(g.model, "reloaded",
|
||||
G_CALLBACK(on_model_reloaded), NULL);
|
||||
g_signal_connect(g.model, "files-changed",
|
||||
G_CALLBACK(on_model_files_changed), NULL);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user