This is a container widget that takes over all the duties of tree expanding and collapsing. It has to be a container so it can capture keybindings while focus is inside the listitem. So far, this widget does not allow interacting with it, but it shows the expander arrow in its correct state. Also, testlistview uses this widget now instead of implementing expanding itself.
665 lines
18 KiB
C
665 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);
|
|
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->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_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;
|
|
}
|