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