forked from AuroraMiddleware/gtk
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:
parent
ce489f21fb
commit
1acfae8df2
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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]);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user