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