gtk2/gtk/gtkfilesystemmodel.c
Matthias Clasen 1028449bcc filesystemmodel: Drop unneded deprecation guards
Nothing deprecated is used here anymore.
2022-10-21 21:48:39 -04:00

1255 lines
39 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* GTK - The GIMP Toolkit
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gtkfilesystemmodel.h"
#include <stdlib.h>
#include <string.h>
#include "gtkfilechooserutils.h"
#include "gtkmarshalers.h"
#include "gtkfilter.h"
#include "gtkprivate.h"
/*** 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.
*
* Internal data structure
* -----------------------
*
* 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.
*
* 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. Also, a file filter may be explicitly set onto the model, for example, to only show files that
* match “*.jpg”. In this case, node->filtered_out says whether the node failed the filter. The ultimate
* decision on whether a node is visible or not in the treeview is distilled into the node->visible field.
* The reason for having a separate node->filtered_out field is so that the file chooser can query whether
* a (filtered-out) folder should be made sensitive in the GUI.
*
* Visible rows vs. possibly-invisible nodes
* -----------------------------------------
*
* Since not all nodes in the model->files array may be visible, we need a way to map visible row indexes from
* the treeview to array indexes in our array of files. And thus we introduce a bit of terminology:
*
* 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
* simpler, believe it or not :) This also means that when the calling GtkTreeView gives us a GtkTreePath, we
* 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 nodes 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.
*
* 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.
*/
/*** DEFINES ***/
/* 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
/* random number that everyone else seems to use, too */
#define FILES_PER_QUERY 100
typedef struct _FileModelNode FileModelNode;
typedef struct _GtkFileSystemModelClass GtkFileSystemModelClass;
struct _FileModelNode
{
GFile * file; /* file represented by this node or NULL for editable */
GFileInfo * info; /* info for this file or NULL if unknown */
guint row; /* if valid (see model->n_valid_indexes), visible nodes before and including
* this one - see the "Structure" comment above.
*/
guint visible :1; /* if the file is currently visible */
guint filtered_out :1;/* if the file is currently filtered out (i.e. it didn't pass the filters) */
guint frozen_add :1; /* true if the model was frozen and the entry has not been added yet */
};
struct _GtkFileSystemModel
{
GObject parent_instance;
GFile * dir; /* directory that's displayed */
guint dir_thaw_source;/* GSource id for unfreezing the model */
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 */
GCancellable * cancellable; /* cancellable in use for all operations - cancelled on dispose */
GArray * files; /* array of FileModelNode containing all our files */
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
* This hash table doesn't always have the same number of entries as the files array;
* The hash table gets re-populated in node_get_for_file() if this mismatch is
* detected.
*/
GtkFileFilter * filter; /* filter to use for deciding which nodes are visible */
guint frozen; /* number of times we're frozen */
gboolean filter_on_thaw :1;/* set when filtering 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 */
guint filter_folders :1;/* whether filter applies to folders */
};
#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;
/* Signals */
void (*finished_loading) (GtkFileSystemModel *model, GError *error);
};
static void freeze_updates (GtkFileSystemModel *model);
static void thaw_updates (GtkFileSystemModel *model);
static guint node_get_for_file (GtkFileSystemModel *model,
GFile *file);
static void add_file (GtkFileSystemModel *model,
GFile *file,
GFileInfo *info);
static void remove_file (GtkFileSystemModel *model,
GFile *file);
/* 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 correspond
* 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
/*** FileModelNode ***/
/* Get a FileModelNode structure given an index in the model->files array of nodes */
#define get_node(_model, _index) ((FileModelNode *) ((_model)->files->data + (_index) * sizeof (FileModelNode)))
/* Get an index within the model->files array of nodes, given a FileModelNode* */
#define node_index(_model, _node) (((char *) (_node) - (_model)->files->data) / sizeof (FileModelNode))
/* @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
*
* 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”.
*/
static void
node_validate_rows (GtkFileSystemModel *model, guint up_to_index, guint up_to_row)
{
guint i, row;
if (model->files->len == 0)
return;
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;
else
row = 0;
while (i <= up_to_index && row <= up_to_row)
{
FileModelNode *node = get_node (model, i);
if (node->visible)
row++;
node->row = row;
i++;
}
model->n_nodes_valid = i;
}
static guint G_GNUC_UNUSED
node_get_tree_row (GtkFileSystemModel *model, guint index)
{
if (model->n_nodes_valid <= index)
node_validate_rows (model, index, G_MAXUINT);
return get_node (model, index)->row - 1;
}
static void
node_invalidate_index (GtkFileSystemModel *model, guint id)
{
model->n_nodes_valid = MIN (model->n_nodes_valid, id);
}
static void
node_set_visible_and_filtered_out (GtkFileSystemModel *model, guint id, gboolean visible, gboolean filtered_out)
{
FileModelNode *node = get_node (model, id);
/* Filteredness */
g_file_info_set_attribute_boolean (node->info, "filechooser::filtered-out", filtered_out);
if (node->filtered_out != filtered_out)
{
node->filtered_out = filtered_out;
}
/* Visibility */
g_file_info_set_attribute_boolean (node->info, "filechooser::visible", visible);
if (node->visible == visible ||
node->frozen_add)
return;
if (visible)
{
node->visible = TRUE;
node_invalidate_index (model, id);
}
else
{
g_assert (node_get_tree_row (model, id) < model->files->len);
node->visible = FALSE;
node_invalidate_index (model, id);
}
}
static gboolean
node_should_be_filtered_out (GtkFileSystemModel *model, guint id)
{
FileModelNode *node = get_node (model, id);
if (node->info == NULL)
return TRUE;
if (model->filter == NULL)
return FALSE;
g_assert (g_file_info_has_attribute (node->info, "standard::file"));
return !gtk_filter_match (GTK_FILTER (model->filter), node->info);
}
static gboolean
node_should_be_visible (GtkFileSystemModel *model, guint id, gboolean filtered_out)
{
FileModelNode *node = get_node (model, id);
gboolean result;
if (node->info == NULL)
return FALSE;
if (!model->show_hidden &&
(g_file_info_get_is_hidden (node->info) || g_file_info_get_is_backup (node->info)))
return FALSE;
if (_gtk_file_info_consider_as_directory (node->info))
{
if (!model->show_folders)
return FALSE;
if (!model->filter_folders)
return TRUE;
}
else
{
if (!model->show_files)
return FALSE;
}
result = !filtered_out;
return result;
}
static void
node_compute_visibility_and_filters (GtkFileSystemModel *model, guint id)
{
gboolean filtered_out;
gboolean visible;
filtered_out = node_should_be_filtered_out (model, id);
visible = node_should_be_visible (model, id, filtered_out);
node_set_visible_and_filtered_out (model, id, visible, filtered_out);
}
/*** GListModel ***/
static GType
list_model_get_item_type (GListModel *list_model)
{
return G_TYPE_FILE_INFO;
}
static guint
list_model_get_n_items (GListModel *list_model)
{
GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (list_model);
return model->files->len - 1;
}
static gpointer
list_model_get_item (GListModel *list_model,
guint position)
{
GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (list_model);
FileModelNode *node;
/* The first items of GtkFileSystemModel is not really a file,
* so ignore it. */
if (position + 1 >= model->files->len)
return NULL;
node = get_node (model, position + 1);
return g_object_ref (node->info);
}
static void
g_list_model_iface_init (GListModelInterface *iface)
{
iface->get_item_type = list_model_get_item_type;
iface->get_n_items = list_model_get_n_items;
iface->get_item = list_model_get_item;
}
/*** GtkFileSystemModel ***/
/* Signal IDs */
enum {
FINISHED_LOADING,
LAST_SIGNAL
};
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 (G_TYPE_LIST_MODEL,
g_list_model_iface_init))
static void
gtk_file_system_model_dispose (GObject *object)
{
GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (object);
if (model->dir_thaw_source)
{
g_source_remove (model->dir_thaw_source);
model->dir_thaw_source = 0;
}
g_cancellable_cancel (model->cancellable);
if (model->dir_monitor)
g_file_monitor_cancel (model->dir_monitor);
G_OBJECT_CLASS (_gtk_file_system_model_parent_class)->dispose (object);
}
static void
gtk_file_system_model_finalize (GObject *object)
{
GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (object);
guint i;
for (i = 0; i < model->files->len; i++)
{
FileModelNode *node = get_node (model, i);
g_clear_object (&node->file);
g_clear_object (&node->info);
}
g_array_free (model->files, TRUE);
g_clear_object (&model->cancellable);
g_clear_pointer (&model->attributes, g_free);
g_clear_object (&model->dir);
g_clear_object (&model->dir_monitor);
g_clear_pointer (&model->file_lookup, g_hash_table_destroy);
g_clear_object (&model->filter);
G_OBJECT_CLASS (_gtk_file_system_model_parent_class)->finalize (object);
}
static void
_gtk_file_system_model_class_init (GtkFileSystemModelClass *class)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
gobject_class->finalize = gtk_file_system_model_finalize;
gobject_class->dispose = gtk_file_system_model_dispose;
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,
NULL,
G_TYPE_NONE, 1, G_TYPE_ERROR);
}
static void
_gtk_file_system_model_init (GtkFileSystemModel *model)
{
model->show_files = TRUE;
model->show_folders = TRUE;
model->show_hidden = FALSE;
model->filter_folders = FALSE;
model->file_lookup = g_hash_table_new (g_file_hash, (GEqualFunc) g_file_equal);
model->cancellable = g_cancellable_new ();
}
/*** API ***/
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);
}
static gboolean
thaw_func (gpointer data)
{
GtkFileSystemModel *model = data;
thaw_updates (model);
model->dir_thaw_source = 0;
return FALSE;
}
static void
gtk_file_system_model_got_files (GObject *object, GAsyncResult *res, gpointer data)
{
GFileEnumerator *enumerator = G_FILE_ENUMERATOR (object);
GtkFileSystemModel *model = data;
GList *walk, *files;
GError *error = NULL;
files = g_file_enumerator_next_files_finish (enumerator, res, &error);
if (files)
{
if (model->dir_thaw_source == 0)
{
freeze_updates (model);
model->dir_thaw_source = g_timeout_add_full (IO_PRIORITY + 1, 50,
thaw_func,
model,
NULL);
gdk_source_set_static_name_by_id (model->dir_thaw_source, "[gtk] thaw_func");
}
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);
add_file (model, file, info);
g_object_unref (file);
g_object_unref (info);
}
g_list_free (files);
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
{
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
{
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;
thaw_updates (model);
}
g_signal_emit (model, file_system_model_signals[FINISHED_LOADING], 0, error);
}
if (error)
g_error_free (error);
}
}
/* Helper for gtk_file_system_model_query_done and
* gtk_file_system_model_one_query_done */
static void
query_done_helper (GObject * object,
GAsyncResult *res,
gpointer data,
gboolean do_thaw_updates)
{
GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (data);
GFile *file = G_FILE (object);
GFileInfo *info;
info = g_file_query_info_finish (file, res, NULL);
if (info)
{
_gtk_file_system_model_update_file (model, file, info);
g_object_unref (info);
}
if (do_thaw_updates)
thaw_updates (model);
}
static void
gtk_file_system_model_query_done (GObject * object,
GAsyncResult *res,
gpointer data)
{
query_done_helper (object, res, data, FALSE);
}
static void
gtk_file_system_model_monitor_change (GFileMonitor * monitor,
GFile * file,
GFile * other_file,
GFileMonitorEvent type,
GtkFileSystemModel *model)
{
switch (type)
{
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,
G_FILE_QUERY_INFO_NONE,
IO_PRIORITY,
model->cancellable,
gtk_file_system_model_query_done,
model);
break;
case G_FILE_MONITOR_EVENT_DELETED:
remove_file (model, file);
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:
case G_FILE_MONITOR_EVENT_MOVED:
case G_FILE_MONITOR_EVENT_RENAMED:
case G_FILE_MONITOR_EVENT_MOVED_IN:
case G_FILE_MONITOR_EVENT_MOVED_OUT:
default:
/* ignore these */
break;
}
}
static void
gtk_file_system_model_got_enumerator (GObject *dir, GAsyncResult *res, gpointer data)
{
GtkFileSystemModel *model = data;
GFileEnumerator *enumerator;
GError *error = NULL;
enumerator = g_file_enumerate_children_finish (G_FILE (dir), res, &error);
if (enumerator == NULL)
{
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);
}
}
else
{
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,
G_FILE_MONITOR_NONE,
model->cancellable,
NULL); /* we don't mind if directory monitoring isn't supported, so the GError is NULL here */
if (model->dir_monitor)
g_signal_connect (model->dir_monitor,
"changed",
G_CALLBACK (gtk_file_system_model_monitor_change),
model);
}
}
static void
gtk_file_system_model_set_directory (GtkFileSystemModel *model,
GFile * dir,
const char * attributes)
{
g_assert (G_IS_FILE (dir));
model->dir = g_object_ref (dir);
model->attributes = g_strdup (attributes);
g_file_enumerate_children_async (model->dir,
attributes,
G_FILE_QUERY_INFO_NONE,
IO_PRIORITY,
model->cancellable,
gtk_file_system_model_got_enumerator,
model);
}
/**
* _gtk_file_system_model_new:
*
* Creates a new `GtkFileSystemModel` object. You need to add files
* to the list using _gtk_file_system_model_add_and_query_file()
* or _gtk_file_system_model_update_file().
*
* Returns: the newly created `GtkFileSystemModel`
**/
GtkFileSystemModel *
_gtk_file_system_model_new (void)
{
GtkFileSystemModel *model;
model = g_object_new (GTK_TYPE_FILE_SYSTEM_MODEL, NULL);
model->files = g_array_sized_new (FALSE, FALSE, sizeof (FileModelNode), FILES_PER_QUERY);
/* add editable node at start */
g_array_set_size (model->files, 1);
memset (get_node (model, 0), 0, sizeof (FileModelNode));
return model;
}
/**
* _gtk_file_system_model_new_for_directory:
* @directory: the directory to show.
* @attributes: (nullable): attributes to immediately load or %NULL for all
*
* Creates a new `GtkFileSystemModel` object.
*
* The `GtkFileSystemModel` object wraps the given @directory as a
* `GtkTreeModel`. The model will query the given directory with the
* given @attributes and add all files inside the directory automatically.
* If supported, it will also monitor the drectory and update the model's
* contents to reflect changes, if the @directory supports monitoring.
*
* Returns: the newly created `GtkFileSystemModel`
**/
GtkFileSystemModel *
_gtk_file_system_model_new_for_directory (GFile *dir,
const char *attributes)
{
GtkFileSystemModel *model;
g_return_val_if_fail (G_IS_FILE (dir), NULL);
model = _gtk_file_system_model_new ();
gtk_file_system_model_set_directory (model, dir, attributes);
return model;
}
static void
gtk_file_system_model_refilter_all (GtkFileSystemModel *model)
{
guint i;
if (model->frozen)
{
model->filter_on_thaw = TRUE;
return;
}
freeze_updates (model);
/* start at index 1, don't change the editable */
for (i = 1; i < model->files->len; i++)
node_compute_visibility_and_filters (model, i);
model->filter_on_thaw = FALSE;
thaw_updates (model);
}
/**
* _gtk_file_system_model_set_show_hidden:
* @model: a `GtkFileSystemModel`
* @show_hidden: whether hidden files should be displayed
*
* Sets whether hidden files should be included in the `GtkTreeModel`
* for display.
**/
void
_gtk_file_system_model_set_show_hidden (GtkFileSystemModel *model,
gboolean show_hidden)
{
g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
show_hidden = show_hidden != FALSE;
if (show_hidden != model->show_hidden)
{
model->show_hidden = show_hidden;
gtk_file_system_model_refilter_all (model);
}
}
/**
* _gtk_file_system_model_set_show_folders:
* @model: a `GtkFileSystemModel`
* @show_folders: whether folders should be displayed
*
* Sets whether folders should be included in the `GtkTreeModel`
* for display.
*/
void
_gtk_file_system_model_set_show_folders (GtkFileSystemModel *model,
gboolean show_folders)
{
g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
show_folders = show_folders != FALSE;
if (show_folders != model->show_folders)
{
model->show_folders = show_folders;
gtk_file_system_model_refilter_all (model);
}
}
/**
* _gtk_file_system_model_set_show_files:
* @model: a `GtkFileSystemModel`
* @show_files: whether files (as opposed to folders) should be displayed.
*
* Sets whether files (as opposed to folders) should be included
* in the `GtkTreeModel` for display.
*/
void
_gtk_file_system_model_set_show_files (GtkFileSystemModel *model,
gboolean show_files)
{
g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
show_files = show_files != FALSE;
if (show_files != model->show_files)
{
model->show_files = show_files;
gtk_file_system_model_refilter_all (model);
}
}
/**
* _gtk_file_system_model_set_filter_folders:
* @model: a `GtkFileSystemModel`
* @filter_folders: whether the filter applies to folders
*
* Sets whether the filter set by _gtk_file_system_model_set_filter()
* applies to folders. By default, it does not and folders are always
* visible.
*/
void
_gtk_file_system_model_set_filter_folders (GtkFileSystemModel *model,
gboolean filter_folders)
{
g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
filter_folders = filter_folders != FALSE;
if (filter_folders != model->filter_folders)
{
model->filter_folders = filter_folders;
gtk_file_system_model_refilter_all (model);
}
}
/**
* _gtk_file_system_model_get_cancellable:
* @model: the model
*
* Gets the cancellable used by the @model. This is the cancellable used
* internally by the @model that will be cancelled when @model is
* disposed. So you can use it for operations that should be cancelled
* when the model goes away.
*
* Returns: The cancellable used by @model
**/
GCancellable *
_gtk_file_system_model_get_cancellable (GtkFileSystemModel *model)
{
g_return_val_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model), NULL);
return model->cancellable;
}
static guint
node_get_for_file (GtkFileSystemModel *model,
GFile * file)
{
guint i;
i = GPOINTER_TO_UINT (g_hash_table_lookup (model->file_lookup, file));
if (i != 0)
return i;
/* Node 0 is the editable row and has no associated file or entry in the table, so we start counting from 1.
*
* The invariant here is that the files in model->files[n] for n < g_hash_table_size (model->file_lookup)
* are already added to the hash table. this loop merely rebuilds our (file -> index) mapping on demand.
*
* If we exit the loop, the next pending batch of mappings will be resolved when this function gets called again
* with another file that is not yet in the mapping.
*/
for (i = g_hash_table_size (model->file_lookup) + 1; i < model->files->len; i++)
{
FileModelNode *node = get_node (model, i);
g_hash_table_insert (model->file_lookup, node->file, GUINT_TO_POINTER (i));
if (g_file_equal (node->file, file))
return i;
}
return 0;
}
GFileInfo *
_gtk_file_system_model_get_info_for_file (GtkFileSystemModel *model,
GFile *file)
{
FileModelNode *node;
guint i;
g_return_val_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model), FALSE);
g_return_val_if_fail (G_IS_FILE (file), FALSE);
i = node_get_for_file (model, file);
if (i == 0)
return NULL;
node = get_node (model, i);
return node->info;
}
/* When an element is added or removed to the model->files array, we need to
* update the model->file_lookup mappings of (node, index), as the indexes
* change. This function adds the specified increment to the index in that pair
* if the index is equal or after the specified id. We use this to slide the
* mappings up or down when a node is added or removed, respectively.
*/
static void
adjust_file_lookup (GtkFileSystemModel *model, guint id, int increment)
{
GHashTableIter iter;
gpointer key;
gpointer value;
g_hash_table_iter_init (&iter, model->file_lookup);
while (g_hash_table_iter_next (&iter, &key, &value))
{
guint index = GPOINTER_TO_UINT (value);
if (index >= id)
{
index += increment;
g_hash_table_iter_replace (&iter, GUINT_TO_POINTER (index));
}
}
}
/**
* add_file:
* @model: the model
* @file: the file to add
* @info: the information to associate with the file
*
* Adds the given @file with its associated @info to the @model.
* If the model is frozen, the file will only show up after it is thawn.
**/
static void
add_file (GtkFileSystemModel *model,
GFile *file,
GFileInfo *info)
{
FileModelNode *node;
guint position;
g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
g_return_if_fail (G_IS_FILE (file));
g_return_if_fail (G_IS_FILE_INFO (info));
node = g_slice_alloc0 (sizeof (FileModelNode));
node->file = g_object_ref (file);
if (info)
{
g_file_info_set_attribute_object (info, "standard::file", G_OBJECT (file));
node->info = g_object_ref (info);
}
node->frozen_add = model->frozen ? TRUE : FALSE;
g_array_append_vals (model->files, node, 1);
g_slice_free1 (sizeof (FileModelNode), node);
position = model->files->len - 1;
if (!model->frozen)
node_compute_visibility_and_filters (model, model->files->len -1);
/* Ignore the first item */
g_list_model_items_changed (G_LIST_MODEL (model), position - 1, 0, 1);
}
/**
* remove_file:
* @model: the model
* @file: file to remove from the model. The file must have been
* added to the model previously
*
* Removes the given file from the model. If the file is not part of
* @model, this function does nothing.
**/
static void
remove_file (GtkFileSystemModel *model,
GFile *file)
{
FileModelNode *node;
guint id;
g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
g_return_if_fail (G_IS_FILE (file));
id = node_get_for_file (model, file);
if (id == 0)
return;
node = get_node (model, id);
node_invalidate_index (model, id);
g_hash_table_remove (model->file_lookup, file);
g_object_unref (node->file);
adjust_file_lookup (model, id, -1);
if (node->info)
g_object_unref (node->info);
g_array_remove_index (model->files, id);
g_list_model_items_changed (G_LIST_MODEL (model), id - 1, 1, 0);
}
/**
* _gtk_file_system_model_update_file:
* @model: the model
* @file: the file
* @info: the new file info
*
* Tells the file system model that the file changed and that the
* new @info should be used for it now. If the file is not part of
* @model, it will get added automatically.
**/
void
_gtk_file_system_model_update_file (GtkFileSystemModel *model,
GFile *file,
GFileInfo *info)
{
FileModelNode *node;
guint id;
g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
g_return_if_fail (G_IS_FILE (file));
g_return_if_fail (G_IS_FILE_INFO (info));
id = node_get_for_file (model, file);
if (id == 0)
{
add_file (model, file, info);
id = node_get_for_file (model, file);
}
node = get_node (model, id);
g_set_object (&node->info, info);
g_file_info_set_attribute_object (info, "standard::file", G_OBJECT (file));
}
void
_gtk_file_system_model_update_files (GtkFileSystemModel *model,
GList *files,
GList *infos)
{
GList *l, *i;
g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
freeze_updates (model);
for (l = files, i = infos; l; l = l->next, i = i->next)
_gtk_file_system_model_update_file (model, (GFile *)l->data, (GFileInfo *)i->data);
thaw_updates (model);
}
/**
* _gtk_file_system_model_set_filter:
* @mode: a `GtkFileSystemModel`
* @filter: (nullable): %NULL or filter to use
*
* Sets a filter to be used for deciding if a row should be visible or not.
* Whether this filter applies to directories can be toggled with
* _gtk_file_system_model_set_filter_folders().
**/
void
_gtk_file_system_model_set_filter (GtkFileSystemModel *model,
GtkFileFilter * filter)
{
GtkFileFilter *old_filter;
g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
g_return_if_fail (filter == NULL || GTK_IS_FILE_FILTER (filter));
if (filter)
g_object_ref (filter);
old_filter = model->filter;
model->filter = filter;
if (old_filter)
g_object_unref (old_filter);
gtk_file_system_model_refilter_all (model);
}
/**
* freeze_updates:
* @model: a `GtkFileSystemModel`
*
* Freezes most updates on the model, so that performing multiple operations on
* the files in the model do not cause any events. Use thaw_updates() to resume
* proper operations. It is fine to call this function multiple times as long as
* freeze and thaw calls are balanced.
**/
static void
freeze_updates (GtkFileSystemModel *model)
{
g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
model->frozen++;
}
/**
* thaw_updates:
* @model: a `GtkFileSystemModel`
*
* Undoes the effect of a previous call to freeze_updates()
**/
static void
thaw_updates (GtkFileSystemModel *model)
{
gboolean stuff_added;
g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
g_return_if_fail (model->frozen > 0);
model->frozen--;
if (model->frozen > 0)
return;
stuff_added = get_node (model, model->files->len - 1)->frozen_add;
if (model->filter_on_thaw)
gtk_file_system_model_refilter_all (model);
if (stuff_added)
{
guint i;
for (i = 0; i < model->files->len; i++)
{
FileModelNode *node = get_node (model, i);
if (!node->frozen_add)
continue;
node->frozen_add = FALSE;
node_compute_visibility_and_filters (model, i);
}
}
}
/**
* _gtk_file_system_model_add_and_query_file:
* @model: a `GtkFileSystemModel`
* @file: the file to add
* @attributes: attributes to query before adding the file
*
* This is a convenience function that calls g_file_query_info_async() on
* the given file, and when successful, adds it to the model.
* Upon failure, the @file is discarded.
**/
void
_gtk_file_system_model_add_and_query_file (GtkFileSystemModel *model,
GFile * file,
const char * attributes)
{
g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
g_return_if_fail (G_IS_FILE (file));
g_return_if_fail (attributes != NULL);
g_file_query_info_async (file,
attributes,
G_FILE_QUERY_INFO_NONE,
IO_PRIORITY,
model->cancellable,
gtk_file_system_model_query_done,
model);
}
static void
gtk_file_system_model_one_query_done (GObject * object,
GAsyncResult *res,
gpointer data)
{
query_done_helper (object, res, data, TRUE);
}
void
_gtk_file_system_model_add_and_query_files (GtkFileSystemModel *model,
GList *list,
const char *attributes)
{
GList *l;
GFile *file;
g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
g_return_if_fail (attributes != NULL);
for (l = list; l; l = l->next)
{
file = (GFile *)l->data;
freeze_updates (model);
g_file_query_info_async (file,
attributes,
G_FILE_QUERY_INFO_NONE,
IO_PRIORITY,
model->cancellable,
gtk_file_system_model_one_query_done,
model);
}
}
GFile *
_gtk_file_system_model_get_directory (GtkFileSystemModel *model)
{
g_return_val_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model), NULL);
return model->dir;
}