gtkfilesystemmodel.c 61.7 KB
Newer Older
Cody Russell's avatar
Cody Russell committed
1
/* GTK - The GIMP Toolkit
Owen Taylor's avatar
Owen Taylor committed
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
 * gtkfilesystemmodel.c: GtkTreeModel wrapping a GtkFileSystem
 * Copyright (C) 2003, Red Hat, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

21
#include "config.h"
22

Owen Taylor's avatar
Owen Taylor committed
23
#include "gtkfilesystemmodel.h"
24 25 26 27

#include <stdlib.h>
#include <string.h>

28
#include "gtkfilesystem.h"
29
#include "gtkintl.h"
Federico Mena Quintero's avatar
Federico Mena Quintero committed
30
#include "gtkmarshalers.h"
31
#include "gtktreedatalist.h"
32
#include "gtktreednd.h"
33
#include "gtktreemodel.h"
Owen Taylor's avatar
Owen Taylor committed
34

35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
/*** Structure: how GtkFileSystemModel works
 *
 * This is a custom GtkTreeModel used to hold a collection of files for GtkFileChooser.  There are two use cases:
 *
 *   1. The model populates itself from a folder, using the GIO file enumerator API.  This happens if you use
 *      _gtk_file_system_model_new_for_directory().  This is the normal usage for showing the contents of a folder.
 *
 *   2. The caller populates the model by hand, with files not necessarily in the same folder.  This happens
 *      if you use _gtk_file_system_model_new() and then _gtk_file_system_model_add_and_query_file().  This is
 *      the special kind of usage for "search" and "recent-files", where the file chooser gives the model the
 *      files to be displayed.
 *
 * Each file is kept in a FileModelNode structure.  Each FileModelNode holds a GFile* and other data.  All the
 * node structures have the same size, determined at runtime, depending on the number of columns that were passed
 * to _gtk_file_system_model_new() or _gtk_file_system_model_new_for_directory() (that is, the size of a node is
 * not sizeof (FileModelNode), but rather model->node_size).  The last field in the FileModelNode structure,
 * node->values[], is an array of GValue, used to hold the data for those columns.
 *
 * The model stores an array of FileModelNode structures in model->files.  This is a GArray where each element is
 * model->node_size bytes in size (the model computes that node size when initializing itself).  There are
 * convenience macros, get_node() and node_index(), to access that array based on an array index or a pointer to
 * a node inside the array.
 *
 * The model accesses files through two of its fields:
 *
 *   model->files - GArray of FileModelNode structures.
 *
 *   model->file_lookup - hash table that maps a GFile* to an index inside the model->files array.
 *
 * The model->file_lookup hash table is populated lazily.  It is both accessed and populated with the
 * node_get_for_file() function.  The invariant is that the files in model->files[n] for n < g_hash_table_size
 * (model->file_lookup) are already added to the hash table. The hash table will get cleared when we re-sort the
 * files, as the array will be in a different order and the indexes need to be rebuilt.
 *
 * Each FileModelNode has a node->visible field, which indicates whether the node is visible in the GtkTreeView.
 * A node may be invisible if, for example, it corresponds to a hidden file and the file chooser is not showing
 * hidden files.
 *
 * Since not all nodes in the model->files array may be visible, we need a way to map visible row indexes from
74
 * the treeview to array indexes in our array of files.  And thus we introduce a bit of terminology:
75
 *
76 77 78 79 80 81 82 83 84 85
 *   index - An index in the model->files array.  All variables/fields that represent indexes are either called
 *   "index" or "i_*", or simply "i" for things like loop counters.
 *
 *   row - An index in the GtkTreeView, i.e. the index of a row within the outward-facing API of the
 *   GtkFileSystemModel.  However, note that our rows are 1-based, not 0-based, for the reason explained in the
 *   following paragraph.  Variables/fields that represent visible rows are called "row", or "r_*", or simply
 *   "r".
 *
 * Each FileModelNode has a node->row field which is the number of visible rows in the treeview, *before and
 * including* that node.  This means that node->row is 1-based, instead of 0-based --- this makes some code
86
 * simpler, believe it or not :)  This also means that when the calling GtkTreeView gives us a GtkTreePath, we
87 88 89 90 91 92 93 94 95 96
 * turn the 0-based treepath into a 1-based row for our purposes.  If a node is not visible, it will have the
 * same row number as its closest preceding visible node.
 *
 * We try to compute the node->row fields lazily.  A node is said to be "valid" if its node->row is accurate.
 * For this, the model keeps a model->n_nodes_valid field which is the count of valid nodes starting from the
 * beginning of the model->files array.  When a node changes its information, or when a node gets deleted, that
 * node and the following ones get invalidated by simply setting model->n_nodes_valid to the array index of the
 * node.  If the model happens to need a node's row number and that node is in the model->files array after
 * model->n_nodes_valid, then the nodes get re-validated up to the sought node.  See node_validate_rows() for
 * this logic.
97
 *
98 99
 * You never access a node->row directly.  Instead, call node_get_tree_row().  That function will validate the nodes
 * up to the sought one if the node is not valid yet, and it will return a proper 0-based row.
100 101
 */

102
/*** DEFINES ***/
103

104 105 106 107
/* priority used for all async callbacks in the main loop
 * This should be higher than redraw priorities so multiple callbacks
 * firing can be handled without intermediate redraws */
#define IO_PRIORITY G_PRIORITY_DEFAULT
108

109 110
/* random number that everyone else seems to use, too */
#define FILES_PER_QUERY 100
111

112 113
typedef struct _FileModelNode           FileModelNode;
typedef struct _GtkFileSystemModelClass GtkFileSystemModelClass;
114

115 116 117 118
struct _FileModelNode
{
  GFile *               file;           /* file represented by this node or NULL for editable */
  GFileInfo *           info;           /* info for this file or NULL if unknown */
119

120
  guint                 row;            /* if valid (see model->n_valid_indexes), visible nodes before and including
121 122
					 * this one - see the "Structure" comment above.
					 */
123

124 125
  guint                 visible :1;     /* if the file is currently visible */
  guint                 frozen_add :1;  /* true if the model was frozen and the entry has not been added yet */
126

127
  GValue                values[1];      /* actually n_columns values */
128 129
};

130
struct _GtkFileSystemModel
131
{
132
  GObject               parent_instance;
133

134 135
  GFile *               dir;            /* directory that's displayed */
  guint                 dir_thaw_source;/* GSource id for unfreezing the model */
136 137
  char *                attributes;     /* attributes the file info must contain, or NULL for all attributes */
  GFileMonitor *        dir_monitor;    /* directory that is monitored, or NULL if monitoring was not supported */
138

139 140
  GCancellable *        cancellable;    /* cancellable in use for all operations - cancelled on dispose */
  GArray *              files;          /* array of FileModelNode containing all our files */
141
  gsize                 node_size;	/* Size of a FileModelNode structure once its ->values field has n_columns */
142 143
  guint                 n_nodes_valid;  /* count of valid nodes (i.e. those whose node->row is accurate) */
  GHashTable *          file_lookup;    /* mapping of GFile => array index in model->files
144 145 146 147 148
					 * This hash table doesn't always have the same number of entries as the files array;
					 * it can get cleared completely when we resort.
					 * The hash table gets re-populated in node_get_for_file() if this mismatch is
					 * detected.
					 */
149

150 151 152 153
  guint                 n_columns;      /* number of columns */
  GType *               column_types;   /* types of each column */
  GtkFileSystemModelGetValue get_func;  /* function to call to fill in values in columns */
  gpointer              get_data;       /* data to pass to get_func */
154

155
  GtkFileFilter *       filter;         /* filter to use for deciding which nodes are visible */
156

157 158 159 160 161 162 163 164
  int                   sort_column_id; /* current sorting column */
  GtkSortType           sort_order;     /* current sorting order */
  GList *               sort_list;      /* list of sorting functions */
  GtkTreeIterCompareFunc default_sort_func; /* default sort function */
  gpointer              default_sort_data; /* data to pass to default sort func */
  GDestroyNotify        default_sort_destroy; /* function to call to destroy default_sort_data */

  guint                 frozen;         /* number of times we're frozen */
165

166 167 168 169 170 171 172
  gboolean              filter_on_thaw :1;/* set when filtering needs to happen upon thawing */
  gboolean              sort_on_thaw :1;/* set when sorting needs to happen upon thawing */

  guint                 show_hidden :1; /* whether to show hidden files */
  guint                 show_folders :1;/* whether to show folders */
  guint                 show_files :1;  /* whether to show files */
};
Owen Taylor's avatar
Owen Taylor committed
173 174 175 176 177 178 179 180

#define GTK_FILE_SYSTEM_MODEL_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILE_SYSTEM_MODEL, GtkFileSystemModelClass))
#define GTK_IS_FILE_SYSTEM_MODEL_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FILE_SYSTEM_MODEL))
#define GTK_FILE_SYSTEM_MODEL_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FILE_SYSTEM_MODEL, GtkFileSystemModelClass))

struct _GtkFileSystemModelClass
{
  GObjectClass parent_class;
Federico Mena Quintero's avatar
Federico Mena Quintero committed
181 182 183

  /* Signals */

184
  void (*finished_loading) (GtkFileSystemModel *model, GError *error);
Federico Mena Quintero's avatar
Federico Mena Quintero committed
185 186
};

187 188 189
static void add_file (GtkFileSystemModel *model,
		      GFile              *file,
		      GFileInfo          *info);
190 191 192
static void remove_file (GtkFileSystemModel *model,
			 GFile              *file);

193 194 195 196 197 198 199 200 201 202 203 204 205 206
/* iter setup:
 * @user_data: the model
 * @user_data2: GUINT_TO_POINTER of array index of current entry
 *
 * All other fields are unused. Note that the array index does not corrspond
 * 1:1 with the path index as entries might not be visible.
 */
#define ITER_INDEX(iter) GPOINTER_TO_UINT((iter)->user_data2)
#define ITER_IS_VALID(model, iter) ((model) == (iter)->user_data)
#define ITER_INIT_FROM_INDEX(model, _iter, _index) G_STMT_START {\
  g_assert (_index < (model)->files->len); \
  (_iter)->user_data = (model); \
  (_iter)->user_data2 = GUINT_TO_POINTER (_index); \
}G_STMT_END
Federico Mena Quintero's avatar
Federico Mena Quintero committed
207

208
/*** FileModelNode ***/
Federico Mena Quintero's avatar
Federico Mena Quintero committed
209

210
/* Get a FileModelNode structure given an index in the model->files array of nodes */
211
#define get_node(_model, _index) ((FileModelNode *) ((_model)->files->data + (_index) * (_model)->node_size))
212 213

/* Get an index within the model->files array of nodes, given a FileModelNode* */
214
#define node_index(_model, _node) (((gchar *) (_node) - (_model)->files->data) / (_model)->node_size)
Owen Taylor's avatar
Owen Taylor committed
215

216 217
/* @up_to_index: smallest model->files array index that will be valid after this call
 * @up_to_row: smallest node->row that will be valid after this call
218
 *
219 220 221
 * If you want to validate up to an index or up to a row, specify the index or
 * the row you want and specify G_MAXUINT for the other argument.  Pass
 * G_MAXUINT for both arguments for "validate everything".
222
 */
Owen Taylor's avatar
Owen Taylor committed
223
static void
224
node_validate_rows (GtkFileSystemModel *model, guint up_to_index, guint up_to_row)
Owen Taylor's avatar
Owen Taylor committed
225
{
226
  guint i, row;
Federico Mena Quintero's avatar
Federico Mena Quintero committed
227

228 229
  if (model->files->len == 0)
    return;
230 231 232 233 234 235

  up_to_index = MIN (up_to_index, model->files->len - 1);

  i = model->n_nodes_valid;
  if (i != 0)
    row = get_node (model, i - 1)->row;
236
  else
237 238 239
    row = 0;

  while (i <= up_to_index && row <= up_to_row)
240
    {
241
      FileModelNode *node = get_node (model, i);
242
      if (node->visible)
243 244 245
        row++;
      node->row = row;
      i++;
246
    }
247
  model->n_nodes_valid = i;
Owen Taylor's avatar
Owen Taylor committed
248 249
}

250
static guint
251
node_get_tree_row (GtkFileSystemModel *model, guint index)
Owen Taylor's avatar
Owen Taylor committed
252
{
253 254
  if (model->n_nodes_valid <= index)
    node_validate_rows (model, index, G_MAXUINT);
255

256
  return get_node (model, index)->row - 1;
Owen Taylor's avatar
Owen Taylor committed
257 258
}

259 260
static void 
node_invalidate_index (GtkFileSystemModel *model, guint id)
Owen Taylor's avatar
Owen Taylor committed
261
{
262
  model->n_nodes_valid = MIN (model->n_nodes_valid, id);
Owen Taylor's avatar
Owen Taylor committed
263 264
}

265 266
static GtkTreePath *
gtk_tree_path_new_from_node (GtkFileSystemModel *model, guint id)
Owen Taylor's avatar
Owen Taylor committed
267
{
268
  guint i = node_get_tree_row (model, id);
Owen Taylor's avatar
Owen Taylor committed
269

270 271 272 273
  g_assert (i < model->files->len);

  return gtk_tree_path_new_from_indices (i, -1);
}
Owen Taylor's avatar
Owen Taylor committed
274

275
static void
276 277 278 279 280 281 282 283 284 285 286 287 288
emit_row_inserted_for_node (GtkFileSystemModel *model, guint id)
{
  GtkTreePath *path;
  GtkTreeIter iter;

  path = gtk_tree_path_new_from_node (model, id);
  ITER_INIT_FROM_INDEX (model, &iter, id);
  gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter);
  gtk_tree_path_free (path);
}

static void
emit_row_changed_for_node (GtkFileSystemModel *model, guint id)
289 290 291
{
  GtkTreePath *path;
  GtkTreeIter iter;
292

293 294 295 296 297 298 299
  path = gtk_tree_path_new_from_node (model, id);
  ITER_INIT_FROM_INDEX (model, &iter, id);
  gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
  gtk_tree_path_free (path);
}

static void
300
emit_row_deleted_for_row (GtkFileSystemModel *model, guint row)
301 302 303
{
  GtkTreePath *path;

304
  path = gtk_tree_path_new_from_indices (row, -1);
305 306 307 308 309 310 311 312 313
  gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
  gtk_tree_path_free (path);
}

static void
node_set_visible (GtkFileSystemModel *model, guint id, gboolean visible)
{
  FileModelNode *node = get_node (model, id);

314 315 316
  if (node->visible == visible ||
      node->frozen_add)
    return;
317

318
  if (visible)
Owen Taylor's avatar
Owen Taylor committed
319
    {
320 321
      node->visible = TRUE;
      node_invalidate_index (model, id);
322
      emit_row_inserted_for_node (model, id);
323 324 325
    }
  else
    {
326
      guint row;
327

328 329
      row = node_get_tree_row (model, id);
      g_assert (row < model->files->len);
330

331 332
      node->visible = FALSE;
      node_invalidate_index (model, id);
333
      emit_row_deleted_for_row (model, row);
Owen Taylor's avatar
Owen Taylor committed
334 335 336
    }
}

337 338
static gboolean
node_should_be_visible (GtkFileSystemModel *model, guint id)
339
{
340
  FileModelNode *node = get_node (model, id);
341 342 343 344 345 346
  GtkFileFilterInfo filter_info = { 0, };
  GtkFileFilterFlags required;
  gboolean is_folder, result;
  char *mime_type = NULL;
  char *filename = NULL;
  char *uri = NULL;
347

348 349
  if (node->info == NULL)
    return FALSE;
350

351 352 353
  if (!model->show_hidden &&
      (g_file_info_get_is_hidden (node->info) || g_file_info_get_is_backup (node->info)))
    return FALSE;
354

355 356 357 358 359 360
  is_folder = _gtk_file_info_consider_as_directory (node->info);
  
  /* wtf? */
  if (model->show_folders != model->show_files &&
      model->show_folders != is_folder)
    return FALSE;
361

362 363
  if (is_folder)
    return TRUE;
364

365 366
  if (model->filter == NULL)
    return TRUE;
367

368 369 370 371 372 373 374 375
  /* fill info */
  required = gtk_file_filter_get_needed (model->filter);

  filter_info.contains = GTK_FILE_FILTER_DISPLAY_NAME;
  filter_info.display_name = g_file_info_get_display_name (node->info);

  if (required & GTK_FILE_FILTER_MIME_TYPE)
    {
376 377 378 379 380 381 382 383 384 385
      const char *s = g_file_info_get_content_type (node->info);
      if (s)
	{
	  mime_type = g_content_type_get_mime_type (s);
	  if (mime_type)
	    {
	      filter_info.mime_type = mime_type;
	      filter_info.contains |= GTK_FILE_FILTER_MIME_TYPE;
	    }
	}
386 387 388 389 390
    }

  if (required & GTK_FILE_FILTER_FILENAME)
    {
      filename = g_file_get_path (node->file);
391
      if (filename)
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414
        {
          filter_info.filename = filename;
	  filter_info.contains |= GTK_FILE_FILTER_FILENAME;
        }
    }

  if (required & GTK_FILE_FILTER_URI)
    {
      uri = g_file_get_uri (node->file);
      if (uri)
        {
          filter_info.uri = uri;
	  filter_info.contains |= GTK_FILE_FILTER_URI;
        }
    }

  result = gtk_file_filter_filter (model->filter, &filter_info);

  g_free (mime_type);
  g_free (filename);
  g_free (uri);

  return result;
415 416
}

417
/*** GtkTreeModel ***/
Owen Taylor's avatar
Owen Taylor committed
418 419 420 421

static GtkTreeModelFlags
gtk_file_system_model_get_flags (GtkTreeModel *tree_model)
{
422 423
  /* GTK_TREE_MODEL_ITERS_PERSIST doesn't work with arrays :( */
  return GTK_TREE_MODEL_LIST_ONLY;
Owen Taylor's avatar
Owen Taylor committed
424 425 426 427 428
}

static gint
gtk_file_system_model_get_n_columns (GtkTreeModel *tree_model)
{
429 430 431
  GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (tree_model);
  
  return model->n_columns;
Owen Taylor's avatar
Owen Taylor committed
432 433 434 435
}

static GType
gtk_file_system_model_get_column_type (GtkTreeModel *tree_model,
436
				       gint          i)
Owen Taylor's avatar
Owen Taylor committed
437
{
438 439 440 441 442
  GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (tree_model);
  
  g_return_val_if_fail (i >= 0 && (guint) i < model->n_columns, G_TYPE_NONE);

  return model->column_types[i];
Owen Taylor's avatar
Owen Taylor committed
443 444
}

445 446
static int
compare_indices (gconstpointer key, gconstpointer _node)
Owen Taylor's avatar
Owen Taylor committed
447
{
448 449
  const FileModelNode *node = _node;

450
  return GPOINTER_TO_UINT (key) - node->row;
451
}
Owen Taylor's avatar
Owen Taylor committed
452

453 454 455 456 457 458 459 460
static gboolean
gtk_file_system_model_iter_nth_child (GtkTreeModel *tree_model,
				      GtkTreeIter  *iter,
				      GtkTreeIter  *parent,
				      gint          n)
{
  GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (tree_model);
  char *node;
461 462
  guint id;
  guint row_to_find;
Owen Taylor's avatar
Owen Taylor committed
463

464
  g_return_val_if_fail (n >= 0, FALSE);
Owen Taylor's avatar
Owen Taylor committed
465

466
  if (parent != NULL)
Owen Taylor's avatar
Owen Taylor committed
467 468
    return FALSE;

469
  row_to_find = n + 1; /* plus one as our node->row numbers are 1-based; see the "Structure" comment at the beginning */
470

471 472
  if (model->n_nodes_valid > 0 &&
      get_node (model, model->n_nodes_valid - 1)->row >= row_to_find)
Owen Taylor's avatar
Owen Taylor committed
473
    {
474 475 476 477 478
      /* Fast path - the nodes are valid up to the sought one.
       *
       * First, find a node with the sought row number...*/

      node = bsearch (GUINT_TO_POINTER (row_to_find), 
479
                      model->files->data,
480
                      model->n_nodes_valid,
481
                      model->node_size,
482 483 484 485
                      compare_indices);
      if (node == NULL)
        return FALSE;

486 487
      /* ... Second, back up until we find the first visible node with that row number */

488 489 490
      id = node_index (model, node);
      while (!get_node (model, id)->visible)
        id--;
491 492

      g_assert (get_node (model, id)->row == row_to_find);
493 494 495
    }
  else
    {
496 497
      /* Slow path - the nodes need to be validated up to the sought one */

498
      node_validate_rows (model, G_MAXUINT, n); /* note that this is really "n", not row_to_find - see node_validate_rows() */
499 500
      id = model->n_nodes_valid - 1;
      if (model->n_nodes_valid == 0 || get_node (model, id)->row != row_to_find)
501
        return FALSE;
Owen Taylor's avatar
Owen Taylor committed
502 503
    }

504
  ITER_INIT_FROM_INDEX (model, iter, id);
Owen Taylor's avatar
Owen Taylor committed
505 506 507
  return TRUE;
}

508 509 510 511 512 513 514 515 516 517
static gboolean
gtk_file_system_model_get_iter (GtkTreeModel *tree_model,
				GtkTreeIter  *iter,
				GtkTreePath  *path)
{
  g_return_val_if_fail (gtk_tree_path_get_depth (path) > 0, FALSE);

  return gtk_file_system_model_iter_nth_child (tree_model, 
                                               iter,
                                               NULL, 
518
                                               gtk_tree_path_get_indices (path)[0]);
519 520
}

Owen Taylor's avatar
Owen Taylor committed
521 522 523 524 525 526
static GtkTreePath *
gtk_file_system_model_get_path (GtkTreeModel *tree_model,
				GtkTreeIter  *iter)
{
  GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (tree_model);
      
527
  g_return_val_if_fail (ITER_IS_VALID (model, iter), NULL);
Owen Taylor's avatar
Owen Taylor committed
528

529
  return gtk_tree_path_new_from_node (model, ITER_INDEX (iter));
Owen Taylor's avatar
Owen Taylor committed
530 531 532 533 534 535 536 537 538
}

static void
gtk_file_system_model_get_value (GtkTreeModel *tree_model,
				 GtkTreeIter  *iter,
				 gint          column,
				 GValue       *value)
{
  GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (tree_model);
539
  const GValue *original;
Owen Taylor's avatar
Owen Taylor committed
540
  
541 542
  g_return_if_fail ((guint) column < model->n_columns);
  g_return_if_fail (ITER_IS_VALID (model, iter));
543

544 545 546 547 548
  original = _gtk_file_system_model_get_value (model, iter, column);
  if (original)
    {
      g_value_init (value, G_VALUE_TYPE (original));
      g_value_copy (original, value);
Owen Taylor's avatar
Owen Taylor committed
549
    }
550 551
  else
    g_value_init (value, model->column_types[column]);
Owen Taylor's avatar
Owen Taylor committed
552 553 554 555 556 557
}

static gboolean
gtk_file_system_model_iter_next (GtkTreeModel *tree_model,
				 GtkTreeIter  *iter)
{
558 559
  GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (tree_model);
  guint i;
Owen Taylor's avatar
Owen Taylor committed
560

561 562 563 564 565
  g_return_val_if_fail (ITER_IS_VALID (model, iter), FALSE);

  for (i = ITER_INDEX (iter) + 1; i < model->files->len; i++) 
    {
      FileModelNode *node = get_node (model, i);
Owen Taylor's avatar
Owen Taylor committed
566

567 568 569 570 571 572 573 574
      if (node->visible)
        {
          ITER_INIT_FROM_INDEX (model, iter, i);
          return TRUE;
        }
    }
      
  return FALSE;
Owen Taylor's avatar
Owen Taylor committed
575 576 577 578 579 580 581
}

static gboolean
gtk_file_system_model_iter_children (GtkTreeModel *tree_model,
				     GtkTreeIter  *iter,
				     GtkTreeIter  *parent)
{
582
  return FALSE;
Owen Taylor's avatar
Owen Taylor committed
583 584 585 586 587 588
}

static gboolean
gtk_file_system_model_iter_has_child (GtkTreeModel *tree_model,
				      GtkTreeIter  *iter)
{
589
  return FALSE;
Owen Taylor's avatar
Owen Taylor committed
590 591 592 593 594 595 596 597 598
}

static gint
gtk_file_system_model_iter_n_children (GtkTreeModel *tree_model,
				       GtkTreeIter  *iter)
{
  GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (tree_model);

  if (iter)
599
    return 0;
Owen Taylor's avatar
Owen Taylor committed
600

601
  return node_get_tree_row (model, model->files->len - 1) + 1;
Owen Taylor's avatar
Owen Taylor committed
602 603 604 605 606 607 608
}

static gboolean
gtk_file_system_model_iter_parent (GtkTreeModel *tree_model,
				   GtkTreeIter  *iter,
				   GtkTreeIter  *child)
{
609
  return FALSE;
Owen Taylor's avatar
Owen Taylor committed
610 611 612 613 614 615
}

static void
gtk_file_system_model_ref_node (GtkTreeModel *tree_model,
				GtkTreeIter  *iter)
{
616
  /* nothing to do */
Owen Taylor's avatar
Owen Taylor committed
617 618 619 620 621 622
}

static void
gtk_file_system_model_unref_node (GtkTreeModel *tree_model,
				  GtkTreeIter  *iter)
{
623
  /* nothing to do */
Owen Taylor's avatar
Owen Taylor committed
624 625
}

626 627
static void
gtk_file_system_model_iface_init (GtkTreeModelIface *iface)
628
{
629 630 631 632 633 634 635 636 637 638 639 640 641 642 643
  iface->get_flags =       gtk_file_system_model_get_flags;
  iface->get_n_columns =   gtk_file_system_model_get_n_columns;
  iface->get_column_type = gtk_file_system_model_get_column_type;
  iface->get_iter =        gtk_file_system_model_get_iter;
  iface->get_path =        gtk_file_system_model_get_path;
  iface->get_value =       gtk_file_system_model_get_value;
  iface->iter_next =       gtk_file_system_model_iter_next;
  iface->iter_children =   gtk_file_system_model_iter_children;
  iface->iter_has_child =  gtk_file_system_model_iter_has_child;
  iface->iter_n_children = gtk_file_system_model_iter_n_children;
  iface->iter_nth_child =  gtk_file_system_model_iter_nth_child;
  iface->iter_parent =     gtk_file_system_model_iter_parent;
  iface->ref_node =        gtk_file_system_model_ref_node;
  iface->unref_node =      gtk_file_system_model_unref_node;
}
644

645
/*** GtkTreeSortable ***/
646

647 648 649 650 651 652 653
typedef struct _SortData SortData;
struct _SortData {
  GtkFileSystemModel *    model;
  GtkTreeIterCompareFunc  func;
  gpointer                data;
  int                     order;        /* -1 to invert sort order or 1 to keep it */
};
654

655 656 657 658 659
/* returns FALSE if no sort necessary */
static gboolean
sort_data_init (SortData *data, GtkFileSystemModel *model)
{
  GtkTreeDataSortHeader *header;
660

661
  if (model->files->len <= 2)
662 663
    return FALSE;

664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681
  switch (model->sort_column_id)
    {
    case GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID:
      if (!model->default_sort_func)
        return FALSE;
      data->func = model->default_sort_func;
      data->data = model->default_sort_data;
      break;
    case GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID:
      return FALSE;
    default:
      header = _gtk_tree_data_list_get_header (model->sort_list, model->sort_column_id);
      if (header == NULL)
        return FALSE;
      data->func = header->func;
      data->data = header->data;
      break;
    }
682

683 684
  data->order = model->sort_order == GTK_SORT_DESCENDING ? -1 : 1;
  data->model = model;
685 686 687
  return TRUE;
}

688 689
static int
compare_array_element (gconstpointer a, gconstpointer b, gpointer user_data)
Federico Mena Quintero's avatar
Federico Mena Quintero committed
690
{
691 692 693 694 695 696
  SortData *data = user_data;
  GtkTreeIter itera, iterb;

  ITER_INIT_FROM_INDEX (data->model, &itera, node_index (data->model, a));
  ITER_INIT_FROM_INDEX (data->model, &iterb, node_index (data->model, b));
  return data->func (GTK_TREE_MODEL (data->model), &itera, &iterb, data->data) * data->order;
Federico Mena Quintero's avatar
Federico Mena Quintero committed
697 698
}

699
static void
700
gtk_file_system_model_sort (GtkFileSystemModel *model)
Federico Mena Quintero's avatar
Federico Mena Quintero committed
701
{
702
  SortData data;
Federico Mena Quintero's avatar
Federico Mena Quintero committed
703

704 705 706 707 708
  if (model->frozen)
    {
      model->sort_on_thaw = TRUE;
      return;
    }
709

710 711 712
  if (sort_data_init (&data, model))
    {
      GtkTreePath *path;
713 714
      guint i;
      guint r, n_visible_rows;
715

716
      node_validate_rows (model, G_MAXUINT, G_MAXUINT);
717
      n_visible_rows = node_get_tree_row (model, model->files->len - 1) + 1;
718
      model->n_nodes_valid = 0;
719
      g_hash_table_remove_all (model->file_lookup);
720
      g_qsort_with_data (get_node (model, 1), /* start at index 1; don't sort the editable row */
721
                         model->files->len - 1,
722
                         model->node_size,
723 724
                         compare_array_element,
                         &data);
725
      g_assert (model->n_nodes_valid == 0);
726
      g_assert (g_hash_table_size (model->file_lookup) == 0);
727
      if (n_visible_rows)
728
        {
729
          int *new_order = g_new (int, n_visible_rows);
730
        
731
          r = 0;
732 733 734 735 736
          for (i = 0; i < model->files->len; i++)
            {
              FileModelNode *node = get_node (model, i);
              if (!node->visible)
                {
737
                  node->row = r;
738 739 740
                  continue;
                }

741
              new_order[r] = node->row - 1;
742 743
              r++;
              node->row = r;
744
            }
745
          g_assert (r == n_visible_rows);
746 747 748 749 750 751 752 753 754
          path = gtk_tree_path_new ();
          gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model),
                                         path,
                                         NULL,
                                         new_order);
          gtk_tree_path_free (path);
          g_free (new_order);
        }
    }
755

756
  model->sort_on_thaw = FALSE;
Federico Mena Quintero's avatar
Federico Mena Quintero committed
757 758
}

759 760
static void
gtk_file_system_model_sort_node (GtkFileSystemModel *model, guint node)
Owen Taylor's avatar
Owen Taylor committed
761
{
762 763 764
  /* FIXME: improve */
  gtk_file_system_model_sort (model);
}
765

766 767 768 769 770 771
static gboolean
gtk_file_system_model_get_sort_column_id (GtkTreeSortable  *sortable,
                                          gint             *sort_column_id,
                                          GtkSortType      *order)
{
  GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (sortable);
772

773 774 775 776
  if (sort_column_id)
    *sort_column_id = model->sort_column_id;
  if (order)
    *order = model->sort_order;
Owen Taylor's avatar
Owen Taylor committed
777

778 779 780
  if (model->sort_column_id == GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID ||
      model->sort_column_id == GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID)
    return FALSE;
781

782
  return TRUE;
Owen Taylor's avatar
Owen Taylor committed
783 784 785
}

static void
786 787 788
gtk_file_system_model_set_sort_column_id (GtkTreeSortable  *sortable,
                                          gint              sort_column_id,
                                          GtkSortType       order)
Owen Taylor's avatar
Owen Taylor committed
789
{
790
  GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (sortable);
Owen Taylor's avatar
Owen Taylor committed
791

792 793
  if ((model->sort_column_id == sort_column_id) &&
      (model->sort_order == order))
Owen Taylor's avatar
Owen Taylor committed
794 795
    return;

796
  if (sort_column_id != GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID)
Owen Taylor's avatar
Owen Taylor committed
797
    {
798
      if (sort_column_id != GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID)
Owen Taylor's avatar
Owen Taylor committed
799
	{
800
	  GtkTreeDataSortHeader *header = NULL;
Owen Taylor's avatar
Owen Taylor committed
801

802 803
	  header = _gtk_tree_data_list_get_header (model->sort_list, 
						   sort_column_id);
Owen Taylor's avatar
Owen Taylor committed
804

805 806 807
	  /* We want to make sure that we have a function */
	  g_return_if_fail (header != NULL);
	  g_return_if_fail (header->func != NULL);
Owen Taylor's avatar
Owen Taylor committed
808 809 810
	}
      else
	{
811
	  g_return_if_fail (model->default_sort_func != NULL);
Owen Taylor's avatar
Owen Taylor committed
812 813 814
	}
    }

815 816 817 818 819 820 821

  model->sort_column_id = sort_column_id;
  model->sort_order = order;

  gtk_tree_sortable_sort_column_changed (sortable);

  gtk_file_system_model_sort (model);
Owen Taylor's avatar
Owen Taylor committed
822 823
}

Owen Taylor's avatar
Owen Taylor committed
824
static void
825 826 827 828 829
gtk_file_system_model_set_sort_func (GtkTreeSortable        *sortable,
                                     gint                    sort_column_id,
                                     GtkTreeIterCompareFunc  func,
                                     gpointer                data,
                                     GDestroyNotify          destroy)
Owen Taylor's avatar
Owen Taylor committed
830
{
831 832 833 834 835
  GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (sortable);

  model->sort_list = _gtk_tree_data_list_set_header (model->sort_list, 
                                                     sort_column_id, 
                                                     func, data, destroy);
Owen Taylor's avatar
Owen Taylor committed
836

837 838
  if (model->sort_column_id == sort_column_id)
    gtk_file_system_model_sort (model);
Owen Taylor's avatar
Owen Taylor committed
839 840
}

841 842 843 844 845
static void
gtk_file_system_model_set_default_sort_func (GtkTreeSortable        *sortable,
                                             GtkTreeIterCompareFunc  func,
                                             gpointer                data,
                                             GDestroyNotify          destroy)
Owen Taylor's avatar
Owen Taylor committed
846
{
847
  GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (sortable);
Owen Taylor's avatar
Owen Taylor committed
848

849
  if (model->default_sort_destroy)
Owen Taylor's avatar
Owen Taylor committed
850
    {
851 852 853 854
      GDestroyNotify d = model->default_sort_destroy;

      model->default_sort_destroy = NULL;
      d (model->default_sort_data);
Owen Taylor's avatar
Owen Taylor committed
855 856
    }

857 858 859
  model->default_sort_func = func;
  model->default_sort_data = data;
  model->default_sort_destroy = destroy;
Owen Taylor's avatar
Owen Taylor committed
860

861 862
  if (model->sort_column_id == GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID)
    gtk_file_system_model_sort (model);
Owen Taylor's avatar
Owen Taylor committed
863 864
}

865 866
static gboolean
gtk_file_system_model_has_default_sort_func (GtkTreeSortable *sortable)
Owen Taylor's avatar
Owen Taylor committed
867
{
868
  GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (sortable);
Owen Taylor's avatar
Owen Taylor committed
869

870
  return (model->default_sort_func != NULL);
Owen Taylor's avatar
Owen Taylor committed
871 872
}

873 874
static void
gtk_file_system_model_sortable_init (GtkTreeSortableIface *iface)
Owen Taylor's avatar
Owen Taylor committed
875
{
876 877 878 879 880
  iface->get_sort_column_id = gtk_file_system_model_get_sort_column_id;
  iface->set_sort_column_id = gtk_file_system_model_set_sort_column_id;
  iface->set_sort_func = gtk_file_system_model_set_sort_func;
  iface->set_default_sort_func = gtk_file_system_model_set_default_sort_func;
  iface->has_default_sort_func = gtk_file_system_model_has_default_sort_func;
Owen Taylor's avatar
Owen Taylor committed
881 882
}

883 884 885 886 887
/*** GtkTreeDragSource ***/

static gboolean
drag_source_row_draggable (GtkTreeDragSource *drag_source,
			   GtkTreePath       *path)
Owen Taylor's avatar
Owen Taylor committed
888
{
889 890
  GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (drag_source);
  GtkTreeIter iter;
Owen Taylor's avatar
Owen Taylor committed
891

892 893
  if (!gtk_file_system_model_get_iter (GTK_TREE_MODEL (model), &iter, path))
    return FALSE;
894

895
  return ITER_INDEX (&iter) != 0;
Owen Taylor's avatar
Owen Taylor committed
896 897
}

898 899 900 901
static gboolean
drag_source_drag_data_get (GtkTreeDragSource *drag_source,
			   GtkTreePath       *path,
			   GtkSelectionData  *selection_data)
Owen Taylor's avatar
Owen Taylor committed
902
{
903 904 905 906
  GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (drag_source);
  FileModelNode *node;
  GtkTreeIter iter;
  char *uris[2]; 
Owen Taylor's avatar
Owen Taylor committed
907

908 909
  if (!gtk_file_system_model_get_iter (GTK_TREE_MODEL (model), &iter, path))
    return FALSE;
Owen Taylor's avatar
Owen Taylor committed
910

911 912 913
  node = get_node (model, ITER_INDEX (&iter));
  if (node->file == NULL)
    return FALSE;
Owen Taylor's avatar
Owen Taylor committed
914

915 916 917 918
  uris[0] = g_file_get_uri (node->file);
  uris[1] = NULL;
  gtk_selection_data_set_uris (selection_data, uris);
  g_free (uris[0]);
Owen Taylor's avatar
Owen Taylor committed
919

920
  return TRUE;
Owen Taylor's avatar
Owen Taylor committed
921
}
Owen Taylor's avatar
Owen Taylor committed
922

923 924
static void
drag_source_iface_init (GtkTreeDragSourceIface *iface)
Owen Taylor's avatar
Owen Taylor committed
925
{
926 927 928
  iface->row_draggable = drag_source_row_draggable;
  iface->drag_data_get = drag_source_drag_data_get;
  iface->drag_data_delete = NULL;
Owen Taylor's avatar
Owen Taylor committed
929 930
}

931
/*** GtkFileSystemModel ***/
932

933 934 935 936
/* Signal IDs */
enum {
  FINISHED_LOADING,
  LAST_SIGNAL
937 938
};

939 940 941 942 943 944 945 946 947 948 949 950
static guint file_system_model_signals[LAST_SIGNAL] = { 0 };



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))

951
static void
952
gtk_file_system_model_dispose (GObject *object)
953
{
954 955
  GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (object);

956 957 958 959 960 961
  if (model->dir_thaw_source)
    {
      g_source_remove (model->dir_thaw_source);
      model->dir_thaw_source = 0;
    }

962 963 964
  g_cancellable_cancel (model->cancellable);
  if (model->dir_monitor)
    g_file_monitor_cancel (model->dir_monitor);
965

966 967
  G_OBJECT_CLASS (_gtk_file_system_model_parent_class)->dispose (object);
}
968 969


970 971 972 973 974
static void
gtk_file_system_model_finalize (GObject *object)
{
  GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (object);
  guint i;
975

976
  for (i = 0; i < model->files->len; i++)
977
    {
978 979
      int v;

980 981 982 983 984
      FileModelNode *node = get_node (model, i);
      if (node->file)
        g_object_unref (node->file);
      if (node->info)
        g_object_unref (node->info);
985

986 987
      for (v = 0; v < model->n_columns; v++)
	if (G_VALUE_TYPE (&node->values[v]) != G_TYPE_INVALID)
988
	  g_value_unset (&node->values[v]);
989 990
    }
  g_array_free (model->files, TRUE);
991

992 993 994 995 996 997 998
  g_object_unref (model->cancellable);
  g_free (model->attributes);
  if (model->dir)
    g_object_unref (model->dir);
  if (model->dir_monitor)
    g_object_unref (model->dir_monitor);
  g_hash_table_destroy (model->file_lookup);
999 1000
  if (model->filter)
    g_object_unref (model->filter);
1001

1002
  g_slice_free1 (sizeof (GType) * model->n_columns, model->column_types);
1003

1004 1005 1006
  _gtk_tree_data_list_header_free (model->sort_list);
  if (model->default_sort_destroy)
    model->default_sort_destroy (model->default_sort_data);
1007

1008 1009
  G_OBJECT_CLASS (_gtk_file_system_model_parent_class)->finalize (object);
}
1010

1011 1012 1013 1014
static void
_gtk_file_system_model_class_init (GtkFileSystemModelClass *class)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
1015

1016 1017
  gobject_class->finalize = gtk_file_system_model_finalize;
  gobject_class->dispose = gtk_file_system_model_dispose;
1018

1019 1020 1021 1022 1023 1024 1025 1026 1027
  file_system_model_signals[FINISHED_LOADING] =
    g_signal_new (I_("finished-loading"),
		  G_OBJECT_CLASS_TYPE (gobject_class),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (GtkFileSystemModelClass, finished_loading),
		  NULL, NULL,
		  _gtk_marshal_VOID__POINTER,
		  G_TYPE_NONE, 1, G_TYPE_POINTER);
}
1028

1029 1030 1031 1032 1033 1034
static void
_gtk_file_system_model_init (GtkFileSystemModel *model)
{
  model->show_files = TRUE;
  model->show_folders = TRUE;
  model->show_hidden = FALSE;
1035

1036
  model->sort_column_id = GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID;
1037

1038 1039 1040
  model->file_lookup = g_hash_table_new (g_file_hash, (GEqualFunc) g_file_equal);
  model->cancellable = g_cancellable_new ();
}
1041

1042
/*** API ***/
1043

1044 1045 1046 1047 1048
static void
gtk_file_system_model_closed_enumerator (GObject *object, GAsyncResult *res, gpointer data)
{
  g_file_enumerator_close_finish (G_FILE_ENUMERATOR (object), res, NULL);
}
1049

1050 1051 1052 1053
static gboolean
thaw_func (gpointer data)
{
  GtkFileSystemModel *model = data;
1054

1055 1056
  _gtk_file_system_model_thaw_updates (model);
  model->dir_thaw_source = 0;
1057

1058
  return FALSE;
1059 1060
}

1061 1062
static void
gtk_file_system_model_got_files (GObject *object, GAsyncResult *res, gpointer data)
Owen Taylor's avatar
Owen Taylor committed
1063
{
1064 1065 1066 1067
  GFileEnumerator *enumerator = G_FILE_ENUMERATOR (object);
  GtkFileSystemModel *model = data;
  GList *walk, *files;
  GError *error = NULL;
Owen Taylor's avatar
Owen Taylor committed
1068

1069
  gdk_threads_enter ();
1070

1071
  files = g_file_enumerator_next_files_finish (enumerator, res, &error);
1072

1073
  if (files)
1074
    {
1075
      if (model->dir_thaw_source == 0)
1076
        {
1077 1078 1079 1080 1081 1082 1083
          _gtk_file_system_model_freeze_updates (model);
          model->dir_thaw_source = gdk_threads_add_timeout_full (IO_PRIORITY + 1,
                                                                 50,
                                                                 thaw_func,
                                                                 model,
                                                                 NULL);
        }
Owen Taylor's avatar
Owen Taylor committed
1084

1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099
      for (walk = files; walk; walk = walk->next)
        {
          const char *name;
          GFileInfo *info;
          GFile *file;
          
          info = walk->data;
          name = g_file_info_get_name (info);
          if (name == NULL)
            {
              /* Shouldn't happen, but the APIs allow it */
              g_object_unref (info);
              continue;
            }
          file = g_file_get_child (model->dir, name);
1100
          add_file (model, file, info);
1101 1102 1103 1104
          g_object_unref (file);
          g_object_unref (info);
        }
      g_list_free (files);
1105

1106 1107 1108 1109 1110 1111 1112 1113
      g_file_enumerator_next_files_async (enumerator,
					  g_file_is_native (model->dir) ? 50 * FILES_PER_QUERY : FILES_PER_QUERY,
					  IO_PRIORITY,
					  model->cancellable,
					  gtk_file_system_model_got_files,
					  model);
    }
  else
1114
    {
1115
      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1116
        {
1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127
          g_file_enumerator_close_async (enumerator,
                                         IO_PRIORITY,
                                         model->cancellable,
                                         gtk_file_system_model_closed_enumerator,
                                         NULL);
          if (model->dir_thaw_source != 0)
            {
              g_source_remove (model->dir_thaw_source);
              model->dir_thaw_source = 0;
              _gtk_file_system_model_thaw_updates (model);
            }
1128

1129 1130
          g_signal_emit (model, file_system_model_signals[FINISHED_LOADING], 0, error);
        }
1131

1132 1133
      if (error)
        g_error_free (error);
1134
    }
Owen Taylor's avatar
Owen Taylor committed
1135

1136
  gdk_threads_leave ();
Owen Taylor's avatar
Owen Taylor committed
1137 1138 1139
}

static void
1140 1141 1142
gtk_file_system_model_query_done (GObject *     object,
                                  GAsyncResult *res,
                                  gpointer      data)
Owen Taylor's avatar
Owen Taylor committed
1143
{
1144 1145 1146
  GtkFileSystemModel *model = data; /* only a valid pointer if not cancelled */
  GFile *file = G_FILE (object);
  GFileInfo *info;
Owen Taylor's avatar
Owen Taylor committed
1147

1148 1149 1150
  info = g_file_query_info_finish (file, res, NULL);
  if (info == NULL)
    return;
Owen Taylor's avatar
Owen Taylor committed
1151

1152
  _gtk_file_system_model_update_file (model, file, info, TRUE);
Owen Taylor's avatar
Owen Taylor committed
1153 1154
}

1155 1156 1157 1158 1159 1160
static void
gtk_file_system_model_monitor_change (GFileMonitor *      monitor,
                                      GFile *             file,
                                      GFile *             other_file,
                                      GFileMonitorEvent   type,
                                      GtkFileSystemModel *model)
Owen Taylor's avatar
Owen Taylor committed
1161
{
1162
  switch (type)
Owen Taylor's avatar
Owen Taylor committed
1163
    {
1164 1165 1166 1167 1168 1169
      case G_FILE_MONITOR_EVENT_CREATED:
      case G_FILE_MONITOR_EVENT_CHANGED:
      case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
        /* We can treat all of these the same way */
        g_file_query_info_async (file,
                                 model->attributes,
1170
                                 G_FILE_QUERY_INFO_NONE,
1171 1172 1173 1174 1175 1176
                                 IO_PRIORITY,
                                 model->cancellable,
                                 gtk_file_system_model_query_done,
                                 model);
        break;
      case G_FILE_MONITOR_EVENT_DELETED:
1177
        remove_file (model, file);
1178 1179 1180 1181 1182 1183 1184 1185
        break;
      case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
        /* FIXME: use freeze/thaw with this somehow? */
      case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
      case G_FILE_MONITOR_EVENT_UNMOUNTED:
      default:
        /* ignore these */
        break;
Owen Taylor's avatar
Owen Taylor committed
1186 1187 1188
    }
}

1189 1190
static void
gtk_file_system_model_got_enumerator (GObject *dir, GAsyncResult *res, gpointer data)
Owen Taylor's avatar
Owen Taylor committed
1191
{
1192 1193 1194
  GtkFileSystemModel *model = data;
  GFileEnumerator *enumerator;
  GError *error = NULL;
1195

1196
  gdk_threads_enter ();
Owen Taylor's avatar
Owen Taylor committed
1197

1198 1199
  enumerator = g_file_enumerate_children_finish (G_FILE (dir), res, &error);
  if (enumerator == NULL)
Owen Taylor's avatar
Owen Taylor committed
1200
    {
1201 1202 1203 1204 1205
      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
      {
        g_signal_emit (model, file_system_model_signals[FINISHED_LOADING], 0, error);
        g_error_free (error);
      }
Owen Taylor's avatar
Owen Taylor committed
1206
    }
1207
  else
1208
    {
1209 1210 1211 1212 1213 1214 1215 1216
      g_file_enumerator_next_files_async (enumerator,
                                          g_file_is_native (model->dir) ? 50 * FILES_PER_QUERY : FILES_PER_QUERY,
                                          IO_PRIORITY,
                                          model->cancellable,
                                          gtk_file_system_model_got_files,
                                          model);
      g_object_unref (enumerator);
      model->dir_monitor = g_file_monitor_directory (model->dir,
1217
                                                     G_FILE_MONITOR_NONE,
1218
                                                     model->cancellable,
1219
                                                     NULL); /* we don't mind if directory monitoring isn't supported, so the GError is NULL here */
1220 1221 1222 1223 1224
      if (model->dir_monitor)
        g_signal_connect (model->dir_monitor,
                          "changed",
                          G_CALLBACK (gtk_file_system_model_monitor_change),
                          model);
1225
    }
1226 1227

  gdk_threads_leave ();
Owen Taylor's avatar
Owen Taylor committed
1228 1229 1230
}

static void
1231 1232 1233
gtk_file_system_model_set_n_columns (GtkFileSystemModel *model,
                                     gint                n_columns,
                                     va_list             args)
Owen Taylor's avatar
Owen Taylor committed
1234
{
1235
  guint i;
1236

1237
  g_assert (model->files == NULL);
1238
  g_assert (n_columns > 0);
1239

1240
  model->n_columns = n_columns;
1241 1242 1243
  model->column_types = g_slice_alloc (sizeof (GType) * n_columns);

  model->node_size = sizeof (FileModelNode) + sizeof (GValue) * (n_columns - 1); /* minus 1 because FileModelNode.values[] has a default size of 1 */
1244

1245
  for (i = 0; i < (guint) n_columns; i++)
1246
    {
1247 1248
      GType type = va_arg (args, GType);
      if (! _gtk_tree_data_list_check_type (type))
1249
	{
1250 1251
	  g_error ("%s: type %s cannot be a column type for GtkFileSystemModel\n", G_STRLOC, g_type_name (type));
          return; /* not reached */
1252 1253
	}

1254
      model->column_types[i] = type;
1255 1256
    }

1257
  model->sort_list = _gtk_tree_data_list_header_new (n_columns, model->column_types);
1258

1259
  model->files = g_array_sized_new (FALSE, FALSE, model->node_size, FILES_PER_QUERY);
1260 1261
  /* add editable node at start */
  g_array_set_size (model->files, 1);
1262
  memset (get_node (model, 0), 0, model->node_size);
Owen Taylor's avatar
Owen Taylor committed
1263 1264 1265
}

static void
1266 1267 1268
gtk_file_system_model_set_directory (GtkFileSystemModel *model,
                                     GFile *             dir,
			             const gchar *       attributes)
Owen Taylor's avatar
Owen Taylor committed
1269
{
1270
  g_assert (G_IS_FILE (dir));
1271 1272 1273 1274 1275 1276

  model->dir = g_object_ref (dir);
  model->attributes = g_strdup (attributes);

  g_file_enumerate_children_async (model->dir,
                                   attributes,
1277
                                   G_FILE_QUERY_INFO_NONE,
1278 1279 1280 1281
                                   IO_PRIORITY,
                                   model->cancellable,
                                   gtk_file_system_model_got_enumerator,
                                   model);
1282

Owen Taylor's avatar
Owen Taylor committed
1283 1284
}

1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301
static GtkFileSystemModel *
_gtk_file_system_model_new_valist (GtkFileSystemModelGetValue get_func,
                                   gpointer            get_data,
                                   guint               n_columns,
                                   va_list             args)
{
  GtkFileSystemModel *model;

  model = g_object_new (GTK_TYPE_FILE_SYSTEM_MODEL, NULL);
  model->get_func = get_func;
  model->get_data = get_data;

  gtk_file_system_model_set_n_columns (model, n_columns, args);

  return model;
}

1302 1303
/**
 * _gtk_file_system_model_new:
1304 1305 1306 1307 1308 1309
 * @get_func: function to call for getting a value
 * @get_data: user data argument passed to @get_func
 * @n_columns: number of columns
 * @...: @n_columns #GType types for the columns
 *
 * Creates a new #GtkFileSystemModel object. You need to add files
1310 1311
 * to the list using _gtk_file_system_model_add_and_query_file()
 * or _gtk_file_system_model_update_file().
1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335
 *
 * Return value: the newly created #GtkFileSystemModel
 **/
GtkFileSystemModel *
_gtk_file_system_model_new (GtkFileSystemModelGetValue get_func,
                            gpointer            get_data,
                            guint               n_columns,
                            ...)
{
  GtkFileSystemModel *model;
  va_list args;

  g_return_val_if_fail (get_func != NULL, NULL);
  g_return_val_if_fail (n_columns > 0, NULL);

  va_start (args, n_columns);
  model = _gtk_file_system_model_new_valist (get_func, get_data, n_columns, args);
  va_end (args);

  return model;
}

/**
 * _gtk_file_system_model_new_for_directory:
1336
 * @directory: the directory to show.
1337
 * @attributes: (allow-none): attributes to immediately load or %NULL for all
1338