diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c index 00bce1a10b..6cadec777b 100644 --- a/gtk/gtklistitemmanager.c +++ b/gtk/gtklistitemmanager.c @@ -24,6 +24,8 @@ #include "gtklistitemprivate.h" #include "gtkwidgetprivate.h" +#define GTK_LIST_VIEW_MAX_LIST_ITEMS 200 + struct _GtkListItemManager { GObject parent_instance; @@ -31,6 +33,14 @@ struct _GtkListItemManager GtkWidget *widget; GtkSelectionModel *model; GtkListItemFactory *factory; + + GtkRbTree *items; + + /* managing the visible region */ + GtkWidget *anchor; /* may be NULL if list is empty */ + int anchor_align; /* what to align the anchor to */ + guint anchor_start; /* start of region we allocate row widgets for */ + guint anchor_end; /* end of same region - first position to not have a widget */ }; struct _GtkListItemManagerClass @@ -45,14 +55,632 @@ struct _GtkListItemManagerChange G_DEFINE_TYPE (GtkListItemManager, gtk_list_item_manager, G_TYPE_OBJECT) +void +gtk_list_item_manager_augment_node (GtkRbTree *tree, + gpointer node_augment, + gpointer node, + gpointer left, + gpointer right) +{ + GtkListItemManagerItem *item = node; + GtkListItemManagerItemAugment *aug = node_augment; + + aug->n_items = item->n_items; + + if (left) + { + GtkListItemManagerItemAugment *left_aug = gtk_rb_tree_get_augment (tree, left); + + aug->n_items += left_aug->n_items; + } + + if (right) + { + GtkListItemManagerItemAugment *right_aug = gtk_rb_tree_get_augment (tree, right); + + aug->n_items += right_aug->n_items; + } +} + +static void +gtk_list_item_manager_clear_node (gpointer _item) +{ + GtkListItemManagerItem *item = _item; + + g_assert (item->widget == NULL); +} + +GtkListItemManager * +gtk_list_item_manager_new_for_size (GtkWidget *widget, + gsize element_size, + gsize augment_size, + GtkRbTreeAugmentFunc augment_func) +{ + GtkListItemManager *self; + + g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); + g_return_val_if_fail (element_size >= sizeof (GtkListItemManagerItem), NULL); + g_return_val_if_fail (augment_size >= sizeof (GtkListItemManagerItemAugment), NULL); + + self = g_object_new (GTK_TYPE_LIST_ITEM_MANAGER, NULL); + + /* not taking a ref because the widget refs us */ + self->widget = widget; + + self->items = gtk_rb_tree_new_for_size (element_size, + augment_size, + augment_func, + gtk_list_item_manager_clear_node, + NULL); + + return self; +} + +gpointer +gtk_list_item_manager_get_first (GtkListItemManager *self) +{ + return gtk_rb_tree_get_first (self->items); +} + +gpointer +gtk_list_item_manager_get_root (GtkListItemManager *self) +{ + return gtk_rb_tree_get_root (self->items); +} + +/* + * gtk_list_item_manager_get_nth: + * @self: a #GtkListItemManager + * @position: position of the item + * @offset: (out): offset into the returned item + * + * Looks up the GtkListItemManagerItem that represents @position. + * + * If a the returned item represents multiple rows, the @offset into + * the returned item for @position will be set. If the returned item + * represents a row with an existing widget, @offset will always be 0. + * + * Returns: (type GtkListItemManagerItem): the item for @position or + * %NULL if position is out of range + **/ +gpointer +gtk_list_item_manager_get_nth (GtkListItemManager *self, + guint position, + guint *offset) +{ + GtkListItemManagerItem *item, *tmp; + + item = gtk_rb_tree_get_root (self->items); + + while (item) + { + tmp = gtk_rb_tree_node_get_left (item); + if (tmp) + { + GtkListItemManagerItemAugment *aug = gtk_rb_tree_get_augment (self->items, tmp); + if (position < aug->n_items) + { + item = tmp; + continue; + } + position -= aug->n_items; + } + + if (position < item->n_items) + break; + position -= item->n_items; + + item = gtk_rb_tree_node_get_right (item); + } + + if (offset) + *offset = item ? position : 0; + + return item; +} + +guint +gtk_list_item_manager_get_item_position (GtkListItemManager *self, + gpointer item) +{ + GtkListItemManagerItem *parent, *left; + int pos; + + left = gtk_rb_tree_node_get_left (item); + if (left) + { + GtkListItemManagerItemAugment *aug = gtk_rb_tree_get_augment (self->items, left); + pos = aug->n_items; + } + else + { + pos = 0; + } + + for (parent = gtk_rb_tree_node_get_parent (item); + parent != NULL; + parent = gtk_rb_tree_node_get_parent (item)) + { + left = gtk_rb_tree_node_get_left (parent); + + if (left != item) + { + if (left) + { + GtkListItemManagerItemAugment *aug = gtk_rb_tree_get_augment (self->items, left); + pos += aug->n_items; + } + pos += parent->n_items; + } + + item = parent; + } + + return pos; +} + +gpointer +gtk_list_item_manager_get_item_augment (GtkListItemManager *self, + gpointer item) +{ + return gtk_rb_tree_get_augment (self->items, item); +} + +static void +gtk_list_item_manager_remove_items (GtkListItemManager *self, + GtkListItemManagerChange *change, + guint position, + guint n_items) +{ + GtkListItemManagerItem *item; + + if (n_items == 0) + return; + + item = gtk_list_item_manager_get_nth (self, position, NULL); + + while (n_items > 0) + { + if (item->n_items > n_items) + { + item->n_items -= n_items; + gtk_rb_tree_node_mark_dirty (item); + n_items = 0; + } + else + { + GtkListItemManagerItem *next = gtk_rb_tree_node_get_next (item); + if (item->widget) + gtk_list_item_manager_release_list_item (self, change, item->widget); + item->widget = NULL; + n_items -= item->n_items; + gtk_rb_tree_remove (self->items, item); + item = next; + } + } + + gtk_widget_queue_resize (GTK_WIDGET (self->widget)); +} + +static void +gtk_list_item_manager_add_items (GtkListItemManager *self, + guint position, + guint n_items) +{ + GtkListItemManagerItem *item; + guint offset; + + if (n_items == 0) + return; + + item = gtk_list_item_manager_get_nth (self, position, &offset); + + if (item == NULL || item->widget) + item = gtk_rb_tree_insert_before (self->items, item); + item->n_items += n_items; + gtk_rb_tree_node_mark_dirty (item); + + gtk_widget_queue_resize (GTK_WIDGET (self->widget)); +} + +static void +gtk_list_item_manager_unset_anchor (GtkListItemManager *self) +{ + self->anchor = NULL; + self->anchor_align = 0; + self->anchor_start = 0; + self->anchor_end = 0; +} + +static gboolean +gtk_list_item_manager_merge_list_items (GtkListItemManager *self, + GtkListItemManagerItem *first, + GtkListItemManagerItem *second) +{ + if (first->widget || second->widget) + return FALSE; + + first->n_items += second->n_items; + gtk_rb_tree_node_mark_dirty (first); + gtk_rb_tree_remove (self->items, second); + + return TRUE; +} + +static void +gtk_list_item_manager_release_items (GtkListItemManager *self, + GQueue *released) +{ + GtkListItemManagerItem *item, *prev, *next; + guint i; + + item = gtk_rb_tree_get_first (self->items); + i = 0; + while (i < self->anchor_start) + { + if (item->widget) + { + g_queue_push_tail (released, item->widget); + item->widget = NULL; + i++; + prev = gtk_rb_tree_node_get_previous (item); + if (prev && gtk_list_item_manager_merge_list_items (self, prev, item)) + item = prev; + next = gtk_rb_tree_node_get_next (item); + if (next && next->widget == NULL) + { + i += next->n_items; + if (!gtk_list_item_manager_merge_list_items (self, next, item)) + g_assert_not_reached (); + item = gtk_rb_tree_node_get_next (next); + } + else + { + item = next; + } + } + else + { + i += item->n_items; + item = gtk_rb_tree_node_get_next (item); + } + } + + item = gtk_list_item_manager_get_nth (self, self->anchor_end, NULL); + if (item == NULL) + return; + + if (item->widget) + { + g_queue_push_tail (released, item->widget); + item->widget = NULL; + prev = gtk_rb_tree_node_get_previous (item); + if (prev && gtk_list_item_manager_merge_list_items (self, prev, item)) + item = prev; + } + + while ((next = gtk_rb_tree_node_get_next (item))) + { + if (next->widget) + { + g_queue_push_tail (released, next->widget); + next->widget = NULL; + } + gtk_list_item_manager_merge_list_items (self, item, next); + } +} + +static void +gtk_list_item_manager_ensure_items (GtkListItemManager *self, + GtkListItemManagerChange *change, + guint update_start) +{ + GtkListItemManagerItem *item, *new_item; + guint i, offset; + GtkWidget *widget, *insert_after; + GQueue released = G_QUEUE_INIT; + + gtk_list_item_manager_release_items (self, &released); + + item = gtk_list_item_manager_get_nth (self, self->anchor_start, &offset); + if (offset > 0) + { + new_item = gtk_rb_tree_insert_before (self->items, item); + new_item->n_items = offset; + item->n_items -= offset; + gtk_rb_tree_node_mark_dirty (item); + } + + insert_after = NULL; + + for (i = self->anchor_start; i < self->anchor_end; i++) + { + if (item->n_items > 1) + { + new_item = gtk_rb_tree_insert_before (self->items, item); + new_item->n_items = 1; + item->n_items--; + gtk_rb_tree_node_mark_dirty (item); + } + else + { + new_item = item; + item = gtk_rb_tree_node_get_next (item); + } + if (new_item->widget == NULL) + { + if (change) + { + new_item->widget = gtk_list_item_manager_try_reacquire_list_item (self, + change, + i, + insert_after); + } + if (new_item->widget == NULL) + { + new_item->widget = g_queue_pop_head (&released); + if (new_item->widget) + { + gtk_list_item_manager_move_list_item (self, + new_item->widget, + i, + insert_after); + } + else + { + new_item->widget = gtk_list_item_manager_acquire_list_item (self, + i, + insert_after); + } + } + } + else + { + if (update_start <= i) + gtk_list_item_manager_update_list_item (self, new_item->widget, i); + } + insert_after = new_item->widget; + } + + while ((widget = g_queue_pop_head (&released))) + gtk_list_item_manager_release_list_item (self, NULL, widget); +} + +void +gtk_list_item_manager_set_anchor (GtkListItemManager *self, + guint position, + double align, + GtkListItemManagerChange *change, + guint update_start) +{ + GtkListItemManagerItem *item; + guint items_before, items_after, total_items, n_items; + + g_assert (align >= 0.0 && align <= 1.0); + + if (self->model) + n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model)); + else + n_items = 0; + if (n_items == 0) + { + gtk_list_item_manager_unset_anchor (self); + return; + } + total_items = MIN (GTK_LIST_VIEW_MAX_LIST_ITEMS, n_items); + if (align < 0.5) + items_before = ceil (total_items * align); + else + items_before = floor (total_items * align); + items_after = total_items - items_before; + self->anchor_start = CLAMP (position, items_before, n_items - items_after) - items_before; + self->anchor_end = self->anchor_start + total_items; + g_assert (self->anchor_end <= n_items); + + gtk_list_item_manager_ensure_items (self, change, update_start); + + item = gtk_list_item_manager_get_nth (self, position, NULL); + self->anchor = item->widget; + g_assert (self->anchor); + self->anchor_align = align; + + gtk_widget_queue_allocate (GTK_WIDGET (self->widget)); +} + +static void +gtk_list_item_manager_model_items_changed_cb (GListModel *model, + guint position, + guint removed, + guint added, + GtkListItemManager *self) +{ + GtkListItemManagerChange *change; + + change = gtk_list_item_manager_begin_change (self); + + gtk_list_item_manager_remove_items (self, change, position, removed); + gtk_list_item_manager_add_items (self, position, added); + + /* The anchor was removed, but it may just have moved to a different position */ + if (self->anchor && gtk_list_item_manager_change_contains (change, self->anchor)) + { + /* The anchor was removed, do a more expensive rebuild trying to find if + * the anchor maybe got readded somewhere else */ + GtkListItemManagerItem *item, *new_item; + GtkWidget *insert_after; + guint i, offset, anchor_pos; + + item = gtk_list_item_manager_get_nth (self, position, &offset); + for (new_item = item ? gtk_rb_tree_node_get_previous (item) : gtk_rb_tree_get_last (self->items); + new_item && new_item->widget == NULL; + new_item = gtk_rb_tree_node_get_previous (new_item)) + { } + if (new_item) + insert_after = new_item->widget; + else + insert_after = NULL; /* we're at the start */ + + for (i = 0; i < added; i++) + { + GtkWidget *widget; + + widget = gtk_list_item_manager_try_reacquire_list_item (self, + change, + position + i, + insert_after); + if (widget == NULL) + { + offset++; + continue; + } + + if (offset > 0) + { + new_item = gtk_rb_tree_insert_before (self->items, item); + new_item->n_items = offset; + item->n_items -= offset; + offset = 0; + gtk_rb_tree_node_mark_dirty (item); + } + + if (item->n_items == 1) + { + new_item = item; + item = gtk_rb_tree_node_get_next (item); + } + else + { + new_item = gtk_rb_tree_insert_before (self->items, item); + new_item->n_items = 1; + item->n_items--; + gtk_rb_tree_node_mark_dirty (item); + } + + new_item->widget = widget; + insert_after = widget; + + if (widget == self->anchor) + { + anchor_pos = position + i; + break; + } + } + + if (i == added) + { + /* The anchor wasn't readded. Guess a good anchor position */ + anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)); + + anchor_pos = position + (anchor_pos - position) * added / removed; + if (anchor_pos >= g_list_model_get_n_items (G_LIST_MODEL (self->model)) && + anchor_pos > 0) + anchor_pos--; + } + gtk_list_item_manager_set_anchor (self, anchor_pos, self->anchor_align, change, position); + } + else + { + /* The anchor is still where it was. + * We just may need to update its position and check that its surrounding widgets + * exist (they might be new ones). */ + guint anchor_pos; + + if (self->anchor) + { + anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)); + + if (anchor_pos >= position) + anchor_pos += added - removed; + } + else + { + anchor_pos = 0; + } + + gtk_list_item_manager_set_anchor (self, anchor_pos, self->anchor_align, change, position); + } + + gtk_list_item_manager_end_change (self, change); +} + +static void +gtk_list_item_manager_model_selection_changed_cb (GListModel *model, + guint position, + guint n_items, + GtkListItemManager *self) +{ + GtkListItemManagerItem *item; + guint offset; + + item = gtk_list_item_manager_get_nth (self, position, &offset); + + if (offset) + { + position += item->n_items - offset; + if (item->n_items - offset > n_items) + n_items = 0; + else + n_items -= item->n_items - offset; + item = gtk_rb_tree_node_get_next (item); + } + + while (n_items > 0) + { + if (item->widget) + gtk_list_item_manager_update_list_item (self, item->widget, position); + position += item->n_items; + n_items -= MIN (n_items, item->n_items); + item = gtk_rb_tree_node_get_next (item); + } +} + +guint +gtk_list_item_manager_get_anchor (GtkListItemManager *self, + double *align) +{ + guint anchor_pos; + + if (self->anchor) + anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)); + else + anchor_pos = 0; + + if (align) + *align = self->anchor_align; + + return anchor_pos; +} + +static void +gtk_list_item_manager_clear_model (GtkListItemManager *self) +{ + if (self->model == NULL) + return; + + gtk_list_item_manager_remove_items (self, NULL, 0, g_list_model_get_n_items (G_LIST_MODEL (self->model))); + + g_signal_handlers_disconnect_by_func (self->model, + gtk_list_item_manager_model_selection_changed_cb, + self); + g_signal_handlers_disconnect_by_func (self->model, + gtk_list_item_manager_model_items_changed_cb, + self); + g_clear_object (&self->model); + + gtk_list_item_manager_unset_anchor (self); +} + static void gtk_list_item_manager_dispose (GObject *object) { GtkListItemManager *self = GTK_LIST_ITEM_MANAGER (object); - g_clear_object (&self->model); + gtk_list_item_manager_clear_model (self); + g_clear_object (&self->factory); + g_clear_pointer (&self->items, gtk_rb_tree_unref); + G_OBJECT_CLASS (gtk_list_item_manager_parent_class)->dispose (object); } @@ -69,33 +697,27 @@ gtk_list_item_manager_init (GtkListItemManager *self) { } -GtkListItemManager * -gtk_list_item_manager_new (GtkWidget *widget) -{ - GtkListItemManager *self; - - g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); - - self = g_object_new (GTK_TYPE_LIST_ITEM_MANAGER, NULL); - - self->widget = widget; - - return self; -} - void gtk_list_item_manager_set_factory (GtkListItemManager *self, GtkListItemFactory *factory) { + guint n_items, anchor; + double anchor_align; + g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (factory)); if (self->factory == factory) return; - g_clear_object (&self->factory); + n_items = self->model ? g_list_model_get_n_items (G_LIST_MODEL (self->model)) : 0; + anchor = gtk_list_item_manager_get_anchor (self, &anchor_align); + gtk_list_item_manager_remove_items (self, NULL, 0, n_items); - self->factory = g_object_ref (factory); + g_set_object (&self->factory, factory); + + gtk_list_item_manager_add_items (self, 0, n_items); + gtk_list_item_manager_set_anchor (self, anchor, anchor_align, NULL, (guint) -1); } GtkListItemFactory * @@ -111,15 +733,30 @@ gtk_list_item_manager_set_model (GtkListItemManager *self, GtkSelectionModel *model) { g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); - g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model)); + g_return_if_fail (model == NULL || GTK_IS_SELECTION_MODEL (model)); if (self->model == model) return; - g_clear_object (&self->model); + gtk_list_item_manager_clear_model (self); if (model) - self->model = g_object_ref (model); + { + self->model = g_object_ref (model); + + g_signal_connect (model, + "items-changed", + G_CALLBACK (gtk_list_item_manager_model_items_changed_cb), + self); + g_signal_connect (model, + "selection-changed", + G_CALLBACK (gtk_list_item_manager_model_selection_changed_cb), + self); + + gtk_list_item_manager_add_items (self, 0, g_list_model_get_n_items (G_LIST_MODEL (model))); + + gtk_list_item_manager_set_anchor (self, 0, 0, NULL, (guint) -1); + } } GtkSelectionModel * @@ -400,3 +1037,4 @@ gtk_list_item_manager_release_list_item (GtkListItemManager *self, gtk_list_item_factory_unbind (self->factory, GTK_LIST_ITEM (item)); gtk_widget_unparent (item); } + diff --git a/gtk/gtklistitemmanagerprivate.h b/gtk/gtklistitemmanagerprivate.h index 47a4434d7d..9e1ebab073 100644 --- a/gtk/gtklistitemmanagerprivate.h +++ b/gtk/gtklistitemmanagerprivate.h @@ -24,6 +24,7 @@ #include "gtk/gtktypes.h" #include "gtk/gtklistitemfactoryprivate.h" +#include "gtk/gtkrbtreeprivate.h" #include "gtk/gtkselectionmodel.h" G_BEGIN_DECLS @@ -38,10 +39,44 @@ G_BEGIN_DECLS typedef struct _GtkListItemManager GtkListItemManager; typedef struct _GtkListItemManagerClass GtkListItemManagerClass; typedef struct _GtkListItemManagerChange GtkListItemManagerChange; +typedef struct _GtkListItemManagerItem GtkListItemManagerItem; /* sorry */ +typedef struct _GtkListItemManagerItemAugment GtkListItemManagerItemAugment; + +struct _GtkListItemManagerItem +{ + GtkWidget *widget; + guint n_items; +}; + +struct _GtkListItemManagerItemAugment +{ + guint n_items; +}; + GType gtk_list_item_manager_get_type (void) G_GNUC_CONST; -GtkListItemManager * gtk_list_item_manager_new (GtkWidget *widget); +GtkListItemManager * gtk_list_item_manager_new_for_size (GtkWidget *widget, + gsize element_size, + gsize augment_size, + GtkRbTreeAugmentFunc augment_func); +#define gtk_list_item_manager_new(widget, type, augment_type, augment_func) \ + gtk_list_item_manager_new_for_size (widget, sizeof (type), sizeof (augment_type), (augment_func)) + +void gtk_list_item_manager_augment_node (GtkRbTree *tree, + gpointer node_augment, + gpointer node, + gpointer left, + gpointer right); +gpointer gtk_list_item_manager_get_root (GtkListItemManager *self); +gpointer gtk_list_item_manager_get_first (GtkListItemManager *self); +gpointer gtk_list_item_manager_get_nth (GtkListItemManager *self, + guint position, + guint *offset); +guint gtk_list_item_manager_get_item_position (GtkListItemManager *self, + gpointer item); +gpointer gtk_list_item_manager_get_item_augment (GtkListItemManager *self, + gpointer item); void gtk_list_item_manager_set_factory (GtkListItemManager *self, GtkListItemFactory *factory); @@ -52,6 +87,14 @@ GtkSelectionModel * gtk_list_item_manager_get_model (GtkListItemMana guint gtk_list_item_manager_get_size (GtkListItemManager *self); +void gtk_list_item_manager_set_anchor (GtkListItemManager *self, + guint position, + double align, + GtkListItemManagerChange *change, + guint update_start); +guint gtk_list_item_manager_get_anchor (GtkListItemManager *self, + double *align); + GtkListItemManagerChange * gtk_list_item_manager_begin_change (GtkListItemManager *self); void gtk_list_item_manager_end_change (GtkListItemManager *self, diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index 36c18d698b..e91b1b7b62 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -58,26 +58,18 @@ struct _GtkListView GtkAdjustment *adjustment[2]; GtkScrollablePolicy scroll_policy[2]; - GtkRbTree *rows; int list_width; - - /* managing the visible region */ - GtkWidget *anchor; /* may be NULL if list is empty */ - int anchor_align; /* what to align the anchor to */ - guint anchor_start; /* start of region we allocate row widgets for */ - guint anchor_end; /* end of same region - first position to not have a widget */ }; struct _ListRow { - guint n_rows; + GtkListItemManagerItem parent; guint height; /* per row */ - GtkWidget *widget; }; struct _ListRowAugment { - guint n_rows; + GtkListItemManagerItemAugment parent; guint height; /* total */ }; @@ -106,15 +98,15 @@ dump (GtkListView *self) n_widgets = 0; n_list_rows = 0; - g_print ("ANCHOR: %u - %u\n", self->anchor_start, self->anchor_end); - for (row = gtk_rb_tree_get_first (self->rows); + //g_print ("ANCHOR: %u - %u\n", self->anchor_start, self->anchor_end); + for (row = gtk_list_item_manager_get_first (self->item_manager); row; row = gtk_rb_tree_node_get_next (row)) { - if (row->widget) + if (row->parent.widget) n_widgets++; n_list_rows++; - g_print (" %4u%s (%upx)\n", row->n_rows, row->widget ? " (widget)" : "", row->height); + g_print (" %4u%s (%upx)\n", row->parent.n_items, row->parent.widget ? " (widget)" : "", row->height); } g_print (" => %u widgets in %u list rows\n", n_widgets, n_list_rows); @@ -130,15 +122,15 @@ list_row_augment (GtkRbTree *tree, ListRow *row = node; ListRowAugment *aug = node_augment; - aug->height = row->height * row->n_rows; - aug->n_rows = row->n_rows; + gtk_list_item_manager_augment_node (tree, node_augment, node, left, right); + + aug->height = row->height * row->parent.n_items; 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) @@ -146,94 +138,9 @@ list_row_augment (GtkRbTree *tree, 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_assert (row->widget == NULL); -} - -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 guint -list_row_get_position (GtkListView *self, - ListRow *row) -{ - ListRow *parent, *left; - int pos; - - left = gtk_rb_tree_node_get_left (row); - if (left) - { - ListRowAugment *aug = gtk_rb_tree_get_augment (self->rows, left); - pos = aug->n_rows; - } - else - { - pos = 0; - } - - for (parent = gtk_rb_tree_node_get_parent (row); - parent != NULL; - parent = gtk_rb_tree_node_get_parent (row)) - { - left = gtk_rb_tree_node_get_left (parent); - - if (left != row) - { - if (left) - { - ListRowAugment *aug = gtk_rb_tree_get_augment (self->rows, left); - pos += aug->n_rows; - } - pos += parent->n_rows; - } - - row = parent; - } - - return pos; -} - static ListRow * gtk_list_view_get_row_at_y (GtkListView *self, int y, @@ -241,14 +148,14 @@ gtk_list_view_get_row_at_y (GtkListView *self, { ListRow *row, *tmp; - row = gtk_rb_tree_get_root (self->rows); + row = gtk_list_item_manager_get_root (self->item_manager); while (row) { tmp = gtk_rb_tree_node_get_left (row); if (tmp) { - ListRowAugment *aug = gtk_rb_tree_get_augment (self->rows, tmp); + ListRowAugment *aug = gtk_list_item_manager_get_item_augment (self->item_manager, tmp); if (y < aug->height) { row = tmp; @@ -257,9 +164,9 @@ gtk_list_view_get_row_at_y (GtkListView *self, y -= aug->height; } - if (y < row->height * row->n_rows) + if (y < row->height * row->parent.n_items) break; - y -= row->height * row->n_rows; + y -= row->height * row->parent.n_items; row = gtk_rb_tree_node_get_right (row); } @@ -280,7 +187,7 @@ list_row_get_y (GtkListView *self, left = gtk_rb_tree_node_get_left (row); if (left) { - ListRowAugment *aug = gtk_rb_tree_get_augment (self->rows, left); + ListRowAugment *aug = gtk_list_item_manager_get_item_augment (self->item_manager, left); y = aug->height; } else @@ -296,10 +203,10 @@ list_row_get_y (GtkListView *self, { if (left) { - ListRowAugment *aug = gtk_rb_tree_get_augment (self->rows, left); + ListRowAugment *aug = gtk_list_item_manager_get_item_augment (self->item_manager, left); y += aug->height; } - y += parent->height * parent->n_rows; + y += parent->height * parent->parent.n_items; } row = parent; @@ -314,218 +221,14 @@ gtk_list_view_get_list_height (GtkListView *self) ListRow *row; ListRowAugment *aug; - row = gtk_rb_tree_get_root (self->rows); + row = gtk_list_item_manager_get_root (self->item_manager); if (row == NULL) return 0; - aug = gtk_rb_tree_get_augment (self->rows, row); + aug = gtk_list_item_manager_get_item_augment (self->item_manager, row); return aug->height; } -static gboolean -gtk_list_view_merge_list_rows (GtkListView *self, - ListRow *first, - ListRow *second) -{ - if (first->widget || second->widget) - return FALSE; - - first->n_rows += second->n_rows; - gtk_rb_tree_node_mark_dirty (first); - gtk_rb_tree_remove (self->rows, second); - - return TRUE; -} - -static void -gtk_list_view_release_rows (GtkListView *self, - GQueue *released) -{ - ListRow *row, *prev, *next; - guint i; - - row = gtk_rb_tree_get_first (self->rows); - i = 0; - while (i < self->anchor_start) - { - if (row->widget) - { - g_queue_push_tail (released, row->widget); - row->widget = NULL; - i++; - prev = gtk_rb_tree_node_get_previous (row); - if (prev && gtk_list_view_merge_list_rows (self, prev, row)) - row = prev; - next = gtk_rb_tree_node_get_next (row); - if (next && next->widget == NULL) - { - i += next->n_rows; - if (!gtk_list_view_merge_list_rows (self, next, row)) - g_assert_not_reached (); - row = gtk_rb_tree_node_get_next (next); - } - else - { - row = next; - } - } - else - { - i += row->n_rows; - row = gtk_rb_tree_node_get_next (row); - } - } - - row = gtk_list_view_get_row (self, self->anchor_end, NULL); - if (row == NULL) - return; - - if (row->widget) - { - g_queue_push_tail (released, row->widget); - row->widget = NULL; - prev = gtk_rb_tree_node_get_previous (row); - if (prev && gtk_list_view_merge_list_rows (self, prev, row)) - row = prev; - } - - while ((next = gtk_rb_tree_node_get_next (row))) - { - if (next->widget) - { - g_queue_push_tail (released, next->widget); - next->widget = NULL; - } - gtk_list_view_merge_list_rows (self, row, next); - } -} - -static void -gtk_list_view_ensure_rows (GtkListView *self, - GtkListItemManagerChange *change, - guint update_start) -{ - ListRow *row, *new_row; - guint i, offset; - GtkWidget *widget, *insert_after; - GQueue released = G_QUEUE_INIT; - - gtk_list_view_release_rows (self, &released); - - row = gtk_list_view_get_row (self, self->anchor_start, &offset); - if (offset > 0) - { - new_row = gtk_rb_tree_insert_before (self->rows, row); - new_row->n_rows = offset; - row->n_rows -= offset; - gtk_rb_tree_node_mark_dirty (row); - } - - insert_after = NULL; - - for (i = self->anchor_start; i < self->anchor_end; i++) - { - if (row->n_rows > 1) - { - new_row = gtk_rb_tree_insert_before (self->rows, row); - new_row->n_rows = 1; - row->n_rows--; - gtk_rb_tree_node_mark_dirty (row); - } - else - { - new_row = row; - row = gtk_rb_tree_node_get_next (row); - } - if (new_row->widget == NULL) - { - if (change) - { - new_row->widget = gtk_list_item_manager_try_reacquire_list_item (self->item_manager, - change, - i, - insert_after); - } - if (new_row->widget == NULL) - { - new_row->widget = g_queue_pop_head (&released); - if (new_row->widget) - { - gtk_list_item_manager_move_list_item (self->item_manager, - new_row->widget, - i, - insert_after); - } - else - { - new_row->widget = gtk_list_item_manager_acquire_list_item (self->item_manager, - i, - insert_after); - } - } - } - else - { - if (update_start <= i) - gtk_list_item_manager_update_list_item (self->item_manager, new_row->widget, i); - } - insert_after = new_row->widget; - } - - while ((widget = g_queue_pop_head (&released))) - gtk_list_item_manager_release_list_item (self->item_manager, NULL, widget); -} - -static void -gtk_list_view_unset_anchor (GtkListView *self) -{ - self->anchor = NULL; - self->anchor_align = 0; - self->anchor_start = 0; - self->anchor_end = 0; -} - -static void -gtk_list_view_set_anchor (GtkListView *self, - guint position, - double align, - GtkListItemManagerChange *change, - guint update_start) -{ - ListRow *row; - guint items_before, items_after, total_items, n_rows; - - g_assert (align >= 0.0 && align <= 1.0); - - if (self->model) - n_rows = g_list_model_get_n_items (self->model); - else - n_rows = 0; - if (n_rows == 0) - { - gtk_list_view_unset_anchor (self); - return; - } - total_items = MIN (GTK_LIST_VIEW_MAX_LIST_ITEMS, n_rows); - if (align < 0.5) - items_before = ceil (total_items * align); - else - items_before = floor (total_items * align); - items_after = total_items - items_before; - self->anchor_start = CLAMP (position, items_before, n_rows - items_after) - items_before; - self->anchor_end = self->anchor_start + total_items; - g_assert (self->anchor_end <= n_rows); - - gtk_list_view_ensure_rows (self, change, update_start); - - row = gtk_list_view_get_row (self, position, NULL); - self->anchor = row->widget; - g_assert (self->anchor); - self->anchor_align = align; - - gtk_widget_queue_allocate (GTK_WIDGET (self)); -} - static void gtk_list_view_adjustment_value_changed_cb (GtkAdjustment *adjustment, GtkListView *self) @@ -539,17 +242,15 @@ gtk_list_view_adjustment_value_changed_cb (GtkAdjustment *adjustment, row = gtk_list_view_get_row_at_y (self, gtk_adjustment_get_value (adjustment), &dy); if (row) { - pos = list_row_get_position (self, row) + dy / row->height; + pos = gtk_list_item_manager_get_item_position (self->item_manager, row) + dy / row->height; } else pos = 0; - gtk_list_view_set_anchor (self, pos, 0, NULL, (guint) -1); - } - else - { - gtk_widget_queue_allocate (GTK_WIDGET (self)); + gtk_list_item_manager_set_anchor (self->item_manager, pos, 0, NULL, (guint) -1); } + + gtk_widget_queue_allocate (GTK_WIDGET (self)); } static void @@ -570,19 +271,19 @@ gtk_list_view_update_adjustments (GtkListView *self, else { ListRow *row; + guint anchor; + double anchor_align; page_size = gtk_widget_get_height (GTK_WIDGET (self)); upper = gtk_list_view_get_list_height (self); - if (self->anchor) - row = gtk_list_view_get_row (self, gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)), NULL); - else - row = NULL; + anchor = gtk_list_item_manager_get_anchor (self->item_manager, &anchor_align); + row = gtk_list_item_manager_get_nth (self->item_manager, anchor, NULL); if (row) value = list_row_get_y (self, row); else value = 0; - value -= self->anchor_align * (page_size - (row ? row->height : 0)); + value -= anchor_align * (page_size - (row ? row->height : 0)); } upper = MAX (upper, page_size); @@ -637,15 +338,15 @@ gtk_list_view_measure_across (GtkWidget *widget, min = 0; nat = 0; - for (row = gtk_rb_tree_get_first (self->rows); + for (row = gtk_list_item_manager_get_first (self->item_manager); row != NULL; row = gtk_rb_tree_node_get_next (row)) { /* ignore unavailable rows */ - if (row->widget == NULL) + if (row->parent.widget == NULL) continue; - gtk_widget_measure (row->widget, + gtk_widget_measure (row->parent.widget, orientation, for_size, &child_min, &child_nat, NULL, NULL); min = MAX (min, child_min); @@ -675,13 +376,13 @@ gtk_list_view_measure_list (GtkWidget *widget, min = 0; nat = 0; - for (row = gtk_rb_tree_get_first (self->rows); + for (row = gtk_list_item_manager_get_first (self->item_manager); row != NULL; row = gtk_rb_tree_node_get_next (row)) { - if (row->widget) + if (row->parent.widget) { - gtk_widget_measure (row->widget, + gtk_widget_measure (row->parent.widget, orientation, for_size, &child_min, &child_nat, NULL, NULL); g_array_append_val (min_heights, child_min); @@ -691,7 +392,7 @@ gtk_list_view_measure_list (GtkWidget *widget, } else { - n_unknown += row->n_rows; + n_unknown += row->parent.n_items; } } @@ -735,7 +436,7 @@ gtk_list_view_size_allocate (GtkWidget *widget, int min, nat, row_height; /* step 0: exit early if list is empty */ - if (gtk_rb_tree_get_root (self->rows) == NULL) + if (gtk_list_item_manager_get_root (self->item_manager) == NULL) return; /* step 1: determine width of the list */ @@ -750,14 +451,14 @@ gtk_list_view_size_allocate (GtkWidget *widget, /* step 2: determine height of known list items */ heights = g_array_new (FALSE, FALSE, sizeof (int)); - for (row = gtk_rb_tree_get_first (self->rows); + for (row = gtk_list_item_manager_get_first (self->item_manager); row != NULL; row = gtk_rb_tree_node_get_next (row)) { - if (row->widget == NULL) + if (row->parent.widget == NULL) continue; - gtk_widget_measure (row->widget, GTK_ORIENTATION_VERTICAL, + gtk_widget_measure (row->parent.widget, GTK_ORIENTATION_VERTICAL, self->list_width, &min, &nat, NULL, NULL); if (self->scroll_policy[GTK_ORIENTATION_VERTICAL] == GTK_SCROLL_MINIMUM) @@ -776,11 +477,11 @@ gtk_list_view_size_allocate (GtkWidget *widget, row_height = gtk_list_view_get_unknown_row_height (self, heights); g_array_free (heights, TRUE); - for (row = gtk_rb_tree_get_first (self->rows); + for (row = gtk_list_item_manager_get_first (self->item_manager); row != NULL; row = gtk_rb_tree_node_get_next (row)) { - if (row->widget) + if (row->parent.widget) continue; if (row->height != row_height) @@ -798,257 +499,20 @@ gtk_list_view_size_allocate (GtkWidget *widget, child_allocation.x = - gtk_adjustment_get_value (self->adjustment[GTK_ORIENTATION_HORIZONTAL]); child_allocation.y = - round (gtk_adjustment_get_value (self->adjustment[GTK_ORIENTATION_VERTICAL])); child_allocation.width = self->list_width; - for (row = gtk_rb_tree_get_first (self->rows); + for (row = gtk_list_item_manager_get_first (self->item_manager); row != NULL; row = gtk_rb_tree_node_get_next (row)) { - if (row->widget) + if (row->parent.widget) { child_allocation.height = row->height; - gtk_widget_size_allocate (row->widget, &child_allocation, -1); + gtk_widget_size_allocate (row->parent.widget, &child_allocation, -1); } - child_allocation.y += row->height * row->n_rows; + child_allocation.y += row->height * row->parent.n_items; } } -static guint -gtk_list_view_get_anchor (GtkListView *self, - double *align) -{ - guint anchor_pos; - - if (self->anchor) - anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)); - else - anchor_pos = 0; - - if (align) - *align = self->anchor_align; - - return anchor_pos; -} - -static void -gtk_list_view_remove_rows (GtkListView *self, - GtkListItemManagerChange *change, - guint position, - guint n_rows) -{ - ListRow *row; - - if (n_rows == 0) - return; - - row = gtk_list_view_get_row (self, position, NULL); - - while (n_rows > 0) - { - if (row->n_rows > n_rows) - { - row->n_rows -= n_rows; - gtk_rb_tree_node_mark_dirty (row); - n_rows = 0; - } - else - { - ListRow *next = gtk_rb_tree_node_get_next (row); - if (row->widget) - gtk_list_item_manager_release_list_item (self->item_manager, change, row->widget); - row->widget = NULL; - n_rows -= row->n_rows; - 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 offset; - - if (n_rows == 0) - return; - - row = gtk_list_view_get_row (self, position, &offset); - - if (row == NULL || row->widget) - row = gtk_rb_tree_insert_before (self->rows, row); - row->n_rows += n_rows; - gtk_rb_tree_node_mark_dirty (row); - - gtk_widget_queue_resize (GTK_WIDGET (self)); -} - -static void -gtk_list_view_model_items_changed_cb (GListModel *model, - guint position, - guint removed, - guint added, - GtkListView *self) -{ - GtkListItemManagerChange *change; - - change = gtk_list_item_manager_begin_change (self->item_manager); - - gtk_list_view_remove_rows (self, change, position, removed); - gtk_list_view_add_rows (self, position, added); - - /* The anchor was removed, but it may just have moved to a different position */ - if (self->anchor && gtk_list_item_manager_change_contains (change, self->anchor)) - { - /* The anchor was removed, do a more expensive rebuild trying to find if - * the anchor maybe got readded somewhere else */ - ListRow *row, *new_row; - GtkWidget *insert_after; - guint i, offset, anchor_pos; - - row = gtk_list_view_get_row (self, position, &offset); - for (new_row = row ? gtk_rb_tree_node_get_previous (row) : gtk_rb_tree_get_last (self->rows); - new_row && new_row->widget == NULL; - new_row = gtk_rb_tree_node_get_previous (new_row)) - { } - if (new_row) - insert_after = new_row->widget; - else - insert_after = NULL; /* we're at the start */ - - for (i = 0; i < added; i++) - { - GtkWidget *widget; - - widget = gtk_list_item_manager_try_reacquire_list_item (self->item_manager, - change, - position + i, - insert_after); - if (widget == NULL) - { - offset++; - continue; - } - - if (offset > 0) - { - new_row = gtk_rb_tree_insert_before (self->rows, row); - new_row->n_rows = offset; - row->n_rows -= offset; - offset = 0; - gtk_rb_tree_node_mark_dirty (row); - } - - if (row->n_rows == 1) - { - new_row = row; - row = gtk_rb_tree_node_get_next (row); - } - else - { - new_row = gtk_rb_tree_insert_before (self->rows, row); - new_row->n_rows = 1; - row->n_rows--; - gtk_rb_tree_node_mark_dirty (row); - } - - new_row->widget = widget; - insert_after = widget; - - if (widget == self->anchor) - { - anchor_pos = position + i; - break; - } - } - - if (i == added) - { - /* The anchor wasn't readded. Guess a good anchor position */ - anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)); - - anchor_pos = position + (anchor_pos - position) * added / removed; - if (anchor_pos >= g_list_model_get_n_items (self->model) && - anchor_pos > 0) - anchor_pos--; - } - gtk_list_view_set_anchor (self, anchor_pos, self->anchor_align, change, position); - } - else - { - /* The anchor is still where it was. - * We just may need to update its position and check that its surrounding widgets - * exist (they might be new ones). */ - guint anchor_pos; - - if (self->anchor) - { - anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)); - - if (anchor_pos >= position) - anchor_pos += added - removed; - } - else - { - anchor_pos = 0; - } - - gtk_list_view_set_anchor (self, anchor_pos, self->anchor_align, change, position); - } - - gtk_list_item_manager_end_change (self->item_manager, change); -} - -static void -gtk_list_view_model_selection_changed_cb (GListModel *model, - guint position, - guint n_items, - GtkListView *self) -{ - ListRow *row; - guint offset; - - row = gtk_list_view_get_row (self, position, &offset); - - if (offset) - { - position += row->n_rows - offset; - n_items -= row->n_rows - offset; - row = gtk_rb_tree_node_get_next (row); - } - - while (n_items > 0) - { - if (row->widget) - gtk_list_item_manager_update_list_item (self->item_manager, row->widget, position); - position += row->n_rows; - n_items -= MIN (n_items, row->n_rows); - row = gtk_rb_tree_node_get_next (row); - } -} - -static void -gtk_list_view_clear_model (GtkListView *self) -{ - if (self->model == NULL) - return; - - gtk_list_view_remove_rows (self, NULL, 0, g_list_model_get_n_items (self->model)); - - g_signal_handlers_disconnect_by_func (self->model, - gtk_list_view_model_selection_changed_cb, - self); - g_signal_handlers_disconnect_by_func (self->model, - gtk_list_view_model_items_changed_cb, - self); - g_clear_object (&self->model); - - gtk_list_view_unset_anchor (self); -} - static void gtk_list_view_clear_adjustment (GtkListView *self, GtkOrientation orientation) @@ -1067,7 +531,7 @@ gtk_list_view_dispose (GObject *object) { GtkListView *self = GTK_LIST_VIEW (object); - gtk_list_view_clear_model (self); + g_clear_object (&self->model); gtk_list_view_clear_adjustment (self, GTK_ORIENTATION_HORIZONTAL); gtk_list_view_clear_adjustment (self, GTK_ORIENTATION_VERTICAL); @@ -1082,7 +546,6 @@ gtk_list_view_finalize (GObject *object) { GtkListView *self = GTK_LIST_VIEW (object); - gtk_rb_tree_unref (self->rows); g_clear_object (&self->item_manager); G_OBJECT_CLASS (gtk_list_view_parent_class)->finalize (object); @@ -1302,13 +765,7 @@ gtk_list_view_class_init (GtkListViewClass *klass) static void gtk_list_view_init (GtkListView *self) { - self->item_manager = gtk_list_item_manager_new (GTK_WIDGET (self)); - - self->rows = gtk_rb_tree_new (ListRow, - ListRowAugment, - list_row_augment, - list_row_clear, - NULL); + self->item_manager = gtk_list_item_manager_new (GTK_WIDGET (self), ListRow, ListRowAugment, list_row_augment); self->adjustment[GTK_ORIENTATION_HORIZONTAL] = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0); self->adjustment[GTK_ORIENTATION_VERTICAL] = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0); @@ -1368,7 +825,7 @@ gtk_list_view_set_model (GtkListView *self, if (self->model == model) return; - gtk_list_view_clear_model (self); + g_clear_object (&self->model); if (model) { @@ -1383,19 +840,7 @@ gtk_list_view_set_model (GtkListView *self, gtk_list_item_manager_set_model (self->item_manager, selection_model); - g_signal_connect (model, - "items-changed", - G_CALLBACK (gtk_list_view_model_items_changed_cb), - self); - g_signal_connect (selection_model, - "selection-changed", - G_CALLBACK (gtk_list_view_model_selection_changed_cb), - self); - g_object_unref (selection_model); - - gtk_list_view_add_rows (self, 0, g_list_model_get_n_items (model)); - gtk_list_view_set_anchor (self, 0, 0, NULL, (guint) -1); } else { @@ -1414,22 +859,13 @@ gtk_list_view_set_functions (GtkListView *self, GDestroyNotify user_destroy) { GtkListItemFactory *factory; - guint n_items, anchor; - double anchor_align; g_return_if_fail (GTK_IS_LIST_VIEW (self)); g_return_if_fail (setup_func || bind_func); g_return_if_fail (user_data != NULL || user_destroy == NULL); - n_items = self->model ? g_list_model_get_n_items (self->model) : 0; - anchor = gtk_list_view_get_anchor (self, &anchor_align); - gtk_list_view_remove_rows (self, NULL, 0, n_items); - factory = gtk_list_item_factory_new (setup_func, bind_func, user_data, user_destroy); gtk_list_item_manager_set_factory (self->item_manager, factory); g_object_unref (factory); - - gtk_list_view_add_rows (self, 0, n_items); - gtk_list_view_set_anchor (self, anchor, anchor_align, NULL, (guint) -1); }