listview: Implement scrolling

This commit is contained in:
Benjamin Otte
2012-05-11 04:42:50 +02:00
parent b5dc516704
commit 3bb8419dff

View File

@@ -19,12 +19,15 @@
#include "gtklistview.h"
#include "gtkadjustment.h"
#include "gtkenums.h"
#include "gtkintl.h"
#include "gtklabel.h"
#include "gtkscrollbar.h"
#include "gtktypebuiltins.h"
#include <math.h>
/**
* SECTION:gtklistview
* @title: GtkListView
@@ -68,6 +71,11 @@ struct _GtkListViewPrivate {
gpointer widget_data;
GtkWidget * scrollbar;
GtkListViewItem * top; /* first displayed item or NULL if none */
GtkListViewItem * start; /* first 'instantiated' item relevant for size requests or NULL if none */
GtkListViewItem * end; /* last 'instantiated' item relevant for size requests or NULL if none */
gint default_height; /* height used for 'noninstantiated' items, set in size_allocate */
};
/* Signals */
@@ -105,27 +113,6 @@ gtk_list_view_item_free (GtkListViewItem *item)
g_slice_free (GtkListViewItem, item);
}
#if 0
static GtkListViewItem *
gtk_list_view_get_item (GtkListView *list_view,
guint index)
{
GtkListViewItem *item;
for (item = list_view->priv->items;
item != NULL && item->pos < index;
item = item->next)
{
/* nothing to do here */
}
if (item->pos == index)
return item;
else
return NULL;
}
#endif
static gboolean
gtk_list_view_contains_path (GtkListView *list_view,
GtkTreePath *path)
@@ -143,6 +130,10 @@ gtk_list_view_clear (GtkListView *list_view)
gtk_list_view_item_free (priv->items);
priv->items = NULL;
}
priv->top = NULL;
priv->start = NULL;
priv->end = NULL;
}
static guint
@@ -264,6 +255,59 @@ gtk_list_view_create_item (GtkListView *list_view,
return item;
}
static GtkListViewItem *
gtk_list_view_get_item (GtkListView *list_view,
guint index,
gboolean create)
{
GtkListViewPrivate *priv = list_view->priv;
GtkListViewItem *item, *prev;
prev = NULL;
for (item = priv->items;
item != NULL && item->pos < index;
item = item->next)
{
prev = item;
}
if (item && item->pos == index)
return item;
if (create)
{
GtkListViewItem *new_item = gtk_list_view_create_item (list_view, index);
if (prev)
prev->next = new_item;
else
priv->items = new_item;
new_item->next = item;
return new_item;
}
else
return NULL;
}
/* We treat the list of rows in the treemodel like a scrollbar:
* +-----------+---------+-------+
* | |xxxxxxxxx| |
* +-----------+---------+-------+
* The slider is the area we care about for size requests and is reduced
* in size. Only for these items do we instantiate widgets. For everything
* else, we just guess width/height and everything else. This function
* ensures this slider exists, and that the visible area is inside this
* slider.
* The area goes from
* priv->first
* to
* priv->last (inclusive)
*
* Note that a bunch of items might still exist in priv->items that are not
* part of this slider. Those are special rows (like the focus when not
* on screen). They are never involved in size computations though.
*/
static void
gtk_list_view_update_items (GtkListView *list_view)
{
@@ -283,7 +327,10 @@ gtk_list_view_update_items (GtkListView *list_view)
n_total_items = gtk_list_view_get_n_items (list_view);
n_cached_items = gdk_screen_get_height (gtk_widget_get_screen (GTK_WIDGET (list_view)));
n_cached_items = MIN (n_cached_items, n_total_items);
first = 0;
if (priv->top)
first = priv->top->pos;
else
first = 0;
first = MIN (first, n_total_items - n_cached_items);
last = first + n_cached_items;
@@ -294,12 +341,16 @@ gtk_list_view_update_items (GtkListView *list_view)
{
if (item->pos < i || item->pos > last)
{
GtkListViewItem *free_me = item;
if (prev)
prev->next = item->next;
else
priv->items = item->next;
item->next = NULL;
gtk_list_view_item_free (item);
item = item->next;
free_me->next = NULL;
gtk_list_view_item_free (free_me);
continue;
}
for (; i < last && i < item->pos; i++)
@@ -330,6 +381,18 @@ gtk_list_view_update_items (GtkListView *list_view)
new_item->next = item;
prev = new_item;
}
if (priv->top == NULL)
priv->top = gtk_list_view_get_item (list_view, first, FALSE);
priv->start = priv->top;
priv->end = gtk_list_view_get_item (list_view, last - 1, FALSE);
g_assert (priv->start == NULL || priv->end != NULL);
}
static GtkSizeRequestMode
gtk_list_view_get_request_mode (GtkWidget *widget)
{
return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
}
/* FIXME: Also allow using natural width for scrollbar */
@@ -359,11 +422,14 @@ gtk_list_view_get_preferred_width (GtkWidget *widget,
min_total = 0;
nat_total = 0;
for (item = priv->items; item; item = item->next)
if (priv->start != NULL)
{
gtk_widget_get_preferred_width (item->widget, &min_child, &nat_child);
min_total = MAX (min_total, min_child);
nat_total = MAX (nat_total, nat_child);
for (item = priv->start; item != priv->end->next; item = item->next)
{
gtk_widget_get_preferred_width (item->widget, &min_child, &nat_child);
min_total = MAX (min_total, min_child);
nat_total = MAX (nat_total, nat_child);
}
}
min_child = gtk_list_view_get_scrollbar_width (list_view);
@@ -384,6 +450,39 @@ gtk_list_view_get_preferred_width_for_height (GtkWidget *widget,
gtk_list_view_get_preferred_width (widget, minimum, natural);
}
static void
gtk_list_view_get_heights (GtkListViewItem *item,
GtkListViewItem *end,
gint for_size,
gint *min_max,
gint *min_sum,
guint *n_items)
{
int child_height;
if (min_max)
*min_max = 0;
if (min_sum)
*min_sum = 0;
if (n_items)
*n_items = 0;
for (; item != end; item = item->next)
{
/* We only ever allocate min sizes, so ignore the other one */
if (for_size < 0)
gtk_widget_get_preferred_height (item->widget, &child_height, NULL);
else
gtk_widget_get_preferred_height_for_width (item->widget, for_size, &child_height, NULL);
if (min_max)
*min_max = MAX (*min_max, child_height);
if (min_sum)
*min_sum += child_height;
if (n_items)
*n_items += 1;
}
}
static void
gtk_list_view_get_preferred_height (GtkWidget *widget,
gint *minimum,
@@ -391,19 +490,20 @@ gtk_list_view_get_preferred_height (GtkWidget *widget,
{
GtkListView *list_view = GTK_LIST_VIEW (widget);
GtkListViewPrivate *priv = list_view->priv;
GtkListViewItem *item;
guint n_items, n_measured_items;
gint min_total, nat_total;
gint min_child, nat_child;
gtk_list_view_update_items (list_view);
min_total = 0;
nat_total = 0;
for (item = priv->items; item; item = item->next)
gtk_list_view_get_heights (priv->start, priv->end, -1, &min_total, &nat_total, &n_measured_items);
if (n_measured_items)
{
gtk_widget_get_preferred_height (item->widget, &min_child, &nat_child);
min_total += min_child;
nat_total += nat_child;
n_items = gtk_list_view_get_n_items (list_view);
/* XXX: overflow? */
nat_child = nat_total / n_measured_items;
nat_total += nat_child * (n_items - n_measured_items);
}
gtk_widget_get_preferred_height_for_width (priv->scrollbar,
@@ -424,7 +524,7 @@ gtk_list_view_get_preferred_height_for_width (GtkWidget *widget,
{
GtkListView *list_view = GTK_LIST_VIEW (widget);
GtkListViewPrivate *priv = list_view->priv;
GtkListViewItem *item;
guint n_measured_items, n_items;
gint scrollbar_width;
gint min_total, nat_total;
gint min_child, nat_child;
@@ -434,16 +534,19 @@ gtk_list_view_get_preferred_height_for_width (GtkWidget *widget,
scrollbar_width = gtk_list_view_get_scrollbar_width (list_view);
width -= scrollbar_width;
min_total = 0;
nat_total = 0;
for (item = priv->items; item; item = item->next)
gtk_list_view_get_heights (priv->start, priv->end, width, &min_total, &nat_total, &n_measured_items);
if (n_measured_items)
{
gtk_widget_get_preferred_height_for_width (item->widget, width, &min_child, &nat_child);
min_total += min_child;
nat_total += nat_child;
n_items = gtk_list_view_get_n_items (list_view);
/* XXX: overflow? */
nat_child = nat_total / n_measured_items;
nat_total += nat_child * (n_items - n_measured_items);
}
gtk_widget_get_preferred_height_for_width (priv->scrollbar, scrollbar_width, &min_child, &nat_child);
gtk_widget_get_preferred_height_for_width (priv->scrollbar,
gtk_list_view_get_scrollbar_width (list_view),
&min_child, &nat_child);
min_total = MAX (min_total, min_child);
nat_total = MAX (nat_total, nat_child);
@@ -458,8 +561,10 @@ gtk_list_view_size_allocate (GtkWidget *widget,
GtkListView *list_view = GTK_LIST_VIEW (widget);
GtkListViewPrivate *priv = list_view->priv;
GtkAllocation child_allocation;
GtkAdjustment *adjustment;
GtkListViewItem *item;
gint scrollbar_width;
gint scrollbar_width, height_total;
guint n_allocated, n_measured;
gtk_widget_set_allocation (widget, allocation);
@@ -470,20 +575,21 @@ gtk_list_view_size_allocate (GtkWidget *widget,
scrollbar_width = gtk_list_view_get_scrollbar_width (list_view);
child_allocation.x = allocation->width - scrollbar_width;
child_allocation.y = 0;
child_allocation.width = scrollbar_width;
child_allocation.height = allocation->height;
gtk_widget_size_allocate (priv->scrollbar, &child_allocation);
child_allocation.x = 0;
child_allocation.y = 0;
child_allocation.width = allocation->width - scrollbar_width;
for (item = priv->items;
item != NULL && child_allocation.y < allocation->height;
item != priv->top;
item = item->next)
{
gtk_widget_get_preferred_height_for_width (item->widget, allocation->width, &child_allocation.height, NULL);
gtk_widget_set_child_visible (item->widget, FALSE);
}
for (n_allocated = 0;
item != NULL && child_allocation.y < allocation->height;
item = item->next, n_allocated++)
{
gtk_widget_get_preferred_height_for_width (item->widget, child_allocation.width, &child_allocation.height, NULL);
gtk_widget_size_allocate (item->widget, &child_allocation);
gtk_widget_set_child_visible (item->widget, TRUE);
@@ -494,6 +600,31 @@ gtk_list_view_size_allocate (GtkWidget *widget,
{
gtk_widget_set_child_visible (item->widget, FALSE);
}
adjustment = gtk_range_get_adjustment (GTK_RANGE (priv->scrollbar));
gtk_list_view_get_heights (priv->start, priv->end, child_allocation.width, NULL, &height_total, &n_measured);
if (n_measured)
{
priv->default_height = height_total / n_measured;
gtk_adjustment_configure (adjustment,
priv->default_height * priv->top->pos,
0,
child_allocation.y + priv->default_height * (gtk_list_view_get_n_items (list_view) - n_allocated),
allocation->height * 0.1,
allocation->height * 0.9,
allocation->height);
}
else
{
priv->default_height = 0;
gtk_adjustment_configure (adjustment, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
}
child_allocation.x = allocation->width - scrollbar_width;
child_allocation.y = 0;
child_allocation.width = scrollbar_width;
child_allocation.height = allocation->height;
gtk_widget_size_allocate (priv->scrollbar, &child_allocation);
}
static void
@@ -677,6 +808,7 @@ gtk_list_view_class_init (GtkListViewClass *klass)
NULL,
G_PARAM_READWRITE));
widget_class->get_request_mode = gtk_list_view_get_request_mode;
widget_class->get_preferred_width = gtk_list_view_get_preferred_width;
widget_class->get_preferred_height = gtk_list_view_get_preferred_height;
widget_class->get_preferred_width_for_height = gtk_list_view_get_preferred_width_for_height;
@@ -689,6 +821,54 @@ gtk_list_view_class_init (GtkListViewClass *klass)
g_type_class_add_private (klass, sizeof (GtkListViewPrivate));
}
static GtkListViewItem *
gtk_list_view_find_item_for_y (GtkListView *list_view,
gint value)
{
GtkListViewPrivate *priv = list_view->priv;
GtkListViewItem *item;
int height;
if (value < 0 || priv->start == NULL)
return NULL;
height = priv->top->pos * priv->default_height;
if (height > value)
return gtk_list_view_get_item (list_view, value / priv->default_height, TRUE);
else
value -= height;
for (item = priv->top; item && gtk_widget_get_child_visible (item->widget); item = item->next)
{
height = gtk_widget_get_allocated_height (item->widget);
if (height > value)
return item;
else
value -= height;
}
if (item)
{
height = priv->default_height * (gtk_list_view_get_n_items (list_view) - item->pos);
if (height > value)
return gtk_list_view_get_item (list_view, value / priv->default_height + item->pos, TRUE);
}
return NULL;
}
static gboolean
gtk_list_view_scrollbar_change_value_cb (GtkScrollbar *scrollbar,
GtkScrollType scroll_type,
double new_value,
GtkListView *list_view)
{
list_view->priv->top = gtk_list_view_find_item_for_y (list_view, round (gtk_adjustment_get_value (gtk_range_get_adjustment (GTK_RANGE (scrollbar)))));
gtk_widget_queue_resize (GTK_WIDGET (list_view));
return FALSE;
}
static void
gtk_list_view_init (GtkListView *list_view)
{
@@ -700,6 +880,10 @@ gtk_list_view_init (GtkListView *list_view)
priv = list_view->priv;
priv->scrollbar = gtk_scrollbar_new (GTK_ORIENTATION_VERTICAL, NULL);
g_signal_connect_after (priv->scrollbar,
"change-value",
G_CALLBACK (gtk_list_view_scrollbar_change_value_cb),
list_view);
gtk_widget_set_parent (priv->scrollbar, GTK_WIDGET (list_view));
gtk_widget_show (priv->scrollbar);
@@ -951,8 +1135,6 @@ static void gtk_list_view_destroy (GtkWidget
static void gtk_list_view_style_updated (GtkWidget *widget);
static void gtk_list_view_state_flags_changed (GtkWidget *widget,
GtkStateFlags previous_state);
static void gtk_list_view_size_allocate (GtkWidget *widget,
GtkAllocation *allocation);
static gboolean gtk_list_view_draw (GtkWidget *widget,
cairo_t *cr);
static gboolean gtk_list_view_motion (GtkWidget *widget,
@@ -1934,20 +2116,6 @@ gtk_list_view_compute_n_items_for_size (GtkListView *list_view,
}
}
static void
gtk_list_view_allocate_children (GtkListView *list_view)
{
GList *list;
for (list = list_view->priv->children; list; list = list->next)
{
GtkListViewChild *child = list->data;
/* totally ignore our child's requisition */
gtk_widget_size_allocate (child->widget, &child->area);
}
}
static gboolean
gtk_list_view_draw (GtkWidget *widget,
cairo_t *cr)