mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-12-30 15:31:34 +00:00
626c525706
Due to the many different ways to set factories, it makes sense to expose them as custom objects. This makes the actual APIs for the list widgets simpler, because they can just have a regular "factory" property. As a convenience function, gtk_list_view_new_with_factory() was added to make this whole approach easy to use from C.
678 lines
19 KiB
C
678 lines
19 KiB
C
#include <gtk/gtk.h>
|
|
|
|
#define FILE_INFO_TYPE_SELECTION (file_info_selection_get_type ())
|
|
|
|
G_DECLARE_FINAL_TYPE (FileInfoSelection, file_info_selection, FILE_INFO, SELECTION, GObject)
|
|
|
|
struct _FileInfoSelection
|
|
{
|
|
GObject parent_instance;
|
|
|
|
GListModel *model;
|
|
};
|
|
|
|
struct _FileInfoSelectionClass
|
|
{
|
|
GObjectClass parent_class;
|
|
};
|
|
|
|
static GType
|
|
file_info_selection_get_item_type (GListModel *list)
|
|
{
|
|
FileInfoSelection *self = FILE_INFO_SELECTION (list);
|
|
|
|
return g_list_model_get_item_type (self->model);
|
|
}
|
|
|
|
static guint
|
|
file_info_selection_get_n_items (GListModel *list)
|
|
{
|
|
FileInfoSelection *self = FILE_INFO_SELECTION (list);
|
|
|
|
return g_list_model_get_n_items (self->model);
|
|
}
|
|
|
|
static gpointer
|
|
file_info_selection_get_item (GListModel *list,
|
|
guint position)
|
|
{
|
|
FileInfoSelection *self = FILE_INFO_SELECTION (list);
|
|
|
|
return g_list_model_get_item (self->model, position);
|
|
}
|
|
|
|
static void
|
|
file_info_selection_list_model_init (GListModelInterface *iface)
|
|
{
|
|
iface->get_item_type = file_info_selection_get_item_type;
|
|
iface->get_n_items = file_info_selection_get_n_items;
|
|
iface->get_item = file_info_selection_get_item;
|
|
}
|
|
|
|
static gboolean
|
|
file_info_selection_is_selected (GtkSelectionModel *model,
|
|
guint position)
|
|
{
|
|
FileInfoSelection *self = FILE_INFO_SELECTION (model);
|
|
gpointer item;
|
|
|
|
item = g_list_model_get_item (self->model, position);
|
|
if (item == NULL)
|
|
return FALSE;
|
|
|
|
if (GTK_IS_TREE_LIST_ROW (item))
|
|
{
|
|
GtkTreeListRow *row = item;
|
|
item = gtk_tree_list_row_get_item (row);
|
|
g_object_unref (row);
|
|
}
|
|
|
|
return g_file_info_get_attribute_boolean (item, "filechooser::selected");
|
|
}
|
|
|
|
static void
|
|
file_info_selection_set_selected (FileInfoSelection *self,
|
|
guint position,
|
|
gboolean selected)
|
|
{
|
|
gpointer item;
|
|
|
|
item = g_list_model_get_item (self->model, position);
|
|
if (item == NULL)
|
|
return;
|
|
|
|
if (GTK_IS_TREE_LIST_ROW (item))
|
|
{
|
|
GtkTreeListRow *row = item;
|
|
item = gtk_tree_list_row_get_item (row);
|
|
g_object_unref (row);
|
|
}
|
|
|
|
g_file_info_set_attribute_boolean (item, "filechooser::selected", selected);
|
|
}
|
|
|
|
static gboolean
|
|
file_info_selection_select_item (GtkSelectionModel *model,
|
|
guint position,
|
|
gboolean exclusive)
|
|
{
|
|
FileInfoSelection *self = FILE_INFO_SELECTION (model);
|
|
|
|
if (exclusive)
|
|
{
|
|
guint i;
|
|
|
|
for (i = 0; i < g_list_model_get_n_items (self->model); i++)
|
|
file_info_selection_set_selected (self, i, i == position);
|
|
|
|
gtk_selection_model_selection_changed (model, 0, g_list_model_get_n_items (self->model));
|
|
}
|
|
else
|
|
{
|
|
file_info_selection_set_selected (self, position, TRUE);
|
|
|
|
gtk_selection_model_selection_changed (model, position, 1);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
file_info_selection_unselect_item (GtkSelectionModel *model,
|
|
guint position)
|
|
{
|
|
FileInfoSelection *self = FILE_INFO_SELECTION (model);
|
|
|
|
file_info_selection_set_selected (self, position, FALSE);
|
|
|
|
gtk_selection_model_selection_changed (model, position, 1);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
file_info_selection_select_range (GtkSelectionModel *model,
|
|
guint position,
|
|
guint n_items,
|
|
gboolean exclusive)
|
|
{
|
|
FileInfoSelection *self = FILE_INFO_SELECTION (model);
|
|
guint i;
|
|
|
|
if (exclusive)
|
|
for (i = 0; i < position; i++)
|
|
file_info_selection_set_selected (self, i, FALSE);
|
|
|
|
for (i = position; i < position + n_items; i++)
|
|
file_info_selection_set_selected (self, i, TRUE);
|
|
|
|
if (exclusive)
|
|
for (i = position + n_items; i < g_list_model_get_n_items (self->model); i++)
|
|
file_info_selection_set_selected (self, i, FALSE);
|
|
|
|
if (exclusive)
|
|
gtk_selection_model_selection_changed (model, 0, g_list_model_get_n_items (self->model));
|
|
else
|
|
gtk_selection_model_selection_changed (model, position, n_items);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
file_info_selection_unselect_range (GtkSelectionModel *model,
|
|
guint position,
|
|
guint n_items)
|
|
{
|
|
FileInfoSelection *self = FILE_INFO_SELECTION (model);
|
|
guint i;
|
|
|
|
for (i = position; i < position + n_items; i++)
|
|
file_info_selection_set_selected (self, i, FALSE);
|
|
|
|
gtk_selection_model_selection_changed (model, position, n_items);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
file_info_selection_selection_model_init (GtkSelectionModelInterface *iface)
|
|
{
|
|
iface->is_selected = file_info_selection_is_selected;
|
|
iface->select_item = file_info_selection_select_item;
|
|
iface->unselect_item = file_info_selection_unselect_item;
|
|
iface->select_range = file_info_selection_select_range;
|
|
iface->unselect_range = file_info_selection_unselect_range;
|
|
}
|
|
|
|
G_DEFINE_TYPE_EXTENDED (FileInfoSelection, file_info_selection, G_TYPE_OBJECT, 0,
|
|
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
|
|
file_info_selection_list_model_init)
|
|
G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL,
|
|
file_info_selection_selection_model_init))
|
|
|
|
static void
|
|
file_info_selection_items_changed_cb (GListModel *model,
|
|
guint position,
|
|
guint removed,
|
|
guint added,
|
|
FileInfoSelection *self)
|
|
{
|
|
g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added);
|
|
}
|
|
|
|
static void
|
|
file_info_selection_clear_model (FileInfoSelection *self)
|
|
{
|
|
if (self->model == NULL)
|
|
return;
|
|
|
|
g_signal_handlers_disconnect_by_func (self->model,
|
|
file_info_selection_items_changed_cb,
|
|
self);
|
|
g_clear_object (&self->model);
|
|
}
|
|
|
|
static void
|
|
file_info_selection_dispose (GObject *object)
|
|
{
|
|
FileInfoSelection *self = FILE_INFO_SELECTION (object);
|
|
|
|
file_info_selection_clear_model (self);
|
|
|
|
G_OBJECT_CLASS (file_info_selection_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
file_info_selection_class_init (FileInfoSelectionClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
|
|
gobject_class->dispose = file_info_selection_dispose;
|
|
}
|
|
|
|
static void
|
|
file_info_selection_init (FileInfoSelection *self)
|
|
{
|
|
}
|
|
|
|
static FileInfoSelection *
|
|
file_info_selection_new (GListModel *model)
|
|
{
|
|
FileInfoSelection *result;
|
|
|
|
result = g_object_new (FILE_INFO_TYPE_SELECTION, NULL);
|
|
|
|
result->model = g_object_ref (model);
|
|
g_signal_connect (result->model, "items-changed",
|
|
G_CALLBACK (file_info_selection_items_changed_cb), result);
|
|
|
|
return result;
|
|
}
|
|
|
|
/*** ---------------------- ***/
|
|
|
|
GSList *pending = NULL;
|
|
guint active = 0;
|
|
|
|
static void
|
|
loading_cb (GtkDirectoryList *dir,
|
|
GParamSpec *pspec,
|
|
gpointer unused)
|
|
{
|
|
if (gtk_directory_list_is_loading (dir))
|
|
{
|
|
active++;
|
|
/* HACK: ensure loading finishes and the dir doesn't get destroyed */
|
|
g_object_ref (dir);
|
|
}
|
|
else
|
|
{
|
|
active--;
|
|
g_object_unref (dir);
|
|
|
|
while (active < 20 && pending)
|
|
{
|
|
GtkDirectoryList *dir2 = pending->data;
|
|
pending = g_slist_remove (pending, dir2);
|
|
gtk_directory_list_set_file (dir2, g_object_get_data (G_OBJECT (dir2), "file"));
|
|
g_object_unref (dir2);
|
|
}
|
|
}
|
|
}
|
|
|
|
static GtkDirectoryList *
|
|
create_directory_list (GFile *file)
|
|
{
|
|
GtkDirectoryList *dir;
|
|
|
|
dir = gtk_directory_list_new (G_FILE_ATTRIBUTE_STANDARD_TYPE
|
|
"," G_FILE_ATTRIBUTE_STANDARD_NAME
|
|
"," G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
|
|
NULL);
|
|
gtk_directory_list_set_io_priority (dir, G_PRIORITY_DEFAULT_IDLE);
|
|
g_signal_connect (dir, "notify::loading", G_CALLBACK (loading_cb), NULL);
|
|
g_assert (!gtk_directory_list_is_loading (dir));
|
|
|
|
if (active > 20)
|
|
{
|
|
g_object_set_data_full (G_OBJECT (dir), "file", g_object_ref (file), g_object_unref);
|
|
pending = g_slist_prepend (pending, g_object_ref (dir));
|
|
}
|
|
else
|
|
{
|
|
gtk_directory_list_set_file (dir, file);
|
|
}
|
|
|
|
return dir;
|
|
}
|
|
|
|
static char *
|
|
get_file_path (GFileInfo *info)
|
|
{
|
|
GFile *file;
|
|
|
|
file = G_FILE (g_file_info_get_attribute_object (info, "standard::file"));
|
|
return g_file_get_path (file);
|
|
}
|
|
|
|
static GListModel *
|
|
create_list_model_for_directory (gpointer file)
|
|
{
|
|
GtkSortListModel *sort;
|
|
GtkDirectoryList *dir;
|
|
GtkSorter *sorter;
|
|
|
|
if (g_file_query_file_type (file, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) != G_FILE_TYPE_DIRECTORY)
|
|
return NULL;
|
|
|
|
dir = create_directory_list (file);
|
|
sorter = gtk_string_sorter_new (gtk_cclosure_expression_new (G_TYPE_STRING, NULL, 0, NULL, (GCallback) get_file_path, NULL, NULL));
|
|
sort = gtk_sort_list_model_new (G_LIST_MODEL (dir), sorter);
|
|
|
|
g_object_unref (sorter);
|
|
g_object_unref (dir);
|
|
|
|
return G_LIST_MODEL (sort);
|
|
}
|
|
|
|
typedef struct _RowData RowData;
|
|
struct _RowData
|
|
{
|
|
GtkWidget *depth_box;
|
|
GtkWidget *expander;
|
|
GtkWidget *icon;
|
|
GtkWidget *name;
|
|
GCancellable *cancellable;
|
|
|
|
GtkTreeListRow *current_item;
|
|
GBinding *expander_binding;
|
|
};
|
|
|
|
static void row_data_notify_item (GtkListItem *item,
|
|
GParamSpec *pspec,
|
|
RowData *data);
|
|
static void
|
|
row_data_unbind (RowData *data)
|
|
{
|
|
if (data->current_item == NULL)
|
|
return;
|
|
|
|
if (data->cancellable)
|
|
{
|
|
g_cancellable_cancel (data->cancellable);
|
|
g_clear_object (&data->cancellable);
|
|
}
|
|
|
|
g_binding_unbind (data->expander_binding);
|
|
|
|
g_clear_object (&data->current_item);
|
|
}
|
|
|
|
static void
|
|
row_data_update_info (RowData *data,
|
|
GFileInfo *info)
|
|
{
|
|
GIcon *icon;
|
|
const char *thumbnail_path;
|
|
|
|
thumbnail_path = g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH);
|
|
if (thumbnail_path)
|
|
{
|
|
/* XXX: not async */
|
|
GFile *thumbnail_file = g_file_new_for_path (thumbnail_path);
|
|
icon = g_file_icon_new (thumbnail_file);
|
|
g_object_unref (thumbnail_file);
|
|
}
|
|
else
|
|
{
|
|
icon = g_file_info_get_icon (info);
|
|
}
|
|
|
|
gtk_widget_set_visible (data->icon, icon != NULL);
|
|
gtk_image_set_from_gicon (GTK_IMAGE (data->icon), icon);
|
|
}
|
|
|
|
static void
|
|
copy_attribute (GFileInfo *to,
|
|
GFileInfo *from,
|
|
const gchar *attribute)
|
|
{
|
|
GFileAttributeType type;
|
|
gpointer value;
|
|
|
|
if (g_file_info_get_attribute_data (from, attribute, &type, &value, NULL))
|
|
g_file_info_set_attribute (to, attribute, type, value);
|
|
}
|
|
|
|
static void
|
|
row_data_got_thumbnail_info_cb (GObject *source,
|
|
GAsyncResult *res,
|
|
gpointer _data)
|
|
{
|
|
RowData *data = _data; /* invalid if operation was cancelled */
|
|
GFile *file = G_FILE (source);
|
|
GFileInfo *queried, *info;
|
|
|
|
queried = g_file_query_info_finish (file, res, NULL);
|
|
if (queried == NULL)
|
|
return;
|
|
|
|
/* now we know row is valid */
|
|
|
|
info = gtk_tree_list_row_get_item (data->current_item);
|
|
|
|
copy_attribute (info, queried, G_FILE_ATTRIBUTE_THUMBNAIL_PATH);
|
|
copy_attribute (info, queried, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED);
|
|
copy_attribute (info, queried, G_FILE_ATTRIBUTE_STANDARD_ICON);
|
|
|
|
g_object_unref (queried);
|
|
|
|
row_data_update_info (data, info);
|
|
|
|
g_clear_object (&data->cancellable);
|
|
}
|
|
|
|
static void
|
|
row_data_bind (RowData *data,
|
|
GtkTreeListRow *item)
|
|
{
|
|
GFileInfo *info;
|
|
guint depth;
|
|
|
|
row_data_unbind (data);
|
|
|
|
if (item == NULL)
|
|
return;
|
|
|
|
data->current_item = g_object_ref (item);
|
|
|
|
depth = gtk_tree_list_row_get_depth (item);
|
|
gtk_widget_set_size_request (data->depth_box, 16 * depth, 0);
|
|
|
|
gtk_widget_set_sensitive (data->expander, gtk_tree_list_row_is_expandable (item));
|
|
data->expander_binding = g_object_bind_property (item, "expanded", data->expander, "active", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
|
|
|
|
info = gtk_tree_list_row_get_item (item);
|
|
|
|
if (!g_file_info_has_attribute (info, "filechooser::queried"))
|
|
{
|
|
data->cancellable = g_cancellable_new ();
|
|
g_file_info_set_attribute_boolean (info, "filechooser::queried", TRUE);
|
|
g_file_query_info_async (G_FILE (g_file_info_get_attribute_object (info, "standard::file")),
|
|
G_FILE_ATTRIBUTE_THUMBNAIL_PATH ","
|
|
G_FILE_ATTRIBUTE_THUMBNAILING_FAILED ","
|
|
G_FILE_ATTRIBUTE_STANDARD_ICON,
|
|
G_FILE_QUERY_INFO_NONE,
|
|
G_PRIORITY_DEFAULT,
|
|
data->cancellable,
|
|
row_data_got_thumbnail_info_cb,
|
|
data);
|
|
}
|
|
|
|
row_data_update_info (data, info);
|
|
|
|
gtk_label_set_label (GTK_LABEL (data->name), g_file_info_get_display_name (info));
|
|
|
|
g_object_unref (info);
|
|
}
|
|
|
|
static void
|
|
row_data_notify_item (GtkListItem *item,
|
|
GParamSpec *pspec,
|
|
RowData *data)
|
|
{
|
|
row_data_bind (data, gtk_list_item_get_item (item));
|
|
}
|
|
|
|
static void
|
|
row_data_free (gpointer _data)
|
|
{
|
|
RowData *data = _data;
|
|
|
|
row_data_unbind (data);
|
|
|
|
g_slice_free (RowData, data);
|
|
}
|
|
|
|
static void
|
|
setup_widget (GtkListItem *list_item,
|
|
gpointer unused)
|
|
{
|
|
GtkWidget *box, *child;
|
|
RowData *data;
|
|
|
|
data = g_slice_new0 (RowData);
|
|
g_signal_connect (list_item, "notify::item", G_CALLBACK (row_data_notify_item), data);
|
|
g_object_set_data_full (G_OBJECT (list_item), "row-data", data, row_data_free);
|
|
|
|
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
|
|
gtk_list_item_set_child (list_item, box);
|
|
|
|
child = gtk_label_new (NULL);
|
|
gtk_label_set_width_chars (GTK_LABEL (child), 5);
|
|
gtk_box_append (GTK_BOX (box), child);
|
|
|
|
data->depth_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
|
|
gtk_box_append (GTK_BOX (box), data->depth_box);
|
|
|
|
data->expander = g_object_new (GTK_TYPE_TOGGLE_BUTTON, "css-name", "expander-widget", NULL);
|
|
gtk_button_set_has_frame (GTK_BUTTON (data->expander), FALSE);
|
|
gtk_box_append (GTK_BOX (box), data->expander);
|
|
child = g_object_new (GTK_TYPE_SPINNER, "css-name", "expander", NULL);
|
|
g_object_bind_property (data->expander, "active", child, "spinning", G_BINDING_SYNC_CREATE);
|
|
gtk_button_set_child (GTK_BUTTON (data->expander), child);
|
|
|
|
data->icon = gtk_image_new ();
|
|
gtk_box_append (GTK_BOX (box), data->icon);
|
|
|
|
data->name = gtk_label_new (NULL);
|
|
gtk_box_append (GTK_BOX (box), data->name);
|
|
}
|
|
|
|
static GListModel *
|
|
create_list_model_for_file_info (gpointer file_info,
|
|
gpointer unused)
|
|
{
|
|
GFile *file = G_FILE (g_file_info_get_attribute_object (file_info, "standard::file"));
|
|
|
|
if (file == NULL)
|
|
return NULL;
|
|
|
|
return create_list_model_for_directory (file);
|
|
}
|
|
|
|
static gboolean
|
|
update_statusbar (GtkStatusbar *statusbar)
|
|
{
|
|
GListModel *model = g_object_get_data (G_OBJECT (statusbar), "model");
|
|
GString *string = g_string_new (NULL);
|
|
guint n;
|
|
gboolean result = G_SOURCE_REMOVE;
|
|
|
|
gtk_statusbar_remove_all (statusbar, 0);
|
|
|
|
n = g_list_model_get_n_items (model);
|
|
g_string_append_printf (string, "%u", n);
|
|
if (GTK_IS_FILTER_LIST_MODEL (model))
|
|
{
|
|
guint n_unfiltered = g_list_model_get_n_items (gtk_filter_list_model_get_model (GTK_FILTER_LIST_MODEL (model)));
|
|
if (n != n_unfiltered)
|
|
g_string_append_printf (string, "/%u", n_unfiltered);
|
|
}
|
|
g_string_append (string, " items");
|
|
|
|
if (pending || active)
|
|
{
|
|
g_string_append_printf (string, " (%u directories remaining)", active + g_slist_length (pending));
|
|
result = G_SOURCE_CONTINUE;
|
|
}
|
|
result = G_SOURCE_CONTINUE;
|
|
|
|
gtk_statusbar_push (statusbar, 0, string->str);
|
|
g_free (string->str);
|
|
|
|
return result;
|
|
}
|
|
|
|
static gboolean
|
|
match_file (gpointer item, gpointer data)
|
|
{
|
|
GtkWidget *search_entry = data;
|
|
GFileInfo *info = gtk_tree_list_row_get_item (item);
|
|
GFile *file = G_FILE (g_file_info_get_attribute_object (info, "standard::file"));
|
|
char *path;
|
|
gboolean result;
|
|
|
|
path = g_file_get_path (file);
|
|
|
|
result = strstr (path, gtk_editable_get_text (GTK_EDITABLE (search_entry))) != NULL;
|
|
|
|
g_object_unref (info);
|
|
g_free (path);
|
|
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
search_changed_cb (GtkSearchEntry *entry,
|
|
GtkFilter *custom_filter)
|
|
{
|
|
gtk_filter_changed (custom_filter, GTK_FILTER_CHANGE_DIFFERENT);
|
|
}
|
|
|
|
int
|
|
main (int argc, char *argv[])
|
|
{
|
|
GtkWidget *win, *vbox, *sw, *listview, *search_entry, *statusbar;
|
|
GListModel *dirmodel;
|
|
GtkTreeListModel *tree;
|
|
GtkFilterListModel *filter;
|
|
GtkFilter *custom_filter;
|
|
FileInfoSelection *selectionmodel;
|
|
GFile *root;
|
|
GListModel *toplevels;
|
|
|
|
gtk_init ();
|
|
|
|
win = gtk_window_new ();
|
|
gtk_window_set_default_size (GTK_WINDOW (win), 400, 600);
|
|
|
|
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
|
|
gtk_window_set_child (GTK_WINDOW (win), vbox);
|
|
|
|
search_entry = gtk_search_entry_new ();
|
|
gtk_box_append (GTK_BOX (vbox), search_entry);
|
|
|
|
sw = gtk_scrolled_window_new (NULL, NULL);
|
|
gtk_widget_set_vexpand (sw, TRUE);
|
|
gtk_search_entry_set_key_capture_widget (GTK_SEARCH_ENTRY (search_entry), sw);
|
|
gtk_box_append (GTK_BOX (vbox), sw);
|
|
|
|
listview = gtk_list_view_new_with_factory (
|
|
gtk_functions_list_item_factory_new (setup_widget,
|
|
NULL,
|
|
NULL, NULL));
|
|
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), listview);
|
|
|
|
if (argc > 1)
|
|
root = g_file_new_for_commandline_arg (argv[1]);
|
|
else
|
|
root = g_file_new_for_path (g_get_current_dir ());
|
|
dirmodel = create_list_model_for_directory (root);
|
|
tree = gtk_tree_list_model_new (FALSE,
|
|
dirmodel,
|
|
TRUE,
|
|
create_list_model_for_file_info,
|
|
NULL, NULL);
|
|
g_object_unref (dirmodel);
|
|
g_object_unref (root);
|
|
|
|
custom_filter = gtk_custom_filter_new (match_file, search_entry, NULL);
|
|
filter = gtk_filter_list_model_new (G_LIST_MODEL (tree), custom_filter);
|
|
g_signal_connect (search_entry, "search-changed", G_CALLBACK (search_changed_cb), custom_filter);
|
|
g_object_unref (custom_filter);
|
|
|
|
selectionmodel = file_info_selection_new (G_LIST_MODEL (filter));
|
|
g_object_unref (filter);
|
|
|
|
gtk_list_view_set_model (GTK_LIST_VIEW (listview), G_LIST_MODEL (selectionmodel));
|
|
|
|
statusbar = gtk_statusbar_new ();
|
|
gtk_widget_add_tick_callback (statusbar, (GtkTickCallback) update_statusbar, NULL, NULL);
|
|
g_object_set_data (G_OBJECT (statusbar), "model", filter);
|
|
g_signal_connect_swapped (filter, "items-changed", G_CALLBACK (update_statusbar), statusbar);
|
|
update_statusbar (GTK_STATUSBAR (statusbar));
|
|
gtk_box_append (GTK_BOX (vbox), statusbar);
|
|
|
|
g_object_unref (tree);
|
|
g_object_unref (selectionmodel);
|
|
|
|
gtk_widget_show (win);
|
|
|
|
toplevels = gtk_window_get_toplevels ();
|
|
while (g_list_model_get_n_items (toplevels))
|
|
g_main_context_iteration (NULL, TRUE);
|
|
|
|
return 0;
|
|
}
|