aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPřemysl Eric Janouch <p@janouch.name>2023-05-30 10:36:11 +0200
committerPřemysl Eric Janouch <p@janouch.name>2023-05-31 18:39:14 +0200
commit200485246bdfda2c77689408dcd407d106639e60 (patch)
tree1d9b30e03d92eade3e70005c7889f10740d60ffc
parent2caebb7d19dea2d7df459006f88ce4951a1a9bf0 (diff)
downloadfiv-200485246bdfda2c77689408dcd407d106639e60.tar.gz
fiv-200485246bdfda2c77689408dcd407d106639e60.tar.xz
fiv-200485246bdfda2c77689408dcd407d106639e60.zip
Process some GFileMonitor events
So far, it's rather crude.
-rw-r--r--fiv-browser.c156
-rw-r--r--fiv-io-model.c396
-rw-r--r--fiv-io-model.h49
-rw-r--r--fiv-sidebar.c4
-rw-r--r--fiv-thumbnail.h2
-rw-r--r--fiv.c18
6 files changed, 463 insertions, 162 deletions
diff --git a/fiv-browser.c b/fiv-browser.c
index 01ff8f0..8c79ca6 100644
--- a/fiv-browser.c
+++ b/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);
+ }
+
+ 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;
}
- if (self->thumbnail) {
+ 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);
diff --git a/fiv-io-model.c b/fiv-io-model.c
index c133238..58cf632 100644
--- a/fiv-io-model.c
+++ b/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)
+{
+ 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)
{
- return g_ptr_array_new_with_free_func(g_rc_box_release);
+ 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;
-
- return strlen(string) + 1;
-}
+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;
-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);
}
diff --git a/fiv-io-model.h b/fiv-io-model.h
index 2fb9ad7..c785130 100644
--- a/fiv-io-model.h
+++ b/fiv-io-model.h
@@ -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);
diff --git a/fiv-sidebar.c b/fiv-sidebar.c
index 6525067..caf2d86 100644
--- a/fiv-sidebar.c
+++ b/fiv-sidebar.c
@@ -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);
diff --git a/fiv-thumbnail.h b/fiv-thumbnail.h
index 05c3dc1..0d53c01 100644
--- a/fiv-thumbnail.h
+++ b/fiv-thumbnail.h
@@ -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") \
diff --git a/fiv.c b/fiv.c
index dad88e2..3911cec 100644
--- a/fiv.c
+++ b/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);
@@ -811,6 +811,13 @@ on_model_files_changed(FivIoModel *model, G_GNUC_UNUSED gpointer user_data)
}
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)
{
gboolean active = gtk_toggle_button_get_active(button);
@@ -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);