forked from AuroraMiddleware/gtk
30f09ea10b
This splits GtkListItem into 2 parts: 1. GtkListItem This is purely a GObject with public API for developers who want to populate lists. There is no chance to cause conflict with GtkWidget properties that the list implementation assumed control over and defines a clear boundary. 2. GtkListItemWidget The widget part of the listitem. This is not only fully in control of the list machinery, the machinery can also use different widget implementations for different list widgets like I inted to for GtkColumnView.
668 lines
18 KiB
C
668 lines
18 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 *expander;
|
|
GtkWidget *icon;
|
|
GtkWidget *name;
|
|
GCancellable *cancellable;
|
|
|
|
GtkTreeListRow *current_item;
|
|
};
|
|
|
|
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_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;
|
|
|
|
row_data_unbind (data);
|
|
|
|
if (item == NULL)
|
|
return;
|
|
|
|
data->current_item = g_object_ref (item);
|
|
|
|
gtk_tree_expander_set_list_row (GTK_TREE_EXPANDER (data->expander), item);
|
|
|
|
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);
|
|
|
|
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->expander = gtk_tree_expander_new ();
|
|
gtk_box_append (GTK_BOX (box), data->expander);
|
|
|
|
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
|
|
gtk_tree_expander_set_child (GTK_TREE_EXPANDER (data->expander), box);
|
|
|
|
data->icon = gtk_image_new ();
|
|
gtk_box_append (GTK_BOX (box), data->icon);
|
|
|
|
data->name = gtk_label_new (NULL);
|
|
gtk_label_set_max_width_chars (GTK_LABEL (data->name), 25);
|
|
gtk_label_set_ellipsize (GTK_LABEL (data->name), PANGO_ELLIPSIZE_END);
|
|
gtk_box_append (GTK_BOX (box), data->name);
|
|
|
|
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);
|
|
}
|
|
|
|
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_grid_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_grid_view_set_model (GTK_GRID_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;
|
|
}
|