forked from AuroraMiddleware/gtk
dd1c0c0b22
Make gtk_tree_list_model_new() take the root model as first argument, and make it transfer full, for consistency with other wrapping list constructors. Update all callers. Still missing here: Make the model property writable, and allow passing NULL in the constructor.
658 lines
18 KiB
C
658 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)
|
|
{
|
|
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));
|
|
|
|
return G_LIST_MODEL (gtk_sort_list_model_new (G_LIST_MODEL (dir), sorter));
|
|
}
|
|
|
|
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 char *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 (GtkSignalListItemFactory *factory,
|
|
GtkListItem *list_item)
|
|
{
|
|
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;
|
|
GtkTreeListModel *tree;
|
|
GtkFilterListModel *filter;
|
|
GtkFilter *custom_filter;
|
|
FileInfoSelection *selectionmodel;
|
|
GFile *root;
|
|
GListModel *toplevels;
|
|
GtkListItemFactory *factory;
|
|
|
|
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 ();
|
|
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);
|
|
|
|
factory = gtk_signal_list_item_factory_new ();
|
|
g_signal_connect (factory, "setup", G_CALLBACK (setup_widget), NULL);
|
|
listview = gtk_list_view_new_with_factory (factory);
|
|
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 ());
|
|
tree = gtk_tree_list_model_new (create_list_model_for_directory (root),
|
|
FALSE,
|
|
TRUE,
|
|
create_list_model_for_file_info,
|
|
NULL, NULL);
|
|
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);
|
|
|
|
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 (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;
|
|
}
|