gtkfilesystemmodel.c 61.5 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"
34
#include "gtkalias.h"
Owen Taylor's avatar
Owen Taylor committed
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 74
/*** 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
75
 * the treeview to array indexes in our array of files.  And thus we introduce a bit of terminology:
76
 *
77 78 79 80 81 82 83 84 85 86
 *   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
87
 * simpler, believe it or not :)  This also means that when the calling GtkTreeView gives us a GtkTreePath, we
88 89 90 91 92 93 94 95 96 97
 * 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.
98
 *
99 100
 * 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.
101 102
 */

103
/*** DEFINES ***/
104

105 106 107 108
/* 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
109

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

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

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

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

125 126
  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 */
127

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

131
struct _GtkFileSystemModel
132
{
133
  GObject               parent_instance;
134

135 136
  GFile *               dir;            /* directory that's displayed */
  guint                 dir_thaw_source;/* GSource id for unfreezing the model */
137 138
  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 */
139

140 141
  GCancellable *        cancellable;    /* cancellable in use for all operations - cancelled on dispose */
  GArray *              files;          /* array of FileModelNode containing all our files */
142
  GSize                 node_size;	/* Size of a FileModelNode structure once its ->values field has n_columns */
143 144
  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
145 146 147 148 149
					 * 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.
					 */
150

151 152 153 154
  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 */
155

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

158 159 160 161 162 163 164 165
  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 */
166

167 168 169 170 171 172 173
  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
174 175 176 177 178 179 180 181

#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
182 183 184

  /* Signals */

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

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

194 195 196 197 198 199 200 201 202 203 204 205 206 207
/* 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
208

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

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

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

217 218
/* @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
219
 *
220 221 222
 * 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".
223
 */
Owen Taylor's avatar
Owen Taylor committed
224
static void
225
node_validate_rows (GtkFileSystemModel *model, guint up_to_index, guint up_to_row)
Owen Taylor's avatar
Owen Taylor committed
226
{
227
  guint i, row;
Federico Mena Quintero's avatar
Federico Mena Quintero committed
228

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

  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;
237
  else
238 239 240
    row = 0;

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

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

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

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

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

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

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

276
static void
277 278 279 280 281 282 283 284 285 286 287 288 289
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)
290 291 292
{
  GtkTreePath *path;
  GtkTreeIter iter;
Federico Mena Quintero's avatar
Federico Mena Quintero committed
293

294 295 296 297 298 299 300
  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
301
emit_row_deleted_for_row (GtkFileSystemModel *model, guint row)
302 303 304
{
  GtkTreePath *path;

305
  path = gtk_tree_path_new_from_indices (row, -1);
306 307 308 309 310 311 312 313 314
  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);

315 316 317
  if (node->visible == visible ||
      node->frozen_add)
    return;
Federico Mena Quintero's avatar
Federico Mena Quintero committed
318

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

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

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

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

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

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

356 357 358 359 360 361
  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;
362

363 364
  if (is_folder)
    return TRUE;
365

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

369 370 371 372 373 374 375 376
  /* 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)
    {
377 378 379 380 381 382 383 384 385 386
      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;
	    }
	}
387 388 389 390 391
    }

  if (required & GTK_FILE_FILTER_FILENAME)
    {
      filename = g_file_get_path (node->file);
392
      if (filename)
393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415
        {
          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;
416 417
}

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

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

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

static GType
gtk_file_system_model_get_column_type (GtkTreeModel *tree_model,
437
				       gint          i)
Owen Taylor's avatar
Owen Taylor committed
438
{
439 440 441 442 443
  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
444 445
}

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

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

454 455 456 457 458 459 460 461
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;
462 463
  guint id;
  guint row_to_find;
Owen Taylor's avatar
Owen Taylor committed
464

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

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

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

472 473
  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
474
    {
475 476 477 478 479
      /* 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), 
480
                      model->files->data,
481
                      model->n_nodes_valid,
482
                      model->node_size,
483 484 485 486
                      compare_indices);
      if (node == NULL)
        return FALSE;

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

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

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

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

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

509 510 511 512 513 514 515 516 517 518 519 520 521
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, 
                                               gtk_tree_path_get_indices (path)[0]);
}

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

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

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);
540
  const GValue *original;
Owen Taylor's avatar
Owen Taylor committed
541
  
542 543
  g_return_if_fail ((guint) column < model->n_columns);
  g_return_if_fail (ITER_IS_VALID (model, iter));
544

545 546 547 548 549
  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
550
    }
551 552
  else
    g_value_init (value, model->column_types[column]);
Owen Taylor's avatar
Owen Taylor committed
553 554 555 556 557 558
}

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

562 563 564 565 566
  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
567

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

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

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

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

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

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

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

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

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

627 628
static void
gtk_file_system_model_iface_init (GtkTreeModelIface *iface)
629
{
630 631 632 633 634 635 636 637 638 639 640 641 642 643 644
  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;
}
645

646
/*** GtkTreeSortable ***/
647

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

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

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

665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682
  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;
    }
683

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

689 690
static int
compare_array_element (gconstpointer a, gconstpointer b, gpointer user_data)
Federico Mena Quintero's avatar
Federico Mena Quintero committed
691
{
692 693 694 695 696 697
  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
698 699
}

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

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

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

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

742 743 744
              new_order[r] = node->row;
              r++;
              node->row = r;
745
            }
746
          g_assert (r == n_visible_rows);
747 748 749 750 751 752 753 754 755
          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);
        }
    }
756

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

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

767 768 769 770 771 772
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);
773

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

779 780 781
  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;
782

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

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

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

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

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

806 807 808
	  /* 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
809 810 811
	}
      else
	{
812
	  g_return_if_fail (model->default_sort_func != NULL);
Owen Taylor's avatar
Owen Taylor committed
813 814 815
	}
    }

816 817 818 819 820 821 822

  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
823 824
}

Owen Taylor's avatar
Owen Taylor committed
825
static void
826 827 828 829 830
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
831
{
832 833 834 835 836
  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
837

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

842 843 844 845 846
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
847
{
848
  GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (sortable);
Owen Taylor's avatar
Owen Taylor committed
849

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

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

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

862 863
  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
864 865
}

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

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

874 875
static void
gtk_file_system_model_sortable_init (GtkTreeSortableIface *iface)
Owen Taylor's avatar
Owen Taylor committed
876
{
877 878 879 880 881
  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
882 883
}

884 885 886 887 888
/*** GtkTreeDragSource ***/

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

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

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

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

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

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

916 917 918 919
  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
920

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

924 925
static void
drag_source_iface_init (GtkTreeDragSourceIface *iface)
Owen Taylor's avatar
Owen Taylor committed
926
{
927 928 929
  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
930 931
}

932
/*** GtkFileSystemModel ***/
933

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

940 941 942 943 944 945 946 947 948 949 950 951
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))

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

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

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

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


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

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

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

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

993 994 995 996 997 998 999
  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);
1000 1001
  if (model->filter)
    g_object_unref (model->filter);
1002

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

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

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

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

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

1020 1021 1022 1023 1024 1025 1026 1027 1028
  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);
}
1029

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

1037
  model->sort_column_id = GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID;
1038

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

1043
/*** API ***/
1044

1045 1046 1047 1048 1049
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);
}
1050

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

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

1059
  return FALSE;
1060 1061
}

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

1070
  gdk_threads_enter ();
1071

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

1074
  if (files)
1075
    {
1076
      if (model->dir_thaw_source == 0)
1077
        {
1078 1079 1080 1081 1082 1083 1084
          _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
1085

1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100
      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);
1101
          add_file (model, file, info);
1102 1103 1104 1105
          g_object_unref (file);
          g_object_unref (info);
        }
      g_list_free (files);
1106

1107 1108 1109 1110 1111 1112 1113 1114
      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
1115
    {
1116 1117 1118 1119
      g_file_enumerator_close_async (enumerator, 
                                     IO_PRIORITY,
                                     model->cancellable,
                                     gtk_file_system_model_closed_enumerator,
1120
                                     model);
1121
      if (model->dir_thaw_source != 0)
1122
        {
1123 1124 1125
          g_source_remove (model->dir_thaw_source);
          model->dir_thaw_source = 0;
          _gtk_file_system_model_thaw_updates (model);
1126 1127
        }

1128 1129
      g_signal_emit (model, file_system_model_signals[FINISHED_LOADING], 0, error);

1130 1131 1132
      if (error)
        g_error_free (error);

1133
      g_object_unref (model);
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