]> Pileus Git - ~andy/gtk/blobdiff - gtk/gtkfilesystemmodel.c
testgtk: Use symbolic names for button numbers
[~andy/gtk] / gtk / gtkfilesystemmodel.c
index 7cba000b2e8a178e3a135b2b2b81ec5433da8c26..7cfc5a156739d4089769f874394d1bf4110ca769 100644 (file)
 #include "gtktreedatalist.h"
 #include "gtktreednd.h"
 #include "gtktreemodel.h"
-#include "gtkalias.h"
+
+/*** Structure: how GtkFileSystemModel works
+ *
+ * This is a custom GtkTreeModel used to hold a collection of files for GtkFileChooser.  There are two use cases:
+ *
+ *   1. The model populates itself from a folder, using the GIO file enumerator API.  This happens if you use
+ *      _gtk_file_system_model_new_for_directory().  This is the normal usage for showing the contents of a folder.
+ *
+ *   2. The caller populates the model by hand, with files not necessarily in the same folder.  This happens
+ *      if you use _gtk_file_system_model_new() and then _gtk_file_system_model_add_and_query_file().  This is
+ *      the special kind of usage for "search" and "recent-files", where the file chooser gives the model the
+ *      files to be displayed.
+ *
+ * Each file is kept in a FileModelNode structure.  Each FileModelNode holds a GFile* and other data.  All the
+ * node structures have the same size, determined at runtime, depending on the number of columns that were passed
+ * to _gtk_file_system_model_new() or _gtk_file_system_model_new_for_directory() (that is, the size of a node is
+ * not sizeof (FileModelNode), but rather model->node_size).  The last field in the FileModelNode structure,
+ * node->values[], is an array of GValue, used to hold the data for those columns.
+ *
+ * The model stores an array of FileModelNode structures in model->files.  This is a GArray where each element is
+ * model->node_size bytes in size (the model computes that node size when initializing itself).  There are
+ * convenience macros, get_node() and node_index(), to access that array based on an array index or a pointer to
+ * a node inside the array.
+ *
+ * The model accesses files through two of its fields:
+ *
+ *   model->files - GArray of FileModelNode structures.
+ *
+ *   model->file_lookup - hash table that maps a GFile* to an index inside the model->files array.
+ *
+ * The model->file_lookup hash table is populated lazily.  It is both accessed and populated with the
+ * node_get_for_file() function.  The invariant is that the files in model->files[n] for n < g_hash_table_size
+ * (model->file_lookup) are already added to the hash table. The hash table will get cleared when we re-sort the
+ * files, as the array will be in a different order and the indexes need to be rebuilt.
+ *
+ * Each FileModelNode has a node->visible field, which indicates whether the node is visible in the GtkTreeView.
+ * A node may be invisible if, for example, it corresponds to a hidden file and the file chooser is not showing
+ * hidden files.
+ *
+ * Since not all nodes in the model->files array may be visible, we need a way to map visible row indexes from
+ * the treeview to array indexes in our array of files.  And thus we introduce a bit of terminology:
+ *
+ *   index - An index in the model->files array.  All variables/fields that represent indexes are either called
+ *   "index" or "i_*", or simply "i" for things like loop counters.
+ *
+ *   row - An index in the GtkTreeView, i.e. the index of a row within the outward-facing API of the
+ *   GtkFileSystemModel.  However, note that our rows are 1-based, not 0-based, for the reason explained in the
+ *   following paragraph.  Variables/fields that represent visible rows are called "row", or "r_*", or simply
+ *   "r".
+ *
+ * Each FileModelNode has a node->row field which is the number of visible rows in the treeview, *before and
+ * including* that node.  This means that node->row is 1-based, instead of 0-based --- this makes some code
+ * simpler, believe it or not :)  This also means that when the calling GtkTreeView gives us a GtkTreePath, we
+ * turn the 0-based treepath into a 1-based row for our purposes.  If a node is not visible, it will have the
+ * same row number as its closest preceding visible node.
+ *
+ * We try to compute the node->row fields lazily.  A node is said to be "valid" if its node->row is accurate.
+ * For this, the model keeps a model->n_nodes_valid field which is the count of valid nodes starting from the
+ * beginning of the model->files array.  When a node changes its information, or when a node gets deleted, that
+ * node and the following ones get invalidated by simply setting model->n_nodes_valid to the array index of the
+ * node.  If the model happens to need a node's row number and that node is in the model->files array after
+ * model->n_nodes_valid, then the nodes get re-validated up to the sought node.  See node_validate_rows() for
+ * this logic.
+ *
+ * You never access a node->row directly.  Instead, call node_get_tree_row().  That function will validate the nodes
+ * up to the sought one if the node is not valid yet, and it will return a proper 0-based row.
+ */
 
 /*** DEFINES ***/
 
@@ -51,7 +117,9 @@ struct _FileModelNode
   GFile *               file;           /* file represented by this node or NULL for editable */
   GFileInfo *           info;           /* info for this file or NULL if unknown */
 
-  guint                 index;          /* if valid, index in path - aka visible nodes before this one */
+  guint                 row;            /* if valid (see model->n_valid_indexes), visible nodes before and including
+                                        * this one - see the "Structure" comment above.
+                                        */
 
   guint                 visible :1;     /* if the file is currently visible */
   guint                 frozen_add :1;  /* true if the model was frozen and the entry has not been added yet */
@@ -65,14 +133,19 @@ struct _GtkFileSystemModel
 
   GFile *               dir;            /* directory that's displayed */
   guint                 dir_thaw_source;/* GSource id for unfreezing the model */
-  char *                attributes;     /* attributes the file info must contain */
-  GFileMonitor *        dir_monitor;    /* directory that is monitored */
+  char *                attributes;     /* attributes the file info must contain, or NULL for all attributes */
+  GFileMonitor *        dir_monitor;    /* directory that is monitored, or NULL if monitoring was not supported */
 
   GCancellable *        cancellable;    /* cancellable in use for all operations - cancelled on dispose */
   GArray *              files;          /* array of FileModelNode containing all our files */
-  GSize                 node_size;     /* Size of a FileModelNode structure once its ->values field has n_columns */
-  guint                 n_indexes_valid;/* count of valid indexes */
-  GHashTable *          file_lookup;    /* file => array index table */
+  gsize                 node_size;     /* Size of a FileModelNode structure once its ->values field has n_columns */
+  guint                 n_nodes_valid;  /* count of valid nodes (i.e. those whose node->row is accurate) */
+  GHashTable *          file_lookup;    /* mapping of GFile => array index in model->files
+                                        * This hash table doesn't always have the same number of entries as the files array;
+                                        * it can get cleared completely when we resort.
+                                        * The hash table gets re-populated in node_get_for_file() if this mismatch is
+                                        * detected.
+                                        */
 
   guint                 n_columns;      /* number of columns */
   GType *               column_types;   /* types of each column */
@@ -96,6 +169,7 @@ struct _GtkFileSystemModel
   guint                 show_hidden :1; /* whether to show hidden files */
   guint                 show_folders :1;/* whether to show folders */
   guint                 show_files :1;  /* whether to show files */
+  guint                 filter_folders :1;/* whether filter applies to folders */
 };
 
 #define GTK_FILE_SYSTEM_MODEL_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILE_SYSTEM_MODEL, GtkFileSystemModelClass))
@@ -111,6 +185,12 @@ struct _GtkFileSystemModelClass
   void (*finished_loading) (GtkFileSystemModel *model, GError *error);
 };
 
+static void add_file (GtkFileSystemModel *model,
+                     GFile              *file,
+                     GFileInfo          *info);
+static void remove_file (GtkFileSystemModel *model,
+                        GFile              *file);
+
 /* iter setup:
  * @user_data: the model
  * @user_data2: GUINT_TO_POINTER of array index of current entry
@@ -128,52 +208,65 @@ struct _GtkFileSystemModelClass
 
 /*** FileModelNode ***/
 
+/* Get a FileModelNode structure given an index in the model->files array of nodes */
 #define get_node(_model, _index) ((FileModelNode *) ((_model)->files->data + (_index) * (_model)->node_size))
+
+/* Get an index within the model->files array of nodes, given a FileModelNode* */
 #define node_index(_model, _node) (((gchar *) (_node) - (_model)->files->data) / (_model)->node_size)
 
+/* @up_to_index: smallest model->files array index that will be valid after this call
+ * @up_to_row: smallest node->row that will be valid after this call
+ *
+ * If you want to validate up to an index or up to a row, specify the index or
+ * the row you want and specify G_MAXUINT for the other argument.  Pass
+ * G_MAXUINT for both arguments for "validate everything".
+ */
 static void
-node_validate_indexes (GtkFileSystemModel *model, guint min_index, guint min_visible)
+node_validate_rows (GtkFileSystemModel *model, guint up_to_index, guint up_to_row)
 {
-  guint validate, current;
+  guint i, row;
 
   if (model->files->len == 0)
     return;
-  min_index = MIN (min_index, model->files->len - 1);
-  validate = model->n_indexes_valid;
-  if (validate)
-    current = get_node (model, validate - 1)->index;
+
+  up_to_index = MIN (up_to_index, model->files->len - 1);
+
+  i = model->n_nodes_valid;
+  if (i != 0)
+    row = get_node (model, i - 1)->row;
   else
-    current = 0;
-  while (validate <= min_index && current <= min_visible)
+    row = 0;
+
+  while (i <= up_to_index && row <= up_to_row)
     {
-      FileModelNode *node = get_node (model, validate);
+      FileModelNode *node = get_node (model, i);
       if (node->visible)
-        current++;
-      node->index = current;
-      validate++;
+        row++;
+      node->row = row;
+      i++;
     }
-  model->n_indexes_valid = validate;
+  model->n_nodes_valid = i;
 }
 
 static guint
-node_get_index (GtkFileSystemModel *model, guint id)
+node_get_tree_row (GtkFileSystemModel *model, guint index)
 {
-  if (model->n_indexes_valid <= id)
-    node_validate_indexes (model, id, G_MAXUINT);
+  if (model->n_nodes_valid <= index)
+    node_validate_rows (model, index, G_MAXUINT);
 
-  return get_node (model, id)->index - 1;
+  return get_node (model, index)->row - 1;
 }
 
 static void 
 node_invalidate_index (GtkFileSystemModel *model, guint id)
 {
-  model->n_indexes_valid = MIN (model->n_indexes_valid, id);
+  model->n_nodes_valid = MIN (model->n_nodes_valid, id);
 }
 
 static GtkTreePath *
 gtk_tree_path_new_from_node (GtkFileSystemModel *model, guint id)
 {
-  guint i = node_get_index (model, id);
+  guint i = node_get_tree_row (model, id);
 
   g_assert (i < model->files->len);
 
@@ -181,12 +274,44 @@ gtk_tree_path_new_from_node (GtkFileSystemModel *model, guint id)
 }
 
 static void
-node_set_visible (GtkFileSystemModel *model, guint id, gboolean visible)
+emit_row_inserted_for_node (GtkFileSystemModel *model, guint id)
+{
+  GtkTreePath *path;
+  GtkTreeIter iter;
+
+  path = gtk_tree_path_new_from_node (model, id);
+  ITER_INIT_FROM_INDEX (model, &iter, id);
+  gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter);
+  gtk_tree_path_free (path);
+}
+
+static void
+emit_row_changed_for_node (GtkFileSystemModel *model, guint id)
 {
-  FileModelNode *node = get_node (model, id);
   GtkTreePath *path;
   GtkTreeIter iter;
 
+  path = gtk_tree_path_new_from_node (model, id);
+  ITER_INIT_FROM_INDEX (model, &iter, id);
+  gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
+  gtk_tree_path_free (path);
+}
+
+static void
+emit_row_deleted_for_row (GtkFileSystemModel *model, guint row)
+{
+  GtkTreePath *path;
+
+  path = gtk_tree_path_new_from_indices (row, -1);
+  gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
+  gtk_tree_path_free (path);
+}
+
+static void
+node_set_visible (GtkFileSystemModel *model, guint id, gboolean visible)
+{
+  FileModelNode *node = get_node (model, id);
+
   if (node->visible == visible ||
       node->frozen_add)
     return;
@@ -195,18 +320,18 @@ node_set_visible (GtkFileSystemModel *model, guint id, gboolean visible)
     {
       node->visible = TRUE;
       node_invalidate_index (model, id);
-      path = gtk_tree_path_new_from_node (model, id);
-      ITER_INIT_FROM_INDEX (model, &iter, id);
-      gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter);
-      gtk_tree_path_free (path);
+      emit_row_inserted_for_node (model, id);
     }
   else
     {
-      path = gtk_tree_path_new_from_node (model, id);
+      guint row;
+
+      row = node_get_tree_row (model, id);
+      g_assert (row < model->files->len);
+
       node->visible = FALSE;
       node_invalidate_index (model, id);
-      gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
-      gtk_tree_path_free (path);
+      emit_row_deleted_for_row (model, row);
     }
 }
 
@@ -216,7 +341,7 @@ node_should_be_visible (GtkFileSystemModel *model, guint id)
   FileModelNode *node = get_node (model, id);
   GtkFileFilterInfo filter_info = { 0, };
   GtkFileFilterFlags required;
-  gboolean is_folder, result;
+  gboolean result;
   char *mime_type = NULL;
   char *filename = NULL;
   char *uri = NULL;
@@ -228,15 +353,19 @@ node_should_be_visible (GtkFileSystemModel *model, guint id)
       (g_file_info_get_is_hidden (node->info) || g_file_info_get_is_backup (node->info)))
     return FALSE;
 
-  is_folder = _gtk_file_info_consider_as_directory (node->info);
-  
-  /* wtf? */
-  if (model->show_folders != model->show_files &&
-      model->show_folders != is_folder)
-    return FALSE;
+  if (_gtk_file_info_consider_as_directory (node->info))
+    {
+      if (!model->show_folders)
+        return FALSE;
 
-  if (is_folder)
-    return TRUE;
+      if (!model->filter_folders)
+        return TRUE;
+    }
+  else
+    {
+      if (!model->show_files)
+        return FALSE;
+    }
 
   if (model->filter == NULL)
     return TRUE;
@@ -249,28 +378,22 @@ node_should_be_visible (GtkFileSystemModel *model, guint id)
 
   if (required & GTK_FILE_FILTER_MIME_TYPE)
     {
-      filter_info.mime_type = g_file_info_get_attribute_string (node->info, "filechooser::mime-type");
-      if (filter_info.mime_type != NULL)
-        filter_info.contains |= GTK_FILE_FILTER_MIME_TYPE;
-      else
-        {
-          const char *s = g_file_info_get_content_type (node->info);
-          if (s)
-            {
-              mime_type = g_content_type_get_mime_type (s);
-              if (mime_type)
-                {
-                  filter_info.mime_type = mime_type;
-                  filter_info.contains |= GTK_FILE_FILTER_MIME_TYPE;
-                }
-            }
-        }
+      const char *s = g_file_info_get_content_type (node->info);
+      if (s)
+       {
+         mime_type = g_content_type_get_mime_type (s);
+         if (mime_type)
+           {
+             filter_info.mime_type = mime_type;
+             filter_info.contains |= GTK_FILE_FILTER_MIME_TYPE;
+           }
+       }
     }
 
   if (required & GTK_FILE_FILTER_FILENAME)
     {
       filename = g_file_get_path (node->file);
-      if (filter_info.filename)
+      if (filename)
         {
           filter_info.filename = filename;
          filter_info.contains |= GTK_FILE_FILTER_FILENAME;
@@ -329,7 +452,7 @@ compare_indices (gconstpointer key, gconstpointer _node)
 {
   const FileModelNode *node = _node;
 
-  return GPOINTER_TO_UINT (key) - node->index;
+  return GPOINTER_TO_UINT (key) - node->row;
 }
 
 static gboolean
@@ -340,36 +463,46 @@ gtk_file_system_model_iter_nth_child (GtkTreeModel *tree_model,
 {
   GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (tree_model);
   char *node;
-  guint id, find;
+  guint id;
+  guint row_to_find;
 
   g_return_val_if_fail (n >= 0, FALSE);
 
   if (parent != NULL)
     return FALSE;
 
-  find = n + 1;
+  row_to_find = n + 1; /* plus one as our node->row numbers are 1-based; see the "Structure" comment at the beginning */
 
-  if (model->n_indexes_valid > 0 &&
-      get_node (model, model->n_indexes_valid - 1)->index >= find)
+  if (model->n_nodes_valid > 0 &&
+      get_node (model, model->n_nodes_valid - 1)->row >= row_to_find)
     {
-      /* fast path */
-      node = bsearch (GUINT_TO_POINTER (find), 
+      /* Fast path - the nodes are valid up to the sought one.
+       *
+       * First, find a node with the sought row number...*/
+
+      node = bsearch (GUINT_TO_POINTER (row_to_find), 
                       model->files->data,
-                      model->n_indexes_valid,
+                      model->n_nodes_valid,
                       model->node_size,
                       compare_indices);
       if (node == NULL)
         return FALSE;
 
+      /* ... Second, back up until we find the first visible node with that row number */
+
       id = node_index (model, node);
       while (!get_node (model, id)->visible)
         id--;
+
+      g_assert (get_node (model, id)->row == row_to_find);
     }
   else
     {
-      node_validate_indexes (model, G_MAXUINT, n);
-      id = model->n_indexes_valid - 1;
-      if (model->n_indexes_valid == 0 || get_node (model, id)->index != find)
+      /* Slow path - the nodes need to be validated up to the sought one */
+
+      node_validate_rows (model, G_MAXUINT, n); /* note that this is really "n", not row_to_find - see node_validate_rows() */
+      id = model->n_nodes_valid - 1;
+      if (model->n_nodes_valid == 0 || get_node (model, id)->row != row_to_find)
         return FALSE;
     }
 
@@ -384,6 +517,9 @@ gtk_file_system_model_get_iter (GtkTreeModel *tree_model,
 {
   g_return_val_if_fail (gtk_tree_path_get_depth (path) > 0, FALSE);
 
+  if (gtk_tree_path_get_depth (path) > 1)
+    return FALSE;
+
   return gtk_file_system_model_iter_nth_child (tree_model, 
                                                iter,
                                                NULL, 
@@ -470,7 +606,7 @@ gtk_file_system_model_iter_n_children (GtkTreeModel *tree_model,
   if (iter)
     return 0;
 
-  return node_get_index (model, model->files->len - 1) + 1;
+  return node_get_tree_row (model, model->files->len - 1) + 1;
 }
 
 static gboolean
@@ -485,14 +621,14 @@ static void
 gtk_file_system_model_ref_node (GtkTreeModel *tree_model,
                                GtkTreeIter  *iter)
 {
-  /* FIXME: implement */
+  /* nothing to do */
 }
 
 static void
 gtk_file_system_model_unref_node (GtkTreeModel *tree_model,
                                  GtkTreeIter  *iter)
 {
-  /* FIXME: implement */
+  /* nothing to do */
 }
 
 static void
@@ -582,38 +718,39 @@ gtk_file_system_model_sort (GtkFileSystemModel *model)
   if (sort_data_init (&data, model))
     {
       GtkTreePath *path;
-      guint i, j, n_elements;
+      guint i;
+      guint r, n_visible_rows;
 
-      node_validate_indexes (model, G_MAXUINT, G_MAXUINT);
-      n_elements = node_get_index (model, model->files->len - 1) + 1;
-      model->n_indexes_valid = 0;
+      node_validate_rows (model, G_MAXUINT, G_MAXUINT);
+      n_visible_rows = node_get_tree_row (model, model->files->len - 1) + 1;
+      model->n_nodes_valid = 0;
       g_hash_table_remove_all (model->file_lookup);
-      g_qsort_with_data (get_node (model, 1),
+      g_qsort_with_data (get_node (model, 1), /* start at index 1; don't sort the editable row */
                          model->files->len - 1,
                          model->node_size,
                          compare_array_element,
                          &data);
-      g_assert (model->n_indexes_valid == 0);
+      g_assert (model->n_nodes_valid == 0);
       g_assert (g_hash_table_size (model->file_lookup) == 0);
-      if (n_elements)
+      if (n_visible_rows)
         {
-          int *new_order = g_new (int, n_elements);
+          int *new_order = g_new (int, n_visible_rows);
         
-          j = 0;
+          r = 0;
           for (i = 0; i < model->files->len; i++)
             {
               FileModelNode *node = get_node (model, i);
               if (!node->visible)
                 {
-                  node->index = j;
+                  node->row = r;
                   continue;
                 }
 
-              new_order[j] = node->index;
-              j++;
-              node->index = j;
+              new_order[r] = node->row - 1;
+              r++;
+              node->row = r;
             }
-          g_assert (j == n_elements);
+          g_assert (r == n_visible_rows);
           path = gtk_tree_path_new ();
           gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model),
                                          path,
@@ -824,6 +961,12 @@ gtk_file_system_model_dispose (GObject *object)
 {
   GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (object);
 
+  if (model->dir_thaw_source)
+    {
+      g_source_remove (model->dir_thaw_source);
+      model->dir_thaw_source = 0;
+    }
+
   g_cancellable_cancel (model->cancellable);
   if (model->dir_monitor)
     g_file_monitor_cancel (model->dir_monitor);
@@ -840,11 +983,17 @@ gtk_file_system_model_finalize (GObject *object)
 
   for (i = 0; i < model->files->len; i++)
     {
+      int v;
+
       FileModelNode *node = get_node (model, i);
       if (node->file)
         g_object_unref (node->file);
       if (node->info)
         g_object_unref (node->info);
+
+      for (v = 0; v < model->n_columns; v++)
+       if (G_VALUE_TYPE (&node->values[v]) != G_TYPE_INVALID)
+         g_value_unset (&node->values[v]);
     }
   g_array_free (model->files, TRUE);
 
@@ -858,6 +1007,8 @@ gtk_file_system_model_finalize (GObject *object)
   if (model->filter)
     g_object_unref (model->filter);
 
+  g_slice_free1 (sizeof (GType) * model->n_columns, model->column_types);
+
   _gtk_tree_data_list_header_free (model->sort_list);
   if (model->default_sort_destroy)
     model->default_sort_destroy (model->default_sort_data);
@@ -889,6 +1040,7 @@ _gtk_file_system_model_init (GtkFileSystemModel *model)
   model->show_files = TRUE;
   model->show_folders = TRUE;
   model->show_hidden = FALSE;
+  model->filter_folders = FALSE;
 
   model->sort_column_id = GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID;
 
@@ -954,41 +1106,41 @@ gtk_file_system_model_got_files (GObject *object, GAsyncResult *res, gpointer da
               continue;
             }
           file = g_file_get_child (model->dir, name);
-          _gtk_file_system_model_add_file (model, file, info);
+          add_file (model, file, info);
           g_object_unref (file);
           g_object_unref (info);
         }
       g_list_free (files);
-    }
 
-  if (files == NULL)
+      g_file_enumerator_next_files_async (enumerator,
+                                         g_file_is_native (model->dir) ? 50 * FILES_PER_QUERY : FILES_PER_QUERY,
+                                         IO_PRIORITY,
+                                         model->cancellable,
+                                         gtk_file_system_model_got_files,
+                                         model);
+    }
+  else
     {
-      g_file_enumerator_close_async (enumerator, 
-                                     IO_PRIORITY,
-                                     model->cancellable,
-                                     gtk_file_system_model_closed_enumerator,
-                                     NULL);
-      if (model->dir_thaw_source != 0)
+      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
         {
-          g_source_remove (model->dir_thaw_source);
-          model->dir_thaw_source = 0;
-          _gtk_file_system_model_thaw_updates (model);
+          g_file_enumerator_close_async (enumerator,
+                                         IO_PRIORITY,
+                                         model->cancellable,
+                                         gtk_file_system_model_closed_enumerator,
+                                         NULL);
+          if (model->dir_thaw_source != 0)
+            {
+              g_source_remove (model->dir_thaw_source);
+              model->dir_thaw_source = 0;
+              _gtk_file_system_model_thaw_updates (model);
+            }
+
+          g_signal_emit (model, file_system_model_signals[FINISHED_LOADING], 0, error);
         }
 
       if (error)
         g_error_free (error);
-      else
-        g_signal_emit (model, file_system_model_signals[FINISHED_LOADING], 0, NULL);
-
-      g_object_unref (model);
     }
-  else
-    g_file_enumerator_next_files_async (enumerator,
-                                        g_file_is_native (model->dir) ? 50 * FILES_PER_QUERY : FILES_PER_QUERY,
-                                        IO_PRIORITY,
-                                        model->cancellable,
-                                        gtk_file_system_model_got_files,
-                                        model);
 
   gdk_threads_leave ();
 }
@@ -1006,7 +1158,9 @@ gtk_file_system_model_query_done (GObject *     object,
   if (info == NULL)
     return;
 
+  gdk_threads_enter ();
   _gtk_file_system_model_update_file (model, file, info, TRUE);
+  gdk_threads_leave ();
 }
 
 static void
@@ -1024,14 +1178,16 @@ gtk_file_system_model_monitor_change (GFileMonitor *      monitor,
         /* We can treat all of these the same way */
         g_file_query_info_async (file,
                                  model->attributes,
-                                 0,
+                                 G_FILE_QUERY_INFO_NONE,
                                  IO_PRIORITY,
                                  model->cancellable,
                                  gtk_file_system_model_query_done,
                                  model);
         break;
       case G_FILE_MONITOR_EVENT_DELETED:
-        _gtk_file_system_model_remove_file (model, file);
+       gdk_threads_enter ();
+        remove_file (model, file);
+       gdk_threads_leave ();
         break;
       case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
         /* FIXME: use freeze/thaw with this somehow? */
@@ -1055,9 +1211,11 @@ gtk_file_system_model_got_enumerator (GObject *dir, GAsyncResult *res, gpointer
   enumerator = g_file_enumerate_children_finish (G_FILE (dir), res, &error);
   if (enumerator == NULL)
     {
-      g_signal_emit (model, file_system_model_signals[FINISHED_LOADING], 0, error);
-      g_object_unref (model);
-      g_error_free (error);
+      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+      {
+        g_signal_emit (model, file_system_model_signals[FINISHED_LOADING], 0, error);
+        g_error_free (error);
+      }
     }
   else
     {
@@ -1069,9 +1227,9 @@ gtk_file_system_model_got_enumerator (GObject *dir, GAsyncResult *res, gpointer
                                           model);
       g_object_unref (enumerator);
       model->dir_monitor = g_file_monitor_directory (model->dir,
-                                                     0,
+                                                     G_FILE_MONITOR_NONE,
                                                      model->cancellable,
-                                                     NULL);
+                                                     NULL); /* we don't mind if directory monitoring isn't supported, so the GError is NULL here */
       if (model->dir_monitor)
         g_signal_connect (model->dir_monitor,
                           "changed",
@@ -1111,7 +1269,7 @@ gtk_file_system_model_set_n_columns (GtkFileSystemModel *model,
 
   model->sort_list = _gtk_tree_data_list_header_new (n_columns, model->column_types);
 
-  model->files = g_array_new (FALSE, FALSE, model->node_size);
+  model->files = g_array_sized_new (FALSE, FALSE, model->node_size, FILES_PER_QUERY);
   /* add editable node at start */
   g_array_set_size (model->files, 1);
   memset (get_node (model, 0), 0, model->node_size);
@@ -1122,16 +1280,14 @@ gtk_file_system_model_set_directory (GtkFileSystemModel *model,
                                      GFile *             dir,
                                     const gchar *       attributes)
 {
-  g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
-  g_return_if_fail (dir == NULL || G_IS_FILE (dir));
+  g_assert (G_IS_FILE (dir));
 
   model->dir = g_object_ref (dir);
   model->attributes = g_strdup (attributes);
 
-  g_object_ref (model);
   g_file_enumerate_children_async (model->dir,
                                    attributes,
-                                   0,
+                                   G_FILE_QUERY_INFO_NONE,
                                    IO_PRIORITY,
                                    model->cancellable,
                                    gtk_file_system_model_got_enumerator,
@@ -1164,7 +1320,8 @@ _gtk_file_system_model_new_valist (GtkFileSystemModelGetValue get_func,
  * @...: @n_columns #GType types for the columns
  *
  * Creates a new #GtkFileSystemModel object. You need to add files
- * to the list using _gtk_file_system_model_add_file().
+ * to the list using _gtk_file_system_model_add_and_query_file()
+ * or _gtk_file_system_model_update_file().
  *
  * Return value: the newly created #GtkFileSystemModel
  **/
@@ -1190,7 +1347,11 @@ _gtk_file_system_model_new (GtkFileSystemModelGetValue get_func,
 /**
  * _gtk_file_system_model_new_for_directory:
  * @directory: the directory to show.
- * @attributes: attributes to immediately load or %NULL for all
+ * @attributes: (allow-none): attributes to immediately load or %NULL for all
+ * @get_func: function that the model should call to query data about a file
+ * @get_data: user data to pass to the @get_func
+ * @n_columns: number of columns
+ * @...: @n_columns #GType types for the columns
  *
  * Creates a new #GtkFileSystemModel object. The #GtkFileSystemModel
  * object wraps the given @directory as a #GtkTreeModel.
@@ -1318,6 +1479,30 @@ _gtk_file_system_model_set_show_files (GtkFileSystemModel *model,
     }
 }
 
+/**
+ * _gtk_file_system_model_set_filter_folders:
+ * @model: a #GtkFileSystemModel
+ * @filter_folders: whether the filter applies to folders
+ * 
+ * Sets whether the filter set by _gtk_file_system_model_set_filter()
+ * applies to folders. By default, it does not and folders are always
+ * visible.
+ **/
+void
+_gtk_file_system_model_set_filter_folders (GtkFileSystemModel *model,
+                                          gboolean            filter_folders)
+{
+  g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
+
+  filter_folders = filter_folders != FALSE;
+
+  if (filter_folders != model->filter_folders)
+    {
+      model->filter_folders = filter_folders;
+      gtk_file_system_model_refilter_all (model);
+    }
+}
+
 /**
  * _gtk_file_system_model_get_cancellable:
  * @model: the model
@@ -1338,7 +1523,7 @@ _gtk_file_system_model_get_cancellable (GtkFileSystemModel *model)
 }
 
 /**
- * _gtk_file_system_model_get_is_visible:
+ * _gtk_file_system_model_iter_is_visible:
  * @model: the model
  * @iter: a valid iterator
  *
@@ -1351,8 +1536,8 @@ _gtk_file_system_model_get_cancellable (GtkFileSystemModel *model)
  * Returns: %TRUE if the iterator is visible
  **/
 gboolean
-_gtk_file_system_model_get_is_visible (GtkFileSystemModel *model,
-                                      GtkTreeIter        *iter)
+_gtk_file_system_model_iter_is_visible (GtkFileSystemModel *model,
+                                       GtkTreeIter        *iter)
 {
   FileModelNode *node;
 
@@ -1475,7 +1660,15 @@ node_get_for_file (GtkFileSystemModel *model,
   if (i != 0)
     return i;
 
-  /* node 0 is the editable row and has no associated file or entry in the table */
+  /* Node 0 is the editable row and has no associated file or entry in the table, so we start counting from 1.
+   *
+   * The invariant here is that the files in model->files[n] for n < g_hash_table_size (model->file_lookup)
+   * are already added to the hash table. The table can get cleared when we re-sort; this loop merely rebuilds
+   * our (file -> index) mapping on demand.
+   *
+   * If we exit the loop, the next pending batch of mappings will be resolved when this function gets called again
+   * with another file that is not yet in the mapping.
+   */
   for (i = g_hash_table_size (model->file_lookup) + 1; i < model->files->len; i++)
     {
       FileModelNode *node = get_node (model, i);
@@ -1497,7 +1690,7 @@ node_get_for_file (GtkFileSystemModel *model,
  * Initializes @iter to point to the row used for @file, if @file is part 
  * of the model. Note that upon successful return, @iter may point to an 
  * invisible row in the @model. Use 
- * _gtk_file_system_model_get_is_visible() to make sure it is visible to
+ * _gtk_file_system_model_iter_is_visible() to make sure it is visible to
  * the tree view.
  *
  * Returns: %TRUE if file is part of the model and @iter was initialized
@@ -1523,7 +1716,7 @@ _gtk_file_system_model_get_iter_for_file (GtkFileSystemModel *model,
 }
 
 /**
- * _gtk_file_system_model_add_file:
+ * add_file:
  * @model: the model
  * @file: the file to add
  * @info: the information to associate with the file
@@ -1531,10 +1724,10 @@ _gtk_file_system_model_get_iter_for_file (GtkFileSystemModel *model,
  * Adds the given @file with its associated @info to the @model. 
  * If the model is frozen, the file will only show up after it is thawn.
  **/
-void
-_gtk_file_system_model_add_file (GtkFileSystemModel *model,
-                                 GFile              *file,
-                                 GFileInfo          *info)
+static void
+add_file (GtkFileSystemModel *model,
+         GFile              *file,
+         GFileInfo          *info)
 {
   FileModelNode *node;
   
@@ -1558,7 +1751,7 @@ _gtk_file_system_model_add_file (GtkFileSystemModel *model,
 }
 
 /**
- * _gtk_file_system_model_remove_file:
+ * remove_file:
  * @model: the model
  * @file: file to remove from the model. The file must have been 
  *        added to the model previously
@@ -1566,9 +1759,9 @@ _gtk_file_system_model_add_file (GtkFileSystemModel *model,
  * Removes the given file from the model. If the file is not part of 
  * @model, this function does nothing.
  **/
-void
-_gtk_file_system_model_remove_file (GtkFileSystemModel *model,
-                                   GFile              *file)
+static void
+remove_file (GtkFileSystemModel *model,
+            GFile              *file)
 {
   FileModelNode *node;
   guint id;
@@ -1582,10 +1775,16 @@ _gtk_file_system_model_remove_file (GtkFileSystemModel *model,
 
   node = get_node (model, id);
   node_set_visible (model, id, FALSE);
+
+  g_hash_table_remove (model->file_lookup, file);
   g_object_unref (node->file);
+
   if (node->info)
     g_object_unref (node->info);
+
   g_array_remove_index (model->files, id);
+  g_hash_table_remove_all (model->file_lookup);
+  /* We don't need to resort, as removing a row doesn't change the sorting order */
 }
 
 /**
@@ -1607,6 +1806,7 @@ _gtk_file_system_model_update_file (GtkFileSystemModel *model,
 {
   FileModelNode *node;
   guint i, id;
+  GFileInfo *old_info;
 
   g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
   g_return_if_fail (G_IS_FILE (file));
@@ -1614,12 +1814,18 @@ _gtk_file_system_model_update_file (GtkFileSystemModel *model,
 
   id = node_get_for_file (model, file);
   if (id == 0)
-    _gtk_file_system_model_add_file (model, file, info);
+    {
+      add_file (model, file, info);
+      id = node_get_for_file (model, file);
+    }
 
   node = get_node (model, id);
-  if (node->info)
-    g_object_unref (node->info);
+
+  old_info = node->info;
   node->info = g_object_ref (info);
+  if (old_info)
+    g_object_unref (old_info);
+
   for (i = 0; i < model->n_columns; i++)
     {
       if (G_VALUE_TYPE (&node->values[i]))
@@ -1627,15 +1833,7 @@ _gtk_file_system_model_update_file (GtkFileSystemModel *model,
     }
 
   if (node->visible)
-    {
-      GtkTreePath *path;
-      GtkTreeIter iter;
-      
-      path = gtk_tree_path_new_from_node (model, id);
-      ITER_INIT_FROM_INDEX (model, &iter, id);
-      gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
-      gtk_tree_path_free (path);
-    }
+    emit_row_changed_for_node (model, id);
 
   if (requires_resort)
     gtk_file_system_model_sort_node (model, id);
@@ -1644,24 +1842,30 @@ _gtk_file_system_model_update_file (GtkFileSystemModel *model,
 /**
  * _gtk_file_system_model_set_filter:
  * @mode: a #GtkFileSystemModel
- * @filter: %NULL or filter to use
+ * @filter: (allow-none): %NULL or filter to use
  * 
  * Sets a filter to be used for deciding if a row should be visible or not.
- * Directories are always visible.
+ * Whether this filter applies to directories can be toggled with
+ * _gtk_file_system_model_set_filter_folders().
  **/
 void
 _gtk_file_system_model_set_filter (GtkFileSystemModel      *model,
                                   GtkFileFilter *          filter)
 {
+  GtkFileFilter *old_filter;
+
   g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
   g_return_if_fail (filter == NULL || GTK_IS_FILE_FILTER (filter));
   
   if (filter)
     g_object_ref (filter);
-  if (model->filter)
-    g_object_unref (model->filter);
+
+  old_filter = model->filter;
   model->filter = filter;
 
+  if (old_filter)
+    g_object_unref (old_filter);
+
   gtk_file_system_model_refilter_all (model);
 }
 
@@ -1762,7 +1966,7 @@ _gtk_file_system_model_thaw_updates (GtkFileSystemModel *model)
 }
 
 /**
- * _gtk_file_system_model_clear_cached_values:
+ * _gtk_file_system_model_clear_cache:
  * @model: a #GtkFileSystemModel
  * @column: the column to clear or -1 for all columns
  *
@@ -1783,7 +1987,7 @@ _gtk_file_system_model_clear_cache (GtkFileSystemModel *model,
   g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
   g_return_if_fail (column >= -1 && (guint) column < model->n_columns);
 
-  if (column)
+  if (column > -1)
     {
       start = column;
       end = column + 1;
@@ -1808,15 +2012,7 @@ _gtk_file_system_model_clear_cache (GtkFileSystemModel *model,
         }
 
       if (changed && node->visible)
-        {
-          GtkTreePath *path;
-          GtkTreeIter iter;
-          
-          path = gtk_tree_path_new_from_node (model, i);
-          ITER_INIT_FROM_INDEX (model, &iter, i);
-          gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
-          gtk_tree_path_free (path);
-        }
+       emit_row_changed_for_node (model, i);
     }
 
   /* FIXME: resort? */
@@ -1829,8 +2025,8 @@ _gtk_file_system_model_clear_cache (GtkFileSystemModel *model,
  * @attributes: attributes to query before adding the file
  *
  * This is a conenience function that calls g_file_query_info_async() on 
- * the given file, and when successful, adds it to the model with
- * _gtk_file_system_model_add_file(). Upon failure, the @file is discarded.
+ * the given file, and when successful, adds it to the model.
+ * Upon failure, the @file is discarded.
  **/
 void
 _gtk_file_system_model_add_and_query_file (GtkFileSystemModel *model,
@@ -1843,7 +2039,7 @@ _gtk_file_system_model_add_and_query_file (GtkFileSystemModel *model,
 
   g_file_query_info_async (file,
                            attributes,
-                           0,
+                           G_FILE_QUERY_INFO_NONE,
                            IO_PRIORITY,
                            model->cancellable,
                            gtk_file_system_model_query_done,