listview: Make widget actually do something
The thing we're actually doing is create and maintain a widget for every row. That's it. Also add a testcase using this. The testcase quickly allocates too many rows though and then becomes unresponsive though. You have been warned.
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
#include "gtklistview.h"
|
||||
|
||||
#include "gtkintl.h"
|
||||
#include "gtkrbtreeprivate.h"
|
||||
#include "gtklistitemfactoryprivate.h"
|
||||
|
||||
/**
|
||||
@@ -33,12 +34,30 @@
|
||||
* GtkListView is a widget to present a view into a large dynamic list of items.
|
||||
*/
|
||||
|
||||
typedef struct _ListRow ListRow;
|
||||
typedef struct _ListRowAugment ListRowAugment;
|
||||
|
||||
struct _GtkListView
|
||||
{
|
||||
GtkWidget parent_instance;
|
||||
|
||||
GListModel *model;
|
||||
GtkListItemFactory *item_factory;
|
||||
|
||||
GtkRbTree *rows;
|
||||
};
|
||||
|
||||
struct _ListRow
|
||||
{
|
||||
guint n_rows;
|
||||
guint height;
|
||||
GtkWidget *widget;
|
||||
};
|
||||
|
||||
struct _ListRowAugment
|
||||
{
|
||||
guint n_rows;
|
||||
guint height;
|
||||
};
|
||||
|
||||
enum
|
||||
@@ -53,10 +72,78 @@ G_DEFINE_TYPE (GtkListView, gtk_list_view, GTK_TYPE_WIDGET)
|
||||
|
||||
static GParamSpec *properties[N_PROPS] = { NULL, };
|
||||
|
||||
static gboolean
|
||||
gtk_list_view_is_empty (GtkListView *self)
|
||||
static void
|
||||
list_row_augment (GtkRbTree *tree,
|
||||
gpointer node_augment,
|
||||
gpointer node,
|
||||
gpointer left,
|
||||
gpointer right)
|
||||
{
|
||||
return self->model == NULL;
|
||||
ListRow *row = node;
|
||||
ListRowAugment *aug = node_augment;
|
||||
|
||||
aug->height = row->height;
|
||||
aug->n_rows = row->n_rows;
|
||||
|
||||
if (left)
|
||||
{
|
||||
ListRowAugment *left_aug = gtk_rb_tree_get_augment (tree, left);
|
||||
|
||||
aug->height += left_aug->height;
|
||||
aug->n_rows += left_aug->n_rows;
|
||||
}
|
||||
|
||||
if (right)
|
||||
{
|
||||
ListRowAugment *right_aug = gtk_rb_tree_get_augment (tree, right);
|
||||
|
||||
aug->height += right_aug->height;
|
||||
aug->n_rows += right_aug->n_rows;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
list_row_clear (gpointer _row)
|
||||
{
|
||||
ListRow *row = _row;
|
||||
|
||||
g_clear_pointer (&row->widget, gtk_widget_unparent);
|
||||
}
|
||||
|
||||
static ListRow *
|
||||
gtk_list_view_get_row (GtkListView *self,
|
||||
guint position,
|
||||
guint *offset)
|
||||
{
|
||||
ListRow *row, *tmp;
|
||||
|
||||
row = gtk_rb_tree_get_root (self->rows);
|
||||
|
||||
while (row)
|
||||
{
|
||||
tmp = gtk_rb_tree_node_get_left (row);
|
||||
if (tmp)
|
||||
{
|
||||
ListRowAugment *aug = gtk_rb_tree_get_augment (self->rows, tmp);
|
||||
if (position < aug->n_rows)
|
||||
{
|
||||
row = tmp;
|
||||
continue;
|
||||
}
|
||||
position -= aug->n_rows;
|
||||
}
|
||||
|
||||
if (position < row->n_rows)
|
||||
break;
|
||||
position -= row->n_rows;
|
||||
|
||||
row = gtk_rb_tree_node_get_right (row);
|
||||
}
|
||||
|
||||
if (offset)
|
||||
*offset = row ? position : 0;
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -69,17 +156,38 @@ gtk_list_view_measure (GtkWidget *widget,
|
||||
int *natural_baseline)
|
||||
{
|
||||
GtkListView *self = GTK_LIST_VIEW (widget);
|
||||
ListRow *row;
|
||||
int min, nat, child_min, child_nat;
|
||||
|
||||
if (gtk_list_view_is_empty (self))
|
||||
/* XXX: Figure out how to split a given height into per-row heights.
|
||||
* Good luck! */
|
||||
if (orientation == GTK_ORIENTATION_HORIZONTAL)
|
||||
for_size = -1;
|
||||
|
||||
min = 0;
|
||||
nat = 0;
|
||||
|
||||
for (row = gtk_rb_tree_get_first (self->rows);
|
||||
row != NULL;
|
||||
row = gtk_rb_tree_node_get_next (row))
|
||||
{
|
||||
*minimum = 0;
|
||||
*natural = 0;
|
||||
return;
|
||||
gtk_widget_measure (row->widget,
|
||||
orientation, for_size,
|
||||
&child_min, &child_nat, NULL, NULL);
|
||||
if (orientation == GTK_ORIENTATION_HORIZONTAL)
|
||||
{
|
||||
min = MAX (min, child_min);
|
||||
nat = MAX (nat, child_nat);
|
||||
}
|
||||
else
|
||||
{
|
||||
min += child_nat;
|
||||
nat = min;
|
||||
}
|
||||
}
|
||||
|
||||
*minimum = 0;
|
||||
*natural = 0;
|
||||
return;
|
||||
*minimum = min;
|
||||
*natural = nat;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -88,7 +196,81 @@ gtk_list_view_size_allocate (GtkWidget *widget,
|
||||
int height,
|
||||
int baseline)
|
||||
{
|
||||
//GtkListView *self = GTK_LIST_VIEW (widget);
|
||||
GtkListView *self = GTK_LIST_VIEW (widget);
|
||||
GtkAllocation child_allocation = { 0, 0, 0, 0 };
|
||||
ListRow *row;
|
||||
int nat;
|
||||
|
||||
child_allocation.width = width;
|
||||
|
||||
for (row = gtk_rb_tree_get_first (self->rows);
|
||||
row != NULL;
|
||||
row = gtk_rb_tree_node_get_next (row))
|
||||
{
|
||||
gtk_widget_measure (row->widget, GTK_ORIENTATION_VERTICAL,
|
||||
width,
|
||||
NULL, &nat, NULL, NULL);
|
||||
if (row->height != nat)
|
||||
{
|
||||
row->height = nat;
|
||||
gtk_rb_tree_node_mark_dirty (row);
|
||||
}
|
||||
child_allocation.height = row->height;
|
||||
gtk_widget_size_allocate (row->widget, &child_allocation, -1);
|
||||
child_allocation.y += child_allocation.height;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_list_view_remove_rows (GtkListView *self,
|
||||
guint position,
|
||||
guint n_rows)
|
||||
{
|
||||
ListRow *row;
|
||||
guint i;
|
||||
|
||||
if (n_rows == 0)
|
||||
return;
|
||||
|
||||
row = gtk_list_view_get_row (self, position, NULL);
|
||||
|
||||
for (i = 0; i < n_rows; i++)
|
||||
{
|
||||
ListRow *next = gtk_rb_tree_node_get_next (row);
|
||||
gtk_rb_tree_remove (self->rows, row);
|
||||
row = next;
|
||||
}
|
||||
|
||||
gtk_widget_queue_resize (GTK_WIDGET (self));
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_list_view_add_rows (GtkListView *self,
|
||||
guint position,
|
||||
guint n_rows)
|
||||
{
|
||||
ListRow *row;
|
||||
guint i;
|
||||
|
||||
if (n_rows == 0)
|
||||
return;
|
||||
|
||||
row = gtk_list_view_get_row (self, position, NULL);
|
||||
|
||||
for (i = 0; i < n_rows; i++)
|
||||
{
|
||||
ListRow *new_row;
|
||||
gpointer item;
|
||||
|
||||
new_row = gtk_rb_tree_insert_before (self->rows, row);
|
||||
new_row->n_rows = 1;
|
||||
new_row->widget = gtk_list_item_factory_create (self->item_factory);
|
||||
gtk_widget_insert_before (new_row->widget, GTK_WIDGET (self), row ? row->widget : NULL);
|
||||
item = g_list_model_get_item (self->model, position + i);
|
||||
gtk_list_item_factory_bind (self->item_factory, new_row->widget, item);
|
||||
}
|
||||
|
||||
gtk_widget_queue_resize (GTK_WIDGET (self));
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -98,6 +280,8 @@ gtk_list_view_model_items_changed_cb (GListModel *model,
|
||||
guint added,
|
||||
GtkListView *self)
|
||||
{
|
||||
gtk_list_view_remove_rows (self, position, removed);
|
||||
gtk_list_view_add_rows (self, position, added);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -106,6 +290,8 @@ gtk_list_view_clear_model (GtkListView *self)
|
||||
if (self->model == NULL)
|
||||
return;
|
||||
|
||||
gtk_list_view_remove_rows (self, 0, g_list_model_get_n_items (self->model));
|
||||
|
||||
g_signal_handlers_disconnect_by_func (self->model, gtk_list_view_model_items_changed_cb, self);
|
||||
g_clear_object (&self->model);
|
||||
}
|
||||
@@ -122,6 +308,16 @@ gtk_list_view_dispose (GObject *object)
|
||||
G_OBJECT_CLASS (gtk_list_view_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_list_view_finalize (GObject *object)
|
||||
{
|
||||
GtkListView *self = GTK_LIST_VIEW (object);
|
||||
|
||||
gtk_rb_tree_unref (self->rows);
|
||||
|
||||
G_OBJECT_CLASS (gtk_list_view_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_list_view_get_property (GObject *object,
|
||||
guint property_id,
|
||||
@@ -172,6 +368,7 @@ gtk_list_view_class_init (GtkListViewClass *klass)
|
||||
widget_class->size_allocate = gtk_list_view_size_allocate;
|
||||
|
||||
gobject_class->dispose = gtk_list_view_dispose;
|
||||
gobject_class->finalize = gtk_list_view_finalize;
|
||||
gobject_class->get_property = gtk_list_view_get_property;
|
||||
gobject_class->set_property = gtk_list_view_set_property;
|
||||
|
||||
@@ -195,6 +392,11 @@ gtk_list_view_class_init (GtkListViewClass *klass)
|
||||
static void
|
||||
gtk_list_view_init (GtkListView *self)
|
||||
{
|
||||
self->rows = gtk_rb_tree_new (ListRow,
|
||||
ListRowAugment,
|
||||
list_row_augment,
|
||||
list_row_clear,
|
||||
NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -253,9 +455,11 @@ gtk_list_view_set_model (GtkListView *self,
|
||||
self->model = g_object_ref (model);
|
||||
|
||||
g_signal_connect (model,
|
||||
"items-changed",
|
||||
"items-changed",
|
||||
G_CALLBACK (gtk_list_view_model_items_changed_cb),
|
||||
self);
|
||||
|
||||
gtk_list_view_add_rows (self, 0, g_list_model_get_n_items (model));
|
||||
}
|
||||
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
|
||||
@@ -268,13 +472,19 @@ gtk_list_view_set_functions (GtkListView *self,
|
||||
gpointer user_data,
|
||||
GDestroyNotify user_destroy)
|
||||
{
|
||||
guint n_items;
|
||||
|
||||
g_return_if_fail (GTK_IS_LIST_VIEW (self));
|
||||
g_return_if_fail (create_func);
|
||||
g_return_if_fail (bind_func);
|
||||
g_return_if_fail (user_data != NULL || user_destroy == NULL);
|
||||
|
||||
g_clear_object (&self->item_factory);
|
||||
n_items = self->model ? g_list_model_get_n_items (self->model) : 0;
|
||||
gtk_list_view_remove_rows (self, 0, n_items);
|
||||
|
||||
g_clear_object (&self->item_factory);
|
||||
self->item_factory = gtk_list_item_factory_new (create_func, bind_func, user_data, user_destroy);
|
||||
|
||||
gtk_list_view_add_rows (self, 0, n_items);
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ gtk_tests = [
|
||||
['testlist2'],
|
||||
['testlist3'],
|
||||
['testlist4'],
|
||||
['testlistview'],
|
||||
['testlevelbar'],
|
||||
['testlockbutton'],
|
||||
['testmenubutton'],
|
||||
|
||||
345
tests/testlistview.c
Normal file
345
tests/testlistview.c
Normal file
@@ -0,0 +1,345 @@
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#define ROWS 30
|
||||
|
||||
GSList *pending = NULL;
|
||||
guint active = 0;
|
||||
|
||||
static void
|
||||
got_files (GObject *enumerate,
|
||||
GAsyncResult *res,
|
||||
gpointer store);
|
||||
|
||||
static gboolean
|
||||
start_enumerate (GListStore *store)
|
||||
{
|
||||
GFileEnumerator *enumerate;
|
||||
GFile *file = g_object_get_data (G_OBJECT (store), "file");
|
||||
GError *error = NULL;
|
||||
|
||||
enumerate = g_file_enumerate_children (file,
|
||||
G_FILE_ATTRIBUTE_STANDARD_TYPE
|
||||
"," G_FILE_ATTRIBUTE_STANDARD_ICON
|
||||
"," G_FILE_ATTRIBUTE_STANDARD_NAME,
|
||||
0,
|
||||
NULL,
|
||||
&error);
|
||||
|
||||
if (enumerate == NULL)
|
||||
{
|
||||
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TOO_MANY_OPEN_FILES) && active)
|
||||
{
|
||||
g_clear_error (&error);
|
||||
pending = g_slist_prepend (pending, g_object_ref (store));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
g_clear_error (&error);
|
||||
g_object_unref (store);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (active > 20)
|
||||
{
|
||||
g_object_unref (enumerate);
|
||||
pending = g_slist_prepend (pending, g_object_ref (store));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
active++;
|
||||
g_file_enumerator_next_files_async (enumerate,
|
||||
g_file_is_native (file) ? 5000 : 100,
|
||||
G_PRIORITY_DEFAULT_IDLE,
|
||||
NULL,
|
||||
got_files,
|
||||
g_object_ref (store));
|
||||
|
||||
g_object_unref (enumerate);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
got_files (GObject *enumerate,
|
||||
GAsyncResult *res,
|
||||
gpointer store)
|
||||
{
|
||||
GList *l, *files;
|
||||
GFile *file = g_object_get_data (store, "file");
|
||||
GPtrArray *array;
|
||||
|
||||
files = g_file_enumerator_next_files_finish (G_FILE_ENUMERATOR (enumerate), res, NULL);
|
||||
if (files == NULL)
|
||||
{
|
||||
g_object_unref (store);
|
||||
if (pending)
|
||||
{
|
||||
GListStore *store = pending->data;
|
||||
pending = g_slist_remove (pending, store);
|
||||
start_enumerate (store);
|
||||
}
|
||||
active--;
|
||||
return;
|
||||
}
|
||||
|
||||
array = g_ptr_array_new ();
|
||||
g_ptr_array_new_with_free_func (g_object_unref);
|
||||
for (l = files; l; l = l->next)
|
||||
{
|
||||
GFileInfo *info = l->data;
|
||||
GFile *child;
|
||||
|
||||
child = g_file_get_child (file, g_file_info_get_name (info));
|
||||
g_object_set_data_full (G_OBJECT (info), "file", child, g_object_unref);
|
||||
g_ptr_array_add (array, info);
|
||||
}
|
||||
g_list_free (files);
|
||||
|
||||
g_list_store_splice (store, g_list_model_get_n_items (store), 0, array->pdata, array->len);
|
||||
g_ptr_array_unref (array);
|
||||
|
||||
g_file_enumerator_next_files_async (G_FILE_ENUMERATOR (enumerate),
|
||||
g_file_is_native (file) ? 5000 : 100,
|
||||
G_PRIORITY_DEFAULT_IDLE,
|
||||
NULL,
|
||||
got_files,
|
||||
store);
|
||||
}
|
||||
|
||||
static char *
|
||||
get_file_path (GFileInfo *info)
|
||||
{
|
||||
GFile *file;
|
||||
|
||||
file = g_object_get_data (G_OBJECT (info), "file");
|
||||
return g_file_get_path (file);
|
||||
}
|
||||
|
||||
static GListModel *
|
||||
create_list_model_for_directory (gpointer file)
|
||||
{
|
||||
GtkSortListModel *sort;
|
||||
GListStore *store;
|
||||
GtkSorter *sorter;
|
||||
|
||||
if (g_file_query_file_type (file, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) != G_FILE_TYPE_DIRECTORY)
|
||||
return NULL;
|
||||
|
||||
store = g_list_store_new (G_TYPE_FILE_INFO);
|
||||
g_object_set_data_full (G_OBJECT (store), "file", g_object_ref (file), g_object_unref);
|
||||
|
||||
if (!start_enumerate (store))
|
||||
return NULL;
|
||||
|
||||
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 (store), sorter);
|
||||
g_object_unref (sorter);
|
||||
|
||||
g_object_unref (store);
|
||||
return G_LIST_MODEL (sort);
|
||||
}
|
||||
|
||||
static GtkWidget *
|
||||
create_widget (gpointer unused)
|
||||
{
|
||||
return gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
|
||||
}
|
||||
|
||||
static void
|
||||
bind_widget (GtkWidget *box,
|
||||
gpointer item,
|
||||
gpointer unused)
|
||||
{
|
||||
GtkWidget *child;
|
||||
GFileInfo *info;
|
||||
GFile *file;
|
||||
guint depth;
|
||||
GIcon *icon;
|
||||
|
||||
while (gtk_widget_get_first_child (box))
|
||||
gtk_container_remove (GTK_CONTAINER (box), gtk_widget_get_first_child (box));
|
||||
|
||||
depth = gtk_tree_list_row_get_depth (item);
|
||||
if (depth > 0)
|
||||
{
|
||||
child = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
gtk_widget_set_size_request (child, 16 * depth, 0);
|
||||
gtk_container_add (GTK_CONTAINER (box), child);
|
||||
}
|
||||
|
||||
if (gtk_tree_list_row_is_expandable (item))
|
||||
{
|
||||
GtkWidget *title, *arrow;
|
||||
|
||||
child = g_object_new (GTK_TYPE_BOX, "css-name", "expander", NULL);
|
||||
|
||||
title = g_object_new (GTK_TYPE_TOGGLE_BUTTON, "css-name", "title", NULL);
|
||||
gtk_button_set_relief (GTK_BUTTON (title), GTK_RELIEF_NONE);
|
||||
g_object_bind_property (item, "expanded", title, "active", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
|
||||
g_object_set_data_full (G_OBJECT (title), "make-sure-its-not-unreffed", g_object_ref (item), g_object_unref);
|
||||
gtk_container_add (GTK_CONTAINER (child), title);
|
||||
|
||||
arrow = g_object_new (GTK_TYPE_SPINNER, "css-name", "arrow", NULL);
|
||||
gtk_container_add (GTK_CONTAINER (title), arrow);
|
||||
}
|
||||
else
|
||||
{
|
||||
child = gtk_image_new (); /* empty whatever */
|
||||
}
|
||||
gtk_container_add (GTK_CONTAINER (box), child);
|
||||
|
||||
info = gtk_tree_list_row_get_item (item);
|
||||
|
||||
icon = g_file_info_get_icon (info);
|
||||
if (icon)
|
||||
{
|
||||
child = gtk_image_new_from_gicon (icon);
|
||||
gtk_container_add (GTK_CONTAINER (box), child);
|
||||
}
|
||||
|
||||
file = g_object_get_data (G_OBJECT (info), "file");
|
||||
child = gtk_label_new (g_file_get_basename (file));
|
||||
g_object_unref (info);
|
||||
|
||||
gtk_container_add (GTK_CONTAINER (box), child);
|
||||
}
|
||||
|
||||
static GListModel *
|
||||
create_list_model_for_file_info (gpointer file_info,
|
||||
gpointer unused)
|
||||
{
|
||||
GFile *file = g_object_get_data (file_info, "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;
|
||||
}
|
||||
|
||||
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_object_get_data (G_OBJECT (info), "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;
|
||||
GFile *root;
|
||||
|
||||
gtk_init ();
|
||||
|
||||
win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||
gtk_window_set_default_size (GTK_WINDOW (win), 400, 600);
|
||||
g_signal_connect (win, "destroy", G_CALLBACK (gtk_main_quit), win);
|
||||
|
||||
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
|
||||
gtk_container_add (GTK_CONTAINER (win), vbox);
|
||||
|
||||
search_entry = gtk_search_entry_new ();
|
||||
gtk_container_add (GTK_CONTAINER (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_container_add (GTK_CONTAINER (vbox), sw);
|
||||
|
||||
listview = gtk_list_view_new ();
|
||||
gtk_list_view_set_functions (GTK_LIST_VIEW (listview),
|
||||
create_widget,
|
||||
bind_widget,
|
||||
NULL, NULL);
|
||||
gtk_container_add (GTK_CONTAINER (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);
|
||||
|
||||
gtk_list_view_set_model (GTK_LIST_VIEW (listview), G_LIST_MODEL (filter));
|
||||
|
||||
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_container_add (GTK_CONTAINER (vbox), statusbar);
|
||||
|
||||
g_object_unref (tree);
|
||||
g_object_unref (filter);
|
||||
|
||||
gtk_widget_show (win);
|
||||
|
||||
gtk_main ();
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user