diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index d725f6a5ea..9588575940 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 + /** * 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)