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