[gtk+/wip/list: 6/8] listview: Implement scrolling
- From: Benjamin Otte <otte src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk+/wip/list: 6/8] listview: Implement scrolling
- Date: Fri, 11 May 2012 02:50:07 +0000 (UTC)
commit 3bb8419dff38a327f2675829a2a0d85742f71590
Author: Benjamin Otte <otte redhat com>
Date: Fri May 11 04:42:50 2012 +0200
listview: Implement scrolling
gtk/gtklistview.c | 304 +++++++++++++++++++++++++++++++++++++++++------------
1 files changed, 236 insertions(+), 68 deletions(-)
---
diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c
index d725f6a..9588575 100644
--- a/gtk/gtklistview.c
+++ b/gtk/gtklistview.c
@@ -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);
@@ -385,25 +451,59 @@ gtk_list_view_get_preferred_width_for_height (GtkWidget *widget,
}
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,
gint *natural)
{
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)
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]