diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 80809369b4..e5b667cf43 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -1444,6 +1444,8 @@ gtk_directory_list_get_file gtk_directory_list_set_file gtk_directory_list_get_io_priority gtk_directory_list_set_io_priority +gtk_directory_list_get_monitored +gtk_directory_list_set_monitored gtk_directory_list_is_loading gtk_directory_list_get_error diff --git a/gtk/gtkdirectorylist.c b/gtk/gtkdirectorylist.c index 053305b674..b4a0d10014 100644 --- a/gtk/gtkdirectorylist.c +++ b/gtk/gtkdirectorylist.c @@ -62,6 +62,7 @@ enum { PROP_IO_PRIORITY, PROP_ITEM_TYPE, PROP_LOADING, + PROP_MONITORED, NUM_PROPERTIES }; @@ -70,8 +71,10 @@ struct _GtkDirectoryList GObject parent_instance; char *attributes; - int io_priority; GFile *file; + GFileMonitor *monitor; + gboolean monitored; + int io_priority; GCancellable *cancellable; GError *error; /* Error while loading */ @@ -147,13 +150,17 @@ gtk_directory_list_set_property (GObject *object, gtk_directory_list_set_io_priority (self, g_value_get_int (value)); break; + case PROP_MONITORED: + gtk_directory_list_set_monitored (self, g_value_get_boolean (value)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } -static void +static void gtk_directory_list_get_property (GObject *object, guint prop_id, GValue *value, @@ -187,6 +194,10 @@ gtk_directory_list_get_property (GObject *object, g_value_set_boolean (value, gtk_directory_list_is_loading (self)); break; + case PROP_MONITORED: + g_value_set_boolean (value, gtk_directory_list_get_monitored (self)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -204,12 +215,27 @@ gtk_directory_list_stop_loading (GtkDirectoryList *self) return TRUE; } +static void directory_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event, + gpointer data); + +static void +gtk_directory_list_stop_monitoring (GtkDirectoryList *self) +{ + if (self->monitor) + g_signal_handlers_disconnect_by_func (self->monitor, directory_changed, self); + g_clear_object (&self->monitor); +} + static void gtk_directory_list_dispose (GObject *object) { GtkDirectoryList *self = GTK_DIRECTORY_LIST (object); gtk_directory_list_stop_loading (self); + gtk_directory_list_stop_monitoring (self); g_clear_object (&self->file); g_clear_pointer (&self->attributes, g_free); @@ -301,6 +327,18 @@ gtk_directory_list_class_init (GtkDirectoryListClass *class) FALSE, GTK_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY); + /** + * GtkDirectoryList:monitored: + * + * %TRUE if the directory is monitored for changed + */ + properties[PROP_MONITORED] = + g_param_spec_boolean ("monitored", + P_("monitored"), + P_("TRUE if the directory is monitored for changes"), + TRUE, + GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties); } @@ -309,6 +347,7 @@ gtk_directory_list_init (GtkDirectoryList *self) { self->items = g_sequence_new (g_object_unref); self->io_priority = G_PRIORITY_DEFAULT; + self->monitored = TRUE; } /** @@ -324,7 +363,6 @@ gtk_directory_list_init (GtkDirectoryList *self) GtkDirectoryList * gtk_directory_list_new (const char *attributes, GFile *file) - { g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL); @@ -410,7 +448,7 @@ gtk_directory_list_got_files_cb (GObject *source, { GFileInfo *info; GFile *file; - + info = l->data; file = g_file_enumerator_get_child (enumerator, info); g_file_info_set_attribute_object (info, "standard::file", G_OBJECT (file)); @@ -496,6 +534,141 @@ gtk_directory_list_start_loading (GtkDirectoryList *self) g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]); } +static void +got_new_file_info_cb (GObject *source, + GAsyncResult *res, + gpointer data) +{ + GFile *file = G_FILE (source); + GtkDirectoryList *self = GTK_DIRECTORY_LIST (data); + GFileInfo *info; + guint position; + + info = g_file_query_info_finish (file, res, NULL); + if (!info) + return; + + g_file_info_set_attribute_object (info, "standard::file", G_OBJECT (file)); + position = g_sequence_get_length (self->items); + g_sequence_append (self->items, info); + g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1); +} + +static void +got_existing_file_info_cb (GObject *source, + GAsyncResult *res, + gpointer data) +{ + GFile *file = G_FILE (source); + GtkDirectoryList *self = GTK_DIRECTORY_LIST (data); + GFileInfo *info; + GSequenceIter *iter; + + info = g_file_query_info_finish (file, res, NULL); + if (!info) + return; + + g_file_info_set_attribute_object (info, "standard::file", G_OBJECT (file)); + + iter = g_sequence_get_begin_iter (self->items); + while (!g_sequence_iter_is_end (iter)) + { + GFileInfo *item = g_sequence_get (iter); + GFile *f = G_FILE (g_file_info_get_attribute_object (item, "standard::file")); + if (g_file_equal (f, file)) + { + guint position = g_sequence_iter_get_position (iter); + g_sequence_set (iter, g_object_ref (info)); + g_list_model_items_changed (G_LIST_MODEL (self), position, 1, 1); + break; + } + } +} + +static void +gtk_directory_list_remove_file (GtkDirectoryList *self, + GFile *file) +{ + GSequenceIter *iter; + + iter = g_sequence_get_begin_iter (self->items); + while (!g_sequence_iter_is_end (iter)) + { + GFileInfo *item = g_sequence_get (iter); + GFile *f = G_FILE (g_file_info_get_attribute_object (item, "standard::file")); + if (g_file_equal (f, file)) + { + guint position = g_sequence_iter_get_position (iter); + g_sequence_remove (iter); + g_list_model_items_changed (G_LIST_MODEL (self), position, 1, 0); + break; + } + } +} + +static void +directory_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event, + gpointer data) +{ + GtkDirectoryList *self = GTK_DIRECTORY_LIST (data); + switch (event) + { + case G_FILE_MONITOR_EVENT_CREATED: + g_file_query_info_async (file, + self->attributes, + G_FILE_QUERY_INFO_NONE, + self->io_priority, + self->cancellable, + got_new_file_info_cb, + self); + break; + + case G_FILE_MONITOR_EVENT_DELETED: + gtk_directory_list_remove_file (self, file); + break; + + case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: + g_file_query_info_async (file, + self->attributes, + G_FILE_QUERY_INFO_NONE, + self->io_priority, + self->cancellable, + got_existing_file_info_cb, + self); + break; + + case G_FILE_MONITOR_EVENT_CHANGED: + case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: + case G_FILE_MONITOR_EVENT_PRE_UNMOUNT: + case G_FILE_MONITOR_EVENT_UNMOUNTED: + case G_FILE_MONITOR_EVENT_MOVED: + case G_FILE_MONITOR_EVENT_RENAMED: + case G_FILE_MONITOR_EVENT_MOVED_IN: + case G_FILE_MONITOR_EVENT_MOVED_OUT: + default: + break; + } +} + +static void +gtk_directory_list_start_monitoring (GtkDirectoryList *self) +{ + g_assert (self->monitor == NULL); + self->monitor = g_file_monitor_directory (self->file, G_FILE_MONITOR_NONE, NULL, NULL); + g_signal_connect (self->monitor, "changed", G_CALLBACK (directory_changed), self); +} + +static void +gtk_directory_list_update_monitoring (GtkDirectoryList *self) +{ + gtk_directory_list_stop_monitoring (self); + if (self->file && self->monitored) + gtk_directory_list_start_monitoring (self); +} + /** * gtk_directory_list_set_file: * @self: a #GtkDirectoryList @@ -520,6 +693,7 @@ gtk_directory_list_set_file (GtkDirectoryList *self, g_set_object (&self->file, file); + gtk_directory_list_update_monitoring (self); gtk_directory_list_start_loading (self); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FILE]); @@ -679,3 +853,52 @@ gtk_directory_list_get_error (GtkDirectoryList *self) return self->error; } +/** + * gtk_directory_list_set_monitored: + * @self: a #GtkDirectoryList + * @monitored: %TRUE to monitor the directory for changes + * + * Sets whether the directory list will monitor the directory + * for changes. If monitoring is enabled, the + * #GListModel::items-changed signal will be emitted when the + * directory contents change. + * + * When monitoring is turned on after the initial creation + * of the directory list, the directory is reloaded to avoid + * missing files that appeared between the initial loading + * and when monitoring was turned on. + */ +void +gtk_directory_list_set_monitored (GtkDirectoryList *self, + gboolean monitored) +{ + g_return_if_fail (GTK_IS_DIRECTORY_LIST (self)); + + if (self->monitored == monitored) + return; + + self->monitored = monitored; + + gtk_directory_list_update_monitoring (self); + if (monitored) + gtk_directory_list_start_loading (self); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MONITORED]); +} + +/** + * gtk_directory_list_get_monitored: + * @self: a #GtkDirectoryList + * + * Returns whether the directory list is monitoring + * the directory for changes. + * + * Returns: %TRUE if the directory is monitored + */ +gboolean +gtk_directory_list_get_monitored (GtkDirectoryList *self) +{ + g_return_val_if_fail (GTK_IS_DIRECTORY_LIST (self), TRUE); + + return self->monitored; +} diff --git a/gtk/gtkdirectorylist.h b/gtk/gtkdirectorylist.h index 0952224c40..a41b4928af 100644 --- a/gtk/gtkdirectorylist.h +++ b/gtk/gtkdirectorylist.h @@ -62,6 +62,12 @@ gboolean gtk_directory_list_is_loading (GtkDirectoryLis GDK_AVAILABLE_IN_ALL const GError * gtk_directory_list_get_error (GtkDirectoryList *self); +GDK_AVAILABLE_IN_ALL +void gtk_directory_list_set_monitored (GtkDirectoryList *self, + gboolean monitored); +GDK_AVAILABLE_IN_ALL +gboolean gtk_directory_list_get_monitored (GtkDirectoryList *self); + G_END_DECLS #endif /* __GTK_DIRECTORY_LIST_H__ */