listitemmanager: Add trackers

... and replace the anchor tracking with a tracker.

Trackers track an item through the list across changes and ensure that
this item (and potentially siblings before/after it) are always backed
by a GtkListItem and that if the item gets removed a replacement gets
chosen.

This is now used for tracking the anchor but can also be used to add
trackers for the cursor later.
This commit is contained in:
Benjamin Otte 2019-02-27 08:45:28 +01:00 committed by Matthias Clasen
parent ce489f21fb
commit 1acfae8df2
3 changed files with 426 additions and 220 deletions

View File

@ -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;
}

View File

@ -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

View File

@ -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]);
}