[gtk/wip/otte/listview: 54/155] listitemmanager: Add trackers
- From: Benjamin Otte <otte src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/wip/otte/listview: 54/155] listitemmanager: Add trackers
- Date: Tue, 4 Feb 2020 19:39:41 +0000 (UTC)
commit cc5172b06f27e0b1a4777c1dd67266cdb60ba508
Author: Benjamin Otte <otte redhat com>
Date: Wed Feb 27 08:45:28 2019 +0100
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.
gtk/gtklistitemmanager.c | 588 ++++++++++++++++++++++++++--------------
gtk/gtklistitemmanagerprivate.h | 16 +-
gtk/gtklistview.c | 38 ++-
gtk/gtkselectionmodel.h | 12 +
4 files changed, 436 insertions(+), 218 deletions(-)
---
diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c
index ac922cc7094..35a894ed51f 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;
+
+ n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model));
+ position = 0;
- item = gtk_rb_tree_get_first (self->items);
- i = 0;
- while (i < self->anchor_start)
+ 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
- {
- new_item = item;
- item = gtk_rb_tree_node_get_next (item);
- }
- if (new_item->widget == NULL)
+
+ for (i = 0; i < query_n_items; i++)
{
- if (change)
+ 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->widget = gtk_list_item_manager_try_reacquire_list_item (self,
- change,
- i,
- insert_after);
+ 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));
+ /* 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;
- 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--;
+ 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;
}
- 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)
+ else if (tracker->position >= position + removed)
{
- anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor));
-
- if (anchor_pos >= position)
- anchor_pos += added - 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 9a506c7573f..c83fc7d92f6 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 e91b1b7b62c..f2363d96616 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 9e8de6a66b6..c982a918ad0 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.
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]