[gtk/wip/otte/listview: 39/112] directorylist: Add
- From: Benjamin Otte <otte src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/wip/otte/listview: 39/112] directorylist: Add
- Date: Tue, 5 Nov 2019 23:06:54 +0000 (UTC)
commit 438c89e41ed7a7eefe53d568d680c7545caacab5
Author: Benjamin Otte <otte redhat com>
Date: Wed Oct 2 05:38:11 2019 +0200
directorylist: Add
Adds a new listmodel called GtkDirectoryList that lists the children of
a GFile as GFileInfos.
This is supposed to be used by the filechooser.
docs/reference/gtk/gtk4-docs.xml | 1 +
docs/reference/gtk/gtk4-sections.txt | 26 ++
gtk/gtk.h | 1 +
gtk/gtkdirectorylist.c | 681 +++++++++++++++++++++++++++++++++++
gtk/gtkdirectorylist.h | 67 ++++
gtk/meson.build | 2 +
tests/testlistview.c | 141 +++-----
7 files changed, 822 insertions(+), 97 deletions(-)
---
diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml
index c8f19276e5..8cc1aa5739 100644
--- a/docs/reference/gtk/gtk4-docs.xml
+++ b/docs/reference/gtk/gtk4-docs.xml
@@ -53,6 +53,7 @@
<xi:include href="xml/gtkselectionmodel.xml" />
<xi:include href="xml/gtknoselection.xml" />
<xi:include href="xml/gtksingleselection.xml" />
+ <xi:include href="xml/gtkdirectorylist.xml" />
</chapter>
<chapter id="Application">
diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt
index d871a24fc3..61f41d616f 100644
--- a/docs/reference/gtk/gtk4-sections.txt
+++ b/docs/reference/gtk/gtk4-sections.txt
@@ -1388,6 +1388,32 @@ GTK_TYPE_FILE_FILTER
gtk_file_filter_get_type
</SECTION>
+<SECTION>
+<FILE>gtkdirectorylist</FILE>
+<TITLE>GtkDirectoryList</TITLE>
+GtkDirectoryList
+gtk_directory_list_new
+gtk_directory_list_get_attributes
+gtk_directory_list_set_attributes
+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_is_loading
+gtk_directory_list_get_error
+<SUBSECTION Standard>
+GTK_DIRECTORY_LIST
+GTK_IS_DIRECTORY_LIST
+GTK_TYPE_DIRECTORY_LIST
+GTK_DIRECTORY_LIST_CLASS
+GTK_IS_DIRECTORY_LIST_CLASS
+GTK_DIRECTORY_LIST_GET_CLASS
+<SUBSECTION Private>
+gtk_directory_list_get_type
+</SECTION>
+
+<SECTION>
+<FILE>gtkspinbutton</FILE>
<SECTION>
<FILE>gtkfilterlistmodel</FILE>
<TITLE>GtkFilterListModel</TITLE>
diff --git a/gtk/gtk.h b/gtk/gtk.h
index bdc5849107..7a5c5ad113 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -90,6 +90,7 @@
#include <gtk/gtkcustomlayout.h>
#include <gtk/gtkdebug.h>
#include <gtk/gtkdialog.h>
+#include <gtk/gtkdirectorylist.h>
#include <gtk/gtkdnd.h>
#include <gtk/gtkdragdest.h>
#include <gtk/gtkdragsource.h>
diff --git a/gtk/gtkdirectorylist.c b/gtk/gtkdirectorylist.c
new file mode 100644
index 0000000000..053305b674
--- /dev/null
+++ b/gtk/gtkdirectorylist.c
@@ -0,0 +1,681 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "gtkdirectorylist.h"
+
+#include "gtkintl.h"
+#include "gtkprivate.h"
+
+/**
+ * SECTION:gtkdirectorylist
+ * @title: GtkDirectoryList
+ * @short_description: A list model for directory listings
+ * @see_also: #GListModel, g_file_enumerate_children()
+ *
+ * #GtkDirectoryList is a list model that wraps g_file_enumerate_children_async().
+ * It presents a #GListModel and fills it asynchronously with the #GFileInfos
+ * returned from that function.
+ *
+ * Enumeration will start automatically when a the GtkDirectoryList:file property
+ * is set.
+ *
+ * While the #GtkDirectoryList is being filled, the GtkDirectoryList:loading
+ * property will be set to %TRUE. You can listen to that property if you want
+ * to show information like a #GtkSpinner or a "Loading..." text.
+ * If loading fails at any point, the GtkDirectoryList:error property will be set
+ * to give more indication about the failure.
+ *
+ * The #GFileInfos returned from a #GtkDirectoryList have the "standard::file"
+ * attribute set to the #GFile they refer to. This way you can get at the file
+ * that is referred to in the same way you would via g_file_enumerator_get_child().
+ * This means you do not need access to the #GtkDirectoryList but can access
+ * the #GFile directly from the #GFileInfo when operating with a #GtkListView or
+ * similar.
+ */
+
+/* random number that everyone else seems to use, too */
+#define FILES_PER_QUERY 100
+
+enum {
+ PROP_0,
+ PROP_ATTRIBUTES,
+ PROP_ERROR,
+ PROP_FILE,
+ PROP_IO_PRIORITY,
+ PROP_ITEM_TYPE,
+ PROP_LOADING,
+ NUM_PROPERTIES
+};
+
+struct _GtkDirectoryList
+{
+ GObject parent_instance;
+
+ char *attributes;
+ int io_priority;
+ GFile *file;
+
+ GCancellable *cancellable;
+ GError *error; /* Error while loading */
+ GSequence *items; /* Use GPtrArray or GListStore here? */
+};
+
+struct _GtkDirectoryListClass
+{
+ GObjectClass parent_class;
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+static GType
+gtk_directory_list_get_item_type (GListModel *list)
+{
+ return G_TYPE_FILE_INFO;
+}
+
+static guint
+gtk_directory_list_get_n_items (GListModel *list)
+{
+ GtkDirectoryList *self = GTK_DIRECTORY_LIST (list);
+
+ return g_sequence_get_length (self->items);
+}
+
+static gpointer
+gtk_directory_list_get_item (GListModel *list,
+ guint position)
+{
+ GtkDirectoryList *self = GTK_DIRECTORY_LIST (list);
+ GSequenceIter *iter;
+
+ iter = g_sequence_get_iter_at_pos (self->items, position);
+
+ if (g_sequence_iter_is_end (iter))
+ return NULL;
+ else
+ return g_object_ref (g_sequence_get (iter));
+}
+
+static void
+gtk_directory_list_model_init (GListModelInterface *iface)
+{
+ iface->get_item_type = gtk_directory_list_get_item_type;
+ iface->get_n_items = gtk_directory_list_get_n_items;
+ iface->get_item = gtk_directory_list_get_item;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GtkDirectoryList, gtk_directory_list, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_directory_list_model_init))
+
+static void
+gtk_directory_list_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkDirectoryList *self = GTK_DIRECTORY_LIST (object);
+
+ switch (prop_id)
+ {
+ case PROP_ATTRIBUTES:
+ gtk_directory_list_set_attributes (self, g_value_get_string (value));
+ break;
+
+ case PROP_FILE:
+ gtk_directory_list_set_file (self, g_value_get_object (value));
+ break;
+
+ case PROP_IO_PRIORITY:
+ gtk_directory_list_set_io_priority (self, g_value_get_int (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_directory_list_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkDirectoryList *self = GTK_DIRECTORY_LIST (object);
+
+ switch (prop_id)
+ {
+ case PROP_ATTRIBUTES:
+ g_value_set_string (value, self->attributes);
+ break;
+
+ case PROP_ERROR:
+ g_value_set_boxed (value, self->error);
+ break;
+
+ case PROP_FILE:
+ g_value_set_object (value, self->file);
+ break;
+
+ case PROP_IO_PRIORITY:
+ g_value_set_int (value, self->io_priority);
+ break;
+
+ case PROP_ITEM_TYPE:
+ g_value_set_gtype (value, G_TYPE_FILE_INFO);
+ break;
+
+ case PROP_LOADING:
+ g_value_set_boolean (value, gtk_directory_list_is_loading (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gtk_directory_list_stop_loading (GtkDirectoryList *self)
+{
+ if (self->cancellable == NULL)
+ return FALSE;
+
+ g_cancellable_cancel (self->cancellable);
+ g_clear_object (&self->cancellable);
+ return TRUE;
+}
+
+static void
+gtk_directory_list_dispose (GObject *object)
+{
+ GtkDirectoryList *self = GTK_DIRECTORY_LIST (object);
+
+ gtk_directory_list_stop_loading (self);
+
+ g_clear_object (&self->file);
+ g_clear_pointer (&self->attributes, g_free);
+
+ g_clear_error (&self->error);
+ g_clear_pointer (&self->items, g_sequence_free);
+
+ G_OBJECT_CLASS (gtk_directory_list_parent_class)->dispose (object);
+}
+
+static void
+gtk_directory_list_class_init (GtkDirectoryListClass *class)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+
+ gobject_class->set_property = gtk_directory_list_set_property;
+ gobject_class->get_property = gtk_directory_list_get_property;
+ gobject_class->dispose = gtk_directory_list_dispose;
+
+ /**
+ * GtkDirectoryList:attributes:
+ *
+ * The attributes to query
+ */
+ properties[PROP_ATTRIBUTES] =
+ g_param_spec_string ("attributes",
+ P_("attributes"),
+ P_("Attributes to query"),
+ NULL,
+ GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GtkDirectoryList:error:
+ *
+ * Error encountered while loading files
+ */
+ properties[PROP_ERROR] =
+ g_param_spec_boxed ("error",
+ P_("error"),
+ P_("Error encountered while loading files"),
+ G_TYPE_ERROR,
+ GTK_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GtkDirectoryList:file:
+ *
+ * File to query
+ */
+ properties[PROP_FILE] =
+ g_param_spec_object ("file",
+ P_("File"),
+ P_("The file to query"),
+ G_TYPE_FILE,
+ GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GtkDirectoryList:io-priority:
+ *
+ * Priority used when loading
+ */
+ properties[PROP_IO_PRIORITY] =
+ g_param_spec_int ("io-priority",
+ P_("IO priority"),
+ P_("Priority used when loading"),
+ -G_MAXINT, G_MAXINT, G_PRIORITY_DEFAULT,
+ GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GtkDirectoryList:item-type:
+ *
+ * The #GType for elements of this object
+ */
+ properties[PROP_ITEM_TYPE] =
+ g_param_spec_gtype ("item-type",
+ P_("Item type"),
+ P_("The type of elements of this object"),
+ G_TYPE_FILE_INFO,
+ GTK_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GtkDirectoryList:loading:
+ *
+ * %TRUE if files are being loaded
+ */
+ properties[PROP_LOADING] =
+ g_param_spec_boolean ("loading",
+ P_("loading"),
+ P_("TRUE if files are being loaded"),
+ FALSE,
+ GTK_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
+}
+
+static void
+gtk_directory_list_init (GtkDirectoryList *self)
+{
+ self->items = g_sequence_new (g_object_unref);
+ self->io_priority = G_PRIORITY_DEFAULT;
+}
+
+/**
+ * gtk_directory_list_new:
+ * @file: (allow-none): The file to query
+ * @attributes: (allow-none): The attributes to query with
+ *
+ * Creates a new #GtkDirectoryList querying the given @file with the given
+ * @attributes.
+ *
+ * Returns: a new #GtkDirectoryList
+ **/
+GtkDirectoryList *
+gtk_directory_list_new (const char *attributes,
+ GFile *file)
+
+{
+ g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL);
+
+ return g_object_new (GTK_TYPE_DIRECTORY_LIST,
+ "attributes", attributes,
+ "file", file,
+ NULL);
+}
+
+static void
+gtk_directory_list_clear_items (GtkDirectoryList *self)
+{
+ guint n_items;
+
+ n_items = g_sequence_get_length (self->items);
+ if (n_items > 0)
+ {
+ g_sequence_remove_range (g_sequence_get_begin_iter (self->items),
+ g_sequence_get_end_iter (self->items));
+
+ g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, 0);
+ }
+
+ if (self->error)
+ {
+ g_clear_error (&self->error);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+ }
+}
+
+static void
+gtk_directory_list_enumerator_closed_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_file_enumerator_close_finish (G_FILE_ENUMERATOR (source), res, NULL);
+}
+
+static void
+gtk_directory_list_got_files_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GtkDirectoryList *self = user_data; /* invalid if cancelled */
+ GFileEnumerator *enumerator = G_FILE_ENUMERATOR (source);
+ GError *error = NULL;
+ GList *l, *files;
+ guint n;
+
+ files = g_file_enumerator_next_files_finish (enumerator, res, &error);
+
+ if (files == NULL)
+ {
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_clear_error (&error);
+ return;
+ }
+
+ g_file_enumerator_close_async (enumerator,
+ self->io_priority,
+ NULL,
+ gtk_directory_list_enumerator_closed_cb,
+ NULL);
+
+ g_object_freeze_notify (G_OBJECT (self));
+
+ g_clear_object (&self->cancellable);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]);
+
+ if (error)
+ {
+ self->error = error;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+ }
+
+ g_object_thaw_notify (G_OBJECT (self));
+ return;
+ }
+
+ n = 0;
+ for (l = files; l; l = l->next)
+ {
+ 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));
+ g_object_unref (file);
+ g_sequence_append (self->items, info);
+ n++;
+ }
+ g_list_free (files);
+
+ g_file_enumerator_next_files_async (enumerator,
+ g_file_is_native (self->file) ? 50 * FILES_PER_QUERY : FILES_PER_QUERY,
+ self->io_priority,
+ self->cancellable,
+ gtk_directory_list_got_files_cb,
+ self);
+
+ if (n > 0)
+ g_list_model_items_changed (G_LIST_MODEL (self), g_sequence_get_length (self->items) - n, 0, n);
+}
+
+static void
+gtk_directory_list_got_enumerator_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GtkDirectoryList *self = user_data; /* invalid if cancelled */
+ GFile *file = G_FILE (source);
+ GFileEnumerator *enumerator;
+ GError *error = NULL;
+
+ enumerator = g_file_enumerate_children_finish (file, res, &error);
+ if (enumerator == NULL)
+ {
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_clear_error (&error);
+ return;
+ }
+
+ g_object_freeze_notify (G_OBJECT (self));
+ self->error = error;
+ g_clear_object (&self->cancellable);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+ g_object_thaw_notify (G_OBJECT (self));
+ return;
+ }
+
+ g_file_enumerator_next_files_async (enumerator,
+ g_file_is_native (file) ? 50 * FILES_PER_QUERY : FILES_PER_QUERY,
+ self->io_priority,
+ self->cancellable,
+ gtk_directory_list_got_files_cb,
+ self);
+ g_object_unref (enumerator);
+}
+
+static void
+gtk_directory_list_start_loading (GtkDirectoryList *self)
+{
+ gboolean was_loading;
+
+ was_loading = gtk_directory_list_stop_loading (self);
+ gtk_directory_list_clear_items (self);
+
+ if (self->file == NULL)
+ {
+ if (was_loading)
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]);
+ return;
+ }
+
+ self->cancellable = g_cancellable_new ();
+ g_file_enumerate_children_async (self->file,
+ self->attributes,
+ G_FILE_QUERY_INFO_NONE,
+ self->io_priority,
+ self->cancellable,
+ gtk_directory_list_got_enumerator_cb,
+ self);
+
+ if (!was_loading)
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]);
+}
+
+/**
+ * gtk_directory_list_set_file:
+ * @self: a #GtkDirectoryList
+ * @file: (allow-none): the #GFile to be enumerated
+ *
+ * Sets the @file to be enumerated and starts the enumeration.
+ *
+ * If @file is %NULL, the result will be an empty list.
+ */
+void
+gtk_directory_list_set_file (GtkDirectoryList *self,
+ GFile *file)
+{
+ g_return_if_fail (GTK_IS_DIRECTORY_LIST (self));
+ g_return_if_fail (file == NULL || G_IS_FILE (file));
+
+ if (self->file == file ||
+ (self->file && file && g_file_equal (self->file, file)))
+ return;
+
+ g_object_freeze_notify (G_OBJECT (self));
+
+ g_set_object (&self->file, file);
+
+ gtk_directory_list_start_loading (self);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FILE]);
+
+ g_object_thaw_notify (G_OBJECT (self));
+}
+
+/**
+ * gtk_directory_list_get_file:
+ * @self: a #GtkDirectoryList
+ *
+ * Gets the file whose children are currently enumerated.
+ *
+ * Returns: (nullable) (transfer none): The file whose children are enumerated
+ **/
+GFile *
+gtk_directory_list_get_file (GtkDirectoryList *self)
+{
+ g_return_val_if_fail (GTK_IS_DIRECTORY_LIST (self), NULL);
+
+ return self->file;
+}
+
+/**
+ * gtk_directory_list_set_attributes:
+ * @self: a #GtkDirectoryList
+ * @attributes: (allow-none): the attributes to enumerate
+ *
+ * Sets the @attributes to be enumerated and starts the enumeration.
+ *
+ * If @attributes is %NULL, no attributes will be queried, but a list
+ * of #GFileInfos will still be created.
+ */
+void
+gtk_directory_list_set_attributes (GtkDirectoryList *self,
+ const char *attributes)
+{
+ g_return_if_fail (GTK_IS_DIRECTORY_LIST (self));
+
+ if (self->attributes == attributes)
+ return;
+
+ g_object_freeze_notify (G_OBJECT (self));
+
+ g_free (self->attributes);
+ self->attributes = g_strdup (attributes);
+
+ gtk_directory_list_start_loading (self);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ATTRIBUTES]);
+
+ g_object_thaw_notify (G_OBJECT (self));
+}
+
+/**
+ * gtk_directory_list_get_attributes:
+ * @self: a #GtkDirectoryList
+ *
+ * Gets the attributes queried on the children.
+ *
+ * Returns: (nullable) (transfer none): The queried attributes
+ */
+const char *
+gtk_directory_list_get_attributes (GtkDirectoryList *self)
+{
+ g_return_val_if_fail (GTK_IS_DIRECTORY_LIST (self), NULL);
+
+ return self->attributes;
+}
+
+/**
+ * gtk_directory_list_set_io_priority:
+ * @self: a #GtkDirectoryList
+ * @io_priority: IO priority to use
+ *
+ * Sets the IO priority to use while loading directories.
+ *
+ * Setting the priority while @self is loading will reprioritize the
+ * ongoing load as soon as possible.
+ *
+ * The default IO priority is %G_PRIORITY_DEFAULT, which is higher than
+ * the GTK redraw priority. If you are loading a lot of directories in
+ * parrallel, lowering it to something like %G_PRIORITY_DEFAULT_IDLE
+ * may increase responsiveness.
+ */
+void
+gtk_directory_list_set_io_priority (GtkDirectoryList *self,
+ int io_priority)
+{
+ g_return_if_fail (GTK_IS_DIRECTORY_LIST (self));
+
+ if (self->io_priority == io_priority)
+ return;
+
+ self->io_priority = io_priority;
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_IO_PRIORITY]);
+}
+
+/**
+ * gtk_directory_list_get_io_priority:
+ * @self: a #GtkDirectoryList
+ *
+ * Gets the IO priority set via gtk_directory_list_set_io_priority().
+ *
+ * Returns: The IO priority.
+ */
+int
+gtk_directory_list_get_io_priority (GtkDirectoryList *self)
+{
+ g_return_val_if_fail (GTK_IS_DIRECTORY_LIST (self), G_PRIORITY_DEFAULT);
+
+ return self->io_priority;
+}
+
+/**
+ * gtk_directory_list_is_loading:
+ * @self: a #GtkDirectoryList
+ *
+ * Returns %TRUE if the children enumeration is currently in
+ * progress.
+ * Files will be added to @self from time to time while loading is
+ * going on. The order in which are added is undefined and may change
+ * inbetween runs.
+ *
+ * Returns: %TRUE if @self is loading
+ */
+gboolean
+gtk_directory_list_is_loading (GtkDirectoryList *self)
+{
+ g_return_val_if_fail (GTK_IS_DIRECTORY_LIST (self), FALSE);
+
+ return self->cancellable != NULL;
+}
+
+/**
+ * gtk_directory_list_get_error:
+ * @self: a #GtkDirectoryList
+ *
+ * Gets the loading error, if any.
+ *
+ * If an error occurs during the loading process, the loading process
+ * will finish and this property allows querying the error that happened.
+ * This error will persist until a file is loaded again.
+ *
+ * An error being set does not mean that no files were loaded, and all
+ * successfully queried files will remain in the list.
+ *
+ * Returns: (nullable) (transfer none): The loading error or %NULL if
+ * loading finished successfully.
+ */
+const GError *
+gtk_directory_list_get_error (GtkDirectoryList *self)
+{
+ g_return_val_if_fail (GTK_IS_DIRECTORY_LIST (self), FALSE);
+
+ return self->error;
+}
+
diff --git a/gtk/gtkdirectorylist.h b/gtk/gtkdirectorylist.h
new file mode 100644
index 0000000000..0952224c40
--- /dev/null
+++ b/gtk/gtkdirectorylist.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __GTK_DIRECTORY_LIST_H__
+#define __GTK_DIRECTORY_LIST_H__
+
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#include <gio/gio.h>
+/* for GDK_AVAILABLE_IN_ALL */
+#include <gdk/gdk.h>
+
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_DIRECTORY_LIST (gtk_directory_list_get_type ())
+
+GDK_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (GtkDirectoryList, gtk_directory_list, GTK, DIRECTORY_LIST, GObject)
+
+GDK_AVAILABLE_IN_ALL
+GtkDirectoryList * gtk_directory_list_new (const char *attributes,
+ GFile *file);
+
+GDK_AVAILABLE_IN_ALL
+void gtk_directory_list_set_file (GtkDirectoryList *self,
+ GFile *file);
+GDK_AVAILABLE_IN_ALL
+GFile * gtk_directory_list_get_file (GtkDirectoryList *self);
+GDK_AVAILABLE_IN_ALL
+void gtk_directory_list_set_attributes (GtkDirectoryList *self,
+ const char *attributes);
+GDK_AVAILABLE_IN_ALL
+const char * gtk_directory_list_get_attributes (GtkDirectoryList *self);
+GDK_AVAILABLE_IN_ALL
+void gtk_directory_list_set_io_priority (GtkDirectoryList *self,
+ int io_priority);
+GDK_AVAILABLE_IN_ALL
+int gtk_directory_list_get_io_priority (GtkDirectoryList *self);
+
+GDK_AVAILABLE_IN_ALL
+gboolean gtk_directory_list_is_loading (GtkDirectoryList *self);
+GDK_AVAILABLE_IN_ALL
+const GError * gtk_directory_list_get_error (GtkDirectoryList *self);
+
+G_END_DECLS
+
+#endif /* __GTK_DIRECTORY_LIST_H__ */
diff --git a/gtk/meson.build b/gtk/meson.build
index 5d4c4abb19..b335e550d9 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -214,6 +214,7 @@ gtk_public_sources = files([
'gtkcontainer.c',
'gtkcssprovider.c',
'gtkdialog.c',
+ 'gtkdirectorylist.c',
'gtkdnd.c',
'gtkdragdest.c',
'gtkdragsource.c',
@@ -486,6 +487,7 @@ gtk_public_headers = files([
'gtkcustomlayout.h',
'gtkdebug.h',
'gtkdialog.h',
+ 'gtkdirectorylist.h',
'gtkdnd.h',
'gtkdragdest.h',
'gtkdragsource.h',
diff --git a/tests/testlistview.c b/tests/testlistview.c
index 300dc28423..309e0c33ff 100644
--- a/tests/testlistview.c
+++ b/tests/testlistview.c
@@ -6,104 +6,56 @@ GSList *pending = NULL;
guint active = 0;
static void
-got_files (GObject *enumerate,
- GAsyncResult *res,
- gpointer store);
-
-static gboolean
-start_enumerate (GListStore *store)
+loading_cb (GtkDirectoryList *dir,
+ GParamSpec *pspec,
+ gpointer unused)
{
- GFileEnumerator *enumerate;
- GFile *file = g_object_get_data (G_OBJECT (store), "file");
- GError *error = NULL;
-
- enumerate = g_file_enumerate_children (file,
- G_FILE_ATTRIBUTE_STANDARD_TYPE
- "," G_FILE_ATTRIBUTE_STANDARD_ICON
- "," G_FILE_ATTRIBUTE_STANDARD_NAME
- "," G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
- 0,
- NULL,
- &error);
-
- if (enumerate == NULL)
+ if (gtk_directory_list_is_loading (dir))
{
- if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TOO_MANY_OPEN_FILES) && active)
- {
- g_clear_error (&error);
- pending = g_slist_prepend (pending, g_object_ref (store));
- return TRUE;
- }
-
- g_clear_error (&error);
- g_object_unref (store);
- return FALSE;
+ active++;
+ /* HACK: ensure loading finishes and the dir doesn't get destroyed */
+ g_object_ref (dir);
}
-
- if (active > 20)
+ else
{
- g_object_unref (enumerate);
- pending = g_slist_prepend (pending, g_object_ref (store));
- return TRUE;
- }
-
- active++;
- g_file_enumerator_next_files_async (enumerate,
- g_file_is_native (file) ? 5000 : 100,
- G_PRIORITY_DEFAULT_IDLE,
- NULL,
- got_files,
- g_object_ref (store));
+ active--;
+ g_object_unref (dir);
- g_object_unref (enumerate);
- return TRUE;
+ while (active < 20 && pending)
+ {
+ GtkDirectoryList *dir2 = pending->data;
+ pending = g_slist_remove (pending, dir2);
+ gtk_directory_list_set_file (dir2, g_object_get_data (G_OBJECT (dir2), "file"));
+ g_object_unref (dir2);
+ }
+ }
}
-static void
-got_files (GObject *enumerate,
- GAsyncResult *res,
- gpointer store)
+static GtkDirectoryList *
+create_directory_list (GFile *file)
{
- GList *l, *files;
- GFile *file = g_object_get_data (store, "file");
- GPtrArray *array;
+ GtkDirectoryList *dir;
+
+ dir = gtk_directory_list_new (G_FILE_ATTRIBUTE_STANDARD_TYPE
+ "," G_FILE_ATTRIBUTE_STANDARD_ICON
+ "," G_FILE_ATTRIBUTE_STANDARD_NAME
+ "," G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
+ NULL);
+ gtk_directory_list_set_io_priority (dir, G_PRIORITY_DEFAULT_IDLE);
+ g_signal_connect (dir, "notify::loading", G_CALLBACK (loading_cb), NULL);
+ g_assert (!gtk_directory_list_is_loading (dir));
- files = g_file_enumerator_next_files_finish (G_FILE_ENUMERATOR (enumerate), res, NULL);
- if (files == NULL)
+ if (active > 20)
{
- g_object_unref (store);
- if (pending)
- {
- GListStore *store = pending->data;
- pending = g_slist_remove (pending, store);
- start_enumerate (store);
- }
- active--;
- return;
+ g_object_set_data_full (G_OBJECT (dir), "file", g_object_ref (file), g_object_unref);
+ pending = g_slist_prepend (pending, g_object_ref (dir));
}
-
- array = g_ptr_array_new ();
- g_ptr_array_new_with_free_func (g_object_unref);
- for (l = files; l; l = l->next)
+ else
{
- GFileInfo *info = l->data;
- GFile *child;
-
- child = g_file_get_child (file, g_file_info_get_name (info));
- g_object_set_data_full (G_OBJECT (info), "file", child, g_object_unref);
- g_ptr_array_add (array, info);
+ gtk_directory_list_set_file (dir, file);
}
- g_list_free (files);
-
- g_list_store_splice (store, g_list_model_get_n_items (store), 0, array->pdata, array->len);
- g_ptr_array_unref (array);
- g_file_enumerator_next_files_async (G_FILE_ENUMERATOR (enumerate),
- g_file_is_native (file) ? 5000 : 100,
- G_PRIORITY_DEFAULT_IDLE,
- NULL,
- got_files,
- store);
+ return dir;
}
static int
@@ -127,8 +79,8 @@ compare_files (gconstpointer first,
return 1;
#endif
- first_file = g_object_get_data (G_OBJECT (first), "file");
- second_file = g_object_get_data (G_OBJECT (second), "file");
+ first_file = G_FILE (g_file_info_get_attribute_object (G_FILE_INFO (first), "standard::file"));
+ second_file = G_FILE (g_file_info_get_attribute_object (G_FILE_INFO (second), "standard::file"));
first_path = g_file_get_path (first_file);
second_path = g_file_get_path (second_file);
@@ -144,21 +96,16 @@ static GListModel *
create_list_model_for_directory (gpointer file)
{
GtkSortListModel *sort;
- GListStore *store;
+ GtkDirectoryList *dir;
if (g_file_query_file_type (file, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) != G_FILE_TYPE_DIRECTORY)
return NULL;
- store = g_list_store_new (G_TYPE_FILE_INFO);
- g_object_set_data_full (G_OBJECT (store), "file", g_object_ref (file), g_object_unref);
-
- if (!start_enumerate (store))
- return NULL;
-
- sort = gtk_sort_list_model_new (G_LIST_MODEL (store),
+ dir = create_directory_list(file);
+ sort = gtk_sort_list_model_new (G_LIST_MODEL (dir),
compare_files,
NULL, NULL);
- g_object_unref (store);
+ g_object_unref (dir);
return G_LIST_MODEL (sort);
}
@@ -289,7 +236,7 @@ static GListModel *
create_list_model_for_file_info (gpointer file_info,
gpointer unused)
{
- GFile *file = g_object_get_data (file_info, "file");
+ GFile *file = G_FILE (g_file_info_get_attribute_object (file_info, "standard::file"));
if (file == NULL)
return NULL;
@@ -334,7 +281,7 @@ match_file (gpointer item, gpointer data)
{
GtkWidget *search_entry = data;
GFileInfo *info = gtk_tree_list_row_get_item (item);
- GFile *file = g_object_get_data (G_OBJECT (info), "file");
+ GFile *file = G_FILE (g_file_info_get_attribute_object (info, "standard::file"));
char *path;
gboolean result;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]