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