* 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.
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gtktreedatalist.h"
#include "gtktreednd.h"
#include "gtktreemodel.h"
-#include "gtkalias.h"
/*** Structure: how GtkFileSystemModel works
*
* 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
*
* 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.
+ * 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:
*
* 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.
+ *
+ * Sorting
+ * -------
+ *
+ * The model implements the GtkTreeSortable interface. To avoid re-sorting
+ * every time a node gets added (which would lead to O(n^2) performance during
+ * the initial population of the model), the model can freeze itself (with
+ * freeze_updates()) during the intial population process. When the model is
+ * frozen, sorting will not happen. The model will sort itself when the freeze
+ * count goes back to zero, via corresponding calls to thaw_updates().
*/
/*** DEFINES ***/
*/
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 */
GValue values[1]; /* actually n_columns values */
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))
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 GtkTreePath *
-gtk_tree_path_new_from_node (GtkFileSystemModel *model, guint id)
+tree_path_new_from_node (GtkFileSystemModel *model, guint id)
{
- guint i = node_get_tree_row (model, id);
+ guint r = node_get_tree_row (model, id);
- g_assert (i < model->files->len);
+ g_assert (r < model->files->len);
- return gtk_tree_path_new_from_indices (i, -1);
+ return gtk_tree_path_new_from_indices (r, -1);
}
static void
GtkTreePath *path;
GtkTreeIter iter;
- path = gtk_tree_path_new_from_node (model, id);
+ path = 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);
GtkTreePath *path;
GtkTreeIter iter;
- path = gtk_tree_path_new_from_node (model, id);
+ path = 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
-node_set_visible (GtkFileSystemModel *model, guint id, gboolean visible)
+node_set_visible_and_filtered_out (GtkFileSystemModel *model, guint id, gboolean visible, gboolean filtered_out)
{
FileModelNode *node = get_node (model, id);
+ /* Filteredness */
+
+ if (node->filtered_out != filtered_out)
+ {
+ node->filtered_out = filtered_out;
+ if (node->visible && visible)
+ emit_row_changed_for_node (model, id);
+ }
+
+ /* Visibility */
+
if (node->visible == visible ||
node->frozen_add)
return;
}
static gboolean
-node_should_be_visible (GtkFileSystemModel *model, guint id)
+node_should_be_filtered_out (GtkFileSystemModel *model, guint id)
{
FileModelNode *node = get_node (model, id);
GtkFileFilterInfo filter_info = { 0, };
GtkFileFilterFlags required;
- gboolean is_folder, result;
+ gboolean result;
char *mime_type = NULL;
char *filename = NULL;
char *uri = NULL;
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;
-
- 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;
-
- if (is_folder)
return TRUE;
if (model->filter == NULL)
- return TRUE;
+ return FALSE;
/* fill info */
required = gtk_file_filter_get_needed (model->filter);
}
}
- result = gtk_file_filter_filter (model->filter, &filter_info);
+ result = !gtk_file_filter_filter (model->filter, &filter_info);
g_free (mime_type);
g_free (filename);
return result;
}
+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);
+}
+
/*** GtkTreeModel ***/
static GtkTreeModelFlags
{
g_return_val_if_fail (gtk_tree_path_get_depth (path) > 0, FALSE);
+ if (gtk_tree_path_get_depth (path) > 1)
+ return FALSE;
+
return gtk_file_system_model_iter_nth_child (tree_model,
iter,
NULL,
g_return_val_if_fail (ITER_IS_VALID (model, iter), NULL);
- return gtk_tree_path_new_from_node (model, ITER_INDEX (iter));
+ return tree_path_new_from_node (model, ITER_INDEX (iter));
}
static void
continue;
}
- new_order[r] = node->row;
+ new_order[r] = node->row - 1;
r++;
node->row = r;
}
model->show_files = TRUE;
model->show_folders = TRUE;
model->show_hidden = FALSE;
+ model->filter_folders = FALSE;
model->sort_column_id = GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID;
{
GtkFileSystemModel *model = data;
- _gtk_file_system_model_thaw_updates (model);
+ thaw_updates (model);
model->dir_thaw_source = 0;
return FALSE;
{
if (model->dir_thaw_source == 0)
{
- _gtk_file_system_model_freeze_updates (model);
+ freeze_updates (model);
model->dir_thaw_source = gdk_threads_add_timeout_full (IO_PRIORITY + 1,
50,
thaw_func,
{
g_source_remove (model->dir_thaw_source);
model->dir_thaw_source = 0;
- _gtk_file_system_model_thaw_updates (model);
+ thaw_updates (model);
}
g_signal_emit (model, file_system_model_signals[FINISHED_LOADING], 0, error);
GtkFileSystemModel *model = data; /* only a valid pointer if not cancelled */
GFile *file = G_FILE (object);
GFileInfo *info;
+ guint id;
info = g_file_query_info_finish (file, res, NULL);
if (info == NULL)
return;
- _gtk_file_system_model_update_file (model, file, info, TRUE);
+ gdk_threads_enter ();
+
+ _gtk_file_system_model_update_file (model, file, info);
+
+ id = node_get_for_file (model, file);
+ gtk_file_system_model_sort_node (model, id);
+
+ gdk_threads_leave ();
}
static void
model);
break;
case G_FILE_MONITOR_EVENT_DELETED:
+ gdk_threads_enter ();
remove_file (model, file);
+ gdk_threads_leave ();
break;
case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
/* FIXME: use freeze/thaw with this somehow? */
enumerator = g_file_enumerate_children_finish (G_FILE (dir), res, &error);
if (enumerator == NULL)
{
- g_signal_emit (model, file_system_model_signals[FINISHED_LOADING], 0, error);
- g_object_unref (model);
- g_error_free (error);
+ 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
{
/**
* _gtk_file_system_model_new_for_directory:
* @directory: the directory to show.
- * @attributes: attributes to immediately load or %NULL for all
+ * @attributes: (allow-none): attributes to immediately load or %NULL for all
* @get_func: function that the model should call to query data about a file
* @get_data: user data to pass to the @get_func
* @n_columns: number of columns
return;
}
- _gtk_file_system_model_freeze_updates (model);
+ freeze_updates (model);
/* start at index 1, don't change the editable */
for (i = 1; i < model->files->len; i++)
- {
- node_set_visible (model, i, node_should_be_visible (model, i));
- }
+ node_compute_visibility_and_filters (model, i);
model->filter_on_thaw = FALSE;
- _gtk_file_system_model_thaw_updates (model);
+ thaw_updates (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
* Checks if the iterator is visible. A visible iterator references
* a row that is currently exposed using the #GtkTreeModel API. If
* the iterator is invisible, it references a file that is not shown
- * for some reason, such as being filtered by the current filter or
+ * for some reason, such as being filtered out by the current filter or
* being a hidden file.
*
* Returns: %TRUE if the iterator is visible
return node->visible;
}
+/**
+ * _gtk_file_system_model_iter_is_filtered_out:
+ * @model: the model
+ * @iter: a valid iterator
+ *
+ * Checks if the iterator is filtered out. This is only useful for rows
+ * that refer to folders, as those are always visible regardless
+ * of what the current filter says. This function lets you see
+ * the results of the filter.
+ *
+ * Returns: %TRUE if the iterator passed the current filter; %FALSE if the
+ * filter would not have let the row pass.
+ **/
+gboolean
+_gtk_file_system_model_iter_is_filtered_out (GtkFileSystemModel *model,
+ GtkTreeIter *iter)
+{
+ FileModelNode *node;
+
+ g_return_val_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model), FALSE);
+ g_return_val_if_fail (iter != NULL, FALSE);
+
+ node = get_node (model, ITER_INDEX (iter));
+ return node->filtered_out;
+}
+
/**
* _gtk_file_system_model_get_info:
* @model: a #GtkFileSystemModel
return TRUE;
}
+/* 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
g_slice_free1 (model->node_size, node);
if (!model->frozen)
- node_set_visible (model, model->files->len -1,
- node_should_be_visible (model, model->files->len - 1));
+ node_compute_visibility_and_filters (model, model->files->len -1);
+
gtk_file_system_model_sort_node (model, model->files->len -1);
}
{
FileModelNode *node;
guint id;
+ guint row;
g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
g_return_if_fail (G_IS_FILE (file));
return;
node = get_node (model, id);
- node_set_visible (model, id, FALSE);
+ row = node_get_tree_row (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);
- /* We don't need to resort, as removing a row doesn't change the sorting order */
+
+ /* We don't need to resort, as removing a row doesn't change the sorting order of the other rows */
+
+ emit_row_deleted_for_row (model, row);
}
/**
* @model: the model
* @file: the file
* @info: the new file info
- * @requires_resort: FIXME: get rid of this argument
*
* 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
void
_gtk_file_system_model_update_file (GtkFileSystemModel *model,
GFile *file,
- GFileInfo *info,
- gboolean requires_resort)
+ GFileInfo *info)
{
FileModelNode *node;
guint i, id;
id = node_get_for_file (model, file);
if (id == 0)
- add_file (model, file, info);
+ {
+ add_file (model, file, info);
+ id = node_get_for_file (model, file);
+ }
node = get_node (model, id);
if (node->visible)
emit_row_changed_for_node (model, id);
-
- if (requires_resort)
- gtk_file_system_model_sort_node (model, id);
}
/**
* _gtk_file_system_model_set_filter:
* @mode: a #GtkFileSystemModel
- * @filter: %NULL or filter to use
+ * @filter: (allow-none): %NULL or filter to use
*
* Sets a filter to be used for deciding if a row should be visible or not.
- * Directories are always visible.
+ * 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,
g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
g_return_if_fail (!get_node (model, 0)->visible);
- node_set_visible (model, 0, TRUE);
+ node_set_visible_and_filtered_out (model, 0, TRUE, FALSE);
ITER_INIT_FROM_INDEX (model, iter, 0);
}
g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
g_return_if_fail (get_node (model, 0)->visible);
- node_set_visible (model, 0, FALSE);
+ node_set_visible_and_filtered_out (model, 0, FALSE, FALSE);
}
/**
- * _gtk_file_system_model_freeze_updates:
+ * 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 _gtk_file_system_model_thaw_updates() to resume proper
- * operations. It is fine to call this function multiple times as
- * long as freeze and thaw calls are balanced.
+ * 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.
**/
-void
-_gtk_file_system_model_freeze_updates (GtkFileSystemModel *model)
+static void
+freeze_updates (GtkFileSystemModel *model)
{
g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
}
/**
- * _gtk_file_system_model_thaw_updates:
+ * thaw_updates:
* @model: a #GtkFileSystemModel
*
- * Undoes the effect of a previous call to
- * _gtk_file_system_model_freeze_updates()
+ * Undoes the effect of a previous call to freeze_updates()
**/
-void
-_gtk_file_system_model_thaw_updates (GtkFileSystemModel *model)
+static void
+thaw_updates (GtkFileSystemModel *model)
{
gboolean stuff_added;
if (!node->frozen_add)
continue;
node->frozen_add = FALSE;
- node_set_visible (model, i, node_should_be_visible (model, i));
+ node_compute_visibility_and_filters (model, i);
}
}
}