1 /* GTK - The GIMP Toolkit
2  * gtkfilesystemmodel.c: GtkTreeModel wrapping a GtkFileSystem
3  * Copyright (C) 2003, Red Hat, Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "config.h"
20 
21 #include "gtkfilesystemmodel.h"
22 
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #include "gtkfilesystem.h"
27 #include "gtkintl.h"
28 #include "gtkmarshalers.h"
29 #include "gtktreedatalist.h"
30 #include "gtktreednd.h"
31 #include "gtktreemodel.h"
32 
33 /*** Structure: how GtkFileSystemModel works
34  *
35  * This is a custom GtkTreeModel used to hold a collection of files for GtkFileChooser.  There are two use cases:
36  *
37  *   1. The model populates itself from a folder, using the GIO file enumerator API.  This happens if you use
38  *      _gtk_file_system_model_new_for_directory().  This is the normal usage for showing the contents of a folder.
39  *
40  *   2. The caller populates the model by hand, with files not necessarily in the same folder.  This happens
41  *      if you use _gtk_file_system_model_new() and then _gtk_file_system_model_add_and_query_file().  This is
42  *      the special kind of usage for “search” and “recent-files”, where the file chooser gives the model the
43  *      files to be displayed.
44  *
45  * Internal data structure
46  * -----------------------
47  *
48  * Each file is kept in a FileModelNode structure.  Each FileModelNode holds a GFile* and other data.  All the
49  * node structures have the same size, determined at runtime, depending on the number of columns that were passed
50  * to _gtk_file_system_model_new() or _gtk_file_system_model_new_for_directory() (that is, the size of a node is
51  * not sizeof (FileModelNode), but rather model->node_size).  The last field in the FileModelNode structure,
52  * node->values[], is an array of GValue, used to hold the data for those columns.
53  *
54  * The model stores an array of FileModelNode structures in model->files.  This is a GArray where each element is
55  * model->node_size bytes in size (the model computes that node size when initializing itself).  There are
56  * convenience macros, get_node() and node_index(), to access that array based on an array index or a pointer to
57  * a node inside the array.
58  *
59  * The model accesses files through two of its fields:
60  *
61  *   model->files - GArray of FileModelNode structures.
62  *
63  *   model->file_lookup - hash table that maps a GFile* to an index inside the model->files array.
64  *
65  * The model->file_lookup hash table is populated lazily.  It is both accessed and populated with the
66  * node_get_for_file() function.  The invariant is that the files in model->files[n] for n < g_hash_table_size
67  * (model->file_lookup) are already added to the hash table. The hash table will get cleared when we re-sort the
68  * files, as the array will be in a different order and the indexes need to be rebuilt.
69  *
70  * Each FileModelNode has a node->visible field, which indicates whether the node is visible in the GtkTreeView.
71  * A node may be invisible if, for example, it corresponds to a hidden file and the file chooser is not showing
72  * hidden files.  Also, a file filter may be explicitly set onto the model, for example, to only show files that
73  * match “*.jpg”.  In this case, node->filtered_out says whether the node failed the filter.  The ultimate
74  * decision on whether a node is visible or not in the treeview is distilled into the node->visible field.
75  * The reason for having a separate node->filtered_out field is so that the file chooser can query whether
76  * a (filtered-out) folder should be made sensitive in the GUI.
77  *
78  * Visible rows vs. possibly-invisible nodes
79  * -----------------------------------------
80  *
81  * Since not all nodes in the model->files array may be visible, we need a way to map visible row indexes from
82  * the treeview to array indexes in our array of files.  And thus we introduce a bit of terminology:
83  *
84  *   index - An index in the model->files array.  All variables/fields that represent indexes are either called
85  *   “index” or “i_*”, or simply “i” for things like loop counters.
86  *
87  *   row - An index in the GtkTreeView, i.e. the index of a row within the outward-facing API of the
88  *   GtkFileSystemModel.  However, note that our rows are 1-based, not 0-based, for the reason explained in the
89  *   following paragraph.  Variables/fields that represent visible rows are called “row”, or “r_*”, or simply
90  *   “r”.
91  *
92  * Each FileModelNode has a node->row field which is the number of visible rows in the treeview, *before and
93  * including* that node.  This means that node->row is 1-based, instead of 0-based --- this makes some code
94  * simpler, believe it or not :)  This also means that when the calling GtkTreeView gives us a GtkTreePath, we
95  * turn the 0-based treepath into a 1-based row for our purposes.  If a node is not visible, it will have the
96  * same row number as its closest preceding visible node.
97  *
98  * We try to compute the node->row fields lazily.  A node is said to be “valid” if its node->row is accurate.
99  * For this, the model keeps a model->n_nodes_valid field which is the count of valid nodes starting from the
100  * beginning of the model->files array.  When a node changes its information, or when a node gets deleted, that
101  * node and the following ones get invalidated by simply setting model->n_nodes_valid to the array index of the
102  * node.  If the model happens to need a node’s row number and that node is in the model->files array after
103  * model->n_nodes_valid, then the nodes get re-validated up to the sought node.  See node_validate_rows() for
104  * this logic.
105  *
106  * You never access a node->row directly.  Instead, call node_get_tree_row().  That function will validate the nodes
107  * up to the sought one if the node is not valid yet, and it will return a proper 0-based row.
108  *
109  * Sorting
110  * -------
111  *
112  * The model implements the GtkTreeSortable interface.  To avoid re-sorting
113  * every time a node gets added (which would lead to O(n^2) performance during
114  * the initial population of the model), the model can freeze itself (with
115  * freeze_updates()) during the intial population process.  When the model is
116  * frozen, sorting will not happen.  The model will sort itself when the freeze
117  * count goes back to zero, via corresponding calls to thaw_updates().
118  */
119 
120 /*** DEFINES ***/
121 
122 /* priority used for all async callbacks in the main loop
123  * This should be higher than redraw priorities so multiple callbacks
124  * firing can be handled without intermediate redraws */
125 #define IO_PRIORITY G_PRIORITY_DEFAULT
126 
127 /* random number that everyone else seems to use, too */
128 #define FILES_PER_QUERY 100
129 
130 typedef struct _FileModelNode           FileModelNode;
131 typedef struct _GtkFileSystemModelClass GtkFileSystemModelClass;
132 
133 struct _FileModelNode
134 {
135   GFile *               file;           /* file represented by this node or NULL for editable */
136   GFileInfo *           info;           /* info for this file or NULL if unknown */
137 
138   guint                 row;            /* if valid (see model->n_valid_indexes), visible nodes before and including
139 					 * this one - see the "Structure" comment above.
140 					 */
141 
142   guint                 visible :1;     /* if the file is currently visible */
143   guint                 filtered_out :1;/* if the file is currently filtered out (i.e. it didn't pass the filters) */
144   guint                 frozen_add :1;  /* true if the model was frozen and the entry has not been added yet */
145 
146   GValue                values[1];      /* actually n_columns values */
147 };
148 
149 struct _GtkFileSystemModel
150 {
151   GObject               parent_instance;
152 
153   GFile *               dir;            /* directory that's displayed */
154   guint                 dir_thaw_source;/* GSource id for unfreezing the model */
155   char *                attributes;     /* attributes the file info must contain, or NULL for all attributes */
156   GFileMonitor *        dir_monitor;    /* directory that is monitored, or NULL if monitoring was not supported */
157 
158   GCancellable *        cancellable;    /* cancellable in use for all operations - cancelled on dispose */
159   GArray *              files;          /* array of FileModelNode containing all our files */
160   gsize                 node_size;	/* Size of a FileModelNode structure once its ->values field has n_columns */
161   guint                 n_nodes_valid;  /* count of valid nodes (i.e. those whose node->row is accurate) */
162   GHashTable *          file_lookup;    /* mapping of GFile => array index in model->files
163 					 * This hash table doesn't always have the same number of entries as the files array;
164 					 * it can get cleared completely when we resort.
165 					 * The hash table gets re-populated in node_get_for_file() if this mismatch is
166 					 * detected.
167 					 */
168 
169   guint                 n_columns;      /* number of columns */
170   GType *               column_types;   /* types of each column */
171   GtkFileSystemModelGetValue get_func;  /* function to call to fill in values in columns */
172   gpointer              get_data;       /* data to pass to get_func */
173 
174   GtkFileFilter *       filter;         /* filter to use for deciding which nodes are visible */
175 
176   int                   sort_column_id; /* current sorting column */
177   GtkSortType           sort_order;     /* current sorting order */
178   GList *               sort_list;      /* list of sorting functions */
179   GtkTreeIterCompareFunc default_sort_func; /* default sort function */
180   gpointer              default_sort_data; /* data to pass to default sort func */
181   GDestroyNotify        default_sort_destroy; /* function to call to destroy default_sort_data */
182 
183   guint                 frozen;         /* number of times we're frozen */
184 
185   gboolean              filter_on_thaw :1;/* set when filtering needs to happen upon thawing */
186   gboolean              sort_on_thaw :1;/* set when sorting needs to happen upon thawing */
187 
188   guint                 show_hidden :1; /* whether to show hidden files */
189   guint                 show_folders :1;/* whether to show folders */
190   guint                 show_files :1;  /* whether to show files */
191   guint                 filter_folders :1;/* whether filter applies to folders */
192 };
193 
194 #define GTK_FILE_SYSTEM_MODEL_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILE_SYSTEM_MODEL, GtkFileSystemModelClass))
195 #define GTK_IS_FILE_SYSTEM_MODEL_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FILE_SYSTEM_MODEL))
196 #define GTK_FILE_SYSTEM_MODEL_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FILE_SYSTEM_MODEL, GtkFileSystemModelClass))
197 
198 struct _GtkFileSystemModelClass
199 {
200   GObjectClass parent_class;
201 
202   /* Signals */
203 
204   void (*finished_loading) (GtkFileSystemModel *model, GError *error);
205 };
206 
207 static void freeze_updates (GtkFileSystemModel *model);
208 static void thaw_updates (GtkFileSystemModel *model);
209 
210 static guint node_get_for_file (GtkFileSystemModel *model,
211 				GFile              *file);
212 
213 static void add_file (GtkFileSystemModel *model,
214 		      GFile              *file,
215 		      GFileInfo          *info);
216 static void remove_file (GtkFileSystemModel *model,
217 			 GFile              *file);
218 
219 /* iter setup:
220  * @user_data: the model
221  * @user_data2: GUINT_TO_POINTER of array index of current entry
222  *
223  * All other fields are unused. Note that the array index does not corrspond
224  * 1:1 with the path index as entries might not be visible.
225  */
226 #define ITER_INDEX(iter) GPOINTER_TO_UINT((iter)->user_data2)
227 #define ITER_IS_VALID(model, iter) ((model) == (iter)->user_data)
228 #define ITER_INIT_FROM_INDEX(model, _iter, _index) G_STMT_START {\
229   g_assert (_index < (model)->files->len); \
230   (_iter)->user_data = (model); \
231   (_iter)->user_data2 = GUINT_TO_POINTER (_index); \
232 }G_STMT_END
233 
234 /*** FileModelNode ***/
235 
236 /* Get a FileModelNode structure given an index in the model->files array of nodes */
237 #define get_node(_model, _index) ((FileModelNode *) ((_model)->files->data + (_index) * (_model)->node_size))
238 
239 /* Get an index within the model->files array of nodes, given a FileModelNode* */
240 #define node_index(_model, _node) (((gchar *) (_node) - (_model)->files->data) / (_model)->node_size)
241 
242 /* @up_to_index: smallest model->files array index that will be valid after this call
243  * @up_to_row: smallest node->row that will be valid after this call
244  *
245  * If you want to validate up to an index or up to a row, specify the index or
246  * the row you want and specify G_MAXUINT for the other argument.  Pass
247  * G_MAXUINT for both arguments for “validate everything”.
248  */
249 static void
node_validate_rows(GtkFileSystemModel * model,guint up_to_index,guint up_to_row)250 node_validate_rows (GtkFileSystemModel *model, guint up_to_index, guint up_to_row)
251 {
252   guint i, row;
253 
254   if (model->files->len == 0)
255     return;
256 
257   up_to_index = MIN (up_to_index, model->files->len - 1);
258 
259   i = model->n_nodes_valid;
260   if (i != 0)
261     row = get_node (model, i - 1)->row;
262   else
263     row = 0;
264 
265   while (i <= up_to_index && row <= up_to_row)
266     {
267       FileModelNode *node = get_node (model, i);
268       if (node->visible)
269         row++;
270       node->row = row;
271       i++;
272     }
273   model->n_nodes_valid = i;
274 }
275 
276 static guint
node_get_tree_row(GtkFileSystemModel * model,guint index)277 node_get_tree_row (GtkFileSystemModel *model, guint index)
278 {
279   if (model->n_nodes_valid <= index)
280     node_validate_rows (model, index, G_MAXUINT);
281 
282   return get_node (model, index)->row - 1;
283 }
284 
285 static void
node_invalidate_index(GtkFileSystemModel * model,guint id)286 node_invalidate_index (GtkFileSystemModel *model, guint id)
287 {
288   model->n_nodes_valid = MIN (model->n_nodes_valid, id);
289 }
290 
291 static GtkTreePath *
tree_path_new_from_node(GtkFileSystemModel * model,guint id)292 tree_path_new_from_node (GtkFileSystemModel *model, guint id)
293 {
294   guint r = node_get_tree_row (model, id);
295 
296   g_assert (r < model->files->len);
297 
298   return gtk_tree_path_new_from_indices (r, -1);
299 }
300 
301 static void
emit_row_inserted_for_node(GtkFileSystemModel * model,guint id)302 emit_row_inserted_for_node (GtkFileSystemModel *model, guint id)
303 {
304   GtkTreePath *path;
305   GtkTreeIter iter;
306 
307   path = tree_path_new_from_node (model, id);
308   ITER_INIT_FROM_INDEX (model, &iter, id);
309   gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter);
310   gtk_tree_path_free (path);
311 }
312 
313 static void
emit_row_changed_for_node(GtkFileSystemModel * model,guint id)314 emit_row_changed_for_node (GtkFileSystemModel *model, guint id)
315 {
316   GtkTreePath *path;
317   GtkTreeIter iter;
318 
319   path = tree_path_new_from_node (model, id);
320   ITER_INIT_FROM_INDEX (model, &iter, id);
321   gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
322   gtk_tree_path_free (path);
323 }
324 
325 static void
emit_row_deleted_for_row(GtkFileSystemModel * model,guint row)326 emit_row_deleted_for_row (GtkFileSystemModel *model, guint row)
327 {
328   GtkTreePath *path;
329 
330   path = gtk_tree_path_new_from_indices (row, -1);
331   gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
332   gtk_tree_path_free (path);
333 }
334 
335 static void
node_set_visible_and_filtered_out(GtkFileSystemModel * model,guint id,gboolean visible,gboolean filtered_out)336 node_set_visible_and_filtered_out (GtkFileSystemModel *model, guint id, gboolean visible, gboolean filtered_out)
337 {
338   FileModelNode *node = get_node (model, id);
339 
340   /* Filteredness */
341 
342   if (node->filtered_out != filtered_out)
343     {
344       node->filtered_out = filtered_out;
345       if (node->visible && visible)
346         emit_row_changed_for_node (model, id);
347     }
348 
349   /* Visibility */
350 
351   if (node->visible == visible ||
352       node->frozen_add)
353     return;
354 
355   if (visible)
356     {
357       node->visible = TRUE;
358       node_invalidate_index (model, id);
359       emit_row_inserted_for_node (model, id);
360     }
361   else
362     {
363       guint row;
364 
365       row = node_get_tree_row (model, id);
366       g_assert (row < model->files->len);
367 
368       node->visible = FALSE;
369       node_invalidate_index (model, id);
370       emit_row_deleted_for_row (model, row);
371     }
372 }
373 
374 static gboolean
node_should_be_filtered_out(GtkFileSystemModel * model,guint id)375 node_should_be_filtered_out (GtkFileSystemModel *model, guint id)
376 {
377   FileModelNode *node = get_node (model, id);
378   GtkFileFilterInfo filter_info = { 0, };
379   GtkFileFilterFlags required;
380   gboolean result;
381   char *mime_type = NULL;
382   char *filename = NULL;
383   char *uri = NULL;
384 
385   if (node->info == NULL)
386     return TRUE;
387 
388   if (model->filter == NULL)
389     return FALSE;
390 
391   /* fill info */
392   required = gtk_file_filter_get_needed (model->filter);
393 
394   filter_info.contains = GTK_FILE_FILTER_DISPLAY_NAME;
395   filter_info.display_name = g_file_info_get_display_name (node->info);
396 
397   if (required & GTK_FILE_FILTER_MIME_TYPE)
398     {
399       const char *s = g_file_info_get_content_type (node->info);
400 
401       if (!s)
402         s = g_file_info_get_attribute_string (node->info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE);
403 
404       if (s)
405 	{
406 	  mime_type = g_content_type_get_mime_type (s);
407 	  if (mime_type)
408 	    {
409 	      filter_info.mime_type = mime_type;
410 	      filter_info.contains |= GTK_FILE_FILTER_MIME_TYPE;
411 	    }
412 	}
413     }
414 
415   if (required & GTK_FILE_FILTER_FILENAME)
416     {
417       filename = g_file_get_path (node->file);
418       if (filename)
419         {
420           filter_info.filename = filename;
421 	  filter_info.contains |= GTK_FILE_FILTER_FILENAME;
422         }
423     }
424 
425   if (required & GTK_FILE_FILTER_URI)
426     {
427       uri = g_file_get_uri (node->file);
428       if (uri)
429         {
430           filter_info.uri = uri;
431 	  filter_info.contains |= GTK_FILE_FILTER_URI;
432         }
433     }
434 
435   result = !gtk_file_filter_filter (model->filter, &filter_info);
436 
437   g_free (mime_type);
438   g_free (filename);
439   g_free (uri);
440 
441   return result;
442 }
443 
444 static gboolean
node_should_be_visible(GtkFileSystemModel * model,guint id,gboolean filtered_out)445 node_should_be_visible (GtkFileSystemModel *model, guint id, gboolean filtered_out)
446 {
447   FileModelNode *node = get_node (model, id);
448   gboolean result;
449 
450   if (node->info == NULL)
451     return FALSE;
452 
453   if (!model->show_hidden &&
454       (g_file_info_get_is_hidden (node->info) || g_file_info_get_is_backup (node->info)))
455     return FALSE;
456 
457   if (_gtk_file_info_consider_as_directory (node->info))
458     {
459       if (!model->show_folders)
460         return FALSE;
461 
462       if (!model->filter_folders)
463         return TRUE;
464     }
465   else
466     {
467       if (!model->show_files)
468         return FALSE;
469     }
470 
471   result = !filtered_out;
472 
473   return result;
474 }
475 
476 static void
node_compute_visibility_and_filters(GtkFileSystemModel * model,guint id)477 node_compute_visibility_and_filters (GtkFileSystemModel *model, guint id)
478 {
479   gboolean filtered_out;
480   gboolean visible;
481 
482   filtered_out = node_should_be_filtered_out (model, id);
483   visible = node_should_be_visible (model, id, filtered_out);
484 
485   node_set_visible_and_filtered_out (model, id, visible, filtered_out);
486 }
487 
488 /*** GtkTreeModel ***/
489 
490 static GtkTreeModelFlags
gtk_file_system_model_get_flags(GtkTreeModel * tree_model)491 gtk_file_system_model_get_flags (GtkTreeModel *tree_model)
492 {
493   /* GTK_TREE_MODEL_ITERS_PERSIST doesn't work with arrays :( */
494   return GTK_TREE_MODEL_LIST_ONLY;
495 }
496 
497 static gint
gtk_file_system_model_get_n_columns(GtkTreeModel * tree_model)498 gtk_file_system_model_get_n_columns (GtkTreeModel *tree_model)
499 {
500   GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (tree_model);
501 
502   return model->n_columns;
503 }
504 
505 static GType
gtk_file_system_model_get_column_type(GtkTreeModel * tree_model,gint i)506 gtk_file_system_model_get_column_type (GtkTreeModel *tree_model,
507 				       gint          i)
508 {
509   GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (tree_model);
510 
511   g_return_val_if_fail (i >= 0 && (guint) i < model->n_columns, G_TYPE_NONE);
512 
513   return model->column_types[i];
514 }
515 
516 static int
compare_indices(gconstpointer key,gconstpointer _node)517 compare_indices (gconstpointer key, gconstpointer _node)
518 {
519   const FileModelNode *node = _node;
520 
521   return GPOINTER_TO_UINT (key) - node->row;
522 }
523 
524 static gboolean
gtk_file_system_model_iter_nth_child(GtkTreeModel * tree_model,GtkTreeIter * iter,GtkTreeIter * parent,gint n)525 gtk_file_system_model_iter_nth_child (GtkTreeModel *tree_model,
526 				      GtkTreeIter  *iter,
527 				      GtkTreeIter  *parent,
528 				      gint          n)
529 {
530   GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (tree_model);
531   char *node;
532   guint id;
533   guint row_to_find;
534 
535   g_return_val_if_fail (n >= 0, FALSE);
536 
537   if (parent != NULL)
538     return FALSE;
539 
540   row_to_find = n + 1; /* plus one as our node->row numbers are 1-based; see the "Structure" comment at the beginning */
541 
542   if (model->n_nodes_valid > 0 &&
543       get_node (model, model->n_nodes_valid - 1)->row >= row_to_find)
544     {
545       /* Fast path - the nodes are valid up to the sought one.
546        *
547        * First, find a node with the sought row number...*/
548 
549       node = bsearch (GUINT_TO_POINTER (row_to_find),
550                       model->files->data,
551                       model->n_nodes_valid,
552                       model->node_size,
553                       compare_indices);
554       if (node == NULL)
555         return FALSE;
556 
557       /* ... Second, back up until we find the first visible node with that row number */
558 
559       id = node_index (model, node);
560       while (!get_node (model, id)->visible)
561         id--;
562 
563       g_assert (get_node (model, id)->row == row_to_find);
564     }
565   else
566     {
567       /* Slow path - the nodes need to be validated up to the sought one */
568 
569       node_validate_rows (model, G_MAXUINT, n); /* note that this is really "n", not row_to_find - see node_validate_rows() */
570       id = model->n_nodes_valid - 1;
571       if (model->n_nodes_valid == 0 || get_node (model, id)->row != row_to_find)
572         return FALSE;
573     }
574 
575   ITER_INIT_FROM_INDEX (model, iter, id);
576   return TRUE;
577 }
578 
579 static gboolean
gtk_file_system_model_get_iter(GtkTreeModel * tree_model,GtkTreeIter * iter,GtkTreePath * path)580 gtk_file_system_model_get_iter (GtkTreeModel *tree_model,
581 				GtkTreeIter  *iter,
582 				GtkTreePath  *path)
583 {
584   g_return_val_if_fail (gtk_tree_path_get_depth (path) > 0, FALSE);
585 
586   if (gtk_tree_path_get_depth (path) > 1)
587     return FALSE;
588 
589   return gtk_file_system_model_iter_nth_child (tree_model,
590                                                iter,
591                                                NULL,
592                                                gtk_tree_path_get_indices (path)[0]);
593 }
594 
595 static GtkTreePath *
gtk_file_system_model_get_path(GtkTreeModel * tree_model,GtkTreeIter * iter)596 gtk_file_system_model_get_path (GtkTreeModel *tree_model,
597 				GtkTreeIter  *iter)
598 {
599   GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (tree_model);
600 
601   g_return_val_if_fail (ITER_IS_VALID (model, iter), NULL);
602 
603   return tree_path_new_from_node (model, ITER_INDEX (iter));
604 }
605 
606 static void
gtk_file_system_model_get_value(GtkTreeModel * tree_model,GtkTreeIter * iter,gint column,GValue * value)607 gtk_file_system_model_get_value (GtkTreeModel *tree_model,
608 				 GtkTreeIter  *iter,
609 				 gint          column,
610 				 GValue       *value)
611 {
612   GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (tree_model);
613   const GValue *original;
614 
615   g_return_if_fail ((guint) column < model->n_columns);
616   g_return_if_fail (ITER_IS_VALID (model, iter));
617 
618   original = _gtk_file_system_model_get_value (model, iter, column);
619   if (original)
620     {
621       g_value_init (value, G_VALUE_TYPE (original));
622       g_value_copy (original, value);
623     }
624   else
625     g_value_init (value, model->column_types[column]);
626 }
627 
628 static gboolean
gtk_file_system_model_iter_next(GtkTreeModel * tree_model,GtkTreeIter * iter)629 gtk_file_system_model_iter_next (GtkTreeModel *tree_model,
630 				 GtkTreeIter  *iter)
631 {
632   GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (tree_model);
633   guint i;
634 
635   g_return_val_if_fail (ITER_IS_VALID (model, iter), FALSE);
636 
637   for (i = ITER_INDEX (iter) + 1; i < model->files->len; i++)
638     {
639       FileModelNode *node = get_node (model, i);
640 
641       if (node->visible)
642         {
643           ITER_INIT_FROM_INDEX (model, iter, i);
644           return TRUE;
645         }
646     }
647 
648   return FALSE;
649 }
650 
651 static gboolean
gtk_file_system_model_iter_children(GtkTreeModel * tree_model,GtkTreeIter * iter,GtkTreeIter * parent)652 gtk_file_system_model_iter_children (GtkTreeModel *tree_model,
653 				     GtkTreeIter  *iter,
654 				     GtkTreeIter  *parent)
655 {
656   return FALSE;
657 }
658 
659 static gboolean
gtk_file_system_model_iter_has_child(GtkTreeModel * tree_model,GtkTreeIter * iter)660 gtk_file_system_model_iter_has_child (GtkTreeModel *tree_model,
661 				      GtkTreeIter  *iter)
662 {
663   return FALSE;
664 }
665 
666 static gint
gtk_file_system_model_iter_n_children(GtkTreeModel * tree_model,GtkTreeIter * iter)667 gtk_file_system_model_iter_n_children (GtkTreeModel *tree_model,
668 				       GtkTreeIter  *iter)
669 {
670   GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (tree_model);
671 
672   if (iter)
673     return 0;
674 
675   return node_get_tree_row (model, model->files->len - 1) + 1;
676 }
677 
678 static gboolean
gtk_file_system_model_iter_parent(GtkTreeModel * tree_model,GtkTreeIter * iter,GtkTreeIter * child)679 gtk_file_system_model_iter_parent (GtkTreeModel *tree_model,
680 				   GtkTreeIter  *iter,
681 				   GtkTreeIter  *child)
682 {
683   return FALSE;
684 }
685 
686 static void
gtk_file_system_model_ref_node(GtkTreeModel * tree_model,GtkTreeIter * iter)687 gtk_file_system_model_ref_node (GtkTreeModel *tree_model,
688 				GtkTreeIter  *iter)
689 {
690   /* nothing to do */
691 }
692 
693 static void
gtk_file_system_model_unref_node(GtkTreeModel * tree_model,GtkTreeIter * iter)694 gtk_file_system_model_unref_node (GtkTreeModel *tree_model,
695 				  GtkTreeIter  *iter)
696 {
697   /* nothing to do */
698 }
699 
700 static void
gtk_file_system_model_iface_init(GtkTreeModelIface * iface)701 gtk_file_system_model_iface_init (GtkTreeModelIface *iface)
702 {
703   iface->get_flags =       gtk_file_system_model_get_flags;
704   iface->get_n_columns =   gtk_file_system_model_get_n_columns;
705   iface->get_column_type = gtk_file_system_model_get_column_type;
706   iface->get_iter =        gtk_file_system_model_get_iter;
707   iface->get_path =        gtk_file_system_model_get_path;
708   iface->get_value =       gtk_file_system_model_get_value;
709   iface->iter_next =       gtk_file_system_model_iter_next;
710   iface->iter_children =   gtk_file_system_model_iter_children;
711   iface->iter_has_child =  gtk_file_system_model_iter_has_child;
712   iface->iter_n_children = gtk_file_system_model_iter_n_children;
713   iface->iter_nth_child =  gtk_file_system_model_iter_nth_child;
714   iface->iter_parent =     gtk_file_system_model_iter_parent;
715   iface->ref_node =        gtk_file_system_model_ref_node;
716   iface->unref_node =      gtk_file_system_model_unref_node;
717 }
718 
719 /*** GtkTreeSortable ***/
720 
721 typedef struct _SortData SortData;
722 struct _SortData {
723   GtkFileSystemModel *    model;
724   GtkTreeIterCompareFunc  func;
725   gpointer                data;
726   int                     order;        /* -1 to invert sort order or 1 to keep it */
727 };
728 
729 /* returns FALSE if no sort necessary */
730 static gboolean
sort_data_init(SortData * data,GtkFileSystemModel * model)731 sort_data_init (SortData *data, GtkFileSystemModel *model)
732 {
733   GtkTreeDataSortHeader *header;
734 
735   if (model->files->len <= 2)
736     return FALSE;
737 
738   switch (model->sort_column_id)
739     {
740     case GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID:
741       if (!model->default_sort_func)
742         return FALSE;
743       data->func = model->default_sort_func;
744       data->data = model->default_sort_data;
745       break;
746     case GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID:
747       return FALSE;
748     default:
749       header = _gtk_tree_data_list_get_header (model->sort_list, model->sort_column_id);
750       if (header == NULL)
751         return FALSE;
752       data->func = header->func;
753       data->data = header->data;
754       break;
755     }
756 
757   data->order = model->sort_order == GTK_SORT_DESCENDING ? -1 : 1;
758   data->model = model;
759   return TRUE;
760 }
761 
762 static int
compare_array_element(gconstpointer a,gconstpointer b,gpointer user_data)763 compare_array_element (gconstpointer a, gconstpointer b, gpointer user_data)
764 {
765   SortData *data = user_data;
766   GtkTreeIter itera, iterb;
767 
768   ITER_INIT_FROM_INDEX (data->model, &itera, node_index (data->model, a));
769   ITER_INIT_FROM_INDEX (data->model, &iterb, node_index (data->model, b));
770   return data->func (GTK_TREE_MODEL (data->model), &itera, &iterb, data->data) * data->order;
771 }
772 
773 static void
gtk_file_system_model_sort(GtkFileSystemModel * model)774 gtk_file_system_model_sort (GtkFileSystemModel *model)
775 {
776   SortData data;
777 
778   if (model->frozen)
779     {
780       model->sort_on_thaw = TRUE;
781       return;
782     }
783 
784   if (sort_data_init (&data, model))
785     {
786       GtkTreePath *path;
787       guint i;
788       guint r, n_visible_rows;
789 
790       node_validate_rows (model, G_MAXUINT, G_MAXUINT);
791       n_visible_rows = node_get_tree_row (model, model->files->len - 1) + 1;
792       model->n_nodes_valid = 0;
793       g_hash_table_remove_all (model->file_lookup);
794       g_qsort_with_data (get_node (model, 1), /* start at index 1; don't sort the editable row */
795                          model->files->len - 1,
796                          model->node_size,
797                          compare_array_element,
798                          &data);
799       g_assert (model->n_nodes_valid == 0);
800       g_assert (g_hash_table_size (model->file_lookup) == 0);
801       if (n_visible_rows)
802         {
803           int *new_order = g_new (int, n_visible_rows);
804 
805           r = 0;
806           for (i = 0; i < model->files->len; i++)
807             {
808               FileModelNode *node = get_node (model, i);
809               if (!node->visible)
810                 {
811                   node->row = r;
812                   continue;
813                 }
814 
815               new_order[r] = node->row - 1;
816               r++;
817               node->row = r;
818             }
819           g_assert (r == n_visible_rows);
820           path = gtk_tree_path_new ();
821           gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model),
822                                          path,
823                                          NULL,
824                                          new_order);
825           gtk_tree_path_free (path);
826           g_free (new_order);
827         }
828     }
829 
830   model->sort_on_thaw = FALSE;
831 }
832 
833 static void
gtk_file_system_model_sort_node(GtkFileSystemModel * model,guint node)834 gtk_file_system_model_sort_node (GtkFileSystemModel *model, guint node)
835 {
836   /* FIXME: improve */
837   gtk_file_system_model_sort (model);
838 }
839 
840 static gboolean
gtk_file_system_model_get_sort_column_id(GtkTreeSortable * sortable,gint * sort_column_id,GtkSortType * order)841 gtk_file_system_model_get_sort_column_id (GtkTreeSortable  *sortable,
842                                           gint             *sort_column_id,
843                                           GtkSortType      *order)
844 {
845   GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (sortable);
846 
847   if (sort_column_id)
848     *sort_column_id = model->sort_column_id;
849   if (order)
850     *order = model->sort_order;
851 
852   if (model->sort_column_id == GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID ||
853       model->sort_column_id == GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID)
854     return FALSE;
855 
856   return TRUE;
857 }
858 
859 static void
gtk_file_system_model_set_sort_column_id(GtkTreeSortable * sortable,gint sort_column_id,GtkSortType order)860 gtk_file_system_model_set_sort_column_id (GtkTreeSortable  *sortable,
861                                           gint              sort_column_id,
862                                           GtkSortType       order)
863 {
864   GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (sortable);
865 
866   if ((model->sort_column_id == sort_column_id) &&
867       (model->sort_order == order))
868     return;
869 
870   if (sort_column_id != GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID)
871     {
872       if (sort_column_id != GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID)
873 	{
874 	  GtkTreeDataSortHeader *header = NULL;
875 
876 	  header = _gtk_tree_data_list_get_header (model->sort_list,
877 						   sort_column_id);
878 
879 	  /* We want to make sure that we have a function */
880 	  g_return_if_fail (header != NULL);
881 	  g_return_if_fail (header->func != NULL);
882 	}
883       else
884 	{
885 	  g_return_if_fail (model->default_sort_func != NULL);
886 	}
887     }
888 
889 
890   model->sort_column_id = sort_column_id;
891   model->sort_order = order;
892 
893   gtk_tree_sortable_sort_column_changed (sortable);
894 
895   gtk_file_system_model_sort (model);
896 }
897 
898 static void
gtk_file_system_model_set_sort_func(GtkTreeSortable * sortable,gint sort_column_id,GtkTreeIterCompareFunc func,gpointer data,GDestroyNotify destroy)899 gtk_file_system_model_set_sort_func (GtkTreeSortable        *sortable,
900                                      gint                    sort_column_id,
901                                      GtkTreeIterCompareFunc  func,
902                                      gpointer                data,
903                                      GDestroyNotify          destroy)
904 {
905   GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (sortable);
906 
907   model->sort_list = _gtk_tree_data_list_set_header (model->sort_list,
908                                                      sort_column_id,
909                                                      func, data, destroy);
910 
911   if (model->sort_column_id == sort_column_id)
912     gtk_file_system_model_sort (model);
913 }
914 
915 static void
gtk_file_system_model_set_default_sort_func(GtkTreeSortable * sortable,GtkTreeIterCompareFunc func,gpointer data,GDestroyNotify destroy)916 gtk_file_system_model_set_default_sort_func (GtkTreeSortable        *sortable,
917                                              GtkTreeIterCompareFunc  func,
918                                              gpointer                data,
919                                              GDestroyNotify          destroy)
920 {
921   GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (sortable);
922 
923   if (model->default_sort_destroy)
924     {
925       GDestroyNotify d = model->default_sort_destroy;
926 
927       model->default_sort_destroy = NULL;
928       d (model->default_sort_data);
929     }
930 
931   model->default_sort_func = func;
932   model->default_sort_data = data;
933   model->default_sort_destroy = destroy;
934 
935   if (model->sort_column_id == GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID)
936     gtk_file_system_model_sort (model);
937 }
938 
939 static gboolean
gtk_file_system_model_has_default_sort_func(GtkTreeSortable * sortable)940 gtk_file_system_model_has_default_sort_func (GtkTreeSortable *sortable)
941 {
942   GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (sortable);
943 
944   return (model->default_sort_func != NULL);
945 }
946 
947 static void
gtk_file_system_model_sortable_init(GtkTreeSortableIface * iface)948 gtk_file_system_model_sortable_init (GtkTreeSortableIface *iface)
949 {
950   iface->get_sort_column_id = gtk_file_system_model_get_sort_column_id;
951   iface->set_sort_column_id = gtk_file_system_model_set_sort_column_id;
952   iface->set_sort_func = gtk_file_system_model_set_sort_func;
953   iface->set_default_sort_func = gtk_file_system_model_set_default_sort_func;
954   iface->has_default_sort_func = gtk_file_system_model_has_default_sort_func;
955 }
956 
957 /*** GtkTreeDragSource ***/
958 
959 static gboolean
drag_source_row_draggable(GtkTreeDragSource * drag_source,GtkTreePath * path)960 drag_source_row_draggable (GtkTreeDragSource *drag_source,
961 			   GtkTreePath       *path)
962 {
963   GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (drag_source);
964   GtkTreeIter iter;
965 
966   if (!gtk_file_system_model_get_iter (GTK_TREE_MODEL (model), &iter, path))
967     return FALSE;
968 
969   return ITER_INDEX (&iter) != 0;
970 }
971 
972 static gboolean
drag_source_drag_data_get(GtkTreeDragSource * drag_source,GtkTreePath * path,GtkSelectionData * selection_data)973 drag_source_drag_data_get (GtkTreeDragSource *drag_source,
974 			   GtkTreePath       *path,
975 			   GtkSelectionData  *selection_data)
976 {
977   GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (drag_source);
978   FileModelNode *node;
979   GtkTreeIter iter;
980   char *uris[2];
981 
982   if (!gtk_file_system_model_get_iter (GTK_TREE_MODEL (model), &iter, path))
983     return FALSE;
984 
985   node = get_node (model, ITER_INDEX (&iter));
986   if (node->file == NULL)
987     return FALSE;
988 
989   uris[0] = g_file_get_uri (node->file);
990   uris[1] = NULL;
991   gtk_selection_data_set_uris (selection_data, uris);
992   g_free (uris[0]);
993 
994   return TRUE;
995 }
996 
997 static void
drag_source_iface_init(GtkTreeDragSourceIface * iface)998 drag_source_iface_init (GtkTreeDragSourceIface *iface)
999 {
1000   iface->row_draggable = drag_source_row_draggable;
1001   iface->drag_data_get = drag_source_drag_data_get;
1002   iface->drag_data_delete = NULL;
1003 }
1004 
1005 /*** GtkFileSystemModel ***/
1006 
1007 /* Signal IDs */
1008 enum {
1009   FINISHED_LOADING,
1010   LAST_SIGNAL
1011 };
1012 
1013 static guint file_system_model_signals[LAST_SIGNAL] = { 0 };
1014 
1015 
1016 
G_DEFINE_TYPE_WITH_CODE(GtkFileSystemModel,_gtk_file_system_model,G_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL,gtk_file_system_model_iface_init)G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_SORTABLE,gtk_file_system_model_sortable_init)G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_DRAG_SOURCE,drag_source_iface_init))1017 G_DEFINE_TYPE_WITH_CODE (GtkFileSystemModel, _gtk_file_system_model, G_TYPE_OBJECT,
1018 			 G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL,
1019 						gtk_file_system_model_iface_init)
1020 			 G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_SORTABLE,
1021 						gtk_file_system_model_sortable_init)
1022 			 G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_DRAG_SOURCE,
1023 						drag_source_iface_init))
1024 
1025 static void
1026 gtk_file_system_model_dispose (GObject *object)
1027 {
1028   GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (object);
1029 
1030   if (model->dir_thaw_source)
1031     {
1032       g_source_remove (model->dir_thaw_source);
1033       model->dir_thaw_source = 0;
1034     }
1035 
1036   g_cancellable_cancel (model->cancellable);
1037   if (model->dir_monitor)
1038     g_file_monitor_cancel (model->dir_monitor);
1039 
1040   G_OBJECT_CLASS (_gtk_file_system_model_parent_class)->dispose (object);
1041 }
1042 
1043 
1044 static void
gtk_file_system_model_finalize(GObject * object)1045 gtk_file_system_model_finalize (GObject *object)
1046 {
1047   GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (object);
1048   guint i;
1049 
1050   for (i = 0; i < model->files->len; i++)
1051     {
1052       int v;
1053 
1054       FileModelNode *node = get_node (model, i);
1055       if (node->file)
1056         g_object_unref (node->file);
1057       if (node->info)
1058         g_object_unref (node->info);
1059 
1060       for (v = 0; v < model->n_columns; v++)
1061 	if (G_VALUE_TYPE (&node->values[v]) != G_TYPE_INVALID)
1062 	  g_value_unset (&node->values[v]);
1063     }
1064   g_array_free (model->files, TRUE);
1065 
1066   g_object_unref (model->cancellable);
1067   g_free (model->attributes);
1068   if (model->dir)
1069     g_object_unref (model->dir);
1070   if (model->dir_monitor)
1071     g_object_unref (model->dir_monitor);
1072   g_hash_table_destroy (model->file_lookup);
1073   if (model->filter)
1074     g_object_unref (model->filter);
1075 
1076   g_slice_free1 (sizeof (GType) * model->n_columns, model->column_types);
1077 
1078   _gtk_tree_data_list_header_free (model->sort_list);
1079   if (model->default_sort_destroy)
1080     model->default_sort_destroy (model->default_sort_data);
1081 
1082   G_OBJECT_CLASS (_gtk_file_system_model_parent_class)->finalize (object);
1083 }
1084 
1085 static void
_gtk_file_system_model_class_init(GtkFileSystemModelClass * class)1086 _gtk_file_system_model_class_init (GtkFileSystemModelClass *class)
1087 {
1088   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
1089 
1090   gobject_class->finalize = gtk_file_system_model_finalize;
1091   gobject_class->dispose = gtk_file_system_model_dispose;
1092 
1093   file_system_model_signals[FINISHED_LOADING] =
1094     g_signal_new (I_("finished-loading"),
1095 		  G_OBJECT_CLASS_TYPE (gobject_class),
1096 		  G_SIGNAL_RUN_LAST,
1097 		  G_STRUCT_OFFSET (GtkFileSystemModelClass, finished_loading),
1098 		  NULL, NULL,
1099 		  NULL,
1100 		  G_TYPE_NONE, 1, G_TYPE_POINTER);
1101 }
1102 
1103 static void
_gtk_file_system_model_init(GtkFileSystemModel * model)1104 _gtk_file_system_model_init (GtkFileSystemModel *model)
1105 {
1106   model->show_files = TRUE;
1107   model->show_folders = TRUE;
1108   model->show_hidden = FALSE;
1109   model->filter_folders = FALSE;
1110 
1111   model->sort_column_id = GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID;
1112 
1113   model->file_lookup = g_hash_table_new (g_file_hash, (GEqualFunc) g_file_equal);
1114   model->cancellable = g_cancellable_new ();
1115 }
1116 
1117 /*** API ***/
1118 
1119 static void
gtk_file_system_model_closed_enumerator(GObject * object,GAsyncResult * res,gpointer data)1120 gtk_file_system_model_closed_enumerator (GObject *object, GAsyncResult *res, gpointer data)
1121 {
1122   g_file_enumerator_close_finish (G_FILE_ENUMERATOR (object), res, NULL);
1123 }
1124 
1125 static gboolean
thaw_func(gpointer data)1126 thaw_func (gpointer data)
1127 {
1128   GtkFileSystemModel *model = data;
1129 
1130   thaw_updates (model);
1131   model->dir_thaw_source = 0;
1132 
1133   return FALSE;
1134 }
1135 
1136 static void
gtk_file_system_model_got_files(GObject * object,GAsyncResult * res,gpointer data)1137 gtk_file_system_model_got_files (GObject *object, GAsyncResult *res, gpointer data)
1138 {
1139   GFileEnumerator *enumerator = G_FILE_ENUMERATOR (object);
1140   GtkFileSystemModel *model = data;
1141   GList *walk, *files;
1142   GError *error = NULL;
1143 
1144   gdk_threads_enter ();
1145 
1146   files = g_file_enumerator_next_files_finish (enumerator, res, &error);
1147 
1148   if (files)
1149     {
1150       if (model->dir_thaw_source == 0)
1151         {
1152           freeze_updates (model);
1153           model->dir_thaw_source = gdk_threads_add_timeout_full (IO_PRIORITY + 1,
1154                                                                  50,
1155                                                                  thaw_func,
1156                                                                  model,
1157                                                                  NULL);
1158           g_source_set_name_by_id (model->dir_thaw_source, "[gtk+] thaw_func");
1159         }
1160 
1161       for (walk = files; walk; walk = walk->next)
1162         {
1163           const char *name;
1164           GFileInfo *info;
1165           GFile *file;
1166 
1167           info = walk->data;
1168           name = g_file_info_get_name (info);
1169           if (name == NULL)
1170             {
1171               /* Shouldn't happen, but the APIs allow it */
1172               g_object_unref (info);
1173               continue;
1174             }
1175           file = g_file_get_child (model->dir, name);
1176           add_file (model, file, info);
1177           g_object_unref (file);
1178           g_object_unref (info);
1179         }
1180       g_list_free (files);
1181 
1182       g_file_enumerator_next_files_async (enumerator,
1183 					  g_file_is_native (model->dir) ? 50 * FILES_PER_QUERY : FILES_PER_QUERY,
1184 					  IO_PRIORITY,
1185 					  model->cancellable,
1186 					  gtk_file_system_model_got_files,
1187 					  model);
1188     }
1189   else
1190     {
1191       if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1192         {
1193           g_file_enumerator_close_async (enumerator,
1194                                          IO_PRIORITY,
1195                                          model->cancellable,
1196                                          gtk_file_system_model_closed_enumerator,
1197                                          NULL);
1198           if (model->dir_thaw_source != 0)
1199             {
1200               g_source_remove (model->dir_thaw_source);
1201               model->dir_thaw_source = 0;
1202               thaw_updates (model);
1203             }
1204 
1205           g_signal_emit (model, file_system_model_signals[FINISHED_LOADING], 0, error);
1206         }
1207 
1208       if (error)
1209         g_error_free (error);
1210     }
1211 
1212   gdk_threads_leave ();
1213 }
1214 
1215 /* Helper for gtk_file_system_model_query_done and
1216  * gtk_file_system_model_one_query_done */
1217 static void
query_done_helper(GObject * object,GAsyncResult * res,gpointer data,gboolean do_thaw_updates)1218 query_done_helper (GObject *     object,
1219                    GAsyncResult *res,
1220                    gpointer      data,
1221                    gboolean      do_thaw_updates)
1222 {
1223   GtkFileSystemModel *model;
1224   GFile *file = G_FILE (object);
1225   GFileInfo *info;
1226   guint id;
1227 
1228   info = g_file_query_info_finish (file, res, NULL);
1229   if (info == NULL)
1230     return;
1231 
1232   model = GTK_FILE_SYSTEM_MODEL (data);
1233 
1234   _gtk_file_system_model_update_file (model, file, info);
1235 
1236   id = node_get_for_file (model, file);
1237   gtk_file_system_model_sort_node (model, id);
1238 
1239   if (do_thaw_updates)
1240     thaw_updates (model);
1241 
1242   g_object_unref (info);
1243 }
1244 
1245 static void
gtk_file_system_model_query_done(GObject * object,GAsyncResult * res,gpointer data)1246 gtk_file_system_model_query_done (GObject *     object,
1247                                   GAsyncResult *res,
1248                                   gpointer      data)
1249 {
1250   gdk_threads_enter ();
1251 
1252   query_done_helper (object, res, data, FALSE);
1253 
1254   gdk_threads_leave ();
1255 }
1256 
1257 static void
gtk_file_system_model_monitor_change(GFileMonitor * monitor,GFile * file,GFile * other_file,GFileMonitorEvent type,GtkFileSystemModel * model)1258 gtk_file_system_model_monitor_change (GFileMonitor *      monitor,
1259                                       GFile *             file,
1260                                       GFile *             other_file,
1261                                       GFileMonitorEvent   type,
1262                                       GtkFileSystemModel *model)
1263 {
1264   switch (type)
1265     {
1266       case G_FILE_MONITOR_EVENT_CREATED:
1267       case G_FILE_MONITOR_EVENT_CHANGED:
1268       case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
1269         /* We can treat all of these the same way */
1270         g_file_query_info_async (file,
1271                                  model->attributes,
1272                                  G_FILE_QUERY_INFO_NONE,
1273                                  IO_PRIORITY,
1274                                  model->cancellable,
1275                                  gtk_file_system_model_query_done,
1276                                  model);
1277         break;
1278       case G_FILE_MONITOR_EVENT_DELETED:
1279 	gdk_threads_enter ();
1280         remove_file (model, file);
1281 	gdk_threads_leave ();
1282         break;
1283       case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
1284         /* FIXME: use freeze/thaw with this somehow? */
1285       case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
1286       case G_FILE_MONITOR_EVENT_UNMOUNTED:
1287       default:
1288         /* ignore these */
1289         break;
1290     }
1291 }
1292 
1293 static void
gtk_file_system_model_got_enumerator(GObject * dir,GAsyncResult * res,gpointer data)1294 gtk_file_system_model_got_enumerator (GObject *dir, GAsyncResult *res, gpointer data)
1295 {
1296   GtkFileSystemModel *model = data;
1297   GFileEnumerator *enumerator;
1298   GError *error = NULL;
1299 
1300   gdk_threads_enter ();
1301 
1302   enumerator = g_file_enumerate_children_finish (G_FILE (dir), res, &error);
1303   if (enumerator == NULL)
1304     {
1305       if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1306       {
1307         g_signal_emit (model, file_system_model_signals[FINISHED_LOADING], 0, error);
1308         g_error_free (error);
1309       }
1310     }
1311   else
1312     {
1313       g_file_enumerator_next_files_async (enumerator,
1314                                           g_file_is_native (model->dir) ? 50 * FILES_PER_QUERY : FILES_PER_QUERY,
1315                                           IO_PRIORITY,
1316                                           model->cancellable,
1317                                           gtk_file_system_model_got_files,
1318                                           model);
1319       g_object_unref (enumerator);
1320       model->dir_monitor = g_file_monitor_directory (model->dir,
1321                                                      G_FILE_MONITOR_NONE,
1322                                                      model->cancellable,
1323                                                      NULL); /* we don't mind if directory monitoring isn't supported, so the GError is NULL here */
1324       if (model->dir_monitor)
1325         g_signal_connect (model->dir_monitor,
1326                           "changed",
1327                           G_CALLBACK (gtk_file_system_model_monitor_change),
1328                           model);
1329     }
1330 
1331   gdk_threads_leave ();
1332 }
1333 
1334 static void
gtk_file_system_model_set_n_columns(GtkFileSystemModel * model,gint n_columns,va_list args)1335 gtk_file_system_model_set_n_columns (GtkFileSystemModel *model,
1336                                      gint                n_columns,
1337                                      va_list             args)
1338 {
1339   guint i;
1340 
1341   g_assert (model->files == NULL);
1342   g_assert (n_columns > 0);
1343 
1344   model->n_columns = n_columns;
1345   model->column_types = g_slice_alloc (sizeof (GType) * n_columns);
1346 
1347   model->node_size = sizeof (FileModelNode) + sizeof (GValue) * (n_columns - 1); /* minus 1 because FileModelNode.values[] has a default size of 1 */
1348 
1349   for (i = 0; i < (guint) n_columns; i++)
1350     {
1351       GType type = va_arg (args, GType);
1352       if (! _gtk_tree_data_list_check_type (type))
1353 	{
1354 	  g_error ("%s: type %s cannot be a column type for GtkFileSystemModel\n", G_STRLOC, g_type_name (type));
1355           return; /* not reached */
1356 	}
1357 
1358       model->column_types[i] = type;
1359     }
1360 
1361   model->sort_list = _gtk_tree_data_list_header_new (n_columns, model->column_types);
1362 
1363   model->files = g_array_sized_new (FALSE, FALSE, model->node_size, FILES_PER_QUERY);
1364   /* add editable node at start */
1365   g_array_set_size (model->files, 1);
1366   memset (get_node (model, 0), 0, model->node_size);
1367 }
1368 
1369 static void
gtk_file_system_model_set_directory(GtkFileSystemModel * model,GFile * dir,const gchar * attributes)1370 gtk_file_system_model_set_directory (GtkFileSystemModel *model,
1371                                      GFile *             dir,
1372 			             const gchar *       attributes)
1373 {
1374   g_assert (G_IS_FILE (dir));
1375 
1376   model->dir = g_object_ref (dir);
1377   model->attributes = g_strdup (attributes);
1378 
1379   g_file_enumerate_children_async (model->dir,
1380                                    attributes,
1381                                    G_FILE_QUERY_INFO_NONE,
1382                                    IO_PRIORITY,
1383                                    model->cancellable,
1384                                    gtk_file_system_model_got_enumerator,
1385                                    model);
1386 
1387 }
1388 
1389 static GtkFileSystemModel *
_gtk_file_system_model_new_valist(GtkFileSystemModelGetValue get_func,gpointer get_data,guint n_columns,va_list args)1390 _gtk_file_system_model_new_valist (GtkFileSystemModelGetValue get_func,
1391                                    gpointer            get_data,
1392                                    guint               n_columns,
1393                                    va_list             args)
1394 {
1395   GtkFileSystemModel *model;
1396 
1397   model = g_object_new (GTK_TYPE_FILE_SYSTEM_MODEL, NULL);
1398   model->get_func = get_func;
1399   model->get_data = get_data;
1400 
1401   gtk_file_system_model_set_n_columns (model, n_columns, args);
1402 
1403   return model;
1404 }
1405 
1406 /**
1407  * _gtk_file_system_model_new:
1408  * @get_func: function to call for getting a value
1409  * @get_data: user data argument passed to @get_func
1410  * @n_columns: number of columns
1411  * @...: @n_columns #GType types for the columns
1412  *
1413  * Creates a new #GtkFileSystemModel object. You need to add files
1414  * to the list using _gtk_file_system_model_add_and_query_file()
1415  * or _gtk_file_system_model_update_file().
1416  *
1417  * Returns: the newly created #GtkFileSystemModel
1418  **/
1419 GtkFileSystemModel *
_gtk_file_system_model_new(GtkFileSystemModelGetValue get_func,gpointer get_data,guint n_columns,...)1420 _gtk_file_system_model_new (GtkFileSystemModelGetValue get_func,
1421                             gpointer            get_data,
1422                             guint               n_columns,
1423                             ...)
1424 {
1425   GtkFileSystemModel *model;
1426   va_list args;
1427 
1428   g_return_val_if_fail (get_func != NULL, NULL);
1429   g_return_val_if_fail (n_columns > 0, NULL);
1430 
1431   va_start (args, n_columns);
1432   model = _gtk_file_system_model_new_valist (get_func, get_data, n_columns, args);
1433   va_end (args);
1434 
1435   return model;
1436 }
1437 
1438 /**
1439  * _gtk_file_system_model_new_for_directory:
1440  * @directory: the directory to show.
1441  * @attributes: (allow-none): attributes to immediately load or %NULL for all
1442  * @get_func: function that the model should call to query data about a file
1443  * @get_data: user data to pass to the @get_func
1444  * @n_columns: number of columns
1445  * @...: @n_columns #GType types for the columns
1446  *
1447  * Creates a new #GtkFileSystemModel object. The #GtkFileSystemModel
1448  * object wraps the given @directory as a #GtkTreeModel.
1449  * The model will query the given directory with the given @attributes
1450  * and add all files inside the directory automatically. If supported,
1451  * it will also monitor the drectory and update the model's
1452  * contents to reflect changes, if the @directory supports monitoring.
1453  *
1454  * Returns: the newly created #GtkFileSystemModel
1455  **/
1456 GtkFileSystemModel *
_gtk_file_system_model_new_for_directory(GFile * dir,const gchar * attributes,GtkFileSystemModelGetValue get_func,gpointer get_data,guint n_columns,...)1457 _gtk_file_system_model_new_for_directory (GFile *                    dir,
1458                                           const gchar *              attributes,
1459                                           GtkFileSystemModelGetValue get_func,
1460                                           gpointer                   get_data,
1461                                           guint                      n_columns,
1462                                           ...)
1463 {
1464   GtkFileSystemModel *model;
1465   va_list args;
1466 
1467   g_return_val_if_fail (G_IS_FILE (dir), NULL);
1468   g_return_val_if_fail (get_func != NULL, NULL);
1469   g_return_val_if_fail (n_columns > 0, NULL);
1470 
1471   va_start (args, n_columns);
1472   model = _gtk_file_system_model_new_valist (get_func, get_data, n_columns, args);
1473   va_end (args);
1474 
1475   gtk_file_system_model_set_directory (model, dir, attributes);
1476 
1477   return model;
1478 }
1479 
1480 static void
gtk_file_system_model_refilter_all(GtkFileSystemModel * model)1481 gtk_file_system_model_refilter_all (GtkFileSystemModel *model)
1482 {
1483   guint i;
1484 
1485   if (model->frozen)
1486     {
1487       model->filter_on_thaw = TRUE;
1488       return;
1489     }
1490 
1491   freeze_updates (model);
1492 
1493   /* start at index 1, don't change the editable */
1494   for (i = 1; i < model->files->len; i++)
1495     node_compute_visibility_and_filters (model, i);
1496 
1497   model->filter_on_thaw = FALSE;
1498   thaw_updates (model);
1499 }
1500 
1501 /**
1502  * _gtk_file_system_model_set_show_hidden:
1503  * @model: a #GtkFileSystemModel
1504  * @show_hidden: whether hidden files should be displayed
1505  *
1506  * Sets whether hidden files should be included in the #GtkTreeModel
1507  * for display.
1508  **/
1509 void
_gtk_file_system_model_set_show_hidden(GtkFileSystemModel * model,gboolean show_hidden)1510 _gtk_file_system_model_set_show_hidden (GtkFileSystemModel *model,
1511 					gboolean            show_hidden)
1512 {
1513   g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
1514 
1515   show_hidden = show_hidden != FALSE;
1516 
1517   if (show_hidden != model->show_hidden)
1518     {
1519       model->show_hidden = show_hidden;
1520       gtk_file_system_model_refilter_all (model);
1521     }
1522 }
1523 
1524 /**
1525  * _gtk_file_system_model_set_show_folders:
1526  * @model: a #GtkFileSystemModel
1527  * @show_folders: whether folders should be displayed
1528  *
1529  * Sets whether folders should be included in the #GtkTreeModel for
1530  * display.
1531  **/
1532 void
_gtk_file_system_model_set_show_folders(GtkFileSystemModel * model,gboolean show_folders)1533 _gtk_file_system_model_set_show_folders (GtkFileSystemModel *model,
1534 					 gboolean            show_folders)
1535 {
1536   g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
1537 
1538   show_folders = show_folders != FALSE;
1539 
1540   if (show_folders != model->show_folders)
1541     {
1542       model->show_folders = show_folders;
1543       gtk_file_system_model_refilter_all (model);
1544     }
1545 }
1546 
1547 /**
1548  * _gtk_file_system_model_set_show_files:
1549  * @model: a #GtkFileSystemModel
1550  * @show_files: whether files (as opposed to folders) should
1551  *              be displayed.
1552  *
1553  * Sets whether files (as opposed to folders) should be included
1554  * in the #GtkTreeModel for display.
1555  **/
1556 void
_gtk_file_system_model_set_show_files(GtkFileSystemModel * model,gboolean show_files)1557 _gtk_file_system_model_set_show_files (GtkFileSystemModel *model,
1558 				       gboolean            show_files)
1559 {
1560   g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
1561 
1562   show_files = show_files != FALSE;
1563 
1564   if (show_files != model->show_files)
1565     {
1566       model->show_files = show_files;
1567       gtk_file_system_model_refilter_all (model);
1568     }
1569 }
1570 
1571 /**
1572  * _gtk_file_system_model_set_filter_folders:
1573  * @model: a #GtkFileSystemModel
1574  * @filter_folders: whether the filter applies to folders
1575  *
1576  * Sets whether the filter set by _gtk_file_system_model_set_filter()
1577  * applies to folders. By default, it does not and folders are always
1578  * visible.
1579  **/
1580 void
_gtk_file_system_model_set_filter_folders(GtkFileSystemModel * model,gboolean filter_folders)1581 _gtk_file_system_model_set_filter_folders (GtkFileSystemModel *model,
1582 					   gboolean            filter_folders)
1583 {
1584   g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
1585 
1586   filter_folders = filter_folders != FALSE;
1587 
1588   if (filter_folders != model->filter_folders)
1589     {
1590       model->filter_folders = filter_folders;
1591       gtk_file_system_model_refilter_all (model);
1592     }
1593 }
1594 
1595 /**
1596  * _gtk_file_system_model_get_cancellable:
1597  * @model: the model
1598  *
1599  * Gets the cancellable used by the @model. This is the cancellable used
1600  * internally by the @model that will be cancelled when @model is
1601  * disposed. So you can use it for operations that should be cancelled
1602  * when the model goes away.
1603  *
1604  * Returns: The cancellable used by @model
1605  **/
1606 GCancellable *
_gtk_file_system_model_get_cancellable(GtkFileSystemModel * model)1607 _gtk_file_system_model_get_cancellable (GtkFileSystemModel *model)
1608 {
1609   g_return_val_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model), NULL);
1610 
1611   return model->cancellable;
1612 }
1613 
1614 /**
1615  * _gtk_file_system_model_iter_is_visible:
1616  * @model: the model
1617  * @iter: a valid iterator
1618  *
1619  * Checks if the iterator is visible. A visible iterator references
1620  * a row that is currently exposed using the #GtkTreeModel API. If
1621  * the iterator is invisible, it references a file that is not shown
1622  * for some reason, such as being filtered out by the current filter or
1623  * being a hidden file.
1624  *
1625  * Returns: %TRUE if the iterator is visible
1626  **/
1627 gboolean
_gtk_file_system_model_iter_is_visible(GtkFileSystemModel * model,GtkTreeIter * iter)1628 _gtk_file_system_model_iter_is_visible (GtkFileSystemModel *model,
1629 					GtkTreeIter        *iter)
1630 {
1631   FileModelNode *node;
1632 
1633   g_return_val_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model), FALSE);
1634   g_return_val_if_fail (iter != NULL, FALSE);
1635 
1636   node = get_node (model, ITER_INDEX (iter));
1637   return node->visible;
1638 }
1639 
1640 /**
1641  * _gtk_file_system_model_iter_is_filtered_out:
1642  * @model: the model
1643  * @iter: a valid iterator
1644  *
1645  * Checks if the iterator is filtered out.  This is only useful for rows
1646  * that refer to folders, as those are always visible regardless
1647  * of what the current filter says.  This function lets you see
1648  * the results of the filter.
1649  *
1650  * Returns: %TRUE if the iterator passed the current filter; %FALSE if the
1651  * filter would not have let the row pass.
1652  **/
1653 gboolean
_gtk_file_system_model_iter_is_filtered_out(GtkFileSystemModel * model,GtkTreeIter * iter)1654 _gtk_file_system_model_iter_is_filtered_out (GtkFileSystemModel *model,
1655 					     GtkTreeIter        *iter)
1656 {
1657   FileModelNode *node;
1658 
1659   g_return_val_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model), FALSE);
1660   g_return_val_if_fail (iter != NULL, FALSE);
1661 
1662   node = get_node (model, ITER_INDEX (iter));
1663   return node->filtered_out;
1664 }
1665 
1666 /**
1667  * _gtk_file_system_model_get_info:
1668  * @model: a #GtkFileSystemModel
1669  * @iter: a #GtkTreeIter pointing to a row of @model
1670  *
1671  * Gets the #GFileInfo-struct for a particular row
1672  * of @model.
1673  *
1674  * Returns: a #GFileInfo-struct. This value
1675  *   is owned by @model and must not be modified or freed.
1676  *   If you want to keep the information for later use,
1677  *   you must take a reference, since the #GFileInfo-struct may be
1678  *   freed on later changes to the file system.
1679  **/
1680 GFileInfo *
_gtk_file_system_model_get_info(GtkFileSystemModel * model,GtkTreeIter * iter)1681 _gtk_file_system_model_get_info (GtkFileSystemModel *model,
1682 				 GtkTreeIter        *iter)
1683 {
1684   FileModelNode *node;
1685 
1686   g_return_val_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model), NULL);
1687   g_return_val_if_fail (iter != NULL, NULL);
1688 
1689   node = get_node (model, ITER_INDEX (iter));
1690   g_assert (node->info == NULL || G_IS_FILE_INFO (node->info));
1691   return node->info;
1692 }
1693 
1694 /**
1695  * _gtk_file_system_model_get_file:
1696  * @model: a #GtkFileSystemModel
1697  * @iter: a #GtkTreeIter pointing to a row of @model
1698  *
1699  * Gets the file for a particular row in @model.
1700  *
1701  * Returns: the file. This object is owned by @model and
1702  *   or freed. If you want to save the path for later use,
1703  *   you must take a ref, since the object may be freed
1704  *   on later changes to the file system.
1705  **/
1706 GFile *
_gtk_file_system_model_get_file(GtkFileSystemModel * model,GtkTreeIter * iter)1707 _gtk_file_system_model_get_file (GtkFileSystemModel *model,
1708 				 GtkTreeIter        *iter)
1709 {
1710   FileModelNode *node;
1711 
1712   g_return_val_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model), NULL);
1713 
1714   node = get_node (model, ITER_INDEX (iter));
1715   return node->file;
1716 }
1717 
1718 /**
1719  * _gtk_file_system_model_get_value:
1720  * @model: a #GtkFileSystemModel
1721  * @iter: a #GtkTreeIter pointing to a row of @model
1722  * @column: the column to get the value for
1723  *
1724  * Gets the value associated with the given row @iter and @column.
1725  * If no value is available yet and the default value should be used,
1726  * %NULL is returned.
1727  * This is a performance optimization for the calls
1728  * gtk_tree_model_get() or gtk_tree_model_get_value(), which copy
1729  * the value and spend a considerable amount of time in iterator
1730  * lookups. Both of which are slow.
1731  *
1732  * Returns: a pointer to the actual value as stored in @model or %NULL
1733  *          if no value available yet.
1734  **/
1735 const GValue *
_gtk_file_system_model_get_value(GtkFileSystemModel * model,GtkTreeIter * iter,int column)1736 _gtk_file_system_model_get_value (GtkFileSystemModel *model,
1737                                   GtkTreeIter *       iter,
1738                                   int                 column)
1739 {
1740   FileModelNode *node;
1741 
1742   g_return_val_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model), NULL);
1743   g_return_val_if_fail (column >= 0 && (guint) column < model->n_columns, NULL);
1744 
1745   node = get_node (model, ITER_INDEX (iter));
1746 
1747   if (!G_VALUE_TYPE (&node->values[column]))
1748     {
1749       g_value_init (&node->values[column], model->column_types[column]);
1750       if (!model->get_func (model,
1751                             node->file,
1752                             node->info,
1753                             column,
1754                             &node->values[column],
1755                             model->get_data))
1756         {
1757           g_value_unset (&node->values[column]);
1758           return NULL;
1759         }
1760     }
1761 
1762   return &node->values[column];
1763 }
1764 
1765 static guint
node_get_for_file(GtkFileSystemModel * model,GFile * file)1766 node_get_for_file (GtkFileSystemModel *model,
1767                    GFile *             file)
1768 {
1769   guint i;
1770 
1771   i = GPOINTER_TO_UINT (g_hash_table_lookup (model->file_lookup, file));
1772   if (i != 0)
1773     return i;
1774 
1775   /* Node 0 is the editable row and has no associated file or entry in the table, so we start counting from 1.
1776    *
1777    * The invariant here is that the files in model->files[n] for n < g_hash_table_size (model->file_lookup)
1778    * are already added to the hash table. The table can get cleared when we re-sort; this loop merely rebuilds
1779    * our (file -> index) mapping on demand.
1780    *
1781    * If we exit the loop, the next pending batch of mappings will be resolved when this function gets called again
1782    * with another file that is not yet in the mapping.
1783    */
1784   for (i = g_hash_table_size (model->file_lookup) + 1; i < model->files->len; i++)
1785     {
1786       FileModelNode *node = get_node (model, i);
1787 
1788       g_hash_table_insert (model->file_lookup, node->file, GUINT_TO_POINTER (i));
1789       if (g_file_equal (node->file, file))
1790         return i;
1791     }
1792 
1793   return 0;
1794 }
1795 
1796 /**
1797  * _gtk_file_system_model_get_iter_for_file:
1798  * @model: the model
1799  * @iter: the iterator to be initialized
1800  * @file: the file to look up
1801  *
1802  * Initializes @iter to point to the row used for @file, if @file is part
1803  * of the model. Note that upon successful return, @iter may point to an
1804  * invisible row in the @model. Use
1805  * _gtk_file_system_model_iter_is_visible() to make sure it is visible to
1806  * the tree view.
1807  *
1808  * Returns: %TRUE if file is part of the model and @iter was initialized
1809  **/
1810 gboolean
_gtk_file_system_model_get_iter_for_file(GtkFileSystemModel * model,GtkTreeIter * iter,GFile * file)1811 _gtk_file_system_model_get_iter_for_file (GtkFileSystemModel *model,
1812 					  GtkTreeIter        *iter,
1813 					  GFile *             file)
1814 {
1815   guint i;
1816 
1817   g_return_val_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model), FALSE);
1818   g_return_val_if_fail (iter != NULL, FALSE);
1819   g_return_val_if_fail (G_IS_FILE (file), FALSE);
1820 
1821   i = node_get_for_file (model, file);
1822 
1823   if (i == 0)
1824     return FALSE;
1825 
1826   ITER_INIT_FROM_INDEX (model, iter, i);
1827   return TRUE;
1828 }
1829 
1830 /* When an element is added or removed to the model->files array, we need to
1831  * update the model->file_lookup mappings of (node, index), as the indexes
1832  * change.  This function adds the specified increment to the index in that pair
1833  * if the index is equal or after the specified id.  We use this to slide the
1834  * mappings up or down when a node is added or removed, respectively.
1835  */
1836 static void
adjust_file_lookup(GtkFileSystemModel * model,guint id,int increment)1837 adjust_file_lookup (GtkFileSystemModel *model, guint id, int increment)
1838 {
1839   GHashTableIter iter;
1840   gpointer key;
1841   gpointer value;
1842 
1843   g_hash_table_iter_init (&iter, model->file_lookup);
1844 
1845   while (g_hash_table_iter_next (&iter, &key, &value))
1846     {
1847       guint index = GPOINTER_TO_UINT (value);
1848 
1849       if (index >= id)
1850 	{
1851 	  index += increment;
1852 	  g_hash_table_iter_replace (&iter, GUINT_TO_POINTER (index));
1853 	}
1854     }
1855 }
1856 
1857 /**
1858  * add_file:
1859  * @model: the model
1860  * @file: the file to add
1861  * @info: the information to associate with the file
1862  *
1863  * Adds the given @file with its associated @info to the @model.
1864  * If the model is frozen, the file will only show up after it is thawn.
1865  **/
1866 static void
add_file(GtkFileSystemModel * model,GFile * file,GFileInfo * info)1867 add_file (GtkFileSystemModel *model,
1868 	  GFile              *file,
1869 	  GFileInfo          *info)
1870 {
1871   FileModelNode *node;
1872 
1873   g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
1874   g_return_if_fail (G_IS_FILE (file));
1875   g_return_if_fail (G_IS_FILE_INFO (info));
1876 
1877   node = g_slice_alloc0 (model->node_size);
1878   node->file = g_object_ref (file);
1879   if (info)
1880     node->info = g_object_ref (info);
1881   node->frozen_add = model->frozen ? TRUE : FALSE;
1882 
1883   g_array_append_vals (model->files, node, 1);
1884   g_slice_free1 (model->node_size, node);
1885 
1886   if (!model->frozen)
1887     node_compute_visibility_and_filters (model, model->files->len -1);
1888 
1889   gtk_file_system_model_sort_node (model, model->files->len -1);
1890 }
1891 
1892 /**
1893  * remove_file:
1894  * @model: the model
1895  * @file: file to remove from the model. The file must have been
1896  *        added to the model previously
1897  *
1898  * Removes the given file from the model. If the file is not part of
1899  * @model, this function does nothing.
1900  **/
1901 static void
remove_file(GtkFileSystemModel * model,GFile * file)1902 remove_file (GtkFileSystemModel *model,
1903 	     GFile              *file)
1904 {
1905   FileModelNode *node;
1906   gboolean was_visible;
1907   guint id;
1908   guint row;
1909 
1910   g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
1911   g_return_if_fail (G_IS_FILE (file));
1912 
1913   id = node_get_for_file (model, file);
1914   if (id == 0)
1915     return;
1916 
1917   node = get_node (model, id);
1918   was_visible = node->visible;
1919   row = node_get_tree_row (model, id);
1920 
1921   node_invalidate_index (model, id);
1922 
1923   g_hash_table_remove (model->file_lookup, file);
1924   g_object_unref (node->file);
1925   adjust_file_lookup (model, id, -1);
1926 
1927   if (node->info)
1928     g_object_unref (node->info);
1929 
1930   g_array_remove_index (model->files, id);
1931 
1932   /* We don't need to resort, as removing a row doesn't change the sorting order of the other rows */
1933 
1934   if (was_visible)
1935     emit_row_deleted_for_row (model, row);
1936 }
1937 
1938 /**
1939  * _gtk_file_system_model_update_file:
1940  * @model: the model
1941  * @file: the file
1942  * @info: the new file info
1943  *
1944  * Tells the file system model that the file changed and that the
1945  * new @info should be used for it now.  If the file is not part of
1946  * @model, it will get added automatically.
1947  **/
1948 void
_gtk_file_system_model_update_file(GtkFileSystemModel * model,GFile * file,GFileInfo * info)1949 _gtk_file_system_model_update_file (GtkFileSystemModel *model,
1950                                     GFile              *file,
1951                                     GFileInfo          *info)
1952 {
1953   FileModelNode *node;
1954   guint i, id;
1955   GFileInfo *old_info;
1956 
1957   g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
1958   g_return_if_fail (G_IS_FILE (file));
1959   g_return_if_fail (G_IS_FILE_INFO (info));
1960 
1961   id = node_get_for_file (model, file);
1962   if (id == 0)
1963     {
1964       add_file (model, file, info);
1965       id = node_get_for_file (model, file);
1966     }
1967 
1968   node = get_node (model, id);
1969 
1970   old_info = node->info;
1971   node->info = g_object_ref (info);
1972   if (old_info)
1973     g_object_unref (old_info);
1974 
1975   for (i = 0; i < model->n_columns; i++)
1976     {
1977       if (G_VALUE_TYPE (&node->values[i]))
1978         g_value_unset (&node->values[i]);
1979     }
1980 
1981   if (node->visible)
1982     emit_row_changed_for_node (model, id);
1983 }
1984 
1985 void
_gtk_file_system_model_update_files(GtkFileSystemModel * model,GList * files,GList * infos)1986 _gtk_file_system_model_update_files (GtkFileSystemModel *model,
1987                                      GList              *files,
1988                                      GList              *infos)
1989 {
1990   GList *l, *i;
1991 
1992   g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
1993 
1994   freeze_updates (model);
1995 
1996   for (l = files, i = infos; l; l = l->next, i = i->next)
1997     _gtk_file_system_model_update_file (model, (GFile *)l->data, (GFileInfo *)i->data);
1998 
1999   thaw_updates (model);
2000 }
2001 
2002 /**
2003  * _gtk_file_system_model_set_filter:
2004  * @mode: a #GtkFileSystemModel
2005  * @filter: (allow-none): %NULL or filter to use
2006  *
2007  * Sets a filter to be used for deciding if a row should be visible or not.
2008  * Whether this filter applies to directories can be toggled with
2009  * _gtk_file_system_model_set_filter_folders().
2010  **/
2011 void
_gtk_file_system_model_set_filter(GtkFileSystemModel * model,GtkFileFilter * filter)2012 _gtk_file_system_model_set_filter (GtkFileSystemModel      *model,
2013 				   GtkFileFilter *          filter)
2014 {
2015   GtkFileFilter *old_filter;
2016 
2017   g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
2018   g_return_if_fail (filter == NULL || GTK_IS_FILE_FILTER (filter));
2019 
2020   if (filter)
2021     g_object_ref (filter);
2022 
2023   old_filter = model->filter;
2024   model->filter = filter;
2025 
2026   if (old_filter)
2027     g_object_unref (old_filter);
2028 
2029   gtk_file_system_model_refilter_all (model);
2030 }
2031 
2032 /**
2033  * freeze_updates:
2034  * @model: a #GtkFileSystemModel
2035  *
2036  * Freezes most updates on the model, so that performing multiple operations on
2037  * the files in the model do not cause any events.  Use thaw_updates() to resume
2038  * proper operations. It is fine to call this function multiple times as long as
2039  * freeze and thaw calls are balanced.
2040  **/
2041 static void
freeze_updates(GtkFileSystemModel * model)2042 freeze_updates (GtkFileSystemModel *model)
2043 {
2044   g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
2045 
2046   model->frozen++;
2047 }
2048 
2049 /**
2050  * thaw_updates:
2051  * @model: a #GtkFileSystemModel
2052  *
2053  * Undoes the effect of a previous call to freeze_updates()
2054  **/
2055 static void
thaw_updates(GtkFileSystemModel * model)2056 thaw_updates (GtkFileSystemModel *model)
2057 {
2058   gboolean stuff_added;
2059 
2060   g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
2061   g_return_if_fail (model->frozen > 0);
2062 
2063   model->frozen--;
2064   if (model->frozen > 0)
2065     return;
2066 
2067   stuff_added = get_node (model, model->files->len - 1)->frozen_add;
2068 
2069   if (model->filter_on_thaw)
2070     gtk_file_system_model_refilter_all (model);
2071   if (model->sort_on_thaw)
2072     gtk_file_system_model_sort (model);
2073   if (stuff_added)
2074     {
2075       guint i;
2076 
2077       for (i = 0; i < model->files->len; i++)
2078         {
2079           FileModelNode *node = get_node (model, i);
2080 
2081           if (!node->frozen_add)
2082             continue;
2083           node->frozen_add = FALSE;
2084           node_compute_visibility_and_filters (model, i);
2085         }
2086     }
2087 }
2088 
2089 /**
2090  * _gtk_file_system_model_clear_cache:
2091  * @model: a #GtkFileSystemModel
2092  * @column: the column to clear or -1 for all columns
2093  *
2094  * Clears the cached values in the model for the given @column. Use
2095  * this function whenever your get_value function would return different
2096  * values for a column.
2097  * The file chooser uses this for example when the icon theme changes to
2098  * invalidate the cached pixbufs.
2099  **/
2100 void
_gtk_file_system_model_clear_cache(GtkFileSystemModel * model,int column)2101 _gtk_file_system_model_clear_cache (GtkFileSystemModel *model,
2102                                     int                 column)
2103 {
2104   guint i;
2105   int start, end;
2106   gboolean changed;
2107 
2108   g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
2109   g_return_if_fail (column >= -1 && (guint) column < model->n_columns);
2110 
2111   if (column > -1)
2112     {
2113       start = column;
2114       end = column + 1;
2115     }
2116   else
2117     {
2118       start = 0;
2119       end = model->n_columns;
2120     }
2121 
2122   for (i = 0; i < model->files->len; i++)
2123     {
2124       FileModelNode *node = get_node (model, i);
2125       changed = FALSE;
2126       for (column = start; column < end; column++)
2127         {
2128           if (!G_VALUE_TYPE (&node->values[column]))
2129             continue;
2130 
2131           g_value_unset (&node->values[column]);
2132           changed = TRUE;
2133         }
2134 
2135       if (changed && node->visible)
2136 	emit_row_changed_for_node (model, i);
2137     }
2138 
2139   /* FIXME: resort? */
2140 }
2141 
2142 /**
2143  * _gtk_file_system_model_add_and_query_file:
2144  * @model: a #GtkFileSystemModel
2145  * @file: the file to add
2146  * @attributes: attributes to query before adding the file
2147  *
2148  * This is a conenience function that calls g_file_query_info_async() on
2149  * the given file, and when successful, adds it to the model.
2150  * Upon failure, the @file is discarded.
2151  **/
2152 void
_gtk_file_system_model_add_and_query_file(GtkFileSystemModel * model,GFile * file,const char * attributes)2153 _gtk_file_system_model_add_and_query_file (GtkFileSystemModel *model,
2154                                            GFile *             file,
2155                                            const char *        attributes)
2156 {
2157   g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
2158   g_return_if_fail (G_IS_FILE (file));
2159   g_return_if_fail (attributes != NULL);
2160 
2161   g_file_query_info_async (file,
2162                            attributes,
2163                            G_FILE_QUERY_INFO_NONE,
2164                            IO_PRIORITY,
2165                            model->cancellable,
2166                            gtk_file_system_model_query_done,
2167                            model);
2168 }
2169 
2170 static void
gtk_file_system_model_one_query_done(GObject * object,GAsyncResult * res,gpointer data)2171 gtk_file_system_model_one_query_done (GObject *     object,
2172                                       GAsyncResult *res,
2173                                       gpointer      data)
2174 {
2175   query_done_helper (object, res, data, TRUE);
2176 }
2177 
2178 void
_gtk_file_system_model_add_and_query_files(GtkFileSystemModel * model,GList * list,const char * attributes)2179 _gtk_file_system_model_add_and_query_files (GtkFileSystemModel *model,
2180                                             GList              *list,
2181                                             const char         *attributes)
2182 {
2183   GList *l;
2184   GFile *file;
2185 
2186   g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
2187   g_return_if_fail (attributes != NULL);
2188 
2189   for (l = list; l; l = l->next)
2190     {
2191       file = (GFile *)l->data;
2192       freeze_updates (model);
2193       g_file_query_info_async (file,
2194                                attributes,
2195                                G_FILE_QUERY_INFO_NONE,
2196                                IO_PRIORITY,
2197                                model->cancellable,
2198                                gtk_file_system_model_one_query_done,
2199                                model);
2200     }
2201 }
2202 
2203 GFile *
_gtk_file_system_model_get_directory(GtkFileSystemModel * model)2204 _gtk_file_system_model_get_directory (GtkFileSystemModel *model)
2205 {
2206   g_return_val_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model), NULL);
2207 
2208   return model->dir;
2209 }
2210 
2211