diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c index ac922cc709..35a894ed51 100644 --- a/gtk/gtklistitemmanager.c +++ b/gtk/gtklistitemmanager.c @@ -35,12 +35,7 @@ struct _GtkListItemManager 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 */ + GSList *trackers; }; struct _GtkListItemManagerClass @@ -48,6 +43,14 @@ struct _GtkListItemManagerClass GObjectClass parent_class; }; +struct _GtkListItemTracker +{ + guint position; + GtkListItem *widget; + guint n_before; + guint n_after; +}; + static GtkWidget * gtk_list_item_manager_acquire_list_item (GtkListItemManager *self, guint position, GtkWidget *prev_sibling); @@ -239,6 +242,104 @@ gtk_list_item_manager_get_item_augment (GtkListItemManager *self, return gtk_rb_tree_get_augment (self->items, item); } +static void +gtk_list_item_tracker_unset_position (GtkListItemManager *self, + GtkListItemTracker *tracker) +{ + tracker->widget = NULL; + tracker->position = GTK_INVALID_LIST_POSITION; +} + +static gboolean +gtk_list_item_tracker_query_range (GtkListItemManager *self, + GtkListItemTracker *tracker, + guint n_items, + guint *out_start, + guint *out_n_items) +{ + /* We can't look at tracker->widget here because we might not + * have set it yet. + */ + if (tracker->position == GTK_INVALID_LIST_POSITION) + return FALSE; + + /* This is magic I made up that is meant to be both + * correct and doesn't overflow when start and/or end are close to 0 or + * close to max. + * But beware, I didn't test it. + */ + *out_n_items = tracker->n_before + tracker->n_after + 1; + *out_n_items = MIN (*out_n_items, n_items); + + *out_start = MAX (tracker->position, tracker->n_before) - tracker->n_before; + *out_start = MIN (*out_start, n_items - *out_n_items); + + return TRUE; +} + +static void +gtk_list_item_query_tracked_range (GtkListItemManager *self, + guint n_items, + guint position, + guint *out_n_items, + gboolean *out_tracked) +{ + GSList *l; + guint tracker_start, tracker_n_items; + + g_assert (position < n_items); + + *out_tracked = FALSE; + *out_n_items = n_items - position; + + /* step 1: Check if position is tracked */ + + for (l = self->trackers; l; l = l->next) + { + if (!gtk_list_item_tracker_query_range (self, l->data, n_items, &tracker_start, &tracker_n_items)) + continue; + + if (tracker_start > position) + { + *out_n_items = MIN (*out_n_items, tracker_start - position); + } + else if (tracker_start + tracker_n_items <= position) + { + /* do nothing */ + } + else + { + *out_tracked = TRUE; + *out_n_items = tracker_start + tracker_n_items - position; + break; + } + } + + /* If nothing's tracked, we're done */ + if (!*out_tracked) + return; + + /* step 2: make the tracked range as large as possible + * NB: This is O(N_TRACKERS^2), but the number of trackers should be <5 */ +restart: + for (l = self->trackers; l; l = l->next) + { + if (!gtk_list_item_tracker_query_range (self, l->data, n_items, &tracker_start, &tracker_n_items)) + continue; + + if (tracker_start + tracker_n_items <= position + *out_n_items) + continue; + if (tracker_start > position + *out_n_items) + continue; + + if (*out_n_items + position < tracker_start + tracker_n_items) + { + *out_n_items = tracker_start + tracker_n_items - position; + goto restart; + } + } +} + static void gtk_list_item_manager_remove_items (GtkListItemManager *self, GHashTable *change, @@ -296,15 +397,6 @@ gtk_list_item_manager_add_items (GtkListItemManager *self, 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, @@ -325,61 +417,53 @@ gtk_list_item_manager_release_items (GtkListItemManager *self, GQueue *released) { GtkListItemManagerItem *item, *prev, *next; - guint i; + guint position, i, n_items, query_n_items; + gboolean tracked; - item = gtk_rb_tree_get_first (self->items); - i = 0; - while (i < self->anchor_start) + n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model)); + position = 0; + + while (position < n_items) { - if (item->widget) + gtk_list_item_query_tracked_range (self, n_items, position, &query_n_items, &tracked); + if (tracked) { - 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) + position += query_n_items; + continue; + } + + item = gtk_list_item_manager_get_nth (self, position, &i); + i = position - i; + while (i < position + query_n_items) + { + if (item->widget) { - 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); + 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 + else { - item = next; + i += item->n_items; + item = gtk_rb_tree_node_get_next (item); } } - 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); + position += query_n_items; } } @@ -389,117 +473,98 @@ gtk_list_item_manager_ensure_items (GtkListItemManager *self, guint update_start) { GtkListItemManagerItem *item, *new_item; - guint i, offset; GtkWidget *widget, *insert_after; + guint position, i, n_items, query_n_items, offset; GQueue released = G_QUEUE_INIT; + gboolean tracked; + + if (self->model == NULL) + return; + + n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model)); + position = 0; gtk_list_item_manager_release_items (self, &released); - item = gtk_list_item_manager_get_nth (self, self->anchor_start, &offset); - if (offset > 0) + while (position < n_items) { - 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); - } + gtk_list_item_query_tracked_range (self, n_items, position, &query_n_items, &tracked); + if (!tracked) + { + position += query_n_items; + continue; + } - insert_after = NULL; + item = gtk_list_item_manager_get_nth (self, position, &offset); + for (new_item = item; + new_item && new_item->widget == NULL; + new_item = gtk_rb_tree_node_get_previous (new_item)) + { /* do nothing */ } + insert_after = new_item ? new_item->widget : NULL; - for (i = self->anchor_start; i < self->anchor_end; i++) - { - if (item->n_items > 1) + if (offset > 0) { new_item = gtk_rb_tree_insert_before (self->items, item); - new_item->n_items = 1; - item->n_items--; + new_item->n_items = offset; + item->n_items -= offset; gtk_rb_tree_node_mark_dirty (item); } - else + + for (i = 0; i < query_n_items; i++) { - new_item = item; - item = gtk_rb_tree_node_get_next (item); - } - if (new_item->widget == NULL) - { - if (change) + if (item->n_items > 1) { - new_item->widget = gtk_list_item_manager_try_reacquire_list_item (self, - change, - i, - insert_after); + 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) { - new_item->widget = g_queue_pop_head (&released); - if (new_item->widget) + if (change) { - gtk_list_item_manager_move_list_item (self, - new_item->widget, - i, - insert_after); + new_item->widget = gtk_list_item_manager_try_reacquire_list_item (self, + change, + position + i, + insert_after); } - else + if (new_item->widget == NULL) { - new_item->widget = gtk_list_item_manager_acquire_list_item (self, - i, - insert_after); + new_item->widget = g_queue_pop_head (&released); + if (new_item->widget) + { + gtk_list_item_manager_move_list_item (self, + new_item->widget, + position + i, + insert_after); + } + else + { + new_item->widget = gtk_list_item_manager_acquire_list_item (self, + position + i, + insert_after); + } } } + else + { + if (update_start <= position + i) + gtk_list_item_manager_update_list_item (self, new_item->widget, position + i); + } + insert_after = new_item->widget; } - else - { - if (update_start <= i) - gtk_list_item_manager_update_list_item (self, new_item->widget, i); - } - insert_after = new_item->widget; + position += query_n_items; } 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, - GHashTable *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, @@ -510,20 +575,34 @@ gtk_list_item_manager_model_items_changed_cb (GListModel *model, GHashTable *change; GHashTableIter iter; gpointer list_item; + GSList *l; + guint n_items; + n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model)); change = g_hash_table_new (g_direct_hash, g_direct_equal); 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 && g_hash_table_lookup (change, gtk_list_item_get_item (GTK_LIST_ITEM (self->anchor))) == self->anchor) + /* Check if any tracked item was removed */ + for (l = self->trackers; l; l = l->next) + { + GtkListItemTracker *tracker = l->data; + + if (tracker->widget == NULL) + continue; + + if (g_hash_table_lookup (change, gtk_list_item_get_item (tracker->widget))) + break; + } + + /* At least one tracked item was removed, do a more expensive rebuild + * trying to find where it moved */ + if (l) { - /* 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; + guint i, offset; 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); @@ -573,50 +652,76 @@ gtk_list_item_manager_model_items_changed_cb (GListModel *model, 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; + /* Update tracker positions if necessary, they need to have correct + * positions for gtk_list_item_manager_ensure_items(). + * We don't update the items, they will be updated by ensure_items() + * and then we can update them. */ + for (l = self->trackers; l; l = l->next) + { + GtkListItemTracker *tracker = l->data; + + if (tracker->position == GTK_INVALID_LIST_POSITION) + { + /* if the list is no longer empty, set the tracker to a valid position. */ + if (n_items > 0 && n_items == added && removed == 0) + tracker->position = 0; + } + else if (tracker->position >= position + removed) + { + tracker->position += added - removed; + } + else if (tracker->position >= position) + { + if (g_hash_table_lookup (change, gtk_list_item_get_item (tracker->widget))) + { + /* The item is gone. Guess a good new position */ + tracker->position = position + (tracker->position - position) * added / removed; + if (tracker->position >= n_items) + { + if (n_items == 0) + tracker->position = GTK_INVALID_LIST_POSITION; + else + tracker->position--; + } + tracker->widget = NULL; + } + else + { + /* item was put in its right place in the expensive loop above, + * and we updated its position while at it. So grab it from there. + */ + tracker->position = gtk_list_item_get_position (tracker->widget); + } } else { - anchor_pos = 0; + /* nothing changed for items before position */ } - - gtk_list_item_manager_set_anchor (self, anchor_pos, self->anchor_align, change, position); } - g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); - g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); + gtk_list_item_manager_ensure_items (self, change, position + added); + + /* final loop through the trackers: Grab the missing widgets. + * For items that had been removed and a new position was set, grab + * their item now that we ensured it exists. + */ + for (l = self->trackers; l; l = l->next) + { + GtkListItemTracker *tracker = l->data; + GtkListItemManagerItem *item; + + if (tracker->widget != NULL || + tracker->position == GTK_INVALID_LIST_POSITION) + continue; + + item = gtk_list_item_manager_get_nth (self, tracker->position, NULL); + g_assert (item != NULL); + g_assert (item->widget); + tracker->widget = GTK_LIST_ITEM (item->widget); + } g_hash_table_iter_init (&iter, change); while (g_hash_table_iter_next (&iter, NULL, &list_item)) @@ -625,6 +730,8 @@ gtk_list_item_manager_model_items_changed_cb (GListModel *model, } g_hash_table_unref (change); + + gtk_widget_queue_resize (self->widget); } static void @@ -658,30 +765,19 @@ gtk_list_item_manager_model_selection_changed_cb (GListModel *model, } } -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) { + GSList *l; + 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))); + for (l = self->trackers; l; l = l->next) + { + gtk_list_item_tracker_unset_position (self, l->data); + } g_signal_handlers_disconnect_by_func (self->model, gtk_list_item_manager_model_selection_changed_cb, @@ -690,8 +786,6 @@ gtk_list_item_manager_clear_model (GtkListItemManager *self) gtk_list_item_manager_model_items_changed_cb, self); g_clear_object (&self->model); - - gtk_list_item_manager_unset_anchor (self); } static void @@ -725,8 +819,8 @@ void gtk_list_item_manager_set_factory (GtkListItemManager *self, GtkListItemFactory *factory) { - guint n_items, anchor; - double anchor_align; + guint n_items; + GSList *l; g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (factory)); @@ -735,13 +829,26 @@ gtk_list_item_manager_set_factory (GtkListItemManager *self, return; 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); 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); + + gtk_list_item_manager_ensure_items (self, NULL, G_MAXUINT); + + for (l = self->trackers; l; l = l->next) + { + GtkListItemTracker *tracker = l->data; + GtkListItemManagerItem *item; + + if (tracker->widget == NULL) + continue; + + item = gtk_list_item_manager_get_nth (self, tracker->position, NULL); + g_assert (item); + tracker->widget = GTK_LIST_ITEM (item->widget); + } } GtkListItemFactory * @@ -778,8 +885,6 @@ gtk_list_item_manager_set_model (GtkListItemManager *self, 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); } } @@ -961,3 +1066,72 @@ gtk_list_item_manager_release_list_item (GtkListItemManager *self, gtk_widget_unparent (item); } +GtkListItemTracker * +gtk_list_item_tracker_new (GtkListItemManager *self) +{ + GtkListItemTracker *tracker; + + g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL); + + tracker = g_slice_new0 (GtkListItemTracker); + + tracker->position = GTK_INVALID_LIST_POSITION; + + self->trackers = g_slist_prepend (self->trackers, tracker); + + return tracker; +} + +void +gtk_list_item_tracker_free (GtkListItemManager *self, + GtkListItemTracker *tracker) +{ + gtk_list_item_tracker_unset_position (self, tracker); + + self->trackers = g_slist_remove (self->trackers, tracker); + + g_slice_free (GtkListItemTracker, tracker); + + gtk_list_item_manager_ensure_items (self, NULL, G_MAXUINT); + + gtk_widget_queue_resize (self->widget); +} + +void +gtk_list_item_tracker_set_position (GtkListItemManager *self, + GtkListItemTracker *tracker, + guint position, + guint n_before, + guint n_after) +{ + GtkListItemManagerItem *item; + guint n_items; + + gtk_list_item_tracker_unset_position (self, tracker); + + if (self->model == NULL) + return; + + n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model)); + if (position >= n_items) + position = n_items - 1; /* for n_items == 0 this underflows to GTK_INVALID_LIST_POSITION */ + + tracker->position = position; + tracker->n_before = n_before; + tracker->n_after = n_after; + + gtk_list_item_manager_ensure_items (self, NULL, G_MAXUINT); + + item = gtk_list_item_manager_get_nth (self, position, NULL); + if (item) + tracker->widget = GTK_LIST_ITEM (item->widget); + + gtk_widget_queue_resize (self->widget); +} + +guint +gtk_list_item_tracker_get_position (GtkListItemManager *self, + GtkListItemTracker *tracker) +{ + return tracker->position; +} diff --git a/gtk/gtklistitemmanagerprivate.h b/gtk/gtklistitemmanagerprivate.h index 9a506c7573..c83fc7d92f 100644 --- a/gtk/gtklistitemmanagerprivate.h +++ b/gtk/gtklistitemmanagerprivate.h @@ -40,6 +40,7 @@ typedef struct _GtkListItemManager GtkListItemManager; typedef struct _GtkListItemManagerClass GtkListItemManagerClass; typedef struct _GtkListItemManagerItem GtkListItemManagerItem; /* sorry */ typedef struct _GtkListItemManagerItemAugment GtkListItemManagerItemAugment; +typedef struct _GtkListItemTracker GtkListItemTracker; struct _GtkListItemManagerItem { @@ -86,13 +87,16 @@ 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, +GtkListItemTracker * gtk_list_item_tracker_new (GtkListItemManager *self); +void gtk_list_item_tracker_free (GtkListItemManager *self, + GtkListItemTracker *tracker); +void gtk_list_item_tracker_set_position (GtkListItemManager *self, + GtkListItemTracker *tracker, guint position, - double align, - GHashTable *change, - guint update_start); -guint gtk_list_item_manager_get_anchor (GtkListItemManager *self, - double *align); + guint n_before, + guint n_after); +guint gtk_list_item_tracker_get_position (GtkListItemManager *self, + GtkListItemTracker *tracker); G_END_DECLS diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index e91b1b7b62..f2363d9661 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -37,6 +37,9 @@ */ #define GTK_LIST_VIEW_MAX_LIST_ITEMS 200 +/* Extra items to keep above + below every tracker */ +#define GTK_LIST_VIEW_EXTRA_ITEMS 2 + /** * SECTION:gtklistview * @title: GtkListView @@ -59,6 +62,9 @@ struct _GtkListView GtkScrollablePolicy scroll_policy[2]; int list_width; + + GtkListItemTracker *anchor; + double anchor_align; }; struct _ListRow @@ -229,6 +235,23 @@ gtk_list_view_get_list_height (GtkListView *self) return aug->height; } +static void +gtk_list_view_set_anchor (GtkListView *self, + guint position, + double align) +{ + gtk_list_item_tracker_set_position (self->item_manager, + self->anchor, + position, + GTK_LIST_VIEW_EXTRA_ITEMS + GTK_LIST_VIEW_MAX_LIST_ITEMS * align, + GTK_LIST_VIEW_EXTRA_ITEMS + GTK_LIST_VIEW_MAX_LIST_ITEMS - 1 - GTK_LIST_VIEW_MAX_LIST_ITEMS * align); + if (self->anchor_align != align) + { + self->anchor_align = align; + gtk_widget_queue_allocate (GTK_WIDGET (self)); + } +} + static void gtk_list_view_adjustment_value_changed_cb (GtkAdjustment *adjustment, GtkListView *self) @@ -247,7 +270,7 @@ gtk_list_view_adjustment_value_changed_cb (GtkAdjustment *adjustment, else pos = 0; - gtk_list_item_manager_set_anchor (self->item_manager, pos, 0, NULL, (guint) -1); + gtk_list_view_set_anchor (self, pos, 0); } gtk_widget_queue_allocate (GTK_WIDGET (self)); @@ -272,18 +295,17 @@ gtk_list_view_update_adjustments (GtkListView *self, { ListRow *row; guint anchor; - double anchor_align; page_size = gtk_widget_get_height (GTK_WIDGET (self)); upper = gtk_list_view_get_list_height (self); - anchor = gtk_list_item_manager_get_anchor (self->item_manager, &anchor_align); + anchor = gtk_list_item_tracker_get_position (self->item_manager, self->anchor); 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 -= anchor_align * (page_size - (row ? row->height : 0)); + value -= self->anchor_align * (page_size - (row ? row->height : 0)); } upper = MAX (upper, page_size); @@ -536,6 +558,11 @@ gtk_list_view_dispose (GObject *object) gtk_list_view_clear_adjustment (self, GTK_ORIENTATION_HORIZONTAL); gtk_list_view_clear_adjustment (self, GTK_ORIENTATION_VERTICAL); + if (self->anchor) + { + gtk_list_item_tracker_free (self->item_manager, self->anchor); + self->anchor = NULL; + } g_clear_object (&self->item_manager); G_OBJECT_CLASS (gtk_list_view_parent_class)->dispose (object); @@ -766,6 +793,7 @@ static void gtk_list_view_init (GtkListView *self) { self->item_manager = gtk_list_item_manager_new (GTK_WIDGET (self), ListRow, ListRowAugment, list_row_augment); + self->anchor = gtk_list_item_tracker_new (self->item_manager); 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); @@ -839,6 +867,7 @@ gtk_list_view_set_model (GtkListView *self, selection_model = GTK_SELECTION_MODEL (gtk_single_selection_new (model)); gtk_list_item_manager_set_model (self->item_manager, selection_model); + gtk_list_view_set_anchor (self, 0, 0.0); g_object_unref (selection_model); } @@ -847,7 +876,6 @@ gtk_list_view_set_model (GtkListView *self, gtk_list_item_manager_set_model (self->item_manager, NULL); } - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]); } diff --git a/gtk/gtkselectionmodel.h b/gtk/gtkselectionmodel.h index 9e8de6a66b..c982a918ad 100644 --- a/gtk/gtkselectionmodel.h +++ b/gtk/gtkselectionmodel.h @@ -33,6 +33,18 @@ G_BEGIN_DECLS GDK_AVAILABLE_IN_ALL G_DECLARE_INTERFACE (GtkSelectionModel, gtk_selection_model, GTK, SELECTION_MODEL, GListModel) +/** + * GTK_INVALID_LIST_POSITION: + * + * The value used to refer to a guaranteed invalid position in a #GListModel. This + * value may be returned from some functions, others may accept it as input. + * Its interpretion may differ for different functions. + * + * Refer to each function's documentation for if this value is allowed and what it + * does. + */ +#define GTK_INVALID_LIST_POSITION (G_MAXUINT) + /** * GtkSelectionModelInterface: * @is_selected: Return if the item at the given position is selected. diff --git a/gtk/gtksingleselection.h b/gtk/gtksingleselection.h index 74107fcc06..f9bbcae9e9 100644 --- a/gtk/gtksingleselection.h +++ b/gtk/gtksingleselection.h @@ -26,18 +26,6 @@ G_BEGIN_DECLS #define GTK_TYPE_SINGLE_SELECTION (gtk_single_selection_get_type ()) -/** - * GTK_INVALID_LIST_POSITION: - * - * The value used to refer to a guaranteed invalid position in a #GListModel. This - * value may be returned from some functions, others may accept it as input. - * Its interpretion may differ for different functions. - * - * Refer to each function's documentation for if this value is allowed and what it - * does. - */ -#define GTK_INVALID_LIST_POSITION (G_MAXUINT) - GDK_AVAILABLE_IN_ALL G_DECLARE_FINAL_TYPE (GtkSingleSelection, gtk_single_selection, GTK, SINGLE_SELECTION, GObject)