[gtk/wip/otte/listview: 39/112] directorylist: Add



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]